c# - 性能计数器 - System.InvalidOperationException : Category does not exist

标签 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();
            }
        }
    }
}

问题是有时会抛出以下异常:
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 实例吗?我做错了什么以至于有时会出现异常?

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

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

最佳答案

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

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

好的,现在解释一下。
  • 首先你会注意到这个类被设计成一个静态的单例。
    你不能加载它的多个副本,它有一个私有(private)构造函数
    并且急切地初始化了自身的内部实例。这使得
    确保您不会意外创建相同的多个副本PerformanceCounter .
  • 接下来你会注意到私有(private)构造函数(这只会运行
    一旦第一次访问类时)我们创建两个PerformanceCounter和一个计时器,用于轮询PerformanceCounter .
  • Timer 的回调方法将创建 PerformanceCounter如果
    需要并获取其下一个值可用。也是每 5 次迭代
    我们将查看自您上次请求PerformanceCounter的值(value)。如果超过 5 秒,我们将
    关闭轮询计时器,因为它目前不需要。我们可以
    如果我们再次需要它,请稍后再启动它。
  • 现在我们有一个名为 GetRequestsPerSecond() 的静态方法为你
    将返回 RequestsPerSecond 的当前值的调用PerformanceCounter .

  • 这种实现的好处是你只创建一次性能计数器,然后继续使用直到你完成它。它易于使用,因为您只需拨打 RequestsPerSecondCollector.GetRequestsPerSecond()从任何你需要的地方(.aspx、.asmx、控制台应用程序、winforms 应用程序等)。永远只有一个 PerformanceCounter并且无论您拨打 RequestsPerSecondCollector.GetRequestsPerSecond() 的速度有多快,它都会以每秒精确的速度轮询 1 次。 .它还会自动关闭并处理 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;
        }
    }
    

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

    关于c# - 性能计数器 - System.InvalidOperationException : Category does not exist,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8171865/

    相关文章:

    asp.net - 在 TFS/版本控制中存储 IIS 配置的最佳实践

    ASP.NET SSL 在 IIS 下设置,但网站不断跳回 http

    asp.net - 是否可以在多个 Web 服务器之间共享 System.Runtime.Caching 缓存对象?

    c# - ASP.net 在一个 ashx 文件中处理自定义文件扩展名的所有请求

    c# - 设置窗体的 MdiParent 属性会中断/阻止触发其 Shown 事件

    c# - 数据集到 xml 空值

    c# - IIS 池回收后的 session 引用(并发字典变为空白)

    c# - Settings.Designer 文件和静态

    c# - 通过 Microsoft.DirectX.AudioVideoPlayback 显示对视频播放的控制

    c# - 如何获取 API 中被调用方法的列表