From b5d1fc565082fa7c2d200a4515be33ef6a1ea398 Mon Sep 17 00:00:00 2001 From: "Doug Cook (WINDOWS)" Date: Sat, 4 Feb 2023 14:30:30 -0800 Subject: [PATCH 1/4] TraceEvent - don't use undocumented APIs Stop using ETWControl and ETWParsing from OSExtensions.dll. Replace them with implementations that use Microsoft-supported APIs. - Use TraceSetInformation for configuring profile interval (no change from existing behavior other than using supported API). - Use TraceSetInformation for configuring stack caching (same behavior, except that the new API is only supported on Windows 10 19582+). - Use TdhEnumerateManifestProviderEvents for reading manifest data (works perfectly for manifests, but doesn't work for MOF). Still using OSExtensions.dll for KernelTraceControl stuff, but that's supported by Microsoft. So perhaps the KernelTraceControl stuff could move into TraceEvent and then OSExtensions.dll would no longer be needed at all. --- src/PerfView/PerfView.csproj | 2 +- src/TraceEvent/RegisteredTraceEventParser.cs | 84 ++++++++++++++------ src/TraceEvent/TraceEventNativeMethods.cs | 13 +++ src/TraceEvent/TraceEventSession.cs | 29 +++++-- 4 files changed, 95 insertions(+), 33 deletions(-) diff --git a/src/PerfView/PerfView.csproj b/src/PerfView/PerfView.csproj index 4fb1f4073..300104fdd 100644 --- a/src/PerfView/PerfView.csproj +++ b/src/PerfView/PerfView.csproj @@ -512,7 +512,7 @@ System.Memory.dll False - + Non-Resx false .\System.Numerics.Vectors.dll diff --git a/src/TraceEvent/RegisteredTraceEventParser.cs b/src/TraceEvent/RegisteredTraceEventParser.cs index fdb36c5be..21fdaacc4 100644 --- a/src/TraceEvent/RegisteredTraceEventParser.cs +++ b/src/TraceEvent/RegisteredTraceEventParser.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. using FastSerialization; using Microsoft.Diagnostics.Tracing.Compatibility; -using Microsoft.Diagnostics.Tracing.Extensions; using Microsoft.Diagnostics.Tracing.Session; using System; using System.Collections.Generic; @@ -88,7 +87,7 @@ public static string GetManifestForRegisteredProvider(string providerName) public static string GetManifestForRegisteredProvider(Guid providerGuid) { int buffSize = 84000; // Still in the small object heap. - byte* buffer = (byte*)System.Runtime.InteropServices.Marshal.AllocHGlobal(buffSize); + var buffer = new byte[buffSize]; // Still in the small object heap. byte* enumBuffer = null; TraceEventNativeMethods.EVENT_RECORD eventRecord = new TraceEventNativeMethods.EVENT_RECORD(); @@ -129,32 +128,55 @@ public static string GetManifestForRegisteredProvider(Guid providerGuid) } } - for (int ver = 0; ver <= 255; ver++) + int status; + + for (; ; ) { - eventRecord.EventHeader.Version = (byte) ver; - int count; - int status; - for (; ; ) + int size = buffer.Length; + status = TdhEnumerateManifestProviderEvents(ref eventRecord.EventHeader.ProviderId, buffer, ref size); + if (status != 122 || 20000000 < size) // 122 == Insufficient buffer keep it under 2Meg { - int dummy; - status = ETWParsing.TdhGetAllEventsInformation(&eventRecord, IntPtr.Zero, out dummy, out count, buffer, ref buffSize); - if (status != 122 || 20000000 < buffSize) // 122 == Insufficient buffer keep it under 2Meg - { - break; - } + break; + } + + buffer = new byte[size]; + } - Marshal.FreeHGlobal((IntPtr)buffer); - buffer = (byte*)Marshal.AllocHGlobal(buffSize); + if (status == 0) + { + const int NumberOfEventsOffset = 0; + const int FirstDescriptorOffset = 8; + int eventCount = BitConverter.ToInt32(buffer, NumberOfEventsOffset); + var descriptors = new EVENT_DESCRIPTOR[eventCount]; + fixed (EVENT_DESCRIPTOR* pDescriptors = descriptors) + { + Marshal.Copy(buffer, FirstDescriptorOffset, (IntPtr)pDescriptors, descriptors.Length * sizeof(EVENT_DESCRIPTOR)); } - // TODO FIX NOW deal with too small of a buffer. - if (status == 0) + foreach (var descriptor in descriptors) { - TRACE_EVENT_INFO** eventInfos = (TRACE_EVENT_INFO**)buffer; - for (int i = 0; i < count; i++) + var descriptorCopy = descriptor; + + for (; ; ) + { + int size = buffer.Length; + status = TdhGetManifestEventInformation(ref eventRecord.EventHeader.ProviderId, ref descriptorCopy, buffer, ref size); + if (status != 122 || 20000000 < size) // 122 == Insufficient buffer keep it under 2Meg + { + break; + } + + buffer = new byte[size]; + } + + if (status != 0) + { + continue; + } + + fixed (byte* eventInfoBuff = buffer) { - TRACE_EVENT_INFO* eventInfo = eventInfos[i]; - byte* eventInfoBuff = (byte*)eventInfo; + var eventInfo = (TRACE_EVENT_INFO*)eventInfoBuff; EVENT_PROPERTY_INFO* propertyInfos = &eventInfo->EventPropertyInfoArray; if (providerName == null) @@ -406,17 +428,12 @@ public static string GetManifestForRegisteredProvider(Guid providerGuid) eventWriter.WriteLine("/>"); } } - else if (status == 1168 && ver != 0) // Not Found give up - { - break; - } } if (enumBuffer != null) { System.Runtime.InteropServices.Marshal.FreeHGlobal((IntPtr)enumBuffer); } - System.Runtime.InteropServices.Marshal.FreeHGlobal((IntPtr)buffer); if (providerName == null) { throw new ApplicationException("Could not find provider with at GUID of " + providerGuid.ToString()); @@ -979,6 +996,21 @@ internal static extern int TdhGetEventMapInformation( ref int infoSize ); + [DllImport("tdh.dll")] + internal static extern int TdhEnumerateManifestProviderEvents( + [In] ref Guid Guid, + [Out] byte[] pBuffer, // Actually PROVIDER_EVENT_INFO* + ref int pBufferSize + ); + + [DllImport("tdh.dll")] + internal static extern int TdhGetManifestEventInformation( + [In] ref Guid Guid, + [In] ref EVENT_DESCRIPTOR eventDesc, + [Out] byte[] pBuffer, + [In, Out] ref int pBufferSize // size in bytes + ); + [Flags] internal enum MAP_FLAGS { diff --git a/src/TraceEvent/TraceEventNativeMethods.cs b/src/TraceEvent/TraceEventNativeMethods.cs index 1b7b28dfd..013962833 100644 --- a/src/TraceEvent/TraceEventNativeMethods.cs +++ b/src/TraceEvent/TraceEventNativeMethods.cs @@ -275,6 +275,9 @@ internal enum TRACE_INFO_CLASS TraceLbrConfigurationInfo = 20, // Filter flags TraceLbrEventListInfo = 21, // int array + + // Win 10 build 19582+ + TraceStackCachingInfo = 24, // TRACE_STACK_CACHING_INFO }; internal struct CLASSIC_EVENT_ID @@ -290,6 +293,16 @@ internal struct CLASSIC_EVENT_ID public int Interval; }; + internal struct TRACE_STACK_CACHING_INFO + { + public byte Enabled; + public byte Padding1; + public byte Padding2; + public byte Padding3; + public int CacheSize; + public int BucketCount; + } + internal struct PROFILE_SOURCE_INFO { public int NextEntryOffset; // relative to the start of this structure, 0 indicates end. diff --git a/src/TraceEvent/TraceEventSession.cs b/src/TraceEvent/TraceEventSession.cs index 42947db04..dd75126f7 100644 --- a/src/TraceEvent/TraceEventSession.cs +++ b/src/TraceEvent/TraceEventSession.cs @@ -5,7 +5,6 @@ // It is available from http://www.codeplex.com/hyperAddin // using Microsoft.Diagnostics.Tracing.Compatibility; -using Microsoft.Diagnostics.Tracing.Extensions; using Microsoft.Diagnostics.Tracing.Parsers; using Microsoft.Diagnostics.Utilities; using Microsoft.Win32; @@ -18,6 +17,10 @@ using System.Threading; using Utilities; +using STACK_TRACING_EVENT_ID = Microsoft.Diagnostics.Tracing.Extensions.STACK_TRACING_EVENT_ID; // Same as CLASSIC_EVENT_ID +using EVENT_TRACE_MERGE_EXTENDED_DATA = Microsoft.Diagnostics.Tracing.Extensions.EVENT_TRACE_MERGE_EXTENDED_DATA; +using ETWKernelControl = Microsoft.Diagnostics.Tracing.Extensions.ETWKernelControl; + namespace Microsoft.Diagnostics.Tracing.Session { /// @@ -657,8 +660,12 @@ public unsafe bool EnableKernelProvider(KernelTraceEventParser.Keywords flags, K throw new ApplicationException("CPU Sampling interval is too large."); } - var succeeded = ETWControl.SetCpuSamplingRate((int)cpu100ns); // Always try to set, since it may not be the default - if (!succeeded && CpuSampleIntervalMSec != 1.0F) + // Always try to set, since it may not be the default + var interval = new TraceEventNativeMethods.TRACE_PROFILE_INTERVAL { Interval = (int)cpu100ns }; + var result = TraceEventNativeMethods.TraceSetInformation(0, + TraceEventNativeMethods.TRACE_INFO_CLASS.TraceSampledProfileIntervalInfo, + &interval, sizeof(TraceEventNativeMethods.TRACE_PROFILE_INTERVAL)); + if (result != 0 && CpuSampleIntervalMSec != 1.0F) { throw new ApplicationException("Can't set CPU sampling to " + CpuSampleIntervalMSec.ToString("f3") + "MSec."); } @@ -733,9 +740,14 @@ public unsafe bool EnableKernelProvider(KernelTraceEventParser.Keywords flags, K Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(dwErr)); m_IsActive = true; - if (OperatingSystemVersion.AtLeast(62) && StackCompression) + if (StackCompression && OperatingSystemVersion.AtLeast(OperatingSystemVersion.Win10)) { - ETWControl.EnableStackCaching(m_SessionHandle.DangerousGetHandle()); + var info = new TraceEventNativeMethods.TRACE_STACK_CACHING_INFO{ Enabled = 1 }; + TraceEventNativeMethods.TraceSetInformation( + m_SessionHandle.DangerousGetHandle(), + TraceEventNativeMethods.TRACE_INFO_CLASS.TraceStackCachingInfo, + &info, + sizeof(TraceEventNativeMethods.TRACE_STACK_CACHING_INFO)); } EnableLastBranchRecordingIfConfigured(); @@ -878,7 +890,12 @@ public bool Stop(bool noThrow = false) } catch (Exception) { Debug.Assert(false); } - ETWControl.SetCpuSamplingRate(10000); // Set sample rate back to default 1 Msec + // Set sample rate back to default 1 Msec + var interval = new TraceEventNativeMethods.TRACE_PROFILE_INTERVAL { Interval = (int)10000 }; + TraceEventNativeMethods.TraceSetInformation(0, + TraceEventNativeMethods.TRACE_INFO_CLASS.TraceSampledProfileIntervalInfo, + &interval, sizeof(TraceEventNativeMethods.TRACE_PROFILE_INTERVAL)); + var propertiesBuff = stackalloc byte[PropertiesSize]; var properties = GetProperties(propertiesBuff); int hr = TraceEventNativeMethods.ControlTrace(0UL, m_SessionName, properties, TraceEventNativeMethods.EVENT_TRACE_CONTROL_STOP); From 8ce764ce7d9650840b422a360f08826646be1470 Mon Sep 17 00:00:00 2001 From: "Doug Cook (WINDOWS)" Date: Mon, 6 Feb 2023 19:01:48 -0800 Subject: [PATCH 2/4] Revert path to Numerics --- src/PerfView/PerfView.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PerfView/PerfView.csproj b/src/PerfView/PerfView.csproj index 300104fdd..4fb1f4073 100644 --- a/src/PerfView/PerfView.csproj +++ b/src/PerfView/PerfView.csproj @@ -512,7 +512,7 @@ System.Memory.dll False - + Non-Resx false .\System.Numerics.Vectors.dll From 226b251570ce3f4ce16a72e9d5e3e5944430d149 Mon Sep 17 00:00:00 2001 From: "Doug Cook (WINDOWS)" Date: Tue, 7 Feb 2023 11:19:10 -0800 Subject: [PATCH 3/4] PR feedback --- src/TraceEvent/RegisteredTraceEventParser.cs | 20 +++++++++----------- src/TraceEvent/TraceEventSession.cs | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/TraceEvent/RegisteredTraceEventParser.cs b/src/TraceEvent/RegisteredTraceEventParser.cs index 21fdaacc4..62665b62a 100644 --- a/src/TraceEvent/RegisteredTraceEventParser.cs +++ b/src/TraceEvent/RegisteredTraceEventParser.cs @@ -133,7 +133,7 @@ public static string GetManifestForRegisteredProvider(Guid providerGuid) for (; ; ) { int size = buffer.Length; - status = TdhEnumerateManifestProviderEvents(ref eventRecord.EventHeader.ProviderId, buffer, ref size); + status = TdhEnumerateManifestProviderEvents(eventRecord.EventHeader.ProviderId, buffer, ref size); if (status != 122 || 20000000 < size) // 122 == Insufficient buffer keep it under 2Meg { break; @@ -155,12 +155,10 @@ public static string GetManifestForRegisteredProvider(Guid providerGuid) foreach (var descriptor in descriptors) { - var descriptorCopy = descriptor; - for (; ; ) { int size = buffer.Length; - status = TdhGetManifestEventInformation(ref eventRecord.EventHeader.ProviderId, ref descriptorCopy, buffer, ref size); + status = TdhGetManifestEventInformation(eventRecord.EventHeader.ProviderId, descriptor, buffer, ref size); if (status != 122 || 20000000 < size) // 122 == Insufficient buffer keep it under 2Meg { break; @@ -998,18 +996,18 @@ ref int infoSize [DllImport("tdh.dll")] internal static extern int TdhEnumerateManifestProviderEvents( - [In] ref Guid Guid, - [Out] byte[] pBuffer, // Actually PROVIDER_EVENT_INFO* + in Guid Guid, + [Out] byte[] pBuffer, // PROVIDER_EVENT_INFO* ref int pBufferSize ); [DllImport("tdh.dll")] internal static extern int TdhGetManifestEventInformation( - [In] ref Guid Guid, - [In] ref EVENT_DESCRIPTOR eventDesc, - [Out] byte[] pBuffer, - [In, Out] ref int pBufferSize // size in bytes - ); + in Guid Guid, + in EVENT_DESCRIPTOR eventDesc, + [Out] byte[] pBuffer, // TRACE_EVENT_INFORMATION* + ref int pBufferSize + ); [Flags] internal enum MAP_FLAGS diff --git a/src/TraceEvent/TraceEventSession.cs b/src/TraceEvent/TraceEventSession.cs index dd75126f7..c9f8b1a26 100644 --- a/src/TraceEvent/TraceEventSession.cs +++ b/src/TraceEvent/TraceEventSession.cs @@ -744,7 +744,7 @@ public unsafe bool EnableKernelProvider(KernelTraceEventParser.Keywords flags, K { var info = new TraceEventNativeMethods.TRACE_STACK_CACHING_INFO{ Enabled = 1 }; TraceEventNativeMethods.TraceSetInformation( - m_SessionHandle.DangerousGetHandle(), + m_SessionHandle, TraceEventNativeMethods.TRACE_INFO_CLASS.TraceStackCachingInfo, &info, sizeof(TraceEventNativeMethods.TRACE_STACK_CACHING_INFO)); From f0548c0fa158fc3cde73afd367c1905f8b23957a Mon Sep 17 00:00:00 2001 From: "Doug Cook (WINDOWS)" Date: Wed, 8 Feb 2023 11:34:02 -0800 Subject: [PATCH 4/4] Add Dispose to DispatchTests --- src/TraceEvent/TraceEvent.Tests/Parsing/DispatchTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/TraceEvent/TraceEvent.Tests/Parsing/DispatchTests.cs b/src/TraceEvent/TraceEvent.Tests/Parsing/DispatchTests.cs index 81f5787e2..3bded3cc1 100644 --- a/src/TraceEvent/TraceEvent.Tests/Parsing/DispatchTests.cs +++ b/src/TraceEvent/TraceEvent.Tests/Parsing/DispatchTests.cs @@ -591,7 +591,7 @@ public event Action Event039 /// Test code to ensure that the TraceEventDispatcher works properly in the face of lots of /// adds and deletes. /// - public class DispatcherTester + public class DispatcherTester : IDisposable { public DispatcherTester(ITestOutputHelper output) { @@ -600,6 +600,11 @@ public DispatcherTester(ITestOutputHelper output) Output = output; } + public void Dispose() + { + this.m_dispatcher.Dispose(); + } + private ITestOutputHelper Output { get;