性能计数器 - System.InvalidOperationException:类别不存在 [英] Performance Counter - System.InvalidOperationException: Category does not exist

查看:45
本文介绍了性能计数器 - System.InvalidOperationException:类别不存在的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下类,它返回 IIS 每秒的当前请求数.我每分钟调用 RefreshCounters 以保持每秒请求数刷新(因为它是平均值,如果我将它保留太久,旧值会影响结果)......当我需要显示当前 RequestsPerSecond 时,我调用该属性.

I have following class that returns number of current Request per Second of IIS. I call RefreshCounters every minute in order to keep Requests per Second value refreshed (because it is average and if I keep it too long old value will influence result too much)... and when I need to display current RequestsPerSecond I call that property.

public class Counters
{
    private static PerformanceCounter pcReqsPerSec;
    private const string counterKey = "Requests_Sec";
    public static object RequestsPerSecond
    {
        get
        {
            lock (counterKey)
            {
                if (pcReqsPerSec != null)
                    return pcReqsPerSec.NextValue().ToString("N2"); // EXCEPTION
                else
                    return "0";
            }
        }
    }

    internal static string RefreshCounters()
    {
        lock (counterKey)
        {
            try
            {
                if (pcReqsPerSec != null)
                {
                    pcReqsPerSec.Dispose();
                    pcReqsPerSec = null;
                }

                pcReqsPerSec = new PerformanceCounter("W3SVC_W3WP", "Requests / Sec", "_Total", true);
                pcReqsPerSec.NextValue();

                PerformanceCounter.CloseSharedResources();

                return null;
            }
            catch (Exception ex)
            {
                return ex.ToString();
            }
        }
    }
}

问题是有时抛出以下异常:

System.InvalidOperationException: Category does not exist.

at System.Diagnostics.PerformanceCounterLib.GetCategorySample(String machine, String category)
at System.Diagnostics.PerformanceCounter.NextSample()
at System.Diagnostics.PerformanceCounter.NextValue()
at BidBop.Admin.PerfCounter.Counters.get_RequestsPerSecond() in [[[pcReqsPerSec.NextValue().ToString("N2");]]]

我是否没有正确关闭以前的 PerformanceCounter 实例?我做错了什么以至于有时会出现异常?

Am I not closing previous instances of PerformanceCounter properly? What am I doing wrong so that I end up with that exception sometimes?

并且只是为了记录,我在 IIS 网站中托管这个类(当然,也就是在具有管理权限的 App Pool 中托管)并从 ASMX 服务调用方法.使用计数器值(显示它们)的站点每 1 分钟调用一次 RefreshCounters,每 5 秒调用一次 RequestsPerSecond;RequestPerSecond 在调用之间缓存.

And just for the record, I am hosting this class in IIS website (that is, of course, hosted in App Pool which has administrative privileges) and invoking methods from ASMX service. Site that uses Counter values (displays them) calls RefreshCounters every 1 minute and RequestsPerSecond every 5 seconds; RequestPerSecond are cached between calls.

我每 1 分钟调用一次 RefreshCounters,因为值往往会变得陈旧" - 太受旧值的影响(例如,实际 1 分钟前).

I am calling RefreshCounters every 1 minute because values tend to become "stale" - too influenced by older values (that were actual 1 minute ago, for example).

推荐答案

Antenka 在这方面为您指明了方向.您不应该在每次更新/请求价值时处理和重新创建性能计数器.实例化性能计数器是有成本的,并且第一次读取可能不准确,如下面的引用所示.此外,您的 lock() { ... } 语句非常广泛(它们涵盖了很多语句)并且速度会很慢.最好让你的锁尽可能小.我正在为 Antenka 投票,以获得高质量的参考和好的建议!

Antenka has led you in a good direction here. You should not be disposing and re-creating the performance counter on every update/request for value. There is a cost for instantiating the performance counters and the first read can be inaccurate as indicated in the quote below. Also your lock() { ... } statements are very broad (they cover a lot of statements) and will be slow. Its better to have your locks as small as possible. I'm giving Antenka a voteup for the quality reference and good advice!

但是,我想我可以为您提供更好的答案.我在监控服务器性能方面有相当多的经验,并且完全了解您的需求.您的代码没有考虑到的一个问题是,任何显示性能计数器的代码(.aspx、.asmx、控制台应用程序、winform 应用程序等)都可能以任何方式请求此统计信息;它可以每 10 秒请求一次,也许每秒 5 次,您不知道也不应该在意.因此,您需要将 PerformanceCounter 收集代码与实际报告当前请求/秒值的代码分开.出于性能方面的考虑,我还将向您展示如何在第一次请求时设置性能计数器,然后一直运行直到 5 秒内没有人提出任何请求,然后正确关闭/处置 PerformanceCounter.

However, I think I can provide a better answer for you. I have a fair bit of experience with monitoring server performance and understand exactly what you need. One problem your code doesn't take into account is that whatever code is displaying your performance counter (.aspx, .asmx, console app, winform app, etc) could be requesting this statistic at any rate; it could be requested once every 10 seconds, maybe 5 times per second, you don't know and shouldn't care. So you need to separate the PerformanceCounter collection code from that does the monitoring from the code that actually reports the current Requests / Second value. And for performance reasons, I'm also going to show you how to setup the performance counter on first request and then keep it going until nobody has made any requests for 5 seconds, then close/dispose the PerformanceCounter properly.

public class RequestsPerSecondCollector
{
    #region General Declaration
    //Static Stuff for the polling timer
    private static System.Threading.Timer pollingTimer;
    private static int stateCounter = 0;
    private static int lockTimerCounter = 0;

    //Instance Stuff for our performance counter
    private static System.Diagnostics.PerformanceCounter pcReqsPerSec;
    private readonly static object threadLock = new object();
    private static decimal CurrentRequestsPerSecondValue;
    private static int LastRequestTicks;
    #endregion

    #region Singleton Implementation
    /// <summary>
    /// Static members are 'eagerly initialized', that is, 
    /// immediately when class is loaded for the first time.
    /// .NET guarantees thread safety for static initialization.
    /// </summary>
    private static readonly RequestsPerSecondCollector _instance = new RequestsPerSecondCollector();
    #endregion

    #region Constructor/Finalizer
    /// <summary>
    /// Private constructor for static singleton instance construction, you won't be able to instantiate this class outside of itself.
    /// </summary>
    private RequestsPerSecondCollector()
    {
        LastRequestTicks = System.Environment.TickCount;

        // Start things up by making the first request.
        GetRequestsPerSecond();
    }
    #endregion

    #region Getter for current requests per second measure
    public static decimal GetRequestsPerSecond()
    {
        if (pollingTimer == null)
        {
            Console.WriteLine("Starting Poll Timer");

            // Let's check the performance counter every 1 second, and don't do the first time until after 1 second.
            pollingTimer = new System.Threading.Timer(OnTimerCallback, null, 1000, 1000);

            // The first read from a performance counter is notoriously inaccurate, so 
            OnTimerCallback(null);
        }

        LastRequestTicks = System.Environment.TickCount;
        lock (threadLock)
        {
            return CurrentRequestsPerSecondValue;
        }
    }
    #endregion

    #region Polling Timer
    static void OnTimerCallback(object state)
    {
        if (System.Threading.Interlocked.CompareExchange(ref lockTimerCounter, 1, 0) == 0)
        {
            if (pcReqsPerSec == null)
                pcReqsPerSec = new System.Diagnostics.PerformanceCounter("W3SVC_W3WP", "Requests / Sec", "_Total", true);

            if (pcReqsPerSec != null)
            {
                try
                {
                    lock (threadLock)
                    {
                        CurrentRequestsPerSecondValue = Convert.ToDecimal(pcReqsPerSec.NextValue().ToString("N2"));
                    }
                }
                catch (Exception) {
                    // We had problem, just get rid of the performance counter and we'll rebuild it next revision
                    if (pcReqsPerSec != null)
                    {
                        pcReqsPerSec.Close();
                        pcReqsPerSec.Dispose();
                        pcReqsPerSec = null;
                    }
                }
            }

            stateCounter++;

            //Check every 5 seconds or so if anybody is still monitoring the server PerformanceCounter, if not shut down our PerformanceCounter
            if (stateCounter % 5 == 0)
            {
                if (System.Environment.TickCount - LastRequestTicks > 5000)
                {
                    Console.WriteLine("Stopping Poll Timer");

                    pollingTimer.Dispose();
                    pollingTimer = null;

                    if (pcReqsPerSec != null)
                    {
                        pcReqsPerSec.Close();
                        pcReqsPerSec.Dispose();
                        pcReqsPerSec = null;
                    }
                }                                                      
            }

            System.Threading.Interlocked.Add(ref lockTimerCounter, -1);
        }
    }
    #endregion
}

好的,现在解释一下.

  1. 首先,您会注意到此类被设计为静态单例.你不能加载它的多个副本,它有一个私有构造函数并且急切地初始化了自身的内部实例.这使得确保您不会意外创建相同的多个副本性能计数器.
  2. 接下来你会注意到私有构造函数(这只会运行一旦第一次访问该类时)我们创建两个PerformanceCounter 和一个用于轮询性能计数器.
  3. Timer 的回调方法将创建 PerformanceCounter 如果需要并获取其下一个值可用.也是每 5 次迭代我们将查看自您上次请求PerformanceCounter 的值.如果超过 5 秒,我们将关闭轮询计时器,因为它目前不需要.我们可以如果我们再次需要它,请务必稍后再次启动它.
  4. 现在我们有一个名为 GetRequestsPerSecond() 的静态方法供您使用将返回 RequestsPerSecond 的当前值的调用性能计数器.
  1. First you'll notice this class is designed to be a static singleton. You can't load multiple copies of it, it has a private constructor and and eagerly initialized internal instance of itself. This makes sure you don't accidentally create multiple copies of the same PerformanceCounter.
  2. Next you'll notice in the private constructor (this will only run once when the class is first accessed) we create both the PerformanceCounter and a timer which will be used to poll the PerformanceCounter.
  3. The Timer's callback method will create the PerformanceCounter if needed and get its next value is available. Also every 5 iterations we're going to see how long its been since your last request for the PerformanceCounter's value. If it's been more than 5 seconds, we'll shutdown the polling timer as its unneeded at the moment. We can always start it up again later if we need it again.
  4. Now we have a static method called GetRequestsPerSecond() for you to call which will return the current value of the RequestsPerSecond PerformanceCounter.

这个实现的好处是你只创建一次性能计数器,然后继续使用直到你完成它.它易于使用,因为您可以从任何需要的地方(.aspx、.asmx、控制台应用程序、winforms 应用程序等)简单地调用 RequestsPerSecondCollector.GetRequestsPerSecond().始终只有一个 PerformanceCounter,并且无论您调用 RequestsPerSecondCollector.GetRequestsPerSecond() 的速度有多快,它都会以每秒精确的 1 次轮询.如果您在 5 秒内没有请求它的值,它也会自动关闭并处理 PerformanceCounter.当然,您可以调整计时器间隔和超时毫秒以满足您的需要.你可以在 60 秒而不是 5 秒内更快地轮询和超时.我选择了 5 秒,因为它证明它在 Visual Studio 中调试时工作得非常快.一旦您测试它并知道它可以工作,您可能需要更长的超时时间.

The benefits of this implementation are that you only create the performance counter once and then keep using until you are finished with it. Its easy to use because you simple call RequestsPerSecondCollector.GetRequestsPerSecond() from wherever you need it (.aspx, .asmx, console app, winforms app, etc). There will always be only one PerformanceCounter and it will always be polled at exactly 1 times per second regardless of how quickly you call RequestsPerSecondCollector.GetRequestsPerSecond(). It will also automatically close and dispose of the PerformanceCounter if you haven't requested its value in more than 5 seconds. Of course you can adjust both the timer interval and the timeout milliseconds to suit your needs. You could poll faster and timeout in say 60 seconds instead of 5. I chose 5 seconds as it proves that it works very quickly while debugging in visual studio. Once you test it and know it works, you might want a longer timeout.

希望这不仅可以帮助您更好地使用 PerformanceCounters,而且可以安全地重用此类与您想要显示统计信息的任何内容分开的类.可重用的代码总是一个加分项!

Hopefully this helps you not only better use PerformanceCounters, but also feel safe to reuse this class which is separate from whatever you want to display the statistics in. Reusable code is always a plus!

作为一个后续问题,如果您想在此性能计数器运行时每 60 秒执行一次清理或保姆任务,该怎么办?好吧,我们已经让计时器每 1 秒运行一次,并且有一个变量跟踪我们的循环迭代,称为 stateCounter,它在每个计时器回调时递增.所以你可以添加一些这样的代码:

As a follow up question, what if you want to performance some cleanup or babysitting task every 60 seconds while this performance counter is running? Well we already have the timer running every 1 second and a variable tracking our loop iterations called stateCounter which is incremented on each timer callback. So you could add in some code like this:

// Every 60 seconds I want to close/dispose my PerformanceCounter
if (stateCounter % 60 == 0)
{
    if (pcReqsPerSec != null)
    {
        pcReqsPerSec.Close();
        pcReqsPerSec.Dispose();
        pcReqsPerSec = null;
    }
}

我应该指出示例中的这个性能计数器不应该过时".我相信请求/秒"应该是一个平均值而不是一个移动平均值统计数据.但是这个示例只是说明了一种你可以的方法> 定期对您的 PerformanceCounter 进行任何类型的清理或照看".在这种情况下,我们将关闭并处置性能计数器,这将导致它在下一次重新创建计时器回调.您可以根据您的用例和您正在使用的特定 PerformanceCounter 修改它.大多数阅读此问题/答案的人应该不需要这样做.检查您想要的 PerformanceCounter 的文档,看看它是否是连续计数,平均值、移动平均值等...并适当调整您的实施.

I should point out that this performance counter in the example should not "go stale". I believe 'Request / Sec" should be an average and not a moving average statistic. But this sample just illustrates a way you could do any type of cleanup or "babysitting" of your PerformanceCounter on a regular time interval. In this case we are closing and disposing the performance counter which will cause it to be recreated on next timer callback. You could modify this for your use case and according the specific PerformanceCounter you are using. Most people reading this question/answer should not need to do this. Check the documentation for your desired PerformanceCounter to see if it is a continuous count, an average, a moving average, etc... and adjust your implementation appropriately.

这篇关于性能计数器 - System.InvalidOperationException:类别不存在的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆