23 August 2007

.NET Performance Counter Problems

I've recently been doing some work with performance counters in .NET and discovered the support for them in the framework is a bit nasty.

One example is that the only way to add counters is through the PerformanceCounterCategory.Create method so you can't append a new counter to an existing category. You have to copy all the counters from the old category, add you new counter, delete the old category and then recreate the category with the new list of counters.

Another example is that in order to use any of the fraction based counters like averages you have to add two separate counters, one for the numerator and one for the denominator.

All very long winded anyway so I decided that I'd wrap up the creation of the counters and categories along with a few other bits in a helper class. That was the plan but it fell at the first hurdle - trying to create categories.

The symptom

This, seemingly innocuous bit of code...

if(!PerformanceCounterCategory.Exists(categoryName))
{
 PerformanceCounterCategory.Create(categoryName, categoryName, new CounterCreationDataCollection() );
}

if(!PerformanceCounterCategory.CounterExists(counterName, categoryName))
{
 //add a counter
}

...was yielding the following error on line 6:

System.InvalidOperationException: Category does not exist.
   at System.Diagnostics.PerformanceCounterLib.CounterExists(String machine, Str
ing category, String counter)
   at System.Diagnostics.PerformanceCounterCategory.CounterExists(String counter
Name, String categoryName, String machineName)
   at System.Diagnostics.PerformanceCounterCategory.CounterExists(String counter
Name, String categoryName)

i.e. three lines after the statement creating a category it says the category doesn't exist. Odd, I thought, so I knocked together a quick Snippet Compiler script:

string categoryName = "foo";
string counterName = "bar";
  
if(PerformanceCounterCategory.Exists(categoryName))
 PerformanceCounterCategory.Delete(categoryName);
  
WL("Category " + categoryName + " exists? " + PerformanceCounterCategory.Exists(categoryName));
  
PerformanceCounterCategory.Create(categoryName, categoryName, new CounterCreationDataCollection() );
  
WL("Category " + categoryName + " exists? " + PerformanceCounterCategory.Exists(categoryName));
      
try
{
 WL("Counter foobar exists? " + PerformanceCounterCategory.CounterExists(counterName, categoryName));
}
catch(Exception ex)
{
 WL(ex.ToString());
}
  
RL();

Further oddness arose from this because the call to PerformanceCounterCategory.Exists after the Create actually gives the correct response, it is only the CounterExists method that fails. I immediately suspected some sort of caching issue.

The cause

After having a poke around with Lutz Roeder's .NET Reflector I discovered that all the Performance Counter classes make use of an internal class called PerformanceCounterLib which has a few hashtables it uses to cache information about the categories and their counters.

The hashtable concerned here was the CategoryTable which, on adding a category for he first time, does not update properly. When the CounterExists call runs this line:

if(this.CategoryTable.ContainsKey(category))

it fails throwing the error however the Exists call has this line:

return (PerformanceCounterLib.IsCustomCategory(machineName, categoryName) 
   || PerformanceCounterLib.CategoryExists(machineName, categoryName));

the right part of this calls the same CategoryTable.ContainsKey however the IsCustomCategory on the left accesses the registry directly and returns the correct answer - true.

Why the CounterExists doesn't call this method itself and opts instead to provide its own implementation i've no idea.

The solution

Luckily the PerformanceCounter.CloseSharedResources() method flushes all these internal caches so the calls all return the same answer. Adding a call to this straight after my PerformanceCounterCategory.Create solved the problem.

5 comments:

Brett said...

Thanks, this helped me

Rohit said...

Thanks a lot for this Blog ...
This is exactly what I was looking for

D. said...

Very informative - found it via topic "Custom Performance Counter Updates the Wrong Counter" in the MSDN forums.

However the PerformanceCounter.ClosedSharedResources() did not solve it for me - only a reboot of the server did it, after half a day of tracing/debugging trying to figure out why my new performance-counter's value appeared in a previously created performance-counter.

Anonymous said...

Great post. I was experiencing same scenario where CounterExists was throwing an exception and once it starts all subsequent calls to the same throws the same exception. I tried PerformanceCounter.Dispose but did not help. I overlooked the method msdn doc multiple times. This posrt came as rescu and great relief. Thanks a ton

Tosh said...

Years later (Mar 2016) this post explained why PerformanceCounterCategory.Exists("MyCategory") worked the first time but then didn't change as I created/deleted the category.