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

发布于 2024-12-15 23:25:47 字数 2019 浏览 1 评论 0原文

我有以下类,它返回 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 网站中(当然,托管在具有管理权限的应用程序池中)并从 ASMX 服务调用方法。使用 Counter 值(显示它们)的站点每 1 分钟调用 RefreshCounters 一次,每 5 秒调用 RequestsPerSecond 一次; RequestPerSecond 在调用之间被缓存。

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

I have following class that returns number of current Request per Second of IIS. I call RefreshCounters every minute in order to keep Requests per Second value refreshed (because it is average and if I keep it too long old value will influence result too much)... and when I need to display current RequestsPerSecond I call that property.

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

The problem is that following Exception is sometimes thrown:

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

Am I not closing previous instances of PerformanceCounter properly? What am I doing wrong so that I end up with that exception sometimes?

EDIT:
And just for the record, I am hosting this class in IIS website (that is, of course, hosted in App Pool which has administrative privileges) and invoking methods from ASMX service. Site that uses Counter values (displays them) calls RefreshCounters every 1 minute and RequestsPerSecond every 5 seconds; RequestPerSecond are cached between calls.

I am calling RefreshCounters every 1 minute because values tend to become "stale" - too influenced by older values (that were actual 1 minute ago, for example).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

千仐 2024-12-22 23:25:47

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
}

好的,现在进行一些解释。

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

此实现的好处是您只需创建一次性能计数器,然后继续使用直到完成为止。它易于使用,因为您只需从需要的任何地方(.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 的文档以查看它是否是连续计数、平均值或平均值。移动平均线等...并适当调整您的实施。

Antenka has led you in a good direction here. You should not be disposing and re-creating the performance counter on every update/request for value. There is a cost for instantiating the performance counters and the first read can be inaccurate as indicated in the quote below. Also your lock() { ... } statements are very broad (they cover a lot of statements) and will be slow. Its better to have your locks as small as possible. I'm giving Antenka a voteup for the quality reference and good advice!

However, I think I can provide a better answer for you. I have a fair bit of experience with monitoring server performance and understand exactly what you need. One problem your code doesn't take into account is that whatever code is displaying your performance counter (.aspx, .asmx, console app, winform app, etc) could be requesting this statistic at any rate; it could be requested once every 10 seconds, maybe 5 times per second, you don't know and shouldn't care. So you need to separate the PerformanceCounter collection code from that does the monitoring from the code that actually reports the current Requests / Second value. And for performance reasons, I'm also going to show you how to setup the performance counter on first request and then keep it going until nobody has made any requests for 5 seconds, then close/dispose the PerformanceCounter properly.

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
}

Ok now for some explanation.

  1. First you'll notice this class is designed to be a static singleton.
    You can't load multiple copies of it, it has a private constructor
    and and eagerly initialized internal instance of itself. This makes
    sure you don't accidentally create multiple copies of the same
    PerformanceCounter.
  2. Next you'll notice in the private constructor (this will only run
    once when the class is first accessed) we create both the
    PerformanceCounter and a timer which will be used to poll the
    PerformanceCounter.
  3. The Timer's callback method will create the PerformanceCounter if
    needed and get its next value is available. Also every 5 iterations
    we're going to see how long its been since your last request for the
    PerformanceCounter's value. If it's been more than 5 seconds, we'll
    shutdown the polling timer as its unneeded at the moment. We can
    always start it up again later if we need it again.
  4. Now we have a static method called GetRequestsPerSecond() for you to
    call which will return the current value of the RequestsPerSecond
    PerformanceCounter.

The benefits of this implementation are that you only create the performance counter once and then keep using until you are finished with it. Its easy to use because you simple call RequestsPerSecondCollector.GetRequestsPerSecond() from wherever you need it (.aspx, .asmx, console app, winforms app, etc). There will always be only one PerformanceCounter and it will always be polled at exactly 1 times per second regardless of how quickly you call RequestsPerSecondCollector.GetRequestsPerSecond(). It will also automatically close and dispose of the PerformanceCounter if you haven't requested its value in more than 5 seconds. Of course you can adjust both the timer interval and the timeout milliseconds to suit your needs. You could poll faster and timeout in say 60 seconds instead of 5. I chose 5 seconds as it proves that it works very quickly while debugging in visual studio. Once you test it and know it works, you might want a longer timeout.

Hopefully this helps you not only better use PerformanceCounters, but also feel safe to reuse this class which is separate from whatever you want to display the statistics in. Reusable code is always a plus!

EDIT: As a follow up question, what if you want to performance some cleanup or babysitting task every 60 seconds while this performance counter is running? Well we already have the timer running every 1 second and a variable tracking our loop iterations called stateCounter which is incremented on each timer callback. So you could add in some code like this:

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

I should point out that this performance counter in the example should not "go stale". I believe 'Request / Sec" should be an average and not a moving average statistic. But this sample just illustrates a way you could do any type of cleanup or "babysitting" of your PerformanceCounter on a regular time interval. In this case we are closing and disposing the performance counter which will cause it to be recreated on next timer callback. You could modify this for your use case and according the specific PerformanceCounter you are using. Most people reading this question/answer should not need to do this. Check the documentation for your desired PerformanceCounter to see if it is a continuous count, an average, a moving average, etc... and adjust your implementation appropriately.

黯淡〆 2024-12-22 23:25:47

我不知道,如果这通过了你..我读过文章 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 方法的调用是否发生得太早了?
另外,我建议您尝试检查类别是否不存在并将信息记录在某处,以便我们可以对其进行分析并确定我们拥有哪些条件以及发生这种情况的频率。

I don't know, if this passes you .. I've read article PerformanceCounter.NextValue Method

And there was a comment:

// 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.

So, I have a question, which can lead to answer: isn't call to a RequestsPerSecond method happends too early?
Also, I would suggest you to to try check if the Category doesn't exists and log the info somewhere, so we can analyze it and determine which conditions we have and how often that happends.

醉梦枕江山 2024-12-22 23:25:47

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

使用,

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

而不是,

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

I just solved this type of error or exception with:

Using,

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

Instead of,

new PerformanceCounter("Processor", "% Processor Time", "_Total");
南城旧梦 2024-12-22 23:25:47

我在使用类似于以下代码的 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 单元测试中,一旦计数器抛出异常,如果您随后在第一个异常之后预热服务器,并创建一个新的PerformanceCounter再查询,依然会抛出异常! (这是一个惊喜,我认为这是因为一些单例操作。很抱歉,在发布此回复之前,我没有足够的时间反编译源代码以进行进一步调查。)

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

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

在WarmingUpServerThrowsException之前创建PerformanceCounter

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

I had an issue retrieving requests per second on IIS using code similar to the following

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

This would sometimes throw InvalidOperationException and I was able to reproduce the exception by restarting IIS. If I run with a non warmed up IIS, e.g. after a laptop reboot or IIS restart, then I get this exception. Hit the website first, make any http request beforehand, and wait a second or two and I don't get the exception. This smells like the performance counters are cached,and when Idle they get dumped, and take a while to re-cache? (or similar).

Update1: Initially when I manually browse to the website and warm it up, it solves the problem. I've tried programmatically warming up the server with new WebClient().DownloadString(); Thread.Sleep() up to 3000ms and this has not worked? So my results of manually warming up server, might somehow be a false positive. I'm leaving my answer here, because it might be the cause, (i.e. manual warming up), and maybe someone else can elaborate further?

Update2: Ah, ok, here are some unit tests that summarises some learning from further experimenting I did yesterday. (There's not a lot on google on this subject btw.)

As far as I can reason, the following statements might be true; (and I submit the unit tests underneath as evidence.) I may have misinterpreted the results, so please double check ;-D

  1. Create a performance counter and calling getValue before the category exists, e.g. querying an IIS counter, while IIS is cold and no process running, will throw InvalidOperation exception "category does not exist". (I assume this is true for all counters, and not just IIS.)

  2. From within a Visual Studio unit test, once your counter throws an exception, if you subsequently warm up the server after the first exception, and create a new PerformanceCounter and query again, it will still throw an exception! (this one was a surprise, I assume this is because of some singleton action. My apologies I have not had enough time to decompile the sources to investigate further before posting this reply.)

  3. In 2 above, if you mark the unit test with [STAThread] then I was able to create a new PerformanceCounter after one has failed. (This might have something to do with Performance counter possibly being singletons? Needs further testing.)

  4. No pause was required for me before creating counter and using it, despite some warnings in MSDN same code documentation, other than the time it takes to create a performance counter itself before calling NextValue().In my case, to warm up the counter and bring the "category" into existance, was for me to fire one shot across the bow of IIS, i.e. make a single GET request, and viola, no longer get "InvalidOperationException", and this seems to be a reliable fix for me, for now. At least when querying IIS performance counters.

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();
    }
}
西瓜 2024-12-22 23:25:47

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

Just out of curiousity, what do you have set for properties in Visual Studio? In VS go to Project Properties, Build, Platform target and change it to AnyCPU. I have seen it before where Performance Counters aren't always retrieved when it is set to x86, and changing it to AnyCPU could fix it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文