Exposing Custom Performance Counters in .NET
One of the first things I do as part of the .NET Performance course is demonstrating some of the ways we have for measuring the performance of individual applications and of the system as a whole.
As part of that quest, we encounter tools that require no ad-hoc participation on our part: Fire them up, give them an executable or a process to do their magic, and analyze the results. Some other tools need more love and care on our part to produce anything remotely useful.
A profiler can tell you that you're spending 90% of the time in the SpamHouse.GenerateLotsOfEmails method, even if it condemns the idea and even if you haven't actively aided the profiler when you implemented the method. On the other hand, performance counters are among those tools that can't tell you anything without our participation, because there are no performance counters for your business logic by default.
Sprinkling performance counters into the application code is not an easy task. You basically attempt to predict what problems you are going to have and where you're going to have them and put performance counters around these sections of code to facilitate instrumentation. Doing this by hand is tedious, although .NET makes it relatively easy; a more appropriate solution is using some sort of AOP framework to make it happen.
For example, the following attribute-based design could specify that all employees should have a performance counter attached monitoring the number of hours they worked this week:
[PerformanceCounterCategory]
public class Employee : IIdentity
{
private int _hours;
public string Id { get; set; }
public string Name { get; set; }
[PerformanceCounter]
public int HoursWorked
{
get { return ++_hours; }
}
public Employee(string id, string name)
{
Id = id;
Name = name;
}
}
The IIdentity implementation (which is an interface I made up, not the .NET security interface) is there to enforce an instance name for each performance counter instance that is being created.
To actually monitor these objects, we use a background timer that updates all counters once a second. We use weak references to the actual instances to ensure that we don't have a memory leak because of performance counters (i.e., if an object is no longer referenced by the application, the performance counters infrastructure we just built is not going to keep it alive). For example, assume we have the following code:
Employee e1 = Activator.CreateInstance<Employee>("12", "Joe");
Employee e2 = Activator.CreateInstance<Employee>("15", "Kate");
GC.Collect();
Thread.Sleep(TimeSpan.FromSeconds(5));
GC.KeepAlive(e1);
GC.Collect();
Thread.Sleep(TimeSpan.FromSeconds(5));
GC.KeepAlive(e2);
GC.Collect();
Console.ReadLine();
If we look at the results in Performance Monitor, we will see that the first employee object is getting collected and stops working 20 seconds before the other. The reason is that the garbage collector collects the first employee object after the control in the Main method passes the GC.KeepAlive line (yes, the GC is that eager to collect unused objects!).
The point at which we can intercept the fact an object needs to be monitored is the point of instantiation, so we provide our own activator (this is very similar to the technique we've used in the past when implementing the Design-By-Contract sample):
public static class Activator
{
public static T CreateInstance<T>(params object[] args)
where T : class, IIdentity
{
EnsureTimerActive();
EnsureTypeRegistered<T>();
T instance = (T)System.Activator.CreateInstance(typeof(T), args);
RegisterCountersFor<T>(instance);
return instance;
}
}
This activator relies on System.Activator to perform the actual instantiation, but it ensures that the timer is active, that the type is registered (i.e. the performance counter category exists), and finally registers the object's properties to be monitored by the background thread.
The sample code used to implement this facility can be downloaded from my SkyDrive. Please note that it's sample code - it's inefficient, lacks thread-safety and generally suffers from a quick-and-dirty approach. However, it shows that the infrastructure code for introducing performance counters into your application can be written once, and its fruits can be enjoyed basically in every module you write that might one day require instrumentation.