Click here to Skip to main content
15,885,944 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  
A generalized code block which can be used in a common scenario for simple performance benchmarking.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace OperationPerformanceCounter.Diagnostics
{
	/// <summary>
	/// Watches an IOperationCompletable and updates the
	/// configured performance counters as the events are received
	/// </summary>
	public class OperationPerformanceCounter
	{
		/// <summary>
		/// Use the local machine
		/// </summary>
		private const string MACHINE_NAME = ".";

		/// <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>
		/// A PerformanceCounter and its CreationData
		/// </summary>
		private 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 = categoryName,
				          		CounterName = counterName,
				          		MachineName = MACHINE_NAME,
				          		ReadOnly = false
				          	};
			}
		}

		/// <summary>
		/// A group of PerformanceCounters used for measuring
		/// operational timing
		/// </summary>
		private class OperationalCounterGroup
		{
			private readonly CounterCreationCounter _countPerSec;

			private readonly CounterCreationCounter _totalCount;

			private readonly CounterCreationCounter _averageDuration;

			private readonly CounterCreationCounter _averageDurationBase;

			private readonly CounterCreationCounter _mostRecentDuration;

			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);

				_totalCount
					= new CounterCreationCounter(
						string.Format("{0} total #", operationName),
						string.Format("Total number of {0}", operationName),
						PerformanceCounterType.NumberOfItems32,
						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);

				_mostRecentDuration
					= new CounterCreationCounter(
						string.Format("{0} most recent duration", operationName),
						string.Format("Most recent duration for {0} in ticks", operationName),
						PerformanceCounterType.NumberOfItems32,
						categoryName);
			}

			/// <summary>
			/// Increment all counters
			/// </summary>
			public void OperationCompleted(TimeSpan elapsed)
			{
				_totalCount.Counter.Increment();
				_countPerSec.Counter.Increment();
				_averageDuration.Counter.IncrementBy(elapsed.Ticks);
				_averageDurationBase.Counter.Increment();
				_mostRecentDuration.Counter.RawValue = elapsed.Ticks;
			}

			public IEnumerable<CounterCreationData> GetCreationData()
			{
				yield return _countPerSec.Creation;
				yield return _totalCount.Creation;
				yield return _averageDuration.Creation;
				yield return _averageDurationBase.Creation;
				yield return _mostRecentDuration.Creation;
			}
		}

		/// <summary>
		/// Handler for operation completion
		/// </summary>
		/// <param name="operationName">Name of the operation.</param>
		/// <param name="elapsed">Time elapsed.</param>
		private void OperationCompleted(string operationName, TimeSpan elapsed)
		{
			if (_counterGroups.ContainsKey(operationName))
				_counterGroups[operationName].OperationCompleted(elapsed);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="OperationPerformanceCounter"/> class.
		/// </summary>
		/// <param name="categoryName">Name of the category.</param>
		/// <param name="handledOperations">The handled operations.</param>
		/// <param name="operationCompletable">The object to watch for operations completing.</param>
		public OperationPerformanceCounter(string categoryName, string[] handledOperations,
		                                   IOperationCompletable operationCompletable)
		{
			_category = categoryName;
			_handledOperations = handledOperations;

			_counterGroups =
				(from c in _handledOperations
				 select new {Name = c, Group = new OperationalCounterGroup(c, _category)})
					.ToDictionary(o => o.Name, o => o.Group);

			UpdateOrCreateCategory((from c in _counterGroups
			                        from cd in c.Value.GetCreationData()
			                        select cd).ToArray());

			operationCompletable.OperationCompleted += OperationCompleted;
		}

		/// <summary>
		/// Updates existing or Creates category and the counters
		/// </summary>
		private void UpdateOrCreateCategory(CounterCreationData[] counterCreationData)
		{
			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 (!installedCounters.SetEquals(knownCounters))
				{
					DeleteCategory();
					CreateCategory(counterCreationData);
				}
			}
			else
			{
				CreateCategory(counterCreationData);
			}
		}

		/// <summary>
		/// Creates a category with the given counter creation data
		/// </summary>
		private void CreateCategory(CounterCreationData[] counterCreationData)
		{
			PerformanceCounterCategory.Create(
				_category, "", PerformanceCounterCategoryType.Unknown,
				new CounterCreationDataCollection(counterCreationData));
			PerformanceCounter.CloseSharedResources();
		}

		/// <summary>
		/// Deletes the category.
		/// </summary>
		public void DeleteCategory()
		{
			if (!PerformanceCounterCategory.Exists(_category)) return;

			PerformanceCounterCategory.Delete(_category);
			PerformanceCounter.CloseSharedResources();
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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