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.

7 comments:

Brett said...

Thanks, this helped me

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

Michael Powell said...

Posted in 2007. Yikes. I'm not sure it's gotten any better. Performance Counters has been giving me fits for a couple solid couple of days now. And here I thought I might be the only one. Heavens no. I can create a category just fine. But when I turn around to work with "new" PerformanceCounters associated with the category, with known names (via the CounterCreationData), I get all sorts of errors like Counter does not exist:

Xunit.Sdk.AllException
Assert.All() Failure: 4 out of 4 items in the collection did not pass.
[3]: System.InvalidOperationException: Counter c52de162309844e490b8d63acecde8b1 does not exist.

That's progress, I think; earlier it was that the counter in the category did not exist. That's a difference of using different constructors and/or initializer lists from the PerformanceCounter perspective. Even that is problematic.

I am also not sure this has to do with OS version; I am developing on a Windows 7 Professional 64-bit.

Michael Powell said...

Overall, the PerformanceCounter(s) are created, but certain properties fail, such as CounterHelp. I also get exceptions like this:

- base {System.InvalidOperationException: Counter 39dcd46cfa3649acac2cad0e427691d1 does not exist.
at System.Diagnostics.PerformanceCounterLib.GetCounterHelp(String category, String counter, Boolean& categoryExists)
at System.Diagnostics.PerformanceCounterLib.GetCounterHelp(String machine, String category, String counter)
at System.Diagnostics.PerformanceCounter.get_CounterHelp()} System.Exception {System.InvalidOperationException}
+ Data {System.Collections.ListDictionaryInternal} System.Collections.IDictionary {System.Collections.ListDictionaryInternal}
HelpLink null string
HResult -2146233079 int
+ InnerException null System.Exception
Message "Counter 39dcd46cfa3649acac2cad0e427691d1 does not exist." string
Source "System" string
StackTrace " at System.Diagnostics.PerformanceCounterLib.GetCounterHelp(String category, String counter, Boolean& categoryExists)\r\n at System.Diagnostics.PerformanceCounterLib.GetCounterHelp(String machine, String category, String counter)\r\n at System.Diagnostics.PerformanceCounter.get_CounterHelp()" string
+ TargetSite {System.String GetCounterHelp(System.String, System.String, Boolean ByRef)} System.Reflection.MethodBase {System.Reflection.RuntimeMethodInfo}
+ Static members
+ Non-Public members