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

我有以下类返回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(); } } } } 

问题是有时抛出以下exception:

 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的先前实例吗? 我做错了什么,以至于我有时会遇到这种exception?

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

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

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 ///  /// Static members are 'eagerly initialized', that is, /// immediately when class is loaded for the first time. /// .NET guarantees thread safety for static initialization. ///  private static readonly RequestsPerSecondCollector _instance = new RequestsPerSecondCollector(); #endregion #region Constructor/Finalizer ///  /// Private constructor for static singleton instance construction, you won't be able to instantiate this class outside of itself. ///  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 ,并获得其下一个值。 此外,我们每隔5次迭代就会看到自上次请求PerformanceCounter值以来已经过了多长时间。 如果它超过5秒,我们将关闭轮询计时器,因为此时不需要它。 如果我们再次需要,我们可以在以后再次启动它。
  4. 现在我们有一个名为GetRequestsPerSecond()的静态方法供您调用,该方法将返回RequestsPerSecond PerformanceCounter的当前值。

此实现的好处是,您只需创建一次性能计数器,然后继续使用,直到完成它为止。 它易于使用,因为您可以从任何需要的地方(.aspx,.asmx,控制台应用程序,winforms应用程序等)简单地调用RequestsPerSecondCollector.GetRequestsPerSecond() )。 总是只有一个PerformanceCounter ,无论你多快调用RequestsPerSecondCollector.GetRequestsPerSecond() ,它总是每秒轮询1次。 如果您在超过5秒内未请求其值,它还将自动关闭并处置PerformanceCounter 。 当然,您可以调整计时器间隔和超时毫秒以满足您的需要。 您可以更快地轮询并在60秒而不是5秒内超时。我选择了5秒,因为它certificate它在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; } } 

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

我不知道,如果这通过了你…我已经阅读了文章PerformanceCounter.NextValue方法

还有评论:

 // 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方法? 此外,我建议您尝试检查类别是否不存在并在某处记录信息,以便我们分析它并确定我们拥有哪些条件以及发生的频率。

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

使用,

 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来重现exception。 如果我使用非预热的IIS运行,例如在笔记本电脑重启或IIS重启后,我就会遇到此exception。 首先点击网站,事先发出任何http请求,然后等待一两秒,我就不会得到例外。 这有点像性能计数器被缓存,当空闲时它们被转储,并需要一段时间来重新缓存? (或类似的)。

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

Update2 :啊,好的,这里有一些unit testing,总结了我昨天进行的进一步实验的一些经验教训。 (关于这个主题的谷歌没有很多btw。)

据我所知,以下陈述可能属实; (我提交下面的unit testing作为证据。)我可能误解了结果,所以请仔细检查;-D

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

  2. 从Visual Studiounit testing中,一旦您的计数器抛出exception,如果您随后在第一个exception之后预热服务器,并再次创建一个新的PerformanceCounter并进行查询,它仍会抛出exception! (这个是一个惊喜,我认为这是因为一些单身人士行动。我道歉我没有足够的时间来反编译来源进一步调查,然后发布这个回复。)

  3. 在上面的2中,如果用[STAThread]标记unit testing,那么我可以在一个失败后创建一个新的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(); } } [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可以修复它。