Performance counters are critical for tracking the overall performance of your application over time. If you are responsible for tracking and improving the performance of your system, performance counters will help you do that. While you can (and should) use them for real-time monitoring, they can be even more valuable if you store them in a database for analysis over long periods of time. In this way, you can see how new releases, usage patterns, or other events affect the performance of your application.
You can consume performance counters in your own code for self-monitoring, archiving, or automated analysis. You can also create and register your own counters which are then available to the same monitoring approach. By correlating your program’s custom counters with the system’s counters for your application, you can often find the source of problems very quickly.
Performance counters are Windows-managed objects that track values over time. These values can be arbitrary numbers, counts, rates, time spans, or other types detailed later. Each counter has a category and a name associated with it. Most counters also have instances, which are specific subdivisions by logical, discrete entities. For example, the % Processor Time counter in the Processor category has instances for each process currently running. Many counters also have meta-instances like _Total or <Global> that aggregate the data across all instances.
Many components in Windows create their own performance counters and .NET is no exception. There are hundreds of counters available to you to track nearly every aspect of your program’s performance. These counters are all described in the relevant chapters earlier in this book.
To track all the installed performance counters on a system, use the PerfMon.exe utility that comes with Windows, as described in Chapter 1. This current chapter discusses programmatic access to these counters, both consuming and creating your own.
To consume a counter, you just need to create a new instance of the PerformanceCounter class and pass it the category and name you want to monitor. You can optionally supply the instance and machine name as well. Here is an example that attaches the counter object to the % Processor Time counter.
PerformanceCounter cpuCtr = new PerformanceCounter("Process",
"% Processor Time", process.ProcessName);
To retrieve values, you periodically call the NextValue method on the counter:
float value = cpuCtr.NextValue();
The API documentation recommends that you call NextValue no more frequently than once per second to allow the counter sufficient time to do the next read.
To see a simple project that demonstrates consuming multiple built-in and custom counters, see the accompanying PerfCountersTypingSpeed sample.
To create your own custom counter, you create an instance of the CounterCreationData class, supplying a name and type. You add this to a collection, which is then added to a category.
const string CategoryName = "PerfCountersTypingSpeed";
if (!PerformanceCounterCategory.Exists(CategoryName))
{
var counterDataCollection = new CounterCreationDataCollection();
var wpmCounter = new CounterCreationData();
wpmCounter.CounterType = PerformanceCounterType.RateOfCountsPerSecond32;
wpmCounter.CounterName = "WPM";
counterDataCollection.Add(wpmCounter);
try
{
// Create the category.
PerformanceCounterCategory.Create(
CategoryName,
"Demo category to show how to create and consume counters",
PerformanceCounterCategoryType.SingleInstance,
counterDataCollection);
}
catch (SecurityException )
{
// Handle error -- no permissions to make this change!
}
}
To create a custom counter, you must have the PerformanceCounterPermission granted. In practice, this means that you should usually create new counters with an installation program that can run with elevated permissions. .NET provides the PerformanceCounterInstaller class, which can wrap multiple instances of the CounterCreationData class and install them for you, with support for rollback and removal.
There are many types of counters, grouped into a few categories, as detailed next. In addition, some counters have 32-bit and 64-bit sizes defined. You can use whichever one is most appropriate for the data you are recording.
These counters show the average of the last two measurements.
The AverageCount64 and AverageTimer32 counters need the help of a second counter, AverageBase, to determine how many operations were completed since the last time the counter was updated. The AverageBase counter must be initialized right after the counter to which it applies. The following code demonstrates how to create these two counters together:
var counterDataCollection = new CounterCreationDataCollection();
// Actual average counter
var bytesPerTx = new CounterCreationData();
bytesPerTx.CounterType = PerformanceCounterType.AverageCount64;
bytesPerTx.CounterName = "BytesPerTransmission";
counterDataCollection.Add(bytesPerTx);
// Base counter to help in calculations
var bytesPerTxBase = new CounterCreationData();
bytesPerTxBase.CounterType = PerformanceCounterType.AverageBase;
bytesPerTxBase.CounterName = "BytesPerTransmissionBase";
counterDataCollection.Add(bytesPerTxBase);
PerformanceCounterCategory.Create(
"Network Statistics",
"Network statistics demo counters",
PerformanceCounterCategoryType.SingleInstance,
counterDataCollection);
To set the values, you adjust each counter according to the number of items and the number of operations to which those items apply. In this example, it is fairly simple:
bytesPerTx.IncrementBy(request.Length);
bytesPerTxBase.IncrementBy(1);
These are the simplest counters. They just reflect the most recent sample value.
Delta counters show the difference between the last two counter values.
Percentage counters show the percent of a resource being used. In some cases, this percent can be greater than 100%. Consider a multiprocessor system: you could represent the CPU usage as a percentage of a single core. Each instance of the counter represents one of the cores. If multiple cores are simultaneously in use, then the percentage will be larger than 100.
All of the CounterMulti- counters require the use of the CounterMultiBase, similar to the AverageCount64 example earlier.
When creating your own performance counters, note that you should not update them too often. A maximum of once per second is a good rule because the data will never be exposed more frequently than that. If you need to generate high-volume performance data, ETW events are a much better choice.
Performance counters are the most basic building block of performance analysis. While your program does not necessarily have to create its own, consider doing so if you have discrete operations, phases, or items that have performance impact.
Consider automated ingestion and analysis of counters via .NET APIs to provide archiving and continual feedback about system performance.