Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance Counter - System.InvalidOperationException: Category does not exist

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

like image 629
nikib3ro Avatar asked Nov 17 '11 17:11

nikib3ro


1 Answers

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.

like image 110
BenSwayne Avatar answered Oct 09 '22 03:10

BenSwayne