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