性能计数器 - 系统无效操作异常: 类别不存在

本文关键字:不存在 异常 操作 系统 无效 性能计数器 | 更新日期: 2023-09-27 18:14:54

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

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");]]]

我是否没有正确关闭以前的性能计数器实例?我做错了什么,以至于有时我最终会出现这种异常?

编辑:仅供记录,我将在 IIS 网站中托管此类(当然,托管在具有管理权限的应用程序池中(并从 ASMX 服务调用方法。使用计数器值(显示它们(的站点每 1 分钟调用一次刷新计数器,每 5 秒调用一次每秒请求数;RequestPerSecond 在调用之间缓存。

我每 1 分钟调用一次刷新计数器,因为值往往会变得"过时"——太受旧值的影响(例如,1 分钟前的实际值(。

性能计数器 - 系统无效操作异常: 类别不存在

>Antenka在这里带领你朝着一个好的方向前进。不应在每次更新/请求值时释放和重新创建性能计数器。实例化性能计数器会产生成本,并且第一次读取可能不准确,如下面的引用所示。此外,您的lock() { ... }语句非常广泛(它们涵盖了很多语句(并且会很慢。最好把锁尽可能小。我正在给Antenka投票以获得质量参考和好建议!

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

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. 计时器的回调方法将创建PerformanceCounter如果需要并获取其下一个值可用。同样每 5 次迭代一次我们将看看自您上次请求以来已经过去了多长时间 PerformanceCounter 的值。如果超过 5 秒,我们将关闭轮询计时器,因为目前不需要它。我们可以如果我们再次需要它,请务必稍后重新启动它。
  4. 现在我们有一个名为 GetRequestsPerSecond() 的静态方法供您使用调用,它将返回每秒请求数的当前值 PerformanceCounter .

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

希望这不仅可以帮助您更好地使用性能计数器,而且还可以安全地重用此类,该类与要在其中显示统计信息的任何内容分开。可重用代码始终是一个加号!

编辑:作为后续问题,如果要在此性能计数器运行时每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进行任何类型的清理或"照看"的方法。在这种情况下,我们将关闭并释放性能计数器,这将导致它在下次计时器回调时重新创建。您可以根据您的用例并根据您使用的特定性能计数器对其进行修改。大多数阅读此问题/答案的人不需要这样做。检查所需性能计数器的文档,看看它是连续计数、平均值、移动平均值等...并适当调整您的实现。

我不知道

,如果这通过了你..我读过文章性能计数器.下一个值方法

并且有评论:

// 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 方法不是发生得太早了吗?另外,我建议您尝试检查该类别是否存在并将信息记录在某处,以便我们可以对其进行分析并确定我们有哪些条件以及发生的频率。

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

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

而不是

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

我在 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请求,然后等待一两秒钟,我没有得到异常。这闻起来就像性能计数器被缓存了,当空闲时它们被转储,需要一段时间才能重新缓存?(或类似(。

更新1:最初当我手动浏览到网站并预热时,它解决了问题。我尝试以编程方式预热服务器,new WebClient().DownloadString(); Thread.Sleep()高达 3000 毫秒,但这不起作用?所以我手动预热服务器的结果可能以某种方式是误报。我在这里留下我的答案,因为这可能是原因(即手动预热(,也许其他人可以进一步详细说明?

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

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

  1. 创建一个性能计数器并在类别存在之前调用 getValue,例如,在 IIS 处于冷状态且没有进程运行时查询 IIS 计数器,将引发 InvalidOperation 异常"类别不存在"。(我假设这适用于所有计数器,而不仅仅是 IIS。

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

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

  4. 在创建计数器并使用它之前,我不需要暂停,尽管 MSDN 相同的代码文档中有一些警告,除了在调用 NextValue(( 之前创建性能计数器本身所需的时间。就我而言,预热计数器并使"类别"存在,对我来说是在 IIS 的弓上开一枪,即发出单个 GET 请求,中提琴不再得到"无效操作异常",这对我来说似乎是一个可靠的解决方案,目前。至少在查询 IIS 性能计数器时是这样。

CreateatingPerformanceCounterBeforeWarmingUpServerThrowsException

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

只是出于好奇,你在Visual Studio中为属性设置了什么? 在 VS 中,转到"项目属性"、"生成"、"平台目标"并将其更改为"AnyCPU"。 我以前见过它,当它设置为 x86 时,并不总是检索性能计数器,将其更改为 AnyCPU 可以修复它。