【问题标题】:Performance Counter - System.InvalidOperationException: Category does not exist性能计数器 - System.InvalidOperationException:类别不存在
【发布时间】:2011-12-31 14:15:19
【问题描述】:

我有以下类,它返回 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 服务调用方法。使用 Counter 值(显示它们)的站点每 1 分钟调用一次 RefreshCounters,每 5 秒调用一次 RequestsPerSecond; RequestPerSecond 在调用之间被缓存。

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

【问题讨论】:

  • 为了记录,我将这个类托管在 IIS 网站中(当然托管在具有管理权限的应用程序池中)并从 ASMX 服务调用方法...

标签: c# asp.net iis performancecounter


【解决方案1】:

出于好奇,您在 Visual Studio 中为属性设置了什么?在 VS 中,转到项目属性、构建、平台目标并将其更改为 AnyCPU。我之前看到过,当性能计数器设置为x86 时,并不总是检索到它,将其更改为AnyCPU 可以解决它。

【讨论】:

  • 它是 AnyCPU... 我正在 x64 机器上编译并在 x64 机器上执行代码。
【解决方案2】:

我不知道,如果这通过你..我读过文章PerformanceCounter.NextValue Method

还有一条评论:

// If the category does not exist, create the category and exit.
// Performance counters should not be created and immediately used.
// There is a latency time to enable the counters, they should be created
// prior to executing the application that uses the counters.
// Execute this sample a second time to use the category.

所以,我有一个问题可以回答:对 RequestsPerSecond 方法的调用是不是太早了? 另外,我建议您尝试检查类别是否不存在并将信息记录在某处,以便我们对其进行分析并确定我们有哪些条件以及发生的频率。

【讨论】:

  • 我认为问题可能是 counterKey 是 const 的事实 - 所以锁定可能无法正常工作。我已将其更改为静态对象 counterKey = new object();前两天没有问题。
  • 嗯,这还有待观察......如果没有任何变化,我会接受你的回答;)
【解决方案3】:

Antenka 将您带向了一个好的方向。您不应该在每次更新/请求价值时处理和重新创建性能计数器。实例化性能计数器是有成本的,并且第一次读取可能不准确,如下面的引用所示。此外,您的 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
}

现在来解释一下。

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

此实现的好处是您只需创建一次性能计数器,然后继续使用直到完成。它易于使用,因为您可以从任何需要它的地方(.aspx、.asmx、控制台应用程序、winforms 应用程序等)简单地调用RequestsPerSecondCollector.GetRequestsPerSecond()。始终只有一个PerformanceCounter,并且无论您调用RequestsPerSecondCollector.GetRequestsPerSecond() 的速度有多快,它都将始终以每秒1 次的频率进行轮询。如果您在超过 5 秒内没有请求它的值,它也会自动关闭并处理 PerformanceCounter。当然,您可以调整计时器间隔和超时毫秒以满足您的需要。您可以更快地轮询并在 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 的文档以查看它是连续计数、平均值还是移动平均等...并适当调整您的实施。

【讨论】:

  • 伙计……你的回答太棒了;)。请阅读编辑(我已经解释了托管上下文)并尽可能编辑您的答案。有一件事特别困扰我(这就是我有 RefreshCounters 方法的原因) - 如果我不每隔一分钟左右重新创建计数器,pcReqsPerSec.NextValue() 就会变得“陈旧”......这个计数器是如何工作的,或者我在做什么有事吗?有没有办法只从最后一分钟获取 AVG 而无需重新创建 PerfCounter?
  • @BenSwayne,哇.. 真的很好,干净且易于理解的解决方案和理论!得到我的支持:)
  • @kape123 我更新了答案,以反映您希望每 60 秒重新创建一次性能计数器的愿望。我认为没有必要,但如果这是你想要的,你当然可以做到!
  • @BenSwayne 是的...在阅读您的答案后,我已将其添加到代码中。将查看有关 PerformanceCounters 的文档 - 但我记得他们计算的 AVG 是基于从一开始就采用的所有值(虽然我显然需要最近的值,最好只是最后一个......但我不想在每次之后重新创建计数器采用)。感谢您的回答!
  • @kape123 作为一个简单的测试,在 windows 窗体项目上抛出一个标签和计时器。在计时器中,用这个小类更新标签的文本。您在隔离的测试机器(如带有 IIS 的 win7)上运行它,然后在您点击您的站点时观察它报告的内容。对我来说,加载页面时总是跳起来,但总是归零。我假设如果它随着时间的推移使用所有收集的值,我会在它接近零但从未达到它时出现“衰减”(这意味着我会看到 0.02 或其他东西,因为它永远不会因为之前收集的值而达到零) .这个逻辑有意义吗?
【解决方案4】:

我刚刚解决了这种类型的错误或异常:

使用,

new PerformanceCounter("Processor Information", "% Processor Time", "_Total");

而不是,

new PerformanceCounter("Processor", "% Processor Time", "_Total");

【讨论】:

  • 处理器信息和仅处理器有什么区别?为什么一个有效而另一个无效?我在谷歌上找不到任何解释。
【解决方案5】:

我在 IIS 上使用类似于以下代码的每秒检索请求时遇到问题

var pc = new PerformanceCounter();
pc.CategoryName = @"W3SVC_W3WP";
pc.InstanceName = @"_Total";
pc.CounterName = @"Requests / Sec";
Console.WriteLine(pc.NextValue());

这有时会抛出InvalidOperationException,我可以通过重新启动 IIS 来重现异常。如果我使用未预热的 IIS 运行,例如笔记本电脑重新启动或 IIS 重新启动后,我得到了这个异常。首先访问网站,事先发出任何http请求,然后等待一两秒钟,我没有得到异常。这闻起来像是性能计数器被缓存了,当空闲时它们被转储,需要一段时间才能重新缓存? (或类似的)。

Update1:最初当我手动浏览网站并预热时,它解决了问题。我已经尝试以编程方式使用new WebClient().DownloadString(); Thread.Sleep() 将服务器预热至 3000 毫秒,但这没有奏效?因此,我手动预热服务器的结果可能会以某种方式出现误报。我在这里留下我的答案,因为这可能是原因(即手动预热),也许其他人可以进一步详细说明?

Update2:啊,好的,这里有一些单元测试,总结了我昨天做的进一步实验的一些经验。 (顺便说一句,谷歌上关于这个主题的内容并不多。)

据我所知,以下陈述可能是正确的; (我在下面提交单元测试作为证据。)我可能误解了结果,所以请仔细检查;-D

  1. 在类别存在之前创建性能计数器并调用 getValue,例如查询 IIS 计数器,当 IIS 处于冷态且没有进程运行时,将抛出 InvalidOperation 异常“类别不存在”。 (我认为这适用于所有计数器,而不仅仅是 IIS。)

  2. 在 Visual Studio 单元测试中,一旦您的计数器引发异常,如果您随后在第一个异常之后预热服务器,并创建一个新的 PerformanceCounter 并再次查询,它仍然会引发异常! (这是一个惊喜,我认为这是因为一些单例操作。抱歉,在发布此回复之前,我没有足够的时间来反编译源以进一步调查。)

  3. 在上面的 2 中,如果你用 [STAThread] 标记单元测试,那么我可以在一个失败后创建一个新的 PerformanceCounter。 (这可能与性能计数器可能是单例有关?需要进一步测试。)

  4. 在创建和使用计数器之前我不需要暂停,尽管在 MSDN 相同的代码文档中有一些警告,除了在调用 NextValue() 之前创建性能计数器本身所花费的时间。在我的情况下,预热柜台并使“类别”存在,对我来说是在 IIS 的弓上开一枪,即发出一个 GET 请求,中提琴,不再得到“InvalidOperationException”,这似乎是一个对我来说可靠的修复,现在。至少在查询 IIS 性能计数器时。

CreatingPerformanceCounterBeforeWarmingUpServerThrowsException

[Test, Ignore("Run manually AFTER restarting IIS with 'iisreset' at cmd prompt.")]
public void CreatingPerformanceCounterBeforeWarmingUpServerThrowsException()
{
    Console.WriteLine("Given a webserver that is cold");
    Console.WriteLine("When I create a performance counter and read next value");
    using (var pc1 = new PerformanceCounter())
    {
        pc1.CategoryName = @"W3SVC_W3WP";
        pc1.InstanceName = @"_Total";
        pc1.CounterName = @"Requests / Sec";
        Action action1 = () => pc1.NextValue();
        Console.WriteLine("Then InvalidOperationException will be thrown");
        action1.ShouldThrow<InvalidOperationException>();                
    }
}


[Test, Ignore("Run manually AFTER restarting IIS with 'iisreset' at cmd prompt.")]
public void CreatingPerformanceCounterAfterWarmingUpServerDoesNotThrowException()
{
    Console.WriteLine("Given a webserver that has been Warmed up");
    using (var client = new WebClient())
    {
        client.DownloadString("http://localhost:8082/small1.json");
    }
    Console.WriteLine("When I create a performance counter and read next value");
    using (var pc2 = new PerformanceCounter())
    {
        pc2.CategoryName = @"W3SVC_W3WP";
        pc2.InstanceName = @"_Total";
        pc2.CounterName = @"Requests / Sec";
        float? result = null;
        Action action2 = () => result = pc2.NextValue();
        Console.WriteLine("Then InvalidOperationException will not be thrown");
        action2.ShouldNotThrow();
        Console.WriteLine("And the counter value will be returned");
        result.HasValue.Should().BeTrue();
    }
}

【讨论】:

  • 如果您有任何问题,您应该考虑发布您自己的问题(如果合适,也可以引用这个问题)。就目前而言,您的帖子不是此处原始问题的答案,因此不属于此处。这是一个截然不同的问题。
猜你喜欢
  • 2017-07-21
  • 2015-02-16
  • 1970-01-01
  • 2010-09-13
  • 2023-03-14
  • 2011-09-28
  • 1970-01-01
  • 1970-01-01
  • 2010-11-21
相关资源
最近更新 更多