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.