Problem: The counter classes provided by .Net framework are useful for reading and writing live counter values but do not appear to be able to bind to performance logs.
Soltuion: Use the PDH API via P/Invoke. Performance Monitoring in MSDN provides all of the basic information on the PDH API and some samples in C++.
Notes:
- Some counters require multiple samples to be calculated. PdhCollectQueryData will need to be called multiple times prior to PdhGetFormattedCounterValue in that case. This demo is using Processor \% Processor Time which requires two samples.
- Multiple log files can be queried together using PdhBindInputDataSource along with the API functions ending in "H".
- Use System.Runtime.Interop.Marshal to unpack strings from PDH_FMT_COUNTERVALUE if required.
The following is a simple console application that demonstrates using the API in C# and is a basic port of the example provided in MSDN.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.Security.Permissions;
using System.Security;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
namespace PdhLogDemo
{
#region PDH Definitions
///
/// A safe wrapper around a PDH Log handle.
///
///
/// Use this along with PdhBindInputDataSource and the "H" APIs to bind multiple logs together
///
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public class PdhLogHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public PdhLogHandle() : base(true)
{
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
return PdhApi.PdhCloseLog(handle, PdhApi.PDH_FLAGS_CLOSE_QUERY) == 0;
}
}
///
/// A safe wrapper arounda query handle
///
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public class PdhQueryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public PdhQueryHandle()
: base(true)
{
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
return PdhApi.PdhCloseQuery(handle) == 0;
}
}
///
/// A safe handle around a counter
///
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public class PdhCounterHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public PdhCounterHandle()
: base(true)
{
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
return PdhApi.PdhRemoveCounter(handle) == 0;
}
}
///
/// The value of a counter as returned byAPI.
///
///In C/C++ this is a union.
[StructLayout(LayoutKind.Explicit)]
public struct PDH_FMT_COUNTERVALUE
{
[FieldOffset(0)]
public UInt32 CStatus;
[FieldOffset(1)]
public int longValue;
[FieldOffset(1)]
public double doubleValue;
[FieldOffset(1)]
public long longLongValue;
[FieldOffset(1)]
public IntPtr AnsiStringValue;
[FieldOffset(1)]
public IntPtr WideStringValue;
}
///
/// The requested format for theAPI.
///
[Flags()]
public enum PdhFormat : uint
{
PDH_FMT_RAW = 0x00000010,
PDH_FMT_ANSI = 0x00000020,
PDH_FMT_UNICODE = 0x00000040,
PDH_FMT_LONG = 0x00000100,
PDH_FMT_DOUBLE = 0x00000200,
PDH_FMT_LARGE = 0x00000400,
PDH_FMT_NOSCALE = 0x00001000,
PDH_FMT_1000 = 0x00002000,
PDH_FMT_NODATA = 0x00004000
}
///
/// Static class containing some usefull PDH API's
///
[SuppressUnmanagedCodeSecurity()]
internal class PdhApi
{
#region A few common flags and status codes
public const UInt32 PDH_FLAGS_CLOSE_QUERY = 1;
public const UInt32 PDH_NO_MORE_DATA = 0xC0000BCC;
public const UInt32 PDH_INVALID_DATA = 0xC0000BC6;
public const UInt32 PDH_ENTRY_NOT_IN_LOG_FILE = 0xC0000BCD;
#endregion
///
/// Opens a query handle
///
///
///
///
///
[DllImport("pdh.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern UInt32 PdhOpenQuery(
string szDataSource,
IntPtr dwUserData,
out PdhQueryHandle phQuery);
///
/// Opens a query against a bound input source.
///
///
///
///
///
[DllImport("pdh.dll", SetLastError = true)]
public static extern UInt32 PdhOpenQueryH(
PdhLogHandle hDataSource,
IntPtr dwUserData,
out PdhQueryHandle phQuery);
///
/// Binds multiple logs files together.
///
///Use this along with the API's ending in 'H' to string multiple files together.
///
///
///
[DllImport("pdh.dll", SetLastError = true)]
public static extern UInt32 PdhBindInputDataSource(
out PdhLogHandle phDataSource,
string szLogFileNameList);
///
/// Closes a handle to a log
///
///
///
///
[DllImport("pdh.dll", SetLastError = true)]
public static extern UInt32 PdhCloseLog(
IntPtr hLog,
long dwFlags);
///
/// Closes a handle to the log
///
///
///
[DllImport("pdh.dll", SetLastError = true)]
public static extern UInt32 PdhCloseQuery(
IntPtr hQuery);
///
/// Removes a counter from the given query.
///
///
///
[DllImport("pdh.dll", SetLastError = true)]
public static extern UInt32 PdhRemoveCounter(
IntPtr hQuery);
///
/// Adds a counter the query and passes out a handle to the counter.
///
///
///
///
///
///
[DllImport("pdh.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern UInt32 PdhAddCounter(
PdhQueryHandle hQuery,
string szFullCounterPath,
IntPtr dwUserData,
out PdhCounterHandle phCounter);
///
/// Retrieves a sample from the source.
///
///
///
[DllImport("pdh.dll", SetLastError = true)]
public static extern UInt32 PdhCollectQueryData(
PdhQueryHandle phQuery);
///
/// Retrieves a specific counter value in the specified format.
///
///
///
///
///
///
[DllImport("pdh.dll", SetLastError = true)]
public static extern UInt32 PdhGetFormattedCounterValue(
PdhCounterHandle phCounter,
PdhFormat dwFormat,
IntPtr lpdwType,
out PDH_FMT_COUNTERVALUE pValue);
}
#endregion
class Program
{
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("PdhLogDemo is a simple C# port of the sample application describe in the PDH API MSDN help.");
Console.WriteLine("The sample takes a single argument which should be the path to perf log containing at least two samples");
Console.WriteLine(@"of the counter \Processor(0)\% Processor Time");
return;
}
//the name of the counter
string counterName = @"\Processor(0)\% Processor Time";
//the location of the log
string log = args[0]; //@"C:\perflogs\Admin\New Data Collector Set\000002\New Data Collector.blg";
PdhQueryHandle query;
PdhCounterHandle counter;
UInt32 result = PdhApi.PdhOpenQuery(log, IntPtr.Zero, out query);
if (result != 0)
{
Console.WriteLine("Unable to open query. Return was: " + result.ToString());
return;
}
//Add the counter that we want to see
//The PdhEnumXXX methods can be used to discover them
result = PdhApi.PdhAddCounter(query, counterName, IntPtr.Zero, out counter);
if (result != 0)
{
//0xC0000BCD (PDH_ENTRY_NOT_IN_LOG_FILE) means the counter was not found in the set
Console.WriteLine("Unable to add counter. Return was: " + result.ToString());
return;
}
//Since we're using % processor time which requires two samples call this once initially to pull the first sample
result = PdhApi.PdhCollectQueryData(query);
if (result == PdhApi.PDH_NO_MORE_DATA)
{
Console.WriteLine("The log file contains no data.");
return;
}
do
{
result = PdhApi.PdhCollectQueryData(query);
if (result == 0)
{
PDH_FMT_COUNTERVALUE value;
result = PdhApi.PdhGetFormattedCounterValue(counter, PdhFormat.PDH_FMT_DOUBLE, IntPtr.Zero, out value);
//Check CStatus to make sure it is ok
//0xC0000BC6 (PDH_INVALID_DATA) can be returned when more samples are needed to calculate the value. Call PdhCollectQueryData
if (value.CStatus == 0)
{
Console.WriteLine("Value = " + value.doubleValue.ToString());
}
else
{
Console.WriteLine("Bad CStatus on Value: " + value.CStatus.ToString());
}
}
}
while (result == 0);
}
}
}