性能计数器 - System.InvalidOperationException:类别不存在

nik*_*3ro 12 c# asp.net iis performancecounter

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

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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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");]]]
Run Code Online (Sandbox Code Playgroud)

我没有正确关闭PerformanceCounter的先前实例吗?我做错了什么,以便我有时会遇到这种异常?

编辑: 只是为了记录,我在IIS网站托管这个类(当然,托管在具有管理权限的应用程序池)和从ASMX服务调用方法.使用Counter值(显示它们)的站点每1分钟调用一次RefreshCounters,每5秒调用一次RequestsPerSecond; RequestPerSecond在调用之间缓存.

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

Ben*_*yne 16

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

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

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
}
Run Code Online (Sandbox Code Playgroud)

现在好了解一下.

  1. 首先你会注意到这个类被设计成一个静态单例.你不能加载它的多个副本,它有一个私有的构造函数,并且急切地初始化了它自己的内部实例.这可以确保您不会意外地创建相同的多个副本 PerformanceCounter.
  2. 接下来你将注意到私有构造函数(这将只在第一次访问类时运行一次)我们创建了一个 PerformanceCounter和一个将用于轮询的计时器 PerformanceCounter.
  3. Timer的回调方法将创建PerformanceCounterif需要并获取其下一个值.此外,我们每隔5次迭代就会看到自上次请求该PerformanceCounter值以来已经过了多长时间 .如果它超过5秒,我们将关闭轮询计时器,因为此时它是不需要的.如果我们再次需要,我们可以在以后再次启动它.
  4. 现在我们有一个调用的静态方法GetRequestsPerSecond(),它将返回RequestsPerSecond的当前值 PerformanceCounter.

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

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

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

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

我应该指出,示例中的这个性能计数器不应该"过时".我认为'Request/Sec'应该是一个平均值而不是移动平均值统计数据.但是这个示例只是说明了一种方法,你可以PerformanceCounter在常规时间间隔内进行任何类型的清理或"保姆" .在这种情况下,我们正在关闭并处理性能计数器,这将导致它在下一次计时器回调时重新创建.您可以根据您的使用情况和您正在使用的特定PerformanceCounter修改它.大多数读这个问题/答案的人不应该这样做.检查所需PerformanceCounter的文档,以查看它是连续计数,平均值,移动平均值等...并适当调整您的实现.

  • 男人......你的答案太棒了;).请阅读编辑(我已解释托管上下文)并尽可能编辑您的答案.特别困扰我的一件事(也就是我有RefreshCounters方法的原因) - 如果我不每分钟重新创建一个计数器,pcReqsPerSec.NextValue()会变得"陈旧"......这个计数器是如何工作的还是我在做什么有问题?有没有办法只从最后一分钟获得AVG而无需重新创建PerfCounter? (2认同)