Tricks with Windows Performance Counters for Automated Tests

I really like Windows Performance Counters. I find them a powerful tool for understanding applications under development and for monitoring and troubleshooting applications in production environments. Windows Performance Counters are available for reporting absolute values (e.g., Memory\Available Bytes), counts (e.g., Network Adapter\Packets Received Discarded), rates (e.g., PhysicalDisk\Disk Transfers/sec), and averages (e.g., PhysicalDisk\Avg. Disk sec/Transfer), among others. In this post I'll describe two somewhat unconventional techniques I have used incorporating Performance Counters in automated tests.

Sample the Performance Counter Across the Entire Test

When writing performance or scalability tests, it is often useful to measure a variable across the test. For example, the average CPU usage; the average, minimum and maximum memory usage; the average disk queue length; or the number of insertions per second into the database. Collecting these statistics with custom code can be challenging and susceptible to sampling errors.

Performance Counters are fundamentally just raw values stored in shared memory. The calculated values are computed by the client only when the counters are sampled. This makes Performance Counters very lightweight and efficient, but it also leaves the client in control of the sampling interval, which can be very powerful.

For performance tests, I take one sample at the beginning of the test and another sample at the end of the test. This will represent the exact count, rate, average, minimum, maximum, etc. across the test. The statistic will be accurate and reliable and I don't have to write custom code to calculate it.

Consider the following C# test as an example. The test performs some IO intensive database operation and measures the average disk queue length across the test.

[TestMethod]
public void MeasureAverageDiskQueueLengthForDatabaseOperation()
{
   var counter = new PerformanceCounter("PhysicalDisk", "Avg. Disk Queue Length", "0 C:");
   
   // Call NextValue once to initialize the counter
   // The first call will always return 0
   counter.NextValue();
   
   // Perform some IO intensive database operation
   PerformDatabaseOperation();

   // Sampling the Performance Counter again will calculate the average across the test
   var avgDiskQueueLength = counter.NextValue();

   // Record the results
   Console.WriteLine("Avg. Disk Queue Length : {0}", avgDiskQueueLength);
}

Use Raw Values Not Just Calculated Values

A second technique I have used is to sample the raw value of the Performance Counter instead of the calculated value. For example, say I want to record the number of disk input/output operations (IOs) that result from a certain database operation. To my knowledge, there is no direct way to measure the number of disk IOs on a volume. There is, however, a counter for Disk Transfers/sec which is the rate of IOs, IOs per second, commonly referred to as IOPS.

Performance Counters that report a rate are implemented by simply incrementing a value in shared memory each time an operation occurs. The rate is calculated by the client only when the counter is sampled. The client uses the value at the start of the interval, the value at the end of the interval, and the length of the interval, to compute the rate. Therefore, we can infer the number of disk IOs by using the raw value of the counter.

[TestMethod]
public void MeasureDiskIOsForDatabaseOperation()
{
    var counter = new PerformanceCounter("LogicalDisk", "Disk Transfers/sec", "C:");

    // Get the raw value of the counter before the test
    var initialDiskIOsCount = counter.RawValue;

    // Perform some IO intensive database operation
    PerformDatabaseOperation();

    // Get the raw value of the counter after the test
    var finalDiskIOsCount = counter.RawValue;

    // Record the results
    Console.WriteLine("Disk IOs : {0}", finalDiskIOsCount - initialDiskIOsCount);
}

Conclusion

Windows Performance Counters are a robust and powerful tool for testing applications. Sampling them programmatically in tests by controlling the sampling interval or sampling the raw counter value can be very powerful. Recording metrics like this from automated tests can be useful for identifying regressions, quantifying performance improvements, and informing service-level agreement (SLA) and capacity planning decisions. Happy testing.