From c3ee660228762f4c212b57deb0281c4b046741e1 Mon Sep 17 00:00:00 2001 From: David Hall Date: Thu, 5 Sep 2019 15:32:41 -0600 Subject: [PATCH] Completed unit tests and updates for pdh.dll --- PInvoke/Pdh/Pdh.cs | 74 +++++++---- UnitTests/PInvoke/Pdh/PdhTests.cs | 270 +++++++++++++++++++++++++++++++++++--- 2 files changed, 300 insertions(+), 44 deletions(-) diff --git a/PInvoke/Pdh/Pdh.cs b/PInvoke/Pdh/Pdh.cs index 8c1f788e..1670be25 100644 --- a/PInvoke/Pdh/Pdh.cs +++ b/PInvoke/Pdh/Pdh.cs @@ -792,6 +792,31 @@ namespace Vanara.PInvoke [PInvokeData("pdh.h", MSDNShortId = "eaed9b28-eb09-4123-9317-5d3d50e2d77a")] public static extern Win32Error PdhBindInputDataSource(out SafePDH_HLOG phDataSource, [In, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(NullTermStringArrayMarshaler), MarshalCookie = "Auto")] string[] LogFileNameList); + /// Binds one or more binary log files together for reading log data. + /// + /// + /// One or more binary log files to bind together. The log file names can contain absolute or relative paths. You cannot specify more + /// than 32 log files. + /// + /// If NULL, the source is a real-time data source. + /// + /// Handle to the bound data sources. + /// + /// + /// This function is used with the PDH functions that require a handle to a data source. For a list of these functions, see See Also. + /// + /// + /// You cannot specify more than one comma-delimited (CSV) or tab-delimited (TSV) file. The list can contain only one type of + /// file—you cannot combine multiple file types. + /// + /// + [PInvokeData("pdh.h", MSDNShortId = "eaed9b28-eb09-4123-9317-5d3d50e2d77a")] + public static SafePDH_HLOG PdhBindInputDataSource(params string[] LogFileNameList) + { + var err = PdhBindInputDataSource(out var hLog, LogFileNameList is null || LogFileNameList.Length == 0 ? null : LogFileNameList); + return err.Succeeded ? hLog : throw err.GetException(); + } + /// /// /// Displays a Browse Counters dialog box that the user can use to select one or more counters that they want to add to the query. @@ -3835,7 +3860,7 @@ namespace Vanara.PInvoke // dwAccessFlags, LPDWORD lpdwLogType, PDH_HQUERY hQuery, DWORD dwMaxSize, LPCSTR szUserCaption, PDH_HLOG *phLog ); [DllImport(Lib.Pdh, SetLastError = false, CharSet = CharSet.Auto)] [PInvokeData("pdh.h", MSDNShortId = "a8457959-af3a-497f-91ca-0876cbb552cc")] - public static extern Win32Error PdhOpenLog(string szLogFileName, PdhLogAccess dwAccessFlags, ref PDH_LOG_TYPE lpdwLogType, [Optional] PDH_HQUERY hQuery, uint dwMaxSize, [Optional] string szUserCaption, out SafePDH_HLOG phLog); + public static extern Win32Error PdhOpenLog(string szLogFileName, PdhLogAccess dwAccessFlags, ref PDH_LOG_TYPE lpdwLogType, [Optional] PDH_HQUERY hQuery, [Optional] uint dwMaxSize, [Optional] string szUserCaption, out SafePDH_HLOG phLog); /// /// Creates a new query that is used to manage the collection of performance data. @@ -4704,10 +4729,10 @@ namespace Vanara.PInvoke /// of this parameter is PDH_MIN_SCALE (–7) (the returned value is the actual value times 10⁷) to PDH_MAX_SCALE (+7) (the /// returned value is the actual value times 10⁺⁷). A value of zero will set the scale to one, so that the actual value is returned /// - public int lScale; + public long lScale; /// Default scale factor as suggested by the counter's provider. - public int lDefaultScale; + public long lDefaultScale; /// The value passed in the dwUserData parameter when calling PdhAddCounter. public IntPtr dwUserData; @@ -4716,46 +4741,40 @@ namespace Vanara.PInvoke public IntPtr dwQueryUserData; /// Null-terminated string that specifies the full counter path. The string follows this structure in memory. - [MarshalAs(UnmanagedType.LPTStr)] public string szFullPath; - - /// A PDH_DATA_ITEM_PATH_ELEMENTS structure. Not used. - public PDH_DATA_ITEM_PATH_ELEMENTS DataItemPath; - - /// A PDH_COUNTER_PATH_ELEMENTS structure. - public PDH_COUNTER_PATH_ELEMENTS CounterPath; + public StrPtrAuto szFullPath; /// /// Null-terminated string that contains the name of the computer specified in the counter path. Is NULL, if the /// path does not specify a computer. The string follows this structure in memory. /// - [MarshalAs(UnmanagedType.LPTStr)] public string szMachineName; + public StrPtrAuto szMachineName; /// /// Null-terminated string that contains the name of the performance object specified in the counter path. The string /// follows this structure in memory. /// - [MarshalAs(UnmanagedType.LPTStr)] public string szObjectName; + public StrPtrAuto szObjectName; /// /// Null-terminated string that contains the name of the object instance specified in the counter path. Is NULL, if /// the path does not specify an instance. The string follows this structure in memory. /// - [MarshalAs(UnmanagedType.LPTStr)] public string szInstanceName; + public StrPtrAuto szInstanceName; /// /// Null-terminated string that contains the name of the parent instance specified in the counter path. Is NULL, if /// the path does not specify a parent instance. The string follows this structure in memory. /// - [MarshalAs(UnmanagedType.LPTStr)] public string szParentInstance; + public StrPtrAuto szParentInstance; /// Instance index specified in the counter path. Is 0, if the path does not specify an instance index. public uint dwInstanceIndex; /// Null-terminated string that contains the counter name. The string follows this structure in memory. - [MarshalAs(UnmanagedType.LPTStr)] public string szCounterName; + public StrPtrAuto szCounterName; /// Help text that describes the counter. Is NULL if the source is a log file. - [MarshalAs(UnmanagedType.LPTStr)] public string szExplainText; + public StrPtrAuto szExplainText; /// Start of the string data that is appended to the structure. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] @@ -4826,7 +4845,7 @@ namespace Vanara.PInvoke // CStatus; union { LONG longValue; double doubleValue; LONGLONG largeValue; LPCSTR AnsiStringValue; LPCWSTR WideStringValue; }; } // PDH_FMT_COUNTERVALUE, *PPDH_FMT_COUNTERVALUE; [PInvokeData("pdh.h", MSDNShortId = "68ccd722-94d2-4610-ba64-f51318f5436e")] - [StructLayout(LayoutKind.Explicit)] + [StructLayout(LayoutKind.Explicit, Size = 16)] public struct PDH_FMT_COUNTERVALUE { /// @@ -4834,26 +4853,26 @@ namespace Vanara.PInvoke /// displaying its value. For a list of possible values, see Checking PDH Interface Return Values. /// [FieldOffset(0)] - public uint CStatus; + public Win32Error CStatus; /// The computed counter value as a LONG. - [FieldOffset(0)] + [FieldOffset(8)] public int longValue; /// The computed counter value as a DOUBLE. - [FieldOffset(0)] + [FieldOffset(8)] public double doubleValue; /// The computed counter value as a LONGLONG. - [FieldOffset(0)] + [FieldOffset(8)] public long largeValue; /// The computed counter value as a LPCSTR. Not supported. - [FieldOffset(0)] + [FieldOffset(8)] public StrPtrAnsi AnsiStringValue; /// The computed counter value as a LPCWSTR. Not supported. - [FieldOffset(0)] + [FieldOffset(8)] public StrPtrUni WideStringValue; } @@ -5116,10 +5135,9 @@ namespace Vanara.PInvoke /// Size of the RawBytes data. public uint dwItems; - private byte _RawBytes; - /// Binary record. - public byte[] RawBytes => StructHelper.FieldToArray(ref _RawBytes, (int)dwItems); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] + public byte[] RawBytes; } /// @@ -5132,7 +5150,7 @@ namespace Vanara.PInvoke public struct PDH_STATISTICS { /// Format of the data. The format is specified in the dwFormat when calling PdhComputeCounterStatistics. - public uint dwFormat; + public PDH_FMT dwFormat; /// Number of values in the array. public uint count; @@ -5153,7 +5171,7 @@ namespace Vanara.PInvoke // https://docs.microsoft.com/en-us/windows/win32/api/pdh/ns-pdh-pdh_time_info typedef struct _PDH_TIME_INFO { LONGLONG StartTime; // LONGLONG EndTime; DWORD SampleCount; } PDH_TIME_INFO, *PPDH_TIME_INFO; [PInvokeData("pdh.h", MSDNShortId = "a747f288-8d6c-401c-a927-a61ffea3d423")] - [StructLayout(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential, Size = 24)] public struct PDH_TIME_INFO { /// Starting time of the sample interval, in local FILETIME format. diff --git a/UnitTests/PInvoke/Pdh/PdhTests.cs b/UnitTests/PInvoke/Pdh/PdhTests.cs index ed7efb36..f8066236 100644 --- a/UnitTests/PInvoke/Pdh/PdhTests.cs +++ b/UnitTests/PInvoke/Pdh/PdhTests.cs @@ -1,6 +1,8 @@ using NUnit.Framework; using System; using System.Linq; +using System.Runtime.InteropServices; +using System.Text; using System.Threading; using Vanara.Extensions; using Vanara.InteropServices; @@ -11,9 +13,9 @@ namespace Vanara.PInvoke.Tests [TestFixture] public class PdhTests { - private const string counterPath = "\\Processor(0)\\% Processor Time"; - private const string dsn = "Test"; - private const string logFile = @"C:\Temp\TestLogFile.etl"; + private const string counterPath = @"\Processor(0)\% Processor Time"; + private const string dsn = "TestSet"; + private const string logFile = @"C:\PerfLogs\Admin\TestSet\System Monitor Log.blg"; [Test] public void BrowsePerfCountersTest() @@ -65,8 +67,8 @@ namespace Vanara.PInvoke.Tests [Test] public void PdhBindInputDataSourceTest() { - throw new NotImplementedException(); - //Assert.That(PdhBindInputDataSource(), ResultIs.Successful); + Assert.That(PdhBindInputDataSource(out var hLog, new[] { logFile }), ResultIs.Successful); + hLog.Dispose(); } [Test] @@ -87,10 +89,6 @@ namespace Vanara.PInvoke.Tests Assert.That(PdhCollectQueryDataEx(Query, 0, evt), ResultIs.Successful); evt.WaitOne(100); } - - // Compute a displayable value for the counter. - Assert.That(PdhGetFormattedCounterValue(Counter, PDH_FMT.PDH_FMT_DOUBLE, out var CounterType, out var DisplayValue), ResultIs.Successful); - TestContext.WriteLine($",\"{DisplayValue.doubleValue}\""); } } } @@ -135,7 +133,7 @@ namespace Vanara.PInvoke.Tests Assert.That(PdhCalculateCounterFromRawValue(Counter, PDH_FMT.PDH_FMT_LONG, rawCounter1, rawCounter2, out var fmtValue), ResultIs.Successful); TestContext.WriteLine($"{fmtValue.longValue}"); - //Assert.That(PdhFormatFromRawValue(CounterType.PERF_100NSEC_TIMER) + Assert.That(PdhFormatFromRawValue(type, PDH_FMT.PDH_FMT_LONG, ft2, rawCounter1, rawCounter2, out fmtValue), ResultIs.Successful); } } } @@ -150,9 +148,10 @@ namespace Vanara.PInvoke.Tests Assert.That(PdhAddCounter(Query, counterPath, default, out var Counter), ResultIs.Successful); using (Counter) { - var first = 0U; - var values = new PDH_RAW_COUNTER[] { }; - Assert.That(PdhComputeCounterStatistics(Counter, PDH_FMT.PDH_FMT_LONG, first, (uint)values.Length, values, out var stats), ResultIs.Failure); + Assert.That(PdhGetRawCounterValue(Counter, out var type, out var rawCounter), ResultIs.Successful); + var values = new PDH_RAW_COUNTER[] { rawCounter }; + Assert.That(PdhComputeCounterStatistics(Counter, PDH_FMT.PDH_FMT_LONG, 0, (uint)values.Length, values, out var stats), ResultIs.Successful); + stats.WriteValues(); } } } @@ -253,6 +252,13 @@ namespace Vanara.PInvoke.Tests TestContext.WriteLine(string.Join("\n", strs)); } + [Test] + public void PdhExpandWildCardPathHTest() + { + Assert.That(CallMethodWithStrings((IntPtr p, ref uint sz) => PdhExpandWildCardPathH(default, @"\Process(*)\ID Process", p, ref sz, 0), out var strs), ResultIs.Successful); + TestContext.WriteLine(string.Join("\n", strs)); + } + [Test] public void PdhExpandWildCardPathTest() { @@ -261,10 +267,228 @@ namespace Vanara.PInvoke.Tests } [Test] - public void PdhExpandWildCardPathHTest() + public void PdhGetCounterInfoTest() { - Assert.That(CallMethodWithStrings((IntPtr p, ref uint sz) => PdhExpandWildCardPathH(default, @"\Process(*)\ID Process", p, ref sz, 0), out var strs), ResultIs.Successful); - TestContext.WriteLine(string.Join("\n", strs)); + Assert.That(PdhOpenQuery(null, default, out var Query), ResultIs.Successful); + using (Query) + { + // Add the selected counter to the query. + Assert.That(PdhAddCounter(Query, counterPath, default, out var Counter), ResultIs.Successful); + using (Counter) + { + Assert.That(PdhSetCounterScaleFactor(Counter, 1), ResultIs.Successful); + + uint sz = 0; + Assert.That(PdhGetCounterInfo(Counter, true, ref sz, default), ResultIs.FailureCode(Win32Error.PDH_MORE_DATA)); + using (var buffer = new SafeHGlobalHandle(sz)) + { + Assert.That(PdhGetCounterInfo(Counter, true, ref sz, buffer), ResultIs.Successful); + buffer.ToStructure().WriteValues(); + } + } + } + } + + [Test] + public void PdhGetCounterTimeBaseTest() + { + Assert.That(PdhOpenQuery(null, default, out var Query), ResultIs.Successful); + using (Query) + { + // Add the selected counter to the query. + Assert.That(PdhAddCounter(Query, counterPath, default, out var Counter), ResultIs.Successful); + using (Counter) + { + Assert.That(PdhGetCounterTimeBase(Counter, out var ft), ResultIs.Successful); + TestContext.Write(ft.ToString("U")); + } + } + } + + [Test] + public void PdhGetDataSourceTimeRangeHTest() + { + Assert.That(PdhBindInputDataSource(out var hLog, new[] { logFile }), ResultIs.Successful); + using (hLog) + { + uint sz = (uint)Marshal.SizeOf(); + Assert.That(PdhGetDataSourceTimeRangeH(hLog, out var cnt, out var info, ref sz), ResultIs.Successful); + Assert.That(cnt, Is.EqualTo(1)); + info.WriteValues(); + } + } + + [Test] + public void PdhGetDataSourceTimeRangeTest() + { + uint sz = (uint)Marshal.SizeOf(); + Assert.That(PdhGetDataSourceTimeRange(logFile, out var cnt, out var info, ref sz), ResultIs.Successful); + Assert.That(cnt, Is.EqualTo(1)); + info.WriteValues(); + } + + [Test] + public void PdhGetDefaultPerfCounterObjectHTest() + { + var sz = 1024U; + var sb = new StringBuilder((int)sz); + Assert.That(PdhGetDefaultPerfObjectH(PDH_HLOG.NULL, null, sb, ref sz), ResultIs.Successful); + TestContext.WriteLine($"DefObj: {sb}"); + + sz = (uint)sb.Capacity; + var obj = sb.ToString(); + Assert.That(PdhGetDefaultPerfCounterH(PDH_HLOG.NULL, null, obj, sb, ref sz), ResultIs.Successful); + TestContext.WriteLine($"DefCntr: {sb}"); + } + + [Test] + public void PdhGetDefaultPerfCounterObjectTest() + { + var sz = 1024U; + var sb = new StringBuilder((int)sz); + Assert.That(PdhGetDefaultPerfObject(null, null, sb, ref sz), ResultIs.Successful); + TestContext.WriteLine($"DefObj: {sb}"); + + sz = (uint)sb.Capacity; + var obj = sb.ToString(); + Assert.That(PdhGetDefaultPerfCounter(null, null, obj, sb, ref sz), ResultIs.Successful); + TestContext.WriteLine($"DefCntr: {sb}"); + } + + [Test] + public void PdhGetDllVersionTest() + { + Assert.That(PdhGetDllVersion(out var ver), ResultIs.Successful); + TestContext.Write(ver); + } + + [Test] + public void PdhGetFormattedRawCounterArrayTest() + { + Assert.That(PdhOpenQuery(null, default, out var Query), ResultIs.Successful); + using (Query) + { + // Add the selected counter to the query. + Assert.That(PdhAddCounter(Query, counterPath.Replace("(0)", "(*)"), default, out var Counter), ResultIs.Successful); + using (Counter) + { + Assert.That(PdhCollectQueryData(Query), ResultIs.Successful); + Assert.That(PdhCollectQueryData(Query), ResultIs.Successful); + + // Compute a displayable value for the counter. + using (var mem = new SafeHGlobalHandle(4096)) + { + var sz = (uint)mem.Size; + Assert.That(PdhGetFormattedCounterArray(Counter, PDH_FMT.PDH_FMT_DOUBLE, ref sz, out var n, mem), ResultIs.Successful); + mem.ToArray((int)n).WriteValues(); + + TestContext.WriteLine("==============================="); + + sz = (uint)mem.Size; + Assert.That(PdhGetRawCounterArray(Counter, ref sz, out n, mem), ResultIs.Successful); + mem.ToArray((int)n).WriteValues(); + } + } + } + } + + [Test] + public void PdhGetLogFileSizeTest() + { + var type = PDH_LOG_TYPE.PDH_LOG_TYPE_UNDEFINED; + Assert.That(PdhOpenLog(logFile, PdhLogAccess.PDH_LOG_READ_ACCESS | PdhLogAccess.PDH_LOG_OPEN_EXISTING, ref type, phLog: out var hlog), ResultIs.Successful); + using (hlog) + { + Assert.That(PdhGetLogFileSize(hlog, out var sz), ResultIs.Successful); + TestContext.Write(sz); + } + } + + [Test] + public void PdhLookupPerfNameByIndexTest() + { + var name = "Cache"; + Assert.That(PdhLookupPerfIndexByName(null, name, out var idx), ResultIs.Successful); + + var sz = 1024U; + var sb = new StringBuilder((int)sz); + Assert.That(PdhLookupPerfNameByIndex(null, idx, sb, ref sz), ResultIs.Successful); + + Assert.That(name, Is.EqualTo(sb.ToString())); + } + + [Test] + public void PdhMakeCounterPathTest() + { + var e = new PDH_COUNTER_PATH_ELEMENTS { szObjectName = "Processor", szInstanceName = "1", szCounterName = "% Processor Time" }; + var sz = 1024U; + var sb = new StringBuilder((int)sz); + Assert.That(PdhMakeCounterPath(e, sb, ref sz, 0, Kernel32.GetSystemDefaultLangID()), ResultIs.Successful); + TestContext.Write(sb); + } + + [Test] + public void PdhParseCounterPathTest() + { + using (var mem = new SafeCoTaskMemHandle(1024)) + { + uint sz = mem.Size; + Assert.That(PdhParseCounterPath(counterPath, mem, ref sz), ResultIs.Successful); + mem.ToStructure().WriteValues(); + } + } + + [Test] + public void PdhParseInstanceNameTest() + { + var sz1 = 1024U; + var sb1 = new StringBuilder((int)sz1); + var sz2 = 1024U; + var sb2 = new StringBuilder((int)sz2); + Assert.That(PdhParseInstanceName("dog/cat#1", sb1, ref sz1, sb2, ref sz2, out var idx), ResultIs.Successful); + Assert.That(sb2.ToString(), Is.EqualTo("dog")); + Assert.That(idx, Is.EqualTo(1)); + } + + [Test] + public void PdhReadRawLogRecordTest() + { + using (var hlog = PdhBindInputDataSource(logFile)) + using (var mem = new SafeCoTaskMemHandle(1024)) + { + uint sz = (uint)Marshal.SizeOf(); + Assert.That(PdhGetDataSourceTimeRangeH(hlog, out var cnt, out var info, ref sz), ResultIs.Successful); + TestContext.WriteLine($"Start:{info.StartTime.ToString("U")}; End:{info.EndTime.ToString("U")}; Cnt:{info.SampleCount}"); + + sz = mem.Size; + // TODO: Can't get this to return anything but PDH_ENTRY_NOT_IN_LOG_FILE + Assert.That(PdhReadRawLogRecord(hlog, info.StartTime, mem, ref sz), ResultIs.FailureCode(Win32Error.PDH_ENTRY_NOT_IN_LOG_FILE)); + //var rec = mem.ToStructure(); + //rec.WriteValues(); + //TestContext.Write(mem.ToArray((int)rec.dwItems, 12).ToHexString((int)rec.dwItems)); + } + } + + [Test] + public void PdhSelectDataSourceTest() + { + var sz = 1024U; + var sb = new StringBuilder((int)sz); + Assert.That(PdhSelectDataSource(default, PdhSelectDataSourceFlags.Default, sb, ref sz), ResultIs.Successful); + } + + [Test] + public void PdhSetQueryTimeRangeTest() + { + using (var hlog = PdhBindInputDataSource(logFile)) + { + uint sz = (uint)Marshal.SizeOf(); + Assert.That(PdhGetDataSourceTimeRangeH(hlog, out var cnt, out var info, ref sz), ResultIs.Successful); + + Assert.That(PdhOpenQueryH(hlog, default, out var Query), ResultIs.Successful); + using (Query) + Assert.That(PdhSetQueryTimeRange(Query, info), ResultIs.Successful); + } } [Test] @@ -275,6 +499,8 @@ namespace Vanara.PInvoke.Tests using (var tmp = new TempFile(null)) using (hQuery) { + Assert.That(PdhIsRealTimeQuery(hQuery), Is.True); + // Add one counter that will provide the data. Assert.That(PdhAddEnglishCounter(hQuery, counterPath, default, out var hCounter), ResultIs.Successful); @@ -286,6 +512,18 @@ namespace Vanara.PInvoke.Tests } } + [Test] + public void PdhValidatePathExWTest() + { + Assert.That(PdhValidatePathExW(PDH_HLOG.NULL, counterPath), ResultIs.Successful); + } + + [Test] + public void PdhValidatePathTest() + { + Assert.That(PdhValidatePath(counterPath), ResultIs.Successful); + } + private static Win32Error CallMethodWithStrings(FunctionHelper.PtrFunc method, out string[] result) { var sz = 0U;