Simplified Performance Counters for Timed Operations





4.00/5 (1 vote)
A generalized code block which can be used in a common scenario for simple performance benchmarking.
Introduction
Setting up and using Performance Counters with the .NET API requires a bit of setup and configuration. This article presents a generalized code block which can be used in a common scenario for simple performance benchmarking.

Background
Finding how fast your application runs would be tricky business lacking any way to measure it. In Windows, we've got the built-in Performance Counters which can easily time operations and gather statistics about your software while it executes.
.NET offers a fairly robust API for accessing Performance Counters. It exposes classes and methods for setting and incrementing the counter values, and also the ability to create all the different varieties directly from your program. For an excellent starting point for using all of these, check out An Introduction To Performance Counters by Michael Groeger and Using performance counters to gather performance data by Shivprasad koirala.
As some superhero may have mentioned, "With great flexibility comes great complexity", so we'll look at a way to wrap a common usage with a reusable code block.
Using the Code
My first annoyance with the performance counter classes in .NET is the need to initialize two different classes for a single counter - one for creating it and another for interacting with the values. Since they have several overlapping properties which, by the way, need to be exactly the same or if they won't work, the definitions can be easily coupled:
class CounterCreationCounter
{
public CounterCreationData Creation { get; private set; }
public PerformanceCounter Counter { get; private set; }
public CounterCreationCounter(
string counterName, string counterHelp,
PerformanceCounterType counterType, string categoryName)
{
Creation = new CounterCreationData {
CounterName = counterName,
CounterHelp = counterHelp,
CounterType = counterType };
Counter = new PerformanceCounter {
CategoryName = cate2goryName,
CounterName = counterName,
MachineName = MACHINE_NAME, // this constant is set to "."
// for localhost
ReadOnly = false };
}
}
Next, I want a way to define a group of common counters that will be relevant for measuring operations while benchmarking. To keep the article a reasonable size, I've selected just two:
- Number of operations per second
- Average duration of the operation in ticks
There are several more (Total number of operations, and Most recent duration) in the source code which are defined in the same manner.
class OperationalCounterGroup
{
private readonly CounterCreationCounter _countPerSec;
private readonly CounterCreationCounter _averageDuration;
private readonly CounterCreationCounter _averageDurationBase;
public OperationalCounterGroup(string operationName, string categoryName)
{
_countPerSec
= new CounterCreationCounter(
string.Format("{0} # / sec", operationName),
string.Format("Number of {0} per second", operationName),
PerformanceCounterType.RateOfCountsPerSecond32,
categoryName);
_averageDuration
= new CounterCreationCounter(
string.Format("{0} avg duration", operationName),
string.Format("Average duration for {0} in ticks", operationName),
PerformanceCounterType.AverageTimer32,
categoryName);
_averageDurationBase
= new CounterCreationCounter(
string.Format("{0} avg duration base", operationName),
string.Format("Average duration for {0} in ticks base", operationName),
PerformanceCounterType.AverageBase,
categoryName);
}
/// <summary>
/// Operation is complete - increment all counters
/// </summary>
public void OperationCompleted(TimeSpan elapsed)
{
_countPerSec.Counter.Increment();
_averageDuration.Counter.IncrementBy(elapsed.Ticks);
_averageDurationBase.Counter.Increment();
}
public IEnumerable GetCreationData()
{
yield return _countPerSec.Creation;
yield return _averageDuration.Creation;
yield return _averageDurationBase.Creation;
}
}
Notice that the OperationCompleted
method has the duty of incrementing all of the counters once an operation has been completed. Now, how is all of this wired to something we'd want to actually measure? First, I'll define an interface which exposes an OpeartionCompleted
event that will be raised by the object when it has something to measure.
public delegate
void OperationCompletedHandler(string operationName, TimeSpan elapsed);
public interface IOperationCompletable
{
event OperationCompletedHandler OperationCompleted;
}
... and here is the OperationPerformanceCounter
class to glue it all together. It requires an object implementing IOperationCompletable
, along with a list of operations that will be measured.
class OperationPerformanceCounter
{
/// <summary>Performance Counter category name</summary>
private readonly string _category;
/// <summary>The operations being raised in IOperationCompletable
/// to watch</summary>
private readonly string[] _handledOperations;
/// <summary>Counter group lookup</summary>
private readonly IDictionary<string, OperationalCounterGroup> _counterGroups;
/// <summary>Handler for IOperationCompletable.OperationCompleted</summary>
private void OperationCompleted(string operationName, TimeSpan elapsed)
{
// Only handle operations we know about
if (_counterGroups.ContainsKey(operationName)) {
// do all the increments in the PerformanceCounter
// objects for this particular operation
_counterGroups[operationName].OperationCompleted(elapsed);
}
}
/// <summary>Constructor</summary>
public OperationPerformanceCounter(string categoryName,
string[] handledOperations,
IOperationCompletable operationCompletable)
{
// Performance category name
_category = categoryName;
// List of all the operations that we're interested in measuring
_handledOperations = handledOperations;
// create Counter and Creation objects for each of the
// interesting operations
_counterGroups =
(from c in _handledOperations
select new {Name = c, Group =
new OperationalCounterGroup(c, _category)})
.ToDictionary(o => o.Name, o => o.Group);
// Create (or update) the counters in Windows
UpdateOrCreateCategory((from c in _counterGroups
from cd in c.Value.GetCreationData()
select cd).ToArray());
// Watch the object for the operations completing
operationCompletable.OperationCompleted += OperationCompleted;
}
}
The UpdateOrCreateCategory
method handles all of the Performance Counter creation automatically. It will also recognize if there are new (or removed) operations and update the local counters:
void UpdateOrCreateCategory(CounterCreationData[] counterCreationData)
{
// update mode
if (PerformanceCounterCategory.Exists(_category))
{
var installedCounters = new HashSet<string>(
from ec in new PerformanceCounterCategory(_category).GetCounters()
select ec.CounterName);
var knownCounters = new HashSet<string>(
from c in counterCreationData
select c.CounterName);
// if the installed ones are different, then update
if (!installedCounters.SetEquals(knownCounters))
{
DeleteCategory();
CreateCategory(counterCreationData);
}
}
else
{
CreateCategory(counterCreationData);
}
}
void CreateCategory(CounterCreationData[] counterCreationData)
{
PerformanceCounterCategory.Create(
_category, "", PerformanceCounterCategoryType.Unknown,
new CounterCreationDataCollection(counterCreationData));
PerformanceCounter.CloseSharedResources();
}
The code to configure and use OperationPerformanceCounter
is quite simple. First, an object implementing IOperationCompletable
. Here is a demonstration - this object can raise events for two operations that we're interested in - FastOp
and SlowOp
.
class DemoObject : IOperationCompletable
{
public event OperationCompletedHandler OperationCompleted;
private int _fastOpSleep;
public void FastOp()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Thread.Sleep(_fastOpSleep);
sw.Stop();
_fastOpSleep += 1; // increment how long it takes next time by 1
OperationCompleted("FastOp", sw.Elapsed);
}
private int _slowOpSleep;
public void SlowOp()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Thread.Sleep(_slowOpSleep);
sw.Stop();
_slowOpSleep += 2; // twice as slow compared to FastOp!
OperationCompleted("SlowOp", sw.Elapsed);
}
}
And a simple harness in a console application. It calls the operations in separate threads so that the Performance Monitor graphs will directly overlap.
static void Main()
{
var demoObject = new DemoObject();
var opCounter =
new OperationPerformanceCounter(
"CodeProject - Simplified Performance Counters",
new[] {"FastOp", "SlowOp"},
demoObject);
var threads
= new[]
{new Thread(() => LoopAction(demoObject.FastOp)),
new Thread(() => LoopAction(demoObject.SlowOp))};
foreach (var t in threads)
t.Start();
foreach (var t in threads) // wait for completion
t.Join();
}
/// <summary>Runs the given action a number of times in a loop</summary>
private static void LoopAction(Action action)
{
for (var i = 0; i < 200; i++)
action();
}
Running this will add the performance counters and record the following data - here are the resulting graphs created while comparing "Operations per Second" and "Average Duration" for "FastOp
" and "SlowOp
".

History
- 20th March, 2010: Initial post