Click here to Skip to main content
15,878,959 members
Articles / Programming Languages / C#

Simplified Performance Counters for Timed Operations

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
20 Mar 2010CC (ASA 2.5)3 min read 31.2K   273   20   3
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.

OperationPerformanceCounter_AddCounters.png

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:

C#
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.

C#
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<countercreationdata /> 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.

C#
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.

C#
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:

C#
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.

C#
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.

C#
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".

OperationPerformanceCounter_OperationsPerSecond.png

OperationPerformanceCounter_AverageDuration.png

History

  • 20th March, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWhat about the StopWatch class Pin
Steve Messer20-Mar-10 17:39
Steve Messer20-Mar-10 17:39 
AnswerRe: What about the StopWatch class Pin
James Kolpack20-Mar-10 17:57
James Kolpack20-Mar-10 17:57 
GeneralRe: What about the StopWatch class Pin
Steve Messer21-Mar-10 11:03
Steve Messer21-Mar-10 11:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.