From b7bb77a0eec18bd5d6b5de39eac6dac2ff871a29 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Thu, 1 Oct 2026 17:22:16 -0700 Subject: [PATCH 01/41] Remove legacy KernelTraceControl support --- src/PerfView/App.cs | 12 ------------ src/PerfView/PerfView.csproj | 7 ------- 2 files changed, 19 deletions(-) diff --git a/src/PerfView/App.cs b/src/PerfView/App.cs index 4a0bd5420..0fd5e9f18 100755 --- a/src/PerfView/App.cs +++ b/src/PerfView/App.cs @@ -521,18 +521,6 @@ public static bool Unpack() File.WriteAllText(Path.Combine(SupportFiles.SupportFileDir, "ExtensionsNotCopied"), ""); } - // The KernelTraceControl that works for Win10 and above does not work properly form older OSes - // The symptom is that when collecting data, it does not properly merge files and you don't get - // the KernelTraceControl events for PDBs and thus symbol lookup does not work. - var version = Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor; - if (version < 62) - { - var kernelTraceControlDir = Path.Combine(SupportFiles.SupportFileDir, "x86"); - var src = Path.Combine(kernelTraceControlDir, "KernelTraceControl.Win61.dll"); - var dest = Path.Combine(kernelTraceControlDir, "KernelTraceControl.dll"); - FileUtilities.ForceCopy(src, dest); - } - SetPermissionsForWin8Apps(); } } diff --git a/src/PerfView/PerfView.csproj b/src/PerfView/PerfView.csproj index ba5618edf..092ccd2d7 100644 --- a/src/PerfView/PerfView.csproj +++ b/src/PerfView/PerfView.csproj @@ -252,13 +252,6 @@ x86\KernelTraceControl.dll False - - Non-Resx - false - .\x86\KernelTraceControl.Win61.dll - x86\KernelTraceControl.Win61.dll - False - Non-Resx false From 6e2670b6ae226aafda8337ff05a6b093adc09dd1 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 10:44:12 -0700 Subject: [PATCH 02/41] Require Windows 10 for Windows hosts --- README.md | 2 + documentation/TraceEvent/TraceEventLibrary.md | 8 ++ src/PerfView.Tests/StartupTests.cs | 41 ++++++++++ src/PerfView/App.cs | 11 --- src/PerfView/PerfView.csproj | 3 +- src/PerfView/Startup.cs | 76 +++++++++++++++++++ src/PerfViewCollect/PerfViewCollect.csproj | 3 + src/TraceEvent/PlatformInitializer.cs | 30 ++++++++ .../Platform/OperatingSystemVersionTests.cs | 44 +++++++++++ src/Utilities/OperatingSystemVersion.cs | 73 +++++++++++++----- 10 files changed, 260 insertions(+), 31 deletions(-) create mode 100644 src/PerfView.Tests/StartupTests.cs create mode 100644 src/PerfView/Startup.cs create mode 100644 src/TraceEvent/PlatformInitializer.cs create mode 100644 src/TraceEvent/TraceEvent.Tests/Platform/OperatingSystemVersionTests.cs diff --git a/README.md b/README.md index c7aa96e40..8c10de3fa 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ current version of PerfView. PerfView requires .NET Framework 4.7.2 or later, which is widely available for all supported versions of Windows. +PerfView and TraceEvent require Windows 10 or Windows Server 2016 or later when running on Windows. + ### Are you here about the TraceEvent Library? PerfView is built on a library called Microsoft.Diagnostics.Tracing.TraceEvent, that knows how to both collect and parse Event Tracing for Windows (ETW) and EventPipe (.NET Core trace) data. Thus if there is any information that PerfView collects and processes that you would like to manipulate yourself programmatically, you would probably be interested in the [TraceEvent Library Documentation](documentation/TraceEvent/TraceEventLibrary.md) diff --git a/documentation/TraceEvent/TraceEventLibrary.md b/documentation/TraceEvent/TraceEventLibrary.md index e95e7c8be..f5677af5b 100644 --- a/documentation/TraceEvent/TraceEventLibrary.md +++ b/documentation/TraceEvent/TraceEventLibrary.md @@ -35,6 +35,14 @@ class Program } ``` +## Supported Windows Versions + +On Windows, TraceEvent requires Windows 10 or Windows Server 2016 or later. +Its module initializer throws `PlatformNotSupportedException` on older Windows; +the CLR may wrap this in an initialization exception. This check is skipped on +Linux and macOS, preserving the library's existing cross-platform capabilities. +Older traces and target runtimes can still be analyzed on supported hosts. + ## TraceEvent Samples To see more complete samples that use the APIs in more sophisticated (but not too sophisticated ways). In the [Samples directory](https://github.com/Microsoft/perfview/tree/main/src/TraceEvent/Samples) here are one or two page samples doing interesting things (collecting data, parsing from files or in real time, transforming one ETL file to another etc). Each sample is independent of the others. diff --git a/src/PerfView.Tests/StartupTests.cs b/src/PerfView.Tests/StartupTests.cs new file mode 100644 index 000000000..50b9fc12c --- /dev/null +++ b/src/PerfView.Tests/StartupTests.cs @@ -0,0 +1,41 @@ +using PerfView; +using System; +using System.ComponentModel; +using Xunit; + +namespace PerfViewTests +{ + public class StartupTests + { + [Fact] + public void UnsupportedWindowsReportsOnceWithoutStarting() + { + int reports = 0; + int result = Startup.Run(() => false, message => + { + reports++; + Assert.Contains("Windows 10", message); + }, () => throw new InvalidOperationException("Application must not start.")); + + Assert.Equal(1, result); + Assert.Equal(1, reports); + } + + [Fact] + public void SupportedWindowsReturnsApplicationStatus() + { + Assert.Equal(42, Startup.Run(() => true, + message => throw new InvalidOperationException(message), () => 42)); + } + + [Fact] + public void VersionQueryFailureReportsWithoutStarting() + { + string reported = null; + int result = Startup.Run(() => throw new Win32Exception("Version query failed."), + message => reported = message, () => throw new InvalidOperationException("Application must not start.")); + Assert.Equal(1, result); + Assert.Equal("Version query failed.", reported); + } + } +} diff --git a/src/PerfView/App.cs b/src/PerfView/App.cs index 0fd5e9f18..9c4acf461 100755 --- a/src/PerfView/App.cs +++ b/src/PerfView/App.cs @@ -226,17 +226,6 @@ private static int DoMain(string[] args, ref bool newConsoleCreated, ref StreamW } #endif - // The 64 bit version of some of the native DLLs we use are built against the new Win10 libraries and will - // fail to bind if they are loaded on an older OS (it would probably work for Win8 but do we care?) - // It also can work if we only do viewing operations (msdia* uses old libraries), but again do we care? - // If we do we can move this to before the DLLs are loaded. - // Give the user a clean error. - if (Environment.Is64BitProcess && Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor < 62) - { - throw new ApplicationException("The PerfView64 does not work properly Windows version < 10\r\n" + - " Please use the 32 bit version (PerfView.exe)."); - } - // For reasons I have not dug into SetFileName does not work if you attach to a session. Warn the user. if (CommandLineArgs.InMemoryCircularBuffer && CommandLineArgs.DoCommand == CommandProcessor.Start) { diff --git a/src/PerfView/PerfView.csproj b/src/PerfView/PerfView.csproj index 092ccd2d7..98f00a18b 100644 --- a/src/PerfView/PerfView.csproj +++ b/src/PerfView/PerfView.csproj @@ -5,7 +5,7 @@ net462 WinExe True - PerfView.App + PerfView.Startup true true false @@ -159,6 +159,7 @@ + memory\GCHeapDumpNames.cs diff --git a/src/PerfView/Startup.cs b/src/PerfView/Startup.cs new file mode 100644 index 000000000..0c19bd61d --- /dev/null +++ b/src/PerfView/Startup.cs @@ -0,0 +1,76 @@ +using Microsoft.Diagnostics.Utilities; +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace PerfView +{ + internal static class Startup + { +#if PERFVIEW_COLLECT + [MTAThread] +#else + [STAThread] +#endif + public static int Main(string[] args) + { + return Run(() => OperatingSystemVersion.IsSupported, ReportUnsupported, () => RunApplication(args)); + } + + /// + /// Checks the Windows requirement before entering TraceEvent-dependent application startup. + /// Delegates let tests exercise rejection and error reporting without launching the application or showing UI. + /// + internal static int Run(Func isSupported, Action reportUnsupported, Func runApplication) + { + bool supported; + try + { + supported = isSupported(); + } + catch (Win32Exception ex) + { + // A failed native version query is not evidence of support. Report it and stop startup. + reportUnsupported(ex.Message); + return 1; + } + + if (!supported) + { + // Pop-up a dialog box or report the error to stderr. + // Return a failure status without entering normal startup or its console/key-wait handling. + reportUnsupported("This operating system is not supported. " + OperatingSystemVersion.Requirement); + return 1; + } + + // Only supported hosts reach the deferred application handoff; preserve its exit status. + return runApplication(); + } + + #region private + // Keep TraceEvent-dependent types out of the unsupported-OS startup path. + [MethodImpl(MethodImplOptions.NoInlining)] + private static int RunApplication(string[] args) + { + return App.Main(args); + } + + private static void ReportUnsupported(string message) + { +#if PERFVIEW_COLLECT + Console.Error.WriteLine("PerfViewCollect: " + message); +#else + // XamlMessageBox requires WPF theme resources that are not initialized at this startup boundary. + // Use the native dialog to report the error before GUI initialization or TraceEvent-dependent startup. + MessageBoxW(IntPtr.Zero, message, "PerfView", 0x10); +#endif + } + +#if !PERFVIEW_COLLECT + [DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int MessageBoxW(IntPtr owner, string text, string caption, uint type); +#endif + #endregion + } +} diff --git a/src/PerfViewCollect/PerfViewCollect.csproj b/src/PerfViewCollect/PerfViewCollect.csproj index 6dfe19ebf..016ca0adb 100644 --- a/src/PerfViewCollect/PerfViewCollect.csproj +++ b/src/PerfViewCollect/PerfViewCollect.csproj @@ -4,6 +4,7 @@ Exe net8.0-windows PerfView + PerfView.Startup @@ -19,6 +20,8 @@ + + diff --git a/src/TraceEvent/PlatformInitializer.cs b/src/TraceEvent/PlatformInitializer.cs new file mode 100644 index 000000000..af0b1e256 --- /dev/null +++ b/src/TraceEvent/PlatformInitializer.cs @@ -0,0 +1,30 @@ +using Microsoft.Diagnostics.Utilities; +using System; +using System.Runtime.CompilerServices; + +namespace System.Runtime.CompilerServices +{ + // File-local so friend assemblies can use their runtime's attribute without a type conflict. + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + file sealed class ModuleInitializerAttribute : Attribute + { + } +} + +namespace Microsoft.Diagnostics.Tracing +{ + /// + /// Enforces the minimum Windows version when the CLR initializes the TraceEvent module, + /// including for library consumers that do not use PerfView's startup checks. + /// Unsupported Windows hosts are rejected with PlatformNotSupportedException. + /// Non-Windows hosts bypass the check without calling Windows APIs. + /// + internal static class PlatformInitializer + { + [ModuleInitializer] + internal static void Initialize() + { + OperatingSystemVersion.EnsureSupported(); + } + } +} diff --git a/src/TraceEvent/TraceEvent.Tests/Platform/OperatingSystemVersionTests.cs b/src/TraceEvent/TraceEvent.Tests/Platform/OperatingSystemVersionTests.cs new file mode 100644 index 000000000..cffbbc05a --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/Platform/OperatingSystemVersionTests.cs @@ -0,0 +1,44 @@ +using Microsoft.Diagnostics.Utilities; +using System; +using System.ComponentModel; +using Xunit; + +namespace TraceEventTests +{ + public class OperatingSystemVersionTests + { + [Theory] + [InlineData(5, false)] + [InlineData(6, false)] + [InlineData(10, true)] + [InlineData(11, true)] + public void WindowsMinimumUsesMajorVersion(uint major, bool supported) + { + Assert.Equal(supported, OperatingSystemVersion.IsSupportedPlatform(PlatformID.Win32NT, () => major)); + } + + [Theory] + [InlineData(PlatformID.Unix)] + [InlineData(PlatformID.MacOSX)] + public void OtherPlatformsNeverQueryWindows(PlatformID platform) + { + // The callback throws InvalidOperationException if the Windows version query runs for a non-Windows platform. + Assert.True(OperatingSystemVersion.IsSupportedPlatform(platform, + () => throw new InvalidOperationException("Windows API must not be called."))); + } + + [Fact] + public void NativeVersionQueryFailureIsNotIgnored() + { + Assert.Throws(() => OperatingSystemVersion.CheckVersionQueryStatus(unchecked((int)0xC0000001))); + OperatingSystemVersion.CheckVersionQueryStatus(0); + } + + [Fact] + public void CurrentHostPassedModuleInitialization() + { + Assert.True(OperatingSystemVersion.IsSupported); + } + + } +} diff --git a/src/Utilities/OperatingSystemVersion.cs b/src/Utilities/OperatingSystemVersion.cs index 773bd002c..cba552072 100644 --- a/src/Utilities/OperatingSystemVersion.cs +++ b/src/Utilities/OperatingSystemVersion.cs @@ -1,15 +1,37 @@ +using System; +using System.ComponentModel; using System.Runtime.InteropServices; namespace Microsoft.Diagnostics.Utilities { - // This class is a helper that hides the OS Version tests that you might have done with Evironment.OSVersion - // but can't because this API was removed in .NET COre. internal static class OperatingSystemVersion { + #region TraceEvent Required Windows Version + public const string Requirement = "Windows 10 or Windows Server 2016 or later is required."; public const int Win10 = 100; public const int Win8 = 62; public const int Win7 = 61; public const int Vista = 60; + + public static bool IsSupported + { + get { return IsSupportedPlatform(Environment.OSVersion.Platform, () => GetWindowsVersion().dwMajorVersion); } + } + + internal static bool IsSupportedPlatform(PlatformID platform, Func getWindowsMajorVersion) + { + return platform != PlatformID.Win32NT || getWindowsMajorVersion() >= 10; + } + + public static void EnsureSupported() + { + if (!IsSupported) + { + throw new PlatformNotSupportedException(Requirement); + } + } + #endregion + /// /// requiredOSVersion is a number that is the major version * 10 + minor. Thus /// Win 10 == 100 @@ -19,9 +41,35 @@ internal static class OperatingSystemVersion /// This returns true if true OS version is >= 'requiredOSVersion /// - // Code borrowed from CoreFX System.PlatformDetection.Windows to allow targeting nestandard1.6 - [StructLayout(LayoutKind.Sequential)] - private struct RTL_OSVERSIONINFOEX + public static bool AtLeast(int requiredOSVersion) + { + var osvi = GetWindowsVersion(); + uint osVersion = osvi.dwMajorVersion * 10 + osvi.dwMinorVersion; + return osVersion >= requiredOSVersion; + } + + #region private + private static RTL_OSVERSIONINFO GetWindowsVersion() + { + var version = new RTL_OSVERSIONINFO(); + version.dwOSVersionInfoSize = (uint)Marshal.SizeOf(version); + CheckVersionQueryStatus(RtlGetVersion(ref version)); + return version; + } + + internal static void CheckVersionQueryStatus(int status) + { + if (status < 0) + { + throw new Win32Exception(status, "Could not determine the Windows version (NTSTATUS 0x" + status.ToString("X8") + ")."); + } + } + + [DllImport("ntdll.dll")] + private static extern int RtlGetVersion(ref RTL_OSVERSIONINFO lpVersionInformation); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct RTL_OSVERSIONINFO { internal uint dwOSVersionInfoSize; internal uint dwMajorVersion; @@ -31,19 +79,6 @@ private struct RTL_OSVERSIONINFOEX [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] internal string szCSDVersion; } - - // Code borrowed from CoreFX System.PlatformDetection.Windows to allow targeting nestandard1.6 - [DllImport("ntdll.dll")] - private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation); - - // Code borrowed from CoreFX System.PlatformDetection.Windows to allow targeting nestandard1.6 - public static bool AtLeast(int requiredOSVersion) - { - RTL_OSVERSIONINFOEX osvi = new RTL_OSVERSIONINFOEX(); - osvi.dwOSVersionInfoSize = (uint)Marshal.SizeOf(osvi); - RtlGetVersion(out osvi); - uint osVersion = osvi.dwMajorVersion * 10 + osvi.dwMinorVersion; - return osVersion >= requiredOSVersion; - } + #endregion } } \ No newline at end of file From e3e80c8f1ca8161e783434869fb354bb5de07a03 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 10:48:17 -0700 Subject: [PATCH 03/41] Build PerfView applications for x64 --- .pipelines/perfview-job.yml | 30 +++++++++++++++++-- src/PerfView.Tests/PerfView.Tests.csproj | 1 + src/PerfView.Tests/StartupTests.cs | 7 +++++ src/PerfView/PerfView.csproj | 5 ++-- src/PerfViewCollect/PerfViewCollect.csproj | 4 +++ .../GlobalSrc/Global.csproj | 1 + 6 files changed, 43 insertions(+), 5 deletions(-) diff --git a/.pipelines/perfview-job.yml b/.pipelines/perfview-job.yml index a9d648e67..7ebf39be9 100644 --- a/.pipelines/perfview-job.yml +++ b/.pipelines/perfview-job.yml @@ -35,21 +35,45 @@ steps: customFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json' - task: VSTest@3 - displayName: 'Run Tests' + displayName: 'Run TraceEvent Tests (x86)' inputs: testAssemblyVer2: | **\bin\**\LinuxTracingTests.dll **\bin\**\CtfTracingTests.dll **\bin\**\SymbolsAuthTests.dll **\bin\**\TraceEventTests.dll - **\bin\**\PerfViewTests.dll **\bin\**\TraceParserGen.Tests.dll - testRunTitle: 'PerfView - ${{ parameters.flavor }}' + testRunTitle: 'TraceEvent x86 - ${{ parameters.flavor }}' vsTestVersion: 'toolsInstaller' runTestsInIsolation: true donotPublishTestResults: true otherConsoleOptions: '--blame' +- task: VSTest@3 + displayName: 'Run TraceEvent Tests (x64)' + inputs: + testAssemblyVer2: | + **\bin\**\LinuxTracingTests.dll + **\bin\**\CtfTracingTests.dll + **\bin\**\SymbolsAuthTests.dll + **\bin\**\TraceEventTests.dll + **\bin\**\TraceParserGen.Tests.dll + testRunTitle: 'TraceEvent x64 - ${{ parameters.flavor }}' + vsTestVersion: 'toolsInstaller' + runTestsInIsolation: true + donotPublishTestResults: true + otherConsoleOptions: '--blame /Platform:x64' + +- task: VSTest@3 + displayName: 'Run PerfView Tests' + inputs: + testAssemblyVer2: '**\bin\**\PerfViewTests.dll' + testRunTitle: 'PerfView x64 - ${{ parameters.flavor }}' + vsTestVersion: 'toolsInstaller' + runTestsInIsolation: true + donotPublishTestResults: true + otherConsoleOptions: '--blame /Platform:x64' + - task: CopyFiles@2 displayName: 'Copy Files to Artifacts Staging' inputs: diff --git a/src/PerfView.Tests/PerfView.Tests.csproj b/src/PerfView.Tests/PerfView.Tests.csproj index 4883538b9..57836450f 100644 --- a/src/PerfView.Tests/PerfView.Tests.csproj +++ b/src/PerfView.Tests/PerfView.Tests.csproj @@ -5,6 +5,7 @@ net462 PerfViewTests PerfViewTests + x64 Unit tests for PerfView. Copyright © Microsoft 2016 diff --git a/src/PerfView.Tests/StartupTests.cs b/src/PerfView.Tests/StartupTests.cs index 50b9fc12c..aa8359bbb 100644 --- a/src/PerfView.Tests/StartupTests.cs +++ b/src/PerfView.Tests/StartupTests.cs @@ -7,6 +7,13 @@ namespace PerfViewTests { public class StartupTests { + [Fact] + public void ApplicationAndTestHostAreX64() + { + Assert.Equal(8, IntPtr.Size); + Assert.Equal(System.Reflection.ProcessorArchitecture.Amd64, typeof(Startup).Assembly.GetName().ProcessorArchitecture); + } + [Fact] public void UnsupportedWindowsReportsOnceWithoutStarting() { diff --git a/src/PerfView/PerfView.csproj b/src/PerfView/PerfView.csproj index 98f00a18b..dbf641a4b 100644 --- a/src/PerfView/PerfView.csproj +++ b/src/PerfView/PerfView.csproj @@ -16,8 +16,9 @@ Copyright © Microsoft 2010 $(PerfViewVersion) - - AnyCPU + + x64 + false diff --git a/src/PerfViewCollect/PerfViewCollect.csproj b/src/PerfViewCollect/PerfViewCollect.csproj index 016ca0adb..375fc19b7 100644 --- a/src/PerfViewCollect/PerfViewCollect.csproj +++ b/src/PerfViewCollect/PerfViewCollect.csproj @@ -5,6 +5,10 @@ net8.0-windows PerfView PerfView.Startup + x64 + win-x64 + false + false diff --git a/src/PerfViewExtensions/GlobalSrc/Global.csproj b/src/PerfViewExtensions/GlobalSrc/Global.csproj index e7549ad64..8be9857fa 100644 --- a/src/PerfViewExtensions/GlobalSrc/Global.csproj +++ b/src/PerfViewExtensions/GlobalSrc/Global.csproj @@ -3,6 +3,7 @@ net462 + x64 From 1cd7351db741b29fd484c96f6ff89138df1739d5 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 10:49:40 -0700 Subject: [PATCH 04/41] Remove ARM32 PerfView paths --- src/PerfView/App.cs | 55 ++++++++------------------------ src/PerfView/CommandLineArgs.cs | 2 +- src/PerfView/CommandProcessor.cs | 2 +- src/PerfView/PerfView.csproj | 7 ---- 4 files changed, 15 insertions(+), 51 deletions(-) diff --git a/src/PerfView/App.cs b/src/PerfView/App.cs index 9c4acf461..1027f56fe 100755 --- a/src/PerfView/App.cs +++ b/src/PerfView/App.cs @@ -25,10 +25,7 @@ namespace PerfView /// /// The App Class is the main program /// - /// We don't do the normal WPF style main (where WPF is responsible for the main program because - /// on ARM devices we don't have WPF, however we still want to enable some things (like data collection). - /// Thus we need to defer touching WPF until we know we need it. To do this we take explicit control over - /// the Entry point and do command line processing first, before doing any GUI stuff. + /// Command-line processing and dependency extraction happen before initializing WPF. /// public class App { @@ -53,11 +50,9 @@ public static int Main(string[] args) try { #if !PERFVIEW_COLLECT - // Can't display on ARM because the SplashScreen is WPF - var noGui = SupportFiles.ProcessArch == ProcessorArchitecture.Arm || - (args.Length > 0 && + var noGui = args.Length > 0 && (string.Compare(args[0], "/noGui", StringComparison.OrdinalIgnoreCase) == 0 || - string.Compare(args[0], 0, "/logFile", 0, 8, StringComparison.OrdinalIgnoreCase) == 0)); + string.Compare(args[0], 0, "/logFile", 0, 8, StringComparison.OrdinalIgnoreCase) == 0); // Users will need to check the return code for failure because there is no console setup yet and we can't log any status. var buildLayout = args.Length > 1 && string.Compare(args[0], "/buildLayout", StringComparison.OrdinalIgnoreCase) == 0; @@ -151,13 +146,7 @@ private static int DoMain(string[] args, ref bool newConsoleCreated, ref StreamW } // Figure out where output goes and set CommandProcessor.LogFile -#if !PERFVIEW_COLLECT - // On ARM we don't have a GUI - if (SupportFiles.ProcessArch == ProcessorArchitecture.Arm) - { - CommandLineArgs.NoGui = true; - } -#else +#if PERFVIEW_COLLECT // We never support GUI in PerfViewCollect CommandLineArgs.NoGui = true; #endif @@ -234,10 +223,7 @@ private static int DoMain(string[] args, ref bool newConsoleCreated, ref StreamW if (CommandLineArgs.NoGui) { - if (SupportFiles.ProcessArch != ProcessorArchitecture.Arm) - { - CloseSplashScreen(); - } + CloseSplashScreen(); if (needNewConsole && !newConsoleCreated) { @@ -333,14 +319,14 @@ private static int DoMain(string[] args, ref bool newConsoleCreated, ref StreamW } else { - // Ask the gui to do the command. This is in its own method so that on ARM we never try to load WPF. + // Initialize the GUI only after dependencies have been unpacked. DoMainForGui(); return 0; // Does not actually return but } } /// - /// Logic in DoMainForGui was segregated into its own method so that we don't load WPF until we need to (for ARM) + /// Defer loading WPF until GUI startup. /// [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private static void DoMainForGui() @@ -476,29 +462,14 @@ public static bool Unpack() if (Environment.OSVersion.Platform != PlatformID.Unix) { - // You don't need amd64 on ARM (TODO remove it on X86 machines too). - if (SupportFiles.ProcessArch == ProcessorArchitecture.Arm) - { - DirectoryUtilities.Clean(Path.Combine(SupportFiles.SupportFileDir, "amd64")); - } - // We have two versions of HeapDump.exe, and they each need their own copy of Microsoft.Diagnostics.Runtime.dll - // so copy this dll to the other architectures. + // so copy this dll to the other architecture. var fromDir = Path.Combine(SupportFiles.SupportFileDir, "x86"); - foreach (var arch in new string[] { "amd64", "arm" }) + var toDir = Path.Combine(SupportFiles.SupportFileDir, "amd64"); + var fromFile = Path.Combine(fromDir, "Microsoft.Diagnostics.Runtime.dll"); + if (Directory.Exists(toDir) && File.Exists(fromFile)) { - var toDir = Path.Combine(SupportFiles.SupportFileDir, arch); - var fromFile = Path.Combine(fromDir, "Microsoft.Diagnostics.Runtime.dll"); - if (Directory.Exists(toDir) && File.Exists(fromFile)) - { - File.Copy(fromFile, Path.Combine(toDir, "Microsoft.Diagnostics.Runtime.dll")); - - // ARM can use the X86 version of the heap dumper. - if (arch == "arm") - { - File.Copy(Path.Combine(fromDir, "HeapDump.exe"), Path.Combine(toDir, "HeapDump.exe")); - } - } + File.Copy(fromFile, Path.Combine(toDir, "Microsoft.Diagnostics.Runtime.dll")); } // To support intellisense for extensions, we need the PerfView.exe to be next to the .XML file that describes it @@ -695,7 +666,7 @@ public static string SymbolPath bool persistSymPath = true; if (symPath.Elements.Count == 0) { - if (SupportFiles.ProcessArch == ProcessorArchitecture.Arm || App.CommandLineArgs.NoGui) + if (App.CommandLineArgs.NoGui) { App.CommandProcessor.LogFile.WriteLine("WARNING NO _NT_SYMBOL_PATH set ..."); persistSymPath = false; // If we could not interact with the user, don't persist the answer. diff --git a/src/PerfView/CommandLineArgs.cs b/src/PerfView/CommandLineArgs.cs index df8e5f76e..69e71f53c 100644 --- a/src/PerfView/CommandLineArgs.cs +++ b/src/PerfView/CommandLineArgs.cs @@ -576,7 +576,7 @@ private void SetupCommandLine(CommandLineParser parser) parser.DefineOptionalQualifier("DisableDotNetVersionLogging", ref DisableDotNetVersionLogging, "Disables capturing of .NET version information during collection."); parser.DefineOptionalQualifier("NoGui", ref NoGui, - "Use the Command line version of the command (like on ARM). Brings up a console window. For batch scripts/automation use /LogFile instead (see users guide under 'Scripting' for more)."); + "Use the Command line version of the command. Brings up a console window. For batch scripts/automation use /LogFile instead (see users guide under 'Scripting' for more)."); parser.DefineOptionalQualifier("SafeMode", ref SafeMode, "Turn off parallelism and other risky features."); parser.DefineOptionalQualifier("RestartingToElevelate", ref RestartingToElevelate, "Internal: indicates that perfView is restarting to get Admin privileges."); diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index 4b6235ae7..2927a6305 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -3294,7 +3294,7 @@ private void CheckAndWarnAboutAspNet(Guid guid) " See 'ASP.NET events' in help for more details."; LogFile.WriteLine(message); - if (App.CommandLineArgs.NoGui || SupportFiles.ProcessArch == ProcessorArchitecture.Arm) + if (App.CommandLineArgs.NoGui) { LogFile.WriteLine("[ASP.NET events will not fire, see log for details.]"); return; diff --git a/src/PerfView/PerfView.csproj b/src/PerfView/PerfView.csproj index dbf641a4b..9d62acb68 100644 --- a/src/PerfView/PerfView.csproj +++ b/src/PerfView/PerfView.csproj @@ -254,13 +254,6 @@ x86\KernelTraceControl.dll False - - Non-Resx - false - .\arm\KernelTraceControl.dll - arm\KernelTraceControl.dll - False - Non-Resx false From 77f680993270d147fe5fae10424cf74a8b8ee6c1 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 10:52:20 -0700 Subject: [PATCH 05/41] Trim host-only PerfView native resources --- src/PerfView.Tests/StartupTests.cs | 20 +++++++++++++ src/PerfView/PerfView.csproj | 22 +------------- src/PerfView/Utilities/SupportFiles.cs | 41 ++++---------------------- 3 files changed, 26 insertions(+), 57 deletions(-) diff --git a/src/PerfView.Tests/StartupTests.cs b/src/PerfView.Tests/StartupTests.cs index aa8359bbb..83f221a74 100644 --- a/src/PerfView.Tests/StartupTests.cs +++ b/src/PerfView.Tests/StartupTests.cs @@ -14,6 +14,26 @@ public void ApplicationAndTestHostAreX64() Assert.Equal(System.Reflection.ProcessorArchitecture.Amd64, typeof(Startup).Assembly.GetName().ProcessorArchitecture); } + [Fact] + public void HostResourcesUseX64AndKeepBothHelpers() + { + Assert.Equal("amd64", global::Utilities.SupportFiles.ProcessArchitectureDirectory); + var resources = typeof(Startup).Assembly.GetManifestResourceNames(); + Assert.Contains(@".\runtimes\win-x64\native\WebView2Loader.dll", resources); + Assert.DoesNotContain(@".\runtimes\win-x86\native\WebView2Loader.dll", resources); + Assert.DoesNotContain(@".\runtimes\win-arm64\native\WebView2Loader.dll", resources); + Assert.Contains(@".\amd64\msdia140.dll", resources); + Assert.DoesNotContain(@".\x86\msdia140.dll", resources); + Assert.DoesNotContain(resources, name => name.StartsWith(@".\arm\", StringComparison.Ordinal)); + foreach (var arch in new[] { "x86", "amd64" }) + { + foreach (var file in new[] { "HeapDump.exe", "EtwClrProfiler.dll", "KernelTraceControl.dll" }) + { + Assert.Contains(@".\" + arch + @"\" + file, resources); + } + } + } + [Fact] public void UnsupportedWindowsReportsOnceWithoutStarting() { diff --git a/src/PerfView/PerfView.csproj b/src/PerfView/PerfView.csproj index 9d62acb68..42ac65577 100644 --- a/src/PerfView/PerfView.csproj +++ b/src/PerfView/PerfView.csproj @@ -240,13 +240,7 @@ - - Non-Resx - false - .\x86\msdia140.dll - x86\msdia140.dll - False - + Non-Resx false @@ -567,13 +561,6 @@ Microsoft.Web.WebView2.Wpf.dll False - - Non-Resx - false - .\runtimes\win-x86\native\WebView2Loader.dll - runtimes\win-x86\native\WebView2Loader.dll - False - Non-Resx false @@ -581,13 +568,6 @@ runtimes\win-x64\native\WebView2Loader.dll False - - Non-Resx - false - .\runtimes\win-arm64\native\WebView2Loader.dll - runtimes\win-arm64\native\WebView2Loader.dll - False - Non-Resx false diff --git a/src/PerfView/Utilities/SupportFiles.cs b/src/PerfView/Utilities/SupportFiles.cs index 3eefecfc9..f8407ab95 100755 --- a/src/PerfView/Utilities/SupportFiles.cs +++ b/src/PerfView/Utilities/SupportFiles.cs @@ -215,40 +215,21 @@ public static string ExePath } /// - /// Get the name of the architecture of the current process + /// Gets the architecture of the current process. PerfView is built and runs only as amd64, + /// including when running under x64 emulation on ARM64 hardware. /// public static ProcessorArchitecture ProcessArch { - get - { - if (s_ProcessArch == ProcessorArchitecture.None) - { - var processorArchitecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); - if (!Enum.TryParse(processorArchitecture, ignoreCase: true, result: out s_ProcessArch)) - { - s_ProcessArch = Environment.Is64BitProcess ? ProcessorArchitecture.Amd64 : ProcessorArchitecture.X86; - } - } - - return s_ProcessArch; - } + get { return ProcessorArchitecture.Amd64; } } /// /// Gets the name of the directory containing compiled binaries (DLLs) which have the same architecture as the - /// currently executing process. + /// currently executing process. PerfView is built and runs only as amd64, so this directory is always amd64. /// public static string ProcessArchitectureDirectory { - get - { - if (s_ProcessArchDirectory == null) - { - s_ProcessArchDirectory = ProcessArch.ToString().ToLowerInvariant(); - } - - return s_ProcessArchDirectory; - } + get { return "amd64"; } } /// @@ -265,14 +246,6 @@ public static void LoadNative(string relativePath) { if (!File.Exists(fullPath)) { - if (ProcessArch != ProcessorArchitecture.X86) - { - var x86FullPath = Path.Combine(SupportFileDir, "x86", relativePath); - if (File.Exists(x86FullPath)) - { - throw new ApplicationException("This operation is not supported for the " + ProcessArch + " architecture."); - } - } throw new ApplicationException("Could not find DLL " + archPath + " in distribution. Application Error."); } } @@ -397,10 +370,6 @@ private static void Cleanup() private static extern IntPtr LoadLibrary(string lpFileName); - private static ProcessorArchitecture s_ProcessArch; - private static string s_ProcessArchDirectory; - - private static string s_supportFileDir; private static string s_supportFileDirBase; private static string s_mainAssemblyPath; From 03f4d887686246c84264e640e7618023eac9baf3 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 10:53:40 -0700 Subject: [PATCH 06/41] Simplify x64 profiler registration --- src/PerfView/CommandProcessor.cs | 75 +++++++++----------------------- 1 file changed, 20 insertions(+), 55 deletions(-) diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index 2927a6305..92abf1b0d 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -2527,7 +2527,6 @@ private static void CreateSolution(string slnFile, List projectFiles) } private static string s_dotNetKey = @"Software\Microsoft\.NETFramework"; - private static string s_dotNetKey32 = @"Software\Wow6432Node\Microsoft\.NETFramework"; // Ensures that our EtwClrProfiler is set up (for both X64 and X86). Does not actually turn on the provider. private static void InstallETWClrProfiler(TextWriter log, int profilerKeywords) @@ -2538,7 +2537,8 @@ private static void InstallETWClrProfiler(TextWriter log, int profilerKeywords) { log.WriteLine("Profiler DLL to load is {0}", profilerDll); log.WriteLine(@"Adding HKLM\Software\Microsoft\.NETFramework\COR* registry keys"); - using (RegistryKey key = Registry.LocalMachine.CreateSubKey(s_dotNetKey)) + using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) + using (RegistryKey key = hklm.CreateSubKey(s_dotNetKey)) { InsertEtwClrProfilerKeys(key, "COR", profilerDll, profilerKeywords, SupportFiles.ProcessArch.ToString(), log); InsertEtwClrProfilerKeys(key, "CORECLR", profilerDll, profilerKeywords, SupportFiles.ProcessArch.ToString(), log); @@ -2549,44 +2549,22 @@ private static void InstallETWClrProfiler(TextWriter log, int profilerKeywords) log.WriteLine("ERROR do not have a ETWClrProfiler.dll for architecture {0}", SupportFiles.ProcessArch); } - // If we are on a 64 bit system (in the wow), also enable the 64 bit version. - var nativeArch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432"); - if (nativeArch != null) + // The x64 host also profiles x86 targets through the 32-bit registry view. + var arch = "x86"; + var profilerNativeDll = Path.Combine(SupportFiles.SupportFileDir, arch, "EtwClrProfiler.dll"); + if (File.Exists(profilerNativeDll)) { - var profilerNativeDll = Path.Combine(SupportFiles.SupportFileDir, nativeArch + "\\EtwClrProfiler.dll"); - if (File.Exists(profilerNativeDll)) - { - log.WriteLine(@"Detected 64 bit system, Adding 64 bit HKLM\Software\Microsoft\.NETFramework\COR* registry keys"); - using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) - using (RegistryKey key = hklm.CreateSubKey(s_dotNetKey)) - { - InsertEtwClrProfilerKeys(key, "COR", profilerNativeDll, profilerKeywords, nativeArch, log); - InsertEtwClrProfilerKeys(key, "CORECLR", profilerNativeDll, profilerKeywords, nativeArch, log); - } - } - else + log.WriteLine(@"Installing in the 32 bit subsystem."); + using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) + using (RegistryKey key = hklm.CreateSubKey(s_dotNetKey)) { - log.WriteLine("ERROR do not have a ETWClrProfiler.dll for architecture {0}", nativeArch); + InsertEtwClrProfilerKeys(key, "COR", profilerNativeDll, profilerKeywords, arch, log); + InsertEtwClrProfilerKeys(key, "CORECLR", profilerNativeDll, profilerKeywords, arch, log); } } - // If we are amd64 process, also install in the 32 bit subsystem. - else if (SupportFiles.ProcessArch == ProcessorArchitecture.Amd64) + else { - var arch = "x86"; - var profilerNativeDll = Path.Combine(SupportFiles.SupportFileDir, arch + "\\EtwClrProfiler.dll"); - if (File.Exists(profilerNativeDll)) - { - log.WriteLine(@"Detected 64 bit system, installing in the 32 bit subsystem."); - using (RegistryKey key = Registry.LocalMachine.CreateSubKey(s_dotNetKey32)) - { - InsertEtwClrProfilerKeys(key, "COR", profilerNativeDll, profilerKeywords, arch, log); - InsertEtwClrProfilerKeys(key, "CORECLR", profilerNativeDll, profilerKeywords, arch, log); - } - } - else - { - log.WriteLine("ERROR do not have a ETWClrProfiler.dll for architecture {0}", arch); - } + log.WriteLine("ERROR do not have a ETWClrProfiler.dll for architecture {0}", arch); } } @@ -2615,32 +2593,19 @@ private static void UninstallETWClrProfiler(TextWriter log) { log.WriteLine("Ensuring .NET Allocation profiler not installed."); - using (RegistryKey key = Registry.LocalMachine.CreateSubKey(s_dotNetKey)) + using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) + using (RegistryKey key = hklm.CreateSubKey(s_dotNetKey)) { DeleteEtwClrProfilerKeys(key, "COR", log); DeleteEtwClrProfilerKeys(key, "CORECLR", log); } - var nativeArch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432"); - if (nativeArch != null) + log.WriteLine(@"Removing 32 bit keys."); + using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) + using (RegistryKey key = hklm.CreateSubKey(s_dotNetKey)) { - log.WriteLine(@"Detected 64 bit system, removing 64 bit keys"); - using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) - using (RegistryKey key = hklm.CreateSubKey(s_dotNetKey)) - { - DeleteEtwClrProfilerKeys(key, "COR", log); - DeleteEtwClrProfilerKeys(key, "CORECLR", log); - } - } - // If we are amd64 process, also uninstall in the WOW. - else if (SupportFiles.ProcessArch == ProcessorArchitecture.Amd64) - { - log.WriteLine(@"Detected 64 bit system, removing 32 bit keys."); - using (RegistryKey key = Registry.LocalMachine.CreateSubKey(s_dotNetKey32)) - { - DeleteEtwClrProfilerKeys(key, "COR", log); - DeleteEtwClrProfilerKeys(key, "CORECLR", log); - } + DeleteEtwClrProfilerKeys(key, "COR", log); + DeleteEtwClrProfilerKeys(key, "CORECLR", log); } } From 234da82437538ac9205206e7000cf5a9dec2a7c0 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 10:55:38 -0700 Subject: [PATCH 07/41] Remove 32-bit host heap-dump branches --- src/PerfView.Tests/HeapDumperTests.cs | 50 +++++++++++++++++++++++++++ src/PerfView/memory/HeapSnapshot.cs | 36 +++++-------------- 2 files changed, 59 insertions(+), 27 deletions(-) create mode 100644 src/PerfView.Tests/HeapDumperTests.cs diff --git a/src/PerfView.Tests/HeapDumperTests.cs b/src/PerfView.Tests/HeapDumperTests.cs new file mode 100644 index 000000000..96f80bca2 --- /dev/null +++ b/src/PerfView.Tests/HeapDumperTests.cs @@ -0,0 +1,50 @@ +using PerfView; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using Xunit; + +namespace PerfViewTests +{ + public class HeapDumperTests + { + [Fact] + public void DetectsX64Target() + { + using (var process = Process.GetCurrentProcess()) + { + Assert.Equal(ProcessorArchitecture.Amd64, HeapDumper.GetArchForProcess(process.Id)); + } + } + + [Fact] + public void DetectsX86TargetFromX64Host() + { + var startInfo = new ProcessStartInfo( + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "cmd.exe"), + "/d /c set /p PERFVIEW_TEST=") + { + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardInput = true + }; + using (var process = Process.Start(startInfo)) + { + try + { + Assert.Equal(ProcessorArchitecture.X86, HeapDumper.GetArchForProcess(process.Id)); + } + finally + { + process.StandardInput.Close(); + if (!process.WaitForExit(5000)) + { + process.Kill(); + process.WaitForExit(); + } + } + } + } + } +} diff --git a/src/PerfView/memory/HeapSnapshot.cs b/src/PerfView/memory/HeapSnapshot.cs index c7d8f09b3..ac43fd86d 100644 --- a/src/PerfView/memory/HeapSnapshot.cs +++ b/src/PerfView/memory/HeapSnapshot.cs @@ -96,27 +96,17 @@ internal static void ForceGC(int processID, TextWriter log = null) /// public static void DumpGCHeap(string processDumpFile, string outputFile, TextWriter log, string qualifiers = "") { - // Determine if we are on a 64 bit system. - if (Environment.Is64BitOperatingSystem) + // TODO FIX NOW. Find a way of determing which architecture a dump is + try { - // TODO FIX NOW. Find a way of determing which architecture a dump is - try - { - log.WriteLine("********** TRYING TO OPEN THE DUMP AS 64 BIT ************"); - DumpGCHeap("/processDump " + qualifiers, processDumpFile, outputFile, log, ProcessorArchitecture.Amd64); - return; // Yeah! success the first time - } - catch (ApplicationException) - { - // It might have failed because this was a 32 bit dump, if so try again. - log.WriteLine("********** TRYING TO OPEN THE DUMP AS 32 BIT ************"); - DumpGCHeap("/processDump" + qualifiers, processDumpFile, outputFile, log, ProcessorArchitecture.X86); - return; - } + log.WriteLine("********** TRYING TO OPEN THE DUMP AS 64 BIT ************"); + DumpGCHeap("/processDump " + qualifiers, processDumpFile, outputFile, log, ProcessorArchitecture.Amd64); } - else + catch (ApplicationException) { - DumpGCHeap("/processDump", processDumpFile, outputFile, log, ProcessorArchitecture.X86); + // It might have failed because this was a 32 bit dump, if so try again. + log.WriteLine("********** TRYING TO OPEN THE DUMP AS 32 BIT ************"); + DumpGCHeap("/processDump" + qualifiers, processDumpFile, outputFile, log, ProcessorArchitecture.X86); } } /// @@ -204,20 +194,12 @@ private static void DumpGCHeap(string qualifiers, string inputArg, string output /// /// Returns processor architecture for a process with a specific process ID. /// - private static ProcessorArchitecture GetArchForProcess(int processID) + internal static ProcessorArchitecture GetArchForProcess(int processID) { try { - // To make error paths simple always try to access the process here even though we don't need it - // for a 32 bit machine. var process = Process.GetProcessById(processID); - // Currently only AMD64 has a wow. - if (!Environment.Is64BitOperatingSystem) - { - return ProcessorArchitecture.X86; - } - bool is32Bit = false; bool ret = IsWow64Process(process.Handle, out is32Bit); GC.KeepAlive(process); From 5209d5f4c412226b83aee43116258ba879c1481e Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 10:57:09 -0700 Subject: [PATCH 08/41] Remove cross-bitness counter relaunch --- src/PerfView/Triggers.cs | 60 ---------------------------------------- 1 file changed, 60 deletions(-) diff --git a/src/PerfView/Triggers.cs b/src/PerfView/Triggers.cs index 0d802181c..8ab83d40b 100644 --- a/src/PerfView/Triggers.cs +++ b/src/PerfView/Triggers.cs @@ -97,14 +97,6 @@ public PerformanceCounterTrigger(string spec, float decayToZeroHours, TextWriter throw new ApplicationException("Count not find performance counter " + counterName + " in category " + categoryName); } - if (categoryName.StartsWith(".NET")) // TODO FIX NOW, remove this condition after we are confident of it. - { - if (SpawnCounterIn64BitProcessIfNecessary()) - { - return; - } - } - // If the instance does not exist, you won't discover it until we fetch the counter later. m_counter = new PerformanceCounter(categoryName, counterName, instanceName); @@ -206,14 +198,6 @@ public float EffectiveThreshold public override void Dispose() { m_monitoringDone = true; - -#if PERFVIEW - var cmd = m_cmd; - if (cmd != null) - { - cmd.Kill(); - } -#endif } public override string Status { @@ -242,47 +226,6 @@ public override string Status } #region private - /// - /// If you are in a 32 bit process you don't see 64 bit perf counters. Returns true if we needed to do this. - /// - private bool SpawnCounterIn64BitProcessIfNecessary() - { -#if PERFVIEW // TODO FIX NOW turn this on and test. - // Do we have to do this? - if (m_triggered == null) - { - return false; - } - - if (!(Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess)) - { - return false; - } - - m_task = Task.Factory.StartNew(delegate - { - m_log.WriteLine("To allow 64 bit processes to participate in the perf counters, we launch a 64 bit process to do the monitoring"); - string heapDumpExe = Path.Combine(Utilities.SupportFiles.SupportFileDir, @"AMD64\HeapDump.exe"); - string commandLine = heapDumpExe + " /MinSecForTrigger:" + MinSecForTrigger + " \"/StopOnPerfCounter:" + m_spec + "\""; - m_log.WriteLine("Exec: {0}", commandLine); - var options = new CommandOptions().AddNoThrow().AddTimeout(CommandOptions.Infinite).AddOutputStream(m_log); - m_cmd = Command.Run(commandLine, options); - if (m_cmd.ExitCode != 0) - { - m_log.WriteLine("Error: heapdump failed with error code {0}", m_cmd.ExitCode); - } - else - { - m_triggered?.Invoke(this); - } - m_cmd = null; - }); - return true; -#else - return false; -#endif - } - private bool IsCurrentlyTrue() { Update(); @@ -330,9 +273,6 @@ private float Update() private bool m_instanceExists; private TextWriter m_log; public event Action m_triggered; -#if PERFVIEW - private Microsoft.Diagnostics.Utilities.Command m_cmd; -#endif private Task m_task; private volatile bool m_monitoringDone; From 46bd81ea4c779e829c25a39b6ab60bd1ecf5f294 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 10:58:20 -0700 Subject: [PATCH 09/41] Remove 32-bit host command redirection --- src/PerfView/CommandProcessor.cs | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index 92abf1b0d..f5018820c 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -940,14 +940,9 @@ public void Start(CommandLineArgs parsedArgs) LogFile.WriteLine("Executing the command: {0}", commandLine); - // Make sure that if we are on a 64 bit machine we run the 64 bit version of netsh. - var cmdExe = Path.Combine(Environment.GetEnvironmentVariable("SystemRoot"), "SysNative", "cmd.exe"); - if (!File.Exists(cmdExe)) - { - cmdExe = cmdExe.Replace("SysNative", "System32"); - } + var cmdExe = Path.Combine(Environment.SystemDirectory, "cmd.exe"); - commandLine = cmdExe + " /c " + commandLine; + commandLine = "\"" + cmdExe + "\" /c " + commandLine; var command = Command.Run(commandLine, new CommandOptions().AddNoThrow().AddOutputStream(LogFile)); string netMonFile = Path.Combine(CacheFiles.CacheDir, "NetMonActive.txt"); @@ -1300,14 +1295,9 @@ public void Stop(CommandLineArgs parsedArgs) LogFile.WriteLine("Executing /StopCommand: {0}", commandToRun); - // We are in the wow, so run this in 64 bit if we need - var cmdExe = Path.Combine(Environment.GetEnvironmentVariable("SystemRoot"), "SysNative", "Cmd.exe"); - if (!File.Exists(cmdExe)) - { - cmdExe = cmdExe.Replace("SysNative", "System32"); - } + var cmdExe = Path.Combine(Environment.SystemDirectory, "cmd.exe"); - commandToRun = cmdExe + " /c " + commandToRun; + commandToRun = "\"" + cmdExe + "\" /c " + commandToRun; var cmd = Command.Run(commandToRun, new CommandOptions().AddOutputStream(LogFile).AddNoThrow().AddTimeout(60000)); if (cmd.ExitCode != 0) { @@ -2086,14 +2076,9 @@ private void DisableNetMonTrace() LogFile.WriteLine("If /NetMonCapture is active this can take a while..."); string commandToRun = "netsh trace stop"; - // We are in the wow, so run this in 64 bit if we need - var cmdExe = Path.Combine(Environment.GetEnvironmentVariable("SystemRoot"), "SysNative", "Cmd.exe"); - if (!File.Exists(cmdExe)) - { - cmdExe = cmdExe.Replace("SysNative", "System32"); - } + var cmdExe = Path.Combine(Environment.SystemDirectory, "cmd.exe"); - commandToRun = cmdExe + " /c " + commandToRun; + commandToRun = "\"" + cmdExe + "\" /c " + commandToRun; Command.Run(commandToRun, new CommandOptions().AddNoThrow().AddOutputStream(LogFile)); FileUtilities.ForceDelete(netMonFile); From 383ceea23973f743cf080d723531a980df5c5b95 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:00:10 -0700 Subject: [PATCH 10/41] Remove the app-permissions OS gate --- src/PerfView/App.cs | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/PerfView/App.cs b/src/PerfView/App.cs index 1027f56fe..f008318aa 100755 --- a/src/PerfView/App.cs +++ b/src/PerfView/App.cs @@ -481,7 +481,7 @@ public static bool Unpack() File.WriteAllText(Path.Combine(SupportFiles.SupportFileDir, "ExtensionsNotCopied"), ""); } - SetPermissionsForWin8Apps(); + SetSupportFilePermissions(); } } return unpacked; @@ -492,23 +492,18 @@ public static bool Unpack() /// unpacked in the previous step. /// [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - private static void SetPermissionsForWin8Apps() + private static void SetSupportFilePermissions() { - // Are we on Win8 or above - var version = Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor; - if (version >= 62) - { - // Make sure that Win8 packages can get at the EtwClrProfiler dll. - // *S-1-15-2-1 == "ALL APPLICATION PACKAGES" we don't use the text because it does not work in other locales - var cmdLine = "icacls.exe \"" + SupportFiles.SupportFileDir + "\" /grant *S-1-15-2-1:(OI)(CI)(RX) /T"; - var cmd = Command.Run(cmdLine, new CommandOptions().AddNoThrow()); - Debug.Assert(cmd.ExitCode == 0); - - // Also grant *S-1-1-0 = everyone read access (so that ASP.NET users can get at the ETW profiler DLL. - cmdLine = "icacls.exe \"" + SupportFiles.SupportFileDir + "\" /grant *S-1-1-0:(OI)(CI)(RX) /T"; - cmd = Command.Run(cmdLine, new CommandOptions().AddNoThrow()); - Debug.Assert(cmd.ExitCode == 0); - } + // Make sure packaged apps can get at the EtwClrProfiler dll. + // *S-1-15-2-1 == "ALL APPLICATION PACKAGES" we don't use the text because it does not work in other locales + var cmdLine = "icacls.exe \"" + SupportFiles.SupportFileDir + "\" /grant *S-1-15-2-1:(OI)(CI)(RX) /T"; + var cmd = Command.Run(cmdLine, new CommandOptions().AddNoThrow()); + Debug.Assert(cmd.ExitCode == 0); + + // Also grant *S-1-1-0 = everyone read access (so that ASP.NET users can get at the ETW profiler DLL. + cmdLine = "icacls.exe \"" + SupportFiles.SupportFileDir + "\" /grant *S-1-1-0:(OI)(CI)(RX) /T"; + cmd = Command.Run(cmdLine, new CommandOptions().AddNoThrow()); + Debug.Assert(cmd.ExitCode == 0); } /// From 10f9cda0ebe1d34f3cda4cff3b312a66631af2df Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:02:21 -0700 Subject: [PATCH 11/41] Remove obsolete kernel-paging commands --- src/PerfView/CommandLineArgs.cs | 7 -- src/PerfView/CommandProcessor.cs | 108 ------------------------------- 2 files changed, 115 deletions(-) diff --git a/src/PerfView/CommandLineArgs.cs b/src/PerfView/CommandLineArgs.cs index 69e71f53c..a3dfc272a 100644 --- a/src/PerfView/CommandLineArgs.cs +++ b/src/PerfView/CommandLineArgs.cs @@ -642,13 +642,6 @@ private void SetupCommandLine(CommandLineParser parser) parser.DefineParameterSet("ListCpuCounters", ref DoCommand, App.CommandProcessor.ListCpuCounters, "Lists the ListCpuCounters CPU counters available on the system (win8+ only)."); - parser.DefineParameterSet("EnableKernelStacks", ref DoCommand, App.CommandProcessor.EnableKernelStacks, - "On X64 machines if you have problems with broken stacks when the code is executing in the kernel," + - " setting this option and rebooting may improve things"); - - parser.DefineParameterSet("DisableKernelStacks", ref DoCommand, App.CommandProcessor.DisableKernelStacks, - "Resets the registry keys set by EnableKernelStack."); - string ProcessParam = null; parser.DefineParameterSet("HeapSnapshot", ref DoCommand, App.CommandProcessor.HeapSnapshot, "Take a snapshot of the CLR GC heap of a process."); diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index f5018820c..1af9284e8 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -314,28 +314,6 @@ public void Start(CommandLineArgs parsedArgs) { LaunchPerfViewElevatedIfNeeded("Start", parsedArgs); - // Are we on an X86 machine? - if (Environment.Is64BitOperatingSystem) - { - if (!IsKernelStacks64Enabled()) - { - var ver = Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor; - if (ver <= 61) - { - LogFile.WriteLine("Warning: This trace is being collected on a X64 machine on a Pre Win8 OS"); - LogFile.WriteLine(" And paging is allowed in the kernel. This can cause stack breakage"); - LogFile.WriteLine(" when samples are taken in the kernel and there is memory pressure."); - LogFile.WriteLine(" It is recommended that you disable paging in the kernel to decrease"); - LogFile.WriteLine(" the number of broken stacks. To do this run the command:"); - LogFile.WriteLine(""); - LogFile.WriteLine(" PerfView EnableKernelStacks "); - LogFile.WriteLine(""); - LogFile.WriteLine(" A reboot will be required for the change to have an effect."); - LogFile.WriteLine(""); - } - } - } - ETWClrProfilerTraceEventParser.Keywords profilerKeywords = 0; if (parsedArgs.DotNetCalls) { @@ -1898,16 +1876,6 @@ public void ListSessions(CommandLineArgs parsedArgs) ShowLog = true; } - public void EnableKernelStacks(CommandLineArgs parsedArgs) - { - SetKernelStacks64(true, LogFile); - ShowLog = true; - } - public void DisableKernelStacks(CommandLineArgs parsedArgs) - { - SetKernelStacks64(false, LogFile); - ShowLog = true; - } public void CreateExtensionProject(CommandLineArgs parsedArgs) { #if !PERFVIEW_COLLECT @@ -2617,82 +2585,6 @@ private static void DeleteEtwClrProfilerKeys(RegistryKey dotnetHive, string pref } } - private static RegistryKey GetMemManagementKey(bool writable) - { - // Open this computer's 64 bit registry (even if this is a 32 bit process. - RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Default); - if (hklm == null) - { - Debug.Assert(false, "Could not get HKLM key"); - return null; - } - RegistryKey memManagment = hklm.OpenSubKey(@"System\CurrentControlSet\Control\Session Manager\Memory Management", writable); - hklm.Dispose(); - return memManagment; - } - private static void SetKernelStacks64(bool crawlable, TextWriter writer) - { - // Are we on a 64 bit system? - if (!Environment.Is64BitOperatingSystem) - { - writer.WriteLine("Disabling kernel paging is only necessary on X64 machines"); - return; - } - - if (IsKernelStacks64Enabled() == crawlable) - { - writer.WriteLine(@"HLKM\" + @"System\CurrentControlSet\Control\Session Manager\Memory Management" + "DisablePagingExecutive" + " already {0}", - crawlable ? "set" : "unset"); - return; - } - - // This is not needed on Windows 8 (mostly) - var ver = Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor; - if (ver > 61) - { - writer.WriteLine("Disabling kernel paging is not necessary on Win8 machines."); - return; - } - - try - { - RegistryKey memKey = GetMemManagementKey(true); - if (memKey != null) - { - memKey.SetValue("DisablePagingExecutive", crawlable ? 1 : 0, RegistryValueKind.DWord); - memKey.Dispose(); - writer.WriteLine(); - writer.WriteLine("The memory management configuration has been {0} for stack crawling.", crawlable ? "enabled" : "disabled"); - writer.WriteLine("However a reboot is needed for it to take effect. You can reboot by executing"); - writer.WriteLine(" shutdown /r /t 1 /f"); - writer.WriteLine(); - } - else - { - writer.WriteLine("Error: Could not access Kernel memory management registry keys."); - } - } - catch (Exception e) - { - writer.WriteLine("Error: Failure setting registry keys: {0}", e.Message); - } - } - private static bool IsKernelStacks64Enabled() - { - bool ret = false; - RegistryKey memKey = GetMemManagementKey(false); - if (memKey != null) - { - object valueObj = memKey.GetValue("DisablePagingExecutive", null); - if (valueObj != null && valueObj is int) - { - ret = ((int)valueObj) != 0; - } - memKey.Dispose(); - } - return ret; - } - public void LaunchPerfViewElevatedIfNeeded(string command, CommandLineArgs parsedArgs) { // Nothing to do if already elevated. From 4a656a9e3e6ee724b301ded0442d75eff96ff649 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:03:33 -0700 Subject: [PATCH 12/41] Remove the custom-session OS restriction --- src/PerfView/CommandLineArgs.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PerfView/CommandLineArgs.cs b/src/PerfView/CommandLineArgs.cs index a3dfc272a..d10a9e7e0 100644 --- a/src/PerfView/CommandLineArgs.cs +++ b/src/PerfView/CommandLineArgs.cs @@ -584,8 +584,6 @@ private void SetupCommandLine(CommandLineParser parser) parser.DefineOptionalQualifier("SessionName", ref sessionName, "Define the name for the user mode session (kernel session will also be named analogously) Useful for collecting traces when another ETW profiler (including PerfView) is being used."); if (sessionName != null) { - if (Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor < 62) - throw new ApplicationException("SessionName qualifier only works on Windows 8 and above."); CommandProcessor.s_UserModeSessionName = sessionName; CommandProcessor.s_KernelessionName = sessionName + "Kernel"; } From 73f7ef233fa94327d82b1018c560fcc68180b24c Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:05:00 -0700 Subject: [PATCH 13/41] Remove the CPU-counter UI OS gate --- src/PerfView/CommandLineArgs.cs | 6 +++--- src/PerfView/Dialogs/RunCommandDialog.xaml.cs | 7 ------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/PerfView/CommandLineArgs.cs b/src/PerfView/CommandLineArgs.cs index d10a9e7e0..1fbee896f 100644 --- a/src/PerfView/CommandLineArgs.cs +++ b/src/PerfView/CommandLineArgs.cs @@ -118,7 +118,7 @@ public static string GetHelpString(int maxLineWidth) public int CircularMB; public bool InMemoryCircularBuffer; // Uses EVENT_TRACE_BUFFERING_MODE for an in-memory circular buffer public KernelTraceEventParser.Keywords KernelEvents = KernelTraceEventParser.Keywords.Default; - public string[] CpuCounters; // Specifies any profile sources (CPU counters) to turn on (Win 8 only) + public string[] CpuCounters; // Specifies any profile sources (CPU counters) to turn on public ClrTraceEventParser.Keywords ClrEvents = ClrTraceEventParser.Keywords.Default; public TraceEventLevel ClrEventLevel = Microsoft.Diagnostics.Tracing.TraceEventLevel.Verbose; // The verbosity of CLR events public TplEtwProviderTraceEventParser.Keywords TplEvents = TplEtwProviderTraceEventParser.Keywords.None; @@ -432,7 +432,7 @@ private void SetupCommandLine(CommandLineParser parser) parser.DefineOptionalQualifier("CpuCounters", ref CpuCounters, "A comma separated list of hardware CPU counters specifications NAME:COUNT to turn on. " + - "See Users guide for details. See ListCpuCounters for available sources (Win8 only)"); + "See Users guide for details. See ListCpuCounters for available sources."); parser.DefineOptionalQualifier("Providers", ref Providers, "Additional providers. This is comma separated list of ProviderGuid:Keywords:Level:Stack specs. " + @@ -638,7 +638,7 @@ private void SetupCommandLine(CommandLineParser parser) "Lists active ETW sessions."); parser.DefineParameterSet("ListCpuCounters", ref DoCommand, App.CommandProcessor.ListCpuCounters, - "Lists the ListCpuCounters CPU counters available on the system (win8+ only)."); + "Lists the CPU counters available on the system."); string ProcessParam = null; parser.DefineParameterSet("HeapSnapshot", ref DoCommand, App.CommandProcessor.HeapSnapshot, diff --git a/src/PerfView/Dialogs/RunCommandDialog.xaml.cs b/src/PerfView/Dialogs/RunCommandDialog.xaml.cs index cff21137d..c4dcfa710 100644 --- a/src/PerfView/Dialogs/RunCommandDialog.xaml.cs +++ b/src/PerfView/Dialogs/RunCommandDialog.xaml.cs @@ -38,13 +38,6 @@ public RunCommandDialog(CommandLineArgs args, MainWindow mainWindow, bool isColl InitializeComponent(); - var osVersion = Environment.OSVersion.Version.Major + Environment.OSVersion.Version.Minor / 10.0; - if (osVersion < 6.2) // CPU Counters only supported on Windows 8 and above - { - CpuCountersListButton.IsEnabled = false; - CpuCountersTextBox.IsEnabled = false; - } - if (args.DataFile == null) { args.DataFile = "PerfViewData.etl"; From b2d3157ee2acdc8b52e96ff8414f612841f955d4 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:06:18 -0700 Subject: [PATCH 14/41] Always log CPU-counter settings on stop --- src/PerfView/CommandProcessor.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index 1af9284e8..8e628e27a 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -1145,20 +1145,16 @@ public void Stop(CommandLineArgs parsedArgs) PerfViewLogger.Log.StartAndStopTimes(); // Also log the CPU Counters mapping. - var osVersion = Environment.OSVersion.Version.Major + Environment.OSVersion.Version.Minor / 10.0; - if (6.2 <= osVersion) // CPU Counters only supported on Windows 8 and above + var cpuCounters = TraceEventProfileSources.GetInfo(); + foreach (var cpuCounter in cpuCounters.Values) { - var cpuCounters = TraceEventProfileSources.GetInfo(); - foreach (var cpuCounter in cpuCounters.Values) + if (string.CompareOrdinal(cpuCounter.Name, "Timer") == 0) { - if (string.CompareOrdinal(cpuCounter.Name, "Timer") == 0) - { - continue; - } - - PerfViewLogger.Log.CpuCounterIntervalSetting(cpuCounter.Name, cpuCounter.Interval, cpuCounter.ID); - // LogFile.WriteLine("Cpu Counter Config {0} ID {1} Interval {2}", cpuCounter.Name, cpuCounter.Interval, cpuCounter.ID); + continue; } + + PerfViewLogger.Log.CpuCounterIntervalSetting(cpuCounter.Name, cpuCounter.Interval, cpuCounter.ID); + // LogFile.WriteLine("Cpu Counter Config {0} ID {1} Interval {2}", cpuCounter.Name, cpuCounter.Interval, cpuCounter.ID); } // Try to stop the kernel session From c2c2cf5a86b7d045e5741857aa8adb3f7533faac Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:07:24 -0700 Subject: [PATCH 15/41] Remove legacy network-capture OS arguments --- src/PerfView/CommandProcessor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index 8e628e27a..3c07a963f 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -894,8 +894,7 @@ public void Start(CommandLineArgs parsedArgs) string perfMerge = "perfMerge=no"; string traceFile = CacheFiles.FindFile(parsedArgs.DataFile, ".netmon.etl"); - var osVer = Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor; - if (parsedArgs.NetMonCapture || osVer < 62) + if (parsedArgs.NetMonCapture) { traceFile = Path.GetFileNameWithoutExtension(parsedArgs.DataFile) + "_netmon.etl"; // We use the _ to avoid conventions about merging. maxSize = ""; From ef997a92d22620e3a8cfceccaade77a238c8793b Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:10:24 -0700 Subject: [PATCH 16/41] Remove the old-host trace warning --- src/PerfView/PerfViewData.cs | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/PerfView/PerfViewData.cs b/src/PerfView/PerfViewData.cs index 2ef858761..ef116d855 100644 --- a/src/PerfView/PerfViewData.cs +++ b/src/PerfView/PerfViewData.cs @@ -7771,30 +7771,6 @@ protected override Action OpenImpl(Window parentWindow, StatusBar worker } }); - // Warn about possible Win8 incompatibility. - var logVer = tracelog.OSVersion.Major * 10 + tracelog.OSVersion.Minor; - if (62 <= logVer) - { - var ver = Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor; - if (ver < 62) // We are decoding on less than windows 8 - { - if (!m_notifiedAboutWin8) - { - m_notifiedAboutWin8 = true; - var versionMismatchWarning = """ - This trace was captured on Window 8 and is being read - on and earlier OS. If you experience any problems please - read the trace on an Windows 8 OS. - """; - worker.LogWriter.WriteLine(versionMismatchWarning); - parentWindow.Dispatcher.BeginInvoke(() => - { - XamlMessageBox.Show(parentWindow, versionMismatchWarning, "Log File Version Mismatch", MessageBoxButton.OK); - }); - } - } - } - var advanced = new PerfViewTreeGroup("Advanced"); var memory = new PerfViewTreeGroup("Memory"); var frameworkAspNetWcf = new PerfViewTreeGroup(".NET Framework ASP.NET/WCF"); @@ -8558,7 +8534,6 @@ private bool HasVSEvents(TraceLog traceLog) private bool m_hasVSEvents; private TraceLog m_traceLog; private bool m_notifiedAboutLostEvents; - private bool m_notifiedAboutWin8; private string m_extraTopStats; #endregion } From 462c8e46f2aed431cb9f5b0ad11214ccfa97fb47 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:11:44 -0700 Subject: [PATCH 17/41] Remove JavaScript heap OS gates --- src/PerfView/memory/JavaScriptHeapDumper.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/PerfView/memory/JavaScriptHeapDumper.cs b/src/PerfView/memory/JavaScriptHeapDumper.cs index 963f4cd93..772d6158e 100644 --- a/src/PerfView/memory/JavaScriptHeapDumper.cs +++ b/src/PerfView/memory/JavaScriptHeapDumper.cs @@ -18,14 +18,6 @@ public class JavaScriptHeapDumper /// public static bool DumpAsEtlFile(int processID, string etlFileName, TextWriter log, MemoryGraph memoryGraph = null) { - var ver = Environment.OSVersion.Version; - var intVer = ver.Major * 10 + ver.Minor; - if (intVer < 62) - { - log.WriteLine("JavaScript Heap Dumping only supported on Win8 or above."); - return false; - } - bool success = false; var kernelFileName = Path.ChangeExtension(etlFileName, ".kernel.etl"); try @@ -69,14 +61,6 @@ public static bool DumpAsEtlFile(int processID, string etlFileName, TextWriter l /// public static bool Dump(int processID, MemoryGraph memoryGraph, TextWriter log) { - var ver = Environment.OSVersion.Version; - var intVer = ver.Major * 10 + ver.Minor; - if (intVer < 62) - { - log.WriteLine("JavaScript Heap Dumping only supported on Win8 or above."); - return false; - } - var sw = Stopwatch.StartNew(); var dumper = new JavaScriptDumpGraphReader(log); bool dumpComplete = false; From 91727412db67d50f6bd3e82ac41501eb64aa9901 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:12:42 -0700 Subject: [PATCH 18/41] Remove the package-lookup OS gate --- src/HeapDump/GCHeapDumper.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/HeapDump/GCHeapDumper.cs b/src/HeapDump/GCHeapDumper.cs index 7a970428b..d62d411d8 100644 --- a/src/HeapDump/GCHeapDumper.cs +++ b/src/HeapDump/GCHeapDumper.cs @@ -684,11 +684,11 @@ private void ResumeProcessIfNecessary(int processID) throw new HeapDumpException("Could not find process with ID " + processID, HR.CouldNotFindProcessId); } - // Determine if we are a Win8 Application. + // Resume packaged applications before collection. var fullPackageName = PackageUtil.FullPackageNameForProcess(process); if (fullPackageName != null) { - m_log.WriteLine("Process {0} is a Windows 8 application, resuming that process.", processID); + m_log.WriteLine("Process {0} is a packaged application, resuming that process.", processID); var pkgDebugSettings = (IPackageDebugSettings)new PackageDebugSettingsClass(); // pkgDebugSettings.EnableDebugging(fullPackageName, null, IntPtr.Zero); pkgDebugSettings.Resume(fullPackageName); @@ -1609,18 +1609,12 @@ private NodeTypeIndex GetTypeIndexForName(string typeName, string moduleName, in public delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); /// -/// Gets at Win8 Package information. +/// Gets package information for a process. /// internal class PackageUtil { public static string FullPackageNameForProcess(Process process) { - var version = Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor; - if (version < 62) - { - return null; // Packages only exist on Windows 8 - } - var packageFullNameBuff = new StringBuilder(512); int packageFullNameBuffLen = packageFullNameBuff.Capacity; var hr = GetPackageFullName(process.Handle, ref packageFullNameBuffLen, packageFullNameBuff); @@ -1656,4 +1650,3 @@ internal interface IPackageDebugSettings #endregion - From fe89d10de78d43f722bbf677273c595ecc085e00 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:14:51 -0700 Subject: [PATCH 19/41] Remove Win61 from TraceEvent packaging --- .../Microsoft.Diagnostics.Tracing.TraceEvent.nuspec | 1 - .../Microsoft.Diagnostics.Tracing.TraceEvent.props | 5 ----- src/TraceEvent/TraceEvent.csproj | 5 ----- 3 files changed, 11 deletions(-) diff --git a/src/TraceEvent/Microsoft.Diagnostics.Tracing.TraceEvent.nuspec b/src/TraceEvent/Microsoft.Diagnostics.Tracing.TraceEvent.nuspec index 185283f94..ea99fb0ff 100644 --- a/src/TraceEvent/Microsoft.Diagnostics.Tracing.TraceEvent.nuspec +++ b/src/TraceEvent/Microsoft.Diagnostics.Tracing.TraceEvent.nuspec @@ -51,7 +51,6 @@ - diff --git a/src/TraceEvent/Microsoft.Diagnostics.Tracing.TraceEvent.props b/src/TraceEvent/Microsoft.Diagnostics.Tracing.TraceEvent.props index 875675aa2..af291e0da 100644 --- a/src/TraceEvent/Microsoft.Diagnostics.Tracing.TraceEvent.props +++ b/src/TraceEvent/Microsoft.Diagnostics.Tracing.TraceEvent.props @@ -13,11 +13,6 @@ PreserveNewest False - - x86\KernelTraceControl.Win61.dll - PreserveNewest - False - x86\msdia140.dll PreserveNewest diff --git a/src/TraceEvent/TraceEvent.csproj b/src/TraceEvent/TraceEvent.csproj index a130ae7f0..a8675cc2f 100644 --- a/src/TraceEvent/TraceEvent.csproj +++ b/src/TraceEvent/TraceEvent.csproj @@ -137,11 +137,6 @@ PreserveNewest False - - x86\KernelTraceControl.Win61.dll - PreserveNewest - False - x86\msdia140.dll PreserveNewest From 58ced5c35d4af59355be7b475dee216c9432c051 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:20:25 -0700 Subject: [PATCH 20/41] Remove Windows 7 real-time session emulation --- src/TraceEvent/ETWTraceEventSource.cs | 18 ---------- src/TraceEvent/TraceEventSession.cs | 40 ++-------------------- src/TraceEvent/TraceLog.cs | 49 ++------------------------- 3 files changed, 5 insertions(+), 102 deletions(-) diff --git a/src/TraceEvent/ETWTraceEventSource.cs b/src/TraceEvent/ETWTraceEventSource.cs index 710805364..523b0a76d 100644 --- a/src/TraceEvent/ETWTraceEventSource.cs +++ b/src/TraceEvent/ETWTraceEventSource.cs @@ -13,7 +13,6 @@ using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; -using System.Threading; using Microsoft.Diagnostics.Utilities; using Address = System.UInt64; @@ -828,11 +827,6 @@ private void RawDispatch(TraceEventNativeMethods.EVENT_RECORD* rawData) return; } - if (lockObj != null) - { - Monitor.Enter(lockObj); - } - Debug.Assert(rawData->EventHeader.HeaderType == 0); // if non-zero probably old-style ETW header // Give it an event ID if it does not have one. @@ -860,10 +854,6 @@ private void RawDispatch(TraceEventNativeMethods.EVENT_RECORD* rawData) Dispatch(anEvent); - if (lockObj != null) - { - Monitor.Exit(lockObj); - } } /// @@ -951,14 +941,6 @@ private bool TraceEventBufferCallback(IntPtr rawLogFile) private IEnumerable fileNames; // Used if more than one file being processed. (Null otherwise) - // TODO this can be removed, and use AddDispatchHook instead. - /// - /// Used by real time TraceLog on Windows7. - /// If we have several real time sources we have them coming in on several threads, but we want the illusion that they - /// are one source (thus being processed one at a time). Thus we want a lock that is taken on every dispatch. - /// - internal object lockObj; - // We do minimal processing to keep track of process names (since they are REALLY handy). private Dictionary processNameForID; diff --git a/src/TraceEvent/TraceEventSession.cs b/src/TraceEvent/TraceEventSession.cs index fa3d72047..323f5c7e6 100644 --- a/src/TraceEvent/TraceEventSession.cs +++ b/src/TraceEvent/TraceEventSession.cs @@ -597,7 +597,7 @@ public static Dictionary MakeDictionary(params string[] keyValue // OS Kernel Provider support /// - /// Enable the kernel provider for the session. Before windows 8 this session must be called 'NT Kernel Session'. + /// Enable the kernel provider for the session. /// This API is OK to call from one thread while Process() is being run on another /// /// Specifies the particular kernel events of interest @@ -635,7 +635,7 @@ public unsafe bool EnableKernelProvider(KernelTraceEventParser.Keywords flags, K throw new NotSupportedException("Kernel Event Tracing is only supported on Windows 6.0 (Vista) and above."); } - if (IsValidSession || m_kernelSession != null) + if (IsValidSession) { throw new Exception("The kernel provider must be enabled first and only once in a session."); } @@ -647,23 +647,7 @@ public unsafe bool EnableKernelProvider(KernelTraceEventParser.Keywords flags, K throw new NotSupportedException("Keyword specified this is only supported on the " + KernelTraceEventParser.KernelSessionName + " session."); } - if (!OperatingSystemVersion.AtLeast(62)) - { - if (m_FileName != null) - { - throw new NotSupportedException("System Tracing is only supported on Windows 8 and above."); - } - - // On windows 7 and Vista, fake the systemTraceProvider for real time sessions, and do the EnableKernelProvider on that. - var kernelSession = new TraceEventSession(KernelTraceEventParser.KernelSessionName); - var nestedRet = kernelSession.EnableKernelProvider(flags, stackCapture); - m_kernelSession = kernelSession; - return nestedRet; - } - else - { - systemTraceProvider = true; - } + systemTraceProvider = true; } // The Profile event requires the SeSystemProfilePrivilege to succeed, so set it. @@ -967,13 +951,6 @@ public void Dispose() m_source = null; } - // on Win7 we might have a real time kernel session, dispose of that if present. - if (m_kernelSession != null) - { - m_kernelSession.Dispose(); - m_kernelSession = null; - } - GC.SuppressFinalize(this); } } @@ -1452,11 +1429,6 @@ public ETWTraceEventSource Source throw new InvalidOperationException("Only non-file based, non-circular ('real time') sessions have can have a source associated with them."); } - if (m_kernelSession != null && !m_associatedWithTraceLog) - { - throw new InvalidOperationException("Can only use Kernel events in real time sessions on Windows 7 if you use TraceLog.CreateFromTraceEventSession"); - } - if (!IsValidSession) { if (m_SessionName == KernelTraceEventParser.KernelSessionName) @@ -2731,11 +2703,6 @@ public static int GetMaxLastBranchRecordingSources() private TraceEventNativeMethods.SafeTraceHandle m_SessionHandle; // OS handle private ETWTraceEventSource m_source; // Sessions can have a source associated with them. - internal TraceEventSession m_kernelSession; // Only needed in Windows 7. Before windows 8 you could not enable Kernel - // events on 'normal' user mode session. This tried to 'fake' Win 8 behavior - // on Win 7. We only do this for real time sessions that are using TraceLog. - internal bool m_associatedWithTraceLog; // Currently we only allow m_kernelSession to be used if you are using TraceLog on the session. - private readonly Dictionary m_enabledProviders = new Dictionary(); #endregion @@ -3543,4 +3510,3 @@ public enum LbrFilterFlags CallstackEnable = 1 << 9, } } - diff --git a/src/TraceEvent/TraceLog.cs b/src/TraceEvent/TraceLog.cs index 1e2b5ff28..537bc60ec 100644 --- a/src/TraceEvent/TraceLog.cs +++ b/src/TraceEvent/TraceLog.cs @@ -151,9 +151,7 @@ public static TraceLog OpenOrConvert(string etlOrEtlxFilePath, TraceLogOptions o /// the .Log Property) which lets you get at aggregated information (Processes, threads, images loaded, and perhaps most /// importantly TraceEvent.CallStack() will work. Thus you can get real time stacks from events). /// - /// Note that in order for native stacks to resolve symbolically, you need to have some Kernel events turned on (Image, and Process) - /// and only windows 8 has a session that allows both kernel and user mode events simultaneously. Thus this is most useful - /// on Win 8 systems. + /// To resolve native stacks symbolically, enable the Image and Process kernel events in the session. /// public static TraceLogEventSource CreateFromTraceEventSession(TraceEventSession session) { @@ -166,9 +164,7 @@ public static TraceLogEventSource CreateFromTraceEventSession(TraceEventSession /// the .Log Property) which lets you get at aggregated information (Processes, threads, images loaded, and perhaps most /// importantly TraceEvent.CallStack() will work. Thus you can get real time stacks from events). /// - /// Note that in order for native stacks to resolve symbolically, you need to have some Kernel events turned on (Image, and Process) - /// and only windows 8 has a session that allows both kernel and user mode events simultaneously. Thus this is most useful - /// on Win 8 systems. + /// To resolve native stacks symbolically, enable the Image and Process kernel events in the session. /// /// The delay in milliseconds between when an event is received in TraceLog and when it is dispatched to the real time event source. public static TraceLogEventSource CreateFromTraceEventSession(TraceEventSession session, int minDispatchDelayMSec) @@ -185,21 +181,6 @@ public static TraceLogEventSource CreateFromTraceEventSession(TraceEventSession traceLog.realTimeFlushTimer = new Timer(_ => traceLog.FlushRealTimeEvents(minDispatchDelayMSec), null, minDispatchDelayMSec, minDispatchDelayMSec); traceLog.rawEventSourceToConvert.AllEvents += traceLog.onAllEventsRealTime; - // See if we are on Win7 and have a separate kernel session associated with 'session' - if (session.m_kernelSession != null) - { - // Make sure both sources only dispatch one at a time by taking a lock during dispatch. - session.m_kernelSession.Source.lockObj = traceLog.realTimeQueue; - session.m_associatedWithTraceLog = true; // Indicate that it is OK to have the m_kernelSession. - session.Source.lockObj = traceLog.realTimeQueue; - - // Set up the callbacks to the kernel session. - traceLog.rawKernelEventSource = session.m_kernelSession.Source; - traceLog.SetupCallbacks(traceLog.rawKernelEventSource); - traceLog.rawKernelEventSource.unhandledEventTemplate.traceEventSource = traceLog; // Make everything point to the log as its source. - traceLog.rawKernelEventSource.AllEvents += traceLog.onAllEventsRealTime; - } - return traceLog.realTimeSource; } @@ -849,14 +830,6 @@ private unsafe void onAllEventsRealTime(TraceEvent data) data.eventIndex = (EventIndex)eventCount; pastEventInfo.LogEvent(data, data.eventIndex, countForEvent); - // currentID is used by the dispatcher to define the EventIndex. Make sure at both sources have the - // same notion of what that is if we have two dispatcher. - if (rawKernelEventSource != null) - { - rawEventSourceToConvert.currentID = (EventIndex)eventCount; - rawKernelEventSource.currentID = (EventIndex)eventCount; - } - // Skip samples from the idle thread. if (data.ProcessID == 0 && data is SampledProfileTraceData) { @@ -4640,7 +4613,6 @@ public EventsToCodeAddressIndex(EventIndex eventIndex, Address address, CodeAddr // These are only used when converting from ETL internal TraceEventDispatcher rawEventSourceToConvert; // used to convert from raw format only. Null for ETLX files. - internal TraceEventDispatcher rawKernelEventSource; // Only used by real time TraceLog on Win7. It is the internal TraceLogOptions options; internal bool registeringStandardParsers; // Are we registering internal NettraceUniversalConverter universalConverter; @@ -4689,24 +4661,7 @@ public override bool Process() { Debug.Assert(this == TraceLog.realTimeSource); - Task kernelTask = null; - if (TraceLog.rawKernelEventSource != null) - { - kernelTask = Task.Factory.StartNew(delegate - { - TraceLog.rawKernelEventSource.Process(); - TraceLog.rawEventSourceToConvert.StopProcessing(); - }); - } TraceLog.rawEventSourceToConvert.Process(); - if (kernelTask != null) - { - TraceLog.rawKernelEventSource.StopProcessing(); - kernelTask.Wait(); - - // Flush all outstanding events in the realTimeQueue. - TraceLog.FlushRealTimeEvents(); - } return true; } Debug.Assert(unhandledEventTemplate.traceEventSource == TraceLog); From 53e4059d652ca0f3403feff7148ce41d9fbd099d Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:22:36 -0700 Subject: [PATCH 21/41] Remove legacy ETW provider-control fallbacks --- src/TraceEvent/TraceEventNativeMethods.cs | 59 -------------- src/TraceEvent/TraceEventSession.cs | 95 ++++++++--------------- 2 files changed, 33 insertions(+), 121 deletions(-) diff --git a/src/TraceEvent/TraceEventNativeMethods.cs b/src/TraceEvent/TraceEventNativeMethods.cs index c6e45d6c0..a651eadca 100644 --- a/src/TraceEvent/TraceEventNativeMethods.cs +++ b/src/TraceEvent/TraceEventNativeMethods.cs @@ -615,65 +615,6 @@ internal static int StartTrace( return dwErr; } - [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] - private static extern int EnableTrace( - [In] uint enable, - [In] int enableFlag, - [In] int enableLevel, - [In] in Guid controlGuid, - [In] TRACEHANDLE sessionHandle); - - internal static int EnableTrace( - uint enable, - int enableFlag, - int enableLevel, - in Guid controlGuid, - SafeTraceHandle sessionHandle) - { - return EnableTrace( - enable, - enableFlag, - enableLevel, - controlGuid, - sessionHandle.DangerousGetHandle()); - } - - [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] - private static extern int EnableTraceEx( - [In] in Guid ProviderId, - [In] Guid* SourceId, - [In] TRACEHANDLE TraceHandle, - [In] int IsEnabled, - [In] byte Level, - [In] ulong MatchAnyKeyword, - [In] ulong MatchAllKeyword, - [In] uint EnableProperty, - [In] EVENT_FILTER_DESCRIPTOR* filterData); - - internal static int EnableTraceEx( - in Guid ProviderId, - Guid* SourceId, - SafeTraceHandle TraceHandle, - bool IsEnabled, - TraceEventLevel Level, - ulong MatchAnyKeyword, - ulong MatchAllKeyword, - uint EnableProperty, - EVENT_FILTER_DESCRIPTOR* filterData) - { - return EnableTraceEx( - ProviderId, - SourceId, - TraceHandle.DangerousGetHandle(), - IsEnabled ? 1 : 0, - (byte)Level, - MatchAnyKeyword, - MatchAllKeyword, - EnableProperty, - filterData - ); - } - [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] private static extern int EnableTraceEx2( [In] TRACEHANDLE TraceHandle, diff --git a/src/TraceEvent/TraceEventSession.cs b/src/TraceEvent/TraceEventSession.cs index 323f5c7e6..b6676e824 100644 --- a/src/TraceEvent/TraceEventSession.cs +++ b/src/TraceEvent/TraceEventSession.cs @@ -436,52 +436,41 @@ public bool EnableProvider(Guid providerGuid, TraceEventLevel providerLevel = Tr filterDescrPtr = null; } - int hr; - try - { - // Try the Win7 API - TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS parameters = new TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS(); - - parameters.Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION; - parameters.FilterDescCount = curDescrIdx; - parameters.EnableFilterDesc = filterDescrPtr; + TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS parameters = new TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS(); - if (options.StacksEnabled || options.EventIDStacksToEnable != null || options.EventIDStacksToDisable != null) - { - parameters.EnableProperty |= TraceEventNativeMethods.EVENT_ENABLE_PROPERTY_STACK_TRACE; - } - if(options.EnableInContainers) - { - parameters.EnableProperty |= TraceEventNativeMethods.EVENT_ENABLE_PROPERTY_ENABLE_SILOS; - } - if(options.EnableSourceContainerTracking) - { - parameters.EnableProperty |= TraceEventNativeMethods.EVENT_ENABLE_PROPERTY_SOURCE_CONTAINER_TRACKING; - } + parameters.Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION; + parameters.FilterDescCount = curDescrIdx; + parameters.EnableFilterDesc = filterDescrPtr; - if (etwFilteringSupported) // If we are on 8.1 we can use the newer API. - { - parameters.Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION_2; - } - else - { - Debug.Assert(curDescrIdx <= 1); - Debug.Assert(filterDescrPtr == null || -100 <= filterDescrPtr[0].Type); // We are not using any of the Win8.1 defined types. - } + if (options.StacksEnabled || options.EventIDStacksToEnable != null || options.EventIDStacksToDisable != null) + { + parameters.EnableProperty |= TraceEventNativeMethods.EVENT_ENABLE_PROPERTY_STACK_TRACE; + } + if (options.EnableInContainers) + { + parameters.EnableProperty |= TraceEventNativeMethods.EVENT_ENABLE_PROPERTY_ENABLE_SILOS; + } + if (options.EnableSourceContainerTracking) + { + parameters.EnableProperty |= TraceEventNativeMethods.EVENT_ENABLE_PROPERTY_SOURCE_CONTAINER_TRACKING; + } - uint eventControlCode = (valueDataType == ControllerCommand.SendManifest - ? TraceEventNativeMethods.EVENT_CONTROL_CODE_CAPTURE_STATE - : TraceEventNativeMethods.EVENT_CONTROL_CODE_ENABLE_PROVIDER); - hr = TraceEventNativeMethods.EnableTraceEx2(m_SessionHandle, providerGuid, - eventControlCode, providerLevel, - matchAnyKeywords, matchAllKeywords, EnableProviderTimeoutMSec, parameters); + if (etwFilteringSupported) // If we are on 8.1 we can use the newer API. + { + parameters.Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION_2; } - catch (TypeLoadException) + else { - // OK that did not work, try the VISTA API - hr = TraceEventNativeMethods.EnableTraceEx(providerGuid, null, m_SessionHandle, true, - providerLevel, matchAnyKeywords, matchAllKeywords, 0, filterDescrPtr); + Debug.Assert(curDescrIdx <= 1); + Debug.Assert(filterDescrPtr == null || -100 <= filterDescrPtr[0].Type); // We are not using any of the Win8.1 defined types. } + + uint eventControlCode = (valueDataType == ControllerCommand.SendManifest + ? TraceEventNativeMethods.EVENT_CONTROL_CODE_CAPTURE_STATE + : TraceEventNativeMethods.EVENT_CONTROL_CODE_ENABLE_PROVIDER); + int hr = TraceEventNativeMethods.EnableTraceEx2(m_SessionHandle, providerGuid, + eventControlCode, providerLevel, + matchAnyKeywords, matchAllKeywords, EnableProviderTimeoutMSec, parameters); Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(hr)); } @@ -829,28 +818,10 @@ public void DisableProvider(Guid providerGuid) { lock (this) { - int hr; - try - { - try - { - // Try the Win7 API - var parameters = new TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS { Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION }; - hr = TraceEventNativeMethods.EnableTraceEx2( - m_SessionHandle, providerGuid, TraceEventNativeMethods.EVENT_CONTROL_CODE_DISABLE_PROVIDER, - 0, 0, 0, EnableProviderTimeoutMSec, parameters); - } - catch (TypeLoadException) - { - // OK that did not work, try the VISTA API - hr = TraceEventNativeMethods.EnableTraceEx(providerGuid, null, m_SessionHandle, false, 0, 0, 0, 0, null); - } - } - catch (TypeLoadException) - { - // Try with the old pre-vista API - hr = TraceEventNativeMethods.EnableTrace(0, 0, 0, providerGuid, m_SessionHandle); - } + var parameters = new TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS { Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION }; + int hr = TraceEventNativeMethods.EnableTraceEx2( + m_SessionHandle, providerGuid, TraceEventNativeMethods.EVENT_CONTROL_CODE_DISABLE_PROVIDER, + 0, 0, 0, EnableProviderTimeoutMSec, parameters); Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(hr)); } } From cca3dafb56c55be91462b5c0fb2cf4a495372ec7 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:24:10 -0700 Subject: [PATCH 22/41] Remove the pre-Vista ETW consumer API --- src/TraceEvent/ETWTraceEventSource.cs | 75 +------------------ .../TraceEvent.Tests/Regression/Issue1634.cs | 3 - src/TraceEvent/TraceEvent.cs | 9 +-- 3 files changed, 7 insertions(+), 80 deletions(-) diff --git a/src/TraceEvent/ETWTraceEventSource.cs b/src/TraceEvent/ETWTraceEventSource.cs index 523b0a76d..7cdf6d331 100644 --- a/src/TraceEvent/ETWTraceEventSource.cs +++ b/src/TraceEvent/ETWTraceEventSource.cs @@ -494,19 +494,8 @@ private void InitializeFiles() // Fill out the first log file information (we will clone it later if we have multiple files). logFiles[0].BufferCallback = TraceEventBufferCallback; - useClassicETW = !OperatingSystemVersion.AtLeast(OperatingSystemVersion.Vista); - if (useClassicETW) - { - var mem = (TraceEventNativeMethods.EVENT_RECORD*)Marshal.AllocHGlobal(sizeof(TraceEventNativeMethods.EVENT_RECORD)); - *mem = default(TraceEventNativeMethods.EVENT_RECORD); - convertedHeader = mem; - logFiles[0].EventCallback = RawDispatchClassic; - } - else - { - logFiles[0].LogFileMode |= TraceEventNativeMethods.PROCESS_TRACE_MODE_EVENT_RECORD; - logFiles[0].EventCallback = RawDispatch; - } + logFiles[0].LogFileMode |= TraceEventNativeMethods.PROCESS_TRACE_MODE_EVENT_RECORD; + logFiles[0].EventCallback = RawDispatch; // We want the raw timestamp because it is needed to match up stacks with the event they go with. logFiles[0].LogFileMode |= TraceEventNativeMethods.PROCESS_TRACE_MODE_RAW_TIMESTAMP; @@ -772,52 +761,6 @@ private bool ProcessOneFile() // method (see http://msdn2.microsoft.com/en-us/library/aa364089.aspx) We set it up so that we call // back to ETWTraceEventSource.Dispatch which is the heart of the event callback logic. // [SecuritySafeCritical] - private void RawDispatchClassic(TraceEventNativeMethods.EVENT_RECORD* eventData) - { - // TODO not really a EVENT_RECORD on input, but it is a pain to be type-correct. - TraceEventNativeMethods.EVENT_TRACE* oldStyleHeader = (TraceEventNativeMethods.EVENT_TRACE*)eventData; - eventData = convertedHeader; - - eventData->EventHeader.Size = (ushort)sizeof(TraceEventNativeMethods.EVENT_TRACE_HEADER); - // HeaderType - eventData->EventHeader.Flags = TraceEventNativeMethods.EVENT_HEADER_FLAG_CLASSIC_HEADER; - - // TODO Figure out if there is a marker that is used in the WOW for the classic providers - // right now I assume they are all the same as the machine. - if (pointerSize == 8) - { - eventData->EventHeader.Flags |= TraceEventNativeMethods.EVENT_HEADER_FLAG_64_BIT_HEADER; - } - else - { - eventData->EventHeader.Flags |= TraceEventNativeMethods.EVENT_HEADER_FLAG_32_BIT_HEADER; - } - - // EventProperty - eventData->EventHeader.ThreadId = oldStyleHeader->Header.ThreadId; - eventData->EventHeader.ProcessId = oldStyleHeader->Header.ProcessId; - eventData->EventHeader.TimeStamp = oldStyleHeader->Header.TimeStamp; - eventData->EventHeader.ProviderId = oldStyleHeader->Header.Guid; // ProviderId = TaskId - // ID left 0 - eventData->EventHeader.Version = (byte)oldStyleHeader->Header.Version; - // Channel - eventData->EventHeader.Level = oldStyleHeader->Header.Level; - eventData->EventHeader.Opcode = oldStyleHeader->Header.Type; - // Task - // Keyword - eventData->EventHeader.KernelTime = oldStyleHeader->Header.KernelTime; - eventData->EventHeader.UserTime = oldStyleHeader->Header.UserTime; - // ActivityID - - eventData->BufferContext = oldStyleHeader->BufferContext; - // ExtendedDataCount - eventData->UserDataLength = (ushort)oldStyleHeader->MofLength; - // ExtendedData - eventData->UserData = oldStyleHeader->MofData; - // UserContext - - RawDispatch(eventData); - } // [SecuritySafeCritical] private void RawDispatch(TraceEventNativeMethods.EVENT_RECORD* rawData) @@ -878,12 +821,6 @@ protected override void Dispose(bool disposing) handles = null; } - if (convertedHeader != null) - { - Marshal.FreeHGlobal((IntPtr)convertedHeader); - convertedHeader = null; - } - traceLoggingEventId.Dispose(); // logFiles = null; Keep the callback delegate alive as long as possible. @@ -913,11 +850,8 @@ private void Reset() handles[i].Dispose(); // Annoying. The OS resets the LogFileMode field, so I have to set it up again. - if (!useClassicETW) - { - logFiles[i].LogFileMode = TraceEventNativeMethods.PROCESS_TRACE_MODE_EVENT_RECORD; - logFiles[i].LogFileMode |= TraceEventNativeMethods.PROCESS_TRACE_MODE_RAW_TIMESTAMP; - } + logFiles[i].LogFileMode = TraceEventNativeMethods.PROCESS_TRACE_MODE_EVENT_RECORD; + logFiles[i].LogFileMode |= TraceEventNativeMethods.PROCESS_TRACE_MODE_RAW_TIMESTAMP; handles[i] = TraceEventNativeMethods.OpenTrace(ref logFiles[i]); } @@ -933,7 +867,6 @@ private bool TraceEventBufferCallback(IntPtr rawLogFile) // #ETWTraceEventSourceFields private bool processTraceCalled; - private TraceEventNativeMethods.EVENT_RECORD* convertedHeader; // Returned from OpenTrace private TraceEventNativeMethods.EVENT_TRACE_LOGFILEW[] logFiles; diff --git a/src/TraceEvent/TraceEvent.Tests/Regression/Issue1634.cs b/src/TraceEvent/TraceEvent.Tests/Regression/Issue1634.cs index 0dfecbae6..49fca9d83 100644 --- a/src/TraceEvent/TraceEvent.Tests/Regression/Issue1634.cs +++ b/src/TraceEvent/TraceEvent.Tests/Regression/Issue1634.cs @@ -41,8 +41,6 @@ public void TraceEventSourceBasicProperties() // These are not guaranteed to be the same due to bookkeeping events being removed. // Assert.Equal(source.sessionEndTimeQPC, traceLog.sessionEndTimeQPC); - Assert.Equal(source.useClassicETW, traceLog.useClassicETW); - // Compare the TraceLog and the TraceLogEventSource. TraceLogEventSource traceLogEventSource = traceLog.Events.GetSource(); @@ -56,7 +54,6 @@ public void TraceEventSourceBasicProperties() Assert.Equal(traceLog._syncTimeUTC, traceLogEventSource._syncTimeUTC); Assert.Equal(traceLog.sessionStartTimeQPC, traceLogEventSource.sessionStartTimeQPC); Assert.Equal(traceLog.sessionEndTimeQPC, traceLogEventSource.sessionEndTimeQPC); - Assert.Equal(traceLog.useClassicETW, traceLogEventSource.useClassicETW); } } } diff --git a/src/TraceEvent/TraceEvent.cs b/src/TraceEvent/TraceEvent.cs index 3d76984af..99a57464b 100644 --- a/src/TraceEvent/TraceEvent.cs +++ b/src/TraceEvent/TraceEvent.cs @@ -423,7 +423,6 @@ internal void CopyFrom(TraceEventSource source) _syncTimeUTC = source._syncTimeUTC; sessionStartTimeQPC = source.sessionStartTimeQPC; sessionEndTimeQPC = source.sessionEndTimeQPC; - useClassicETW = source.useClassicETW; } internal /*protected*/ IDictionary userData; @@ -441,7 +440,6 @@ internal void CopyFrom(TraceEventSource source) internal /*protected*/ long sessionStartTimeQPC; internal /*protected*/ long sessionEndTimeQPC; - internal /*protected*/ bool useClassicETW; internal /*protected*/ ClrTraceEventParser _CLR; internal /*protected*/ KernelTraceEventParser _Kernel; #if !NOT_WINDOWS && !NO_DYNAMIC_TRACEEVENTPARSER @@ -3655,7 +3653,7 @@ private void DoDispatch(TraceEvent anEvent) /// /// Lookup up the event based on its ProviderID (GUID) and EventId (Classic use the TaskId and the - /// Opcode field for lookup, but use these same fields (see ETWTraceEventSource.RawDispatchClassic) + /// Opcode field for lookup, but use these same fields). /// internal TraceEvent Lookup(TraceEventNativeMethods.EVENT_RECORD* eventRecord) { @@ -4073,13 +4071,12 @@ internal override void RegisterEventTemplateImpl(TraceEvent template) // Trace.WriteLine("Registering template " + template.ProviderName + " " + template.ID + " Name " + template.EventName + " Guid " + template.ProviderGuid + " Task " + template.taskGuid + " opcode " + template.Opcode); // Debug.WriteLine("callback count = " + CallbackCount()); - // Use the old style exclusive if we are using old ETW APIs, or the provider does not - // support it (This currently includes the Kernel Events) + // Use classic lookup for providers without event IDs, including kernel events. // If the event is tracelogging, do not register it as classic. #if !NOT_WINDOWS Debug.Assert(!(template.ProviderGuid == KernelTraceEventParser.ProviderGuid && template.eventID != TraceEventID.Illegal)); #endif - if (useClassicETW || template.eventID == TraceEventID.Illegal) + if (template.eventID == TraceEventID.Illegal) { // Use classic lookup mechanism (Task Guid, Opcode) template.lookupAsClassic = true; From 142de6879949b4d115c3d0fb12c934b8f0f9e945 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:25:13 -0700 Subject: [PATCH 23/41] Remove obsolete kernel-session version checks --- src/TraceEvent/TraceEventSession.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/TraceEvent/TraceEventSession.cs b/src/TraceEvent/TraceEventSession.cs index b6676e824..a01f2a8c5 100644 --- a/src/TraceEvent/TraceEventSession.cs +++ b/src/TraceEvent/TraceEventSession.cs @@ -36,11 +36,8 @@ namespace Microsoft.Diagnostics.Tracing.Session /// property to false (its default is true). /// /// - /// Kernel events have additional restrictions. In particular, there is a special API (EnableKernelProvider). - /// Before Windows 8, there was a restriction that kernel events could only be enabled from a session - /// with a special name (see KernelTraceEventParser.KernelSessionName) and thus there could only be a single - /// session that could log kernel events (and that session could not log non-kernel events). These - /// restrictions were dropped in Windows 8. + /// Enable kernel events with EnableKernelProvider before enabling other providers in the session. + /// Some specialized keywords still require the dedicated KernelTraceEventParser.KernelSessionName session. /// /// public sealed unsafe class TraceEventSession : IDisposable @@ -619,11 +616,6 @@ public unsafe bool EnableKernelProvider(KernelTraceEventParser.Keywords flags, K } bool systemTraceProvider = false; - if (!OperatingSystemVersion.AtLeast(60)) - { - throw new NotSupportedException("Kernel Event Tracing is only supported on Windows 6.0 (Vista) and above."); - } - if (IsValidSession) { throw new Exception("The kernel provider must be enabled first and only once in a session."); @@ -722,7 +714,7 @@ public unsafe bool EnableKernelProvider(KernelTraceEventParser.Keywords flags, K m_SessionHandle = new TraceEventNativeMethods.SafeTraceHandle(kernelSessionHandle); } - if (dwErr == 5 && OperatingSystemVersion.AtLeast(51)) // On Vista and we get a 'Accessed Denied' message + if (dwErr == 5) // Access denied { throw new UnauthorizedAccessException("Error Starting ETW: Access Denied (Administrator rights required to start ETW)"); } @@ -2398,7 +2390,7 @@ private void EnsureStarted(TraceEventNativeMethods.EVENT_TRACE_PROPERTIES* prope Thread.Sleep(100); // Give it some time to stop. retCode = TraceEventNativeMethods.StartTrace(out m_SessionHandle, m_SessionName, properties); } - if (retCode == 5 && OperatingSystemVersion.AtLeast(51)) // On Vista and we get a 'Accessed Denied' message + if (retCode == 5) // Access denied { throw new UnauthorizedAccessException("Error Starting ETW: Access Denied (Administrator rights required to start ETW)"); } From f92b9186f162f7d12a768cf0410277cbc78fbf12 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:26:10 -0700 Subject: [PATCH 24/41] Remove profile-source OS gates --- src/TraceEvent/TraceEventSession.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/TraceEvent/TraceEventSession.cs b/src/TraceEvent/TraceEventSession.cs index a01f2a8c5..867b078a9 100644 --- a/src/TraceEvent/TraceEventSession.cs +++ b/src/TraceEvent/TraceEventSession.cs @@ -3276,11 +3276,6 @@ public static class TraceEventProfileSources /// public static unsafe Dictionary GetInfo() { - if (!OperatingSystemVersion.AtLeast(62)) - { - throw new ApplicationException("Profile source only available on Win8 and beyond."); - } - var ret = new Dictionary(StringComparer.OrdinalIgnoreCase); // Figure out how much space we need. @@ -3370,11 +3365,6 @@ public static unsafe void Set(int profileSourceID, int profileSourceInterval) /// public static unsafe void Set(int[] profileSourceIDs, int[] profileSourceIntervals) { - if (!OperatingSystemVersion.AtLeast(62)) - { - throw new ApplicationException("Profile source only available on Win8 and beyond."); - } - TraceEventNativeMethods.SetPrivilege(TraceEventNativeMethods.SE_SYSTEM_PROFILE_PRIVILEGE); var interval = new TraceEventNativeMethods.TRACE_PROFILE_INTERVAL(); for (int i = 0; i < profileSourceIntervals.Length; i++) From 51603c7a757e5614d488712611b60b519ca13e28 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:30:03 -0700 Subject: [PATCH 25/41] Remove the relogger OS gate --- src/TraceEvent/ETWReloggerTraceEventSource.cs | 9 +-- .../TraceEvent.Tests/Parsing/ReloggerTests.cs | 71 +++++++++++++++++++ 2 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 src/TraceEvent/TraceEvent.Tests/Parsing/ReloggerTests.cs diff --git a/src/TraceEvent/ETWReloggerTraceEventSource.cs b/src/TraceEvent/ETWReloggerTraceEventSource.cs index 088f38a43..e0de67fe3 100644 --- a/src/TraceEvent/ETWReloggerTraceEventSource.cs +++ b/src/TraceEvent/ETWReloggerTraceEventSource.cs @@ -7,7 +7,6 @@ using System.Diagnostics.Tracing; using System.Runtime.InteropServices; using TraceReloggerLib; -using Microsoft.Diagnostics.Utilities; #pragma warning disable 0414 // This is is because m_scratchBufferSize was #if conditionally removed, and I don't want it to complain about it. @@ -15,7 +14,6 @@ namespace Microsoft.Diagnostics.Tracing { /// /// ETWReloggerTraceEventSource is designed to be able to write ETW files using an existing ETW input stream (either a file, files or real time session) as a basis. - /// The relogger capabilities only exist on Windows 8 OSes and beyond. /// /// The right way to think about this class is that it is just like ETWTraceEventSource, but it also has a output file associated with it, and WriteEvent APIs that /// can be used to either copy events from the event stream (the common case), or inject new events (high level stats). @@ -40,11 +38,6 @@ public ETWReloggerTraceEventSource(string inputFileName, string outputFileName) public ETWReloggerTraceEventSource(string fileOrSessionName, TraceEventSourceType type, string outputFileName) : base() { - if (!OperatingSystemVersion.AtLeast(62)) - { - throw new NotSupportedException("System Tracing is only supported on Windows 8 and above."); - } - m_relogger = new CTraceRelogger(); if (type == TraceEventSourceType.FileOnly) { @@ -78,7 +71,7 @@ public ETWReloggerTraceEventSource(string fileOrSessionName, TraceEventSourceTyp } /// - /// The output file can use a compressed form or not. Compressed forms can only be read on Win8 and beyond. Defaults to true. + /// The output file can use a compressed form or not. Defaults to true. /// public bool OutputUsesCompressedFormat { set { m_relogger.SetCompressionMode((sbyte)(value ? 1 : 0)); } } diff --git a/src/TraceEvent/TraceEvent.Tests/Parsing/ReloggerTests.cs b/src/TraceEvent/TraceEvent.Tests/Parsing/ReloggerTests.cs new file mode 100644 index 000000000..3ccdf5884 --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/Parsing/ReloggerTests.cs @@ -0,0 +1,71 @@ +using Microsoft.Diagnostics.Tracing; +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; +using Xunit.Abstractions; + +namespace TraceEventTests +{ + public class ReloggerTests : TestBase + { + public ReloggerTests(ITestOutputHelper output) : base(output) + { + } + + [WindowsFact] + public void RelogsCompressedEvents() + { + VerifyRelogging(true); + } + + [WindowsFact] + public void RelogsUncompressedEvents() + { + VerifyRelogging(false); + } + + private void VerifyRelogging(bool compressed) + { + Directory.CreateDirectory(OutputDir); + string input = Path.Combine("inputs", "Regression", "SelfDescribingSingleEvent.etl"); + string output = Path.Combine(OutputDir, "relogged.etl"); + var expected = new List(); + var providers = new HashSet(); + using (var source = new ETWReloggerTraceEventSource(input, TraceEventSourceType.FileOnly, output)) + { + source.OutputUsesCompressedFormat = compressed; + source.AllEvents += data => + { + if (!data.IsClassicProvider) + { + providers.Add(data.ProviderGuid); + expected.Add(EventSignature(data)); + } + source.WriteEvent(data); + }; + source.Process(); + } + + Assert.NotEmpty(expected); + var actual = new List(); + using (var source = new ETWTraceEventSource(output)) + { + source.AllEvents += data => + { + if (providers.Contains(data.ProviderGuid)) + { + actual.Add(EventSignature(data)); + } + }; + source.Process(); + } + Assert.Equal(expected, actual); + } + + private static string EventSignature(TraceEvent data) + { + return data.ProviderGuid + ":" + data.ID + ":" + data.Opcode + ":" + Convert.ToBase64String(data.EventData()); + } + } +} From d98175d4c41d295ea75ebd0a7a4a182d2c04ac1a Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:31:03 -0700 Subject: [PATCH 26/41] Simplify the kernel keyword mask --- src/TraceEvent/Parsers/KernelTraceEventParser.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/TraceEvent/Parsers/KernelTraceEventParser.cs b/src/TraceEvent/Parsers/KernelTraceEventParser.cs index f773f91d6..dd618d5f4 100644 --- a/src/TraceEvent/Parsers/KernelTraceEventParser.cs +++ b/src/TraceEvent/Parsers/KernelTraceEventParser.cs @@ -226,10 +226,7 @@ internal static Keywords NonOSKeywords { get { - var ret = (Keywords)unchecked((int)0xf84c8000); // PMCProfile ReferenceSet ThreadPriority IOQueue Handle VAMap - if (OperatingSystemVersion.AtLeast(OperatingSystemVersion.Win8)) - ret &= ~Keywords.VAMap; - return ret; + return (Keywords)unchecked((int)0xf84c0000); // PMCProfile ReferenceSet ThreadPriority IOQueue Handle } } From 47835c735142795069d8ed324dd15397dcc1def1 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:32:35 -0700 Subject: [PATCH 27/41] Remove the merge-compression OS fallback --- src/TraceEvent/TraceEventSession.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TraceEvent/TraceEventSession.cs b/src/TraceEvent/TraceEventSession.cs index 867b078a9..abaf55c15 100644 --- a/src/TraceEvent/TraceEventSession.cs +++ b/src/TraceEvent/TraceEventSession.cs @@ -1598,7 +1598,7 @@ public static void Merge(string[] inputETLFileNames, string outputETLFileName, T EVENT_TRACE_MERGE_EXTENDED_DATA.EVENT_METADATA | EVENT_TRACE_MERGE_EXTENDED_DATA.VOLUME_MAPPING; - if ((options & TraceEventMergeOptions.Compress) != 0 && OperatingSystemVersion.AtLeast(62)) + if ((options & TraceEventMergeOptions.Compress) != 0) { flags |= EVENT_TRACE_MERGE_EXTENDED_DATA.COMPRESS_TRACE; } From 0b6e8b27b8f81adc9afd7362004e3cafea541617 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:34:13 -0700 Subject: [PATCH 28/41] Replace the obsolete ETL error explanation --- src/TraceEvent/ETWTraceEventSource.cs | 5 --- .../Parsing/ETWTraceEventSourceTests.cs | 40 +++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 src/TraceEvent/TraceEvent.Tests/Parsing/ETWTraceEventSourceTests.cs diff --git a/src/TraceEvent/ETWTraceEventSource.cs b/src/TraceEvent/ETWTraceEventSource.cs index 7cdf6d331..bb61f30f9 100644 --- a/src/TraceEvent/ETWTraceEventSource.cs +++ b/src/TraceEvent/ETWTraceEventSource.cs @@ -737,11 +737,6 @@ internal static List GetMergeAllLogFiles(string fileName) private bool ProcessOneFile() { int dwErr = TraceEventNativeMethods.ProcessTrace(handles, (IntPtr)0, (IntPtr)0); - if (dwErr == 6) - { - throw new ApplicationException("Error opening ETL file. Most likely caused by opening a Win8 Trace on a Pre Win8 OS."); - } - // ETW returns 1223 when you stop processing explicitly if (!(dwErr == 1223 && stopProcessing)) { diff --git a/src/TraceEvent/TraceEvent.Tests/Parsing/ETWTraceEventSourceTests.cs b/src/TraceEvent/TraceEvent.Tests/Parsing/ETWTraceEventSourceTests.cs new file mode 100644 index 000000000..c23ecc79f --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/Parsing/ETWTraceEventSourceTests.cs @@ -0,0 +1,40 @@ +using Microsoft.Diagnostics.Tracing; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using Xunit; + +namespace TraceEventTests +{ + public class ETWTraceEventSourceTests + { + [WindowsFact] + public void InvalidHandleReportsTheNativeError() + { + using (var source = OpenFixture()) + { + var field = typeof(ETWTraceEventSource).GetField("handles", BindingFlags.NonPublic | BindingFlags.Instance); + var handles = (TraceEventNativeMethods.SafeTraceHandle[])field.GetValue(source); + handles[0].Dispose(); + handles[0] = new TraceEventNativeMethods.SafeTraceHandle(ulong.MaxValue); + var error = Assert.Throws(() => source.Process()); + Assert.Equal(unchecked((int)0x80070006), error.HResult); + } + } + + [WindowsFact] + public void ExplicitCancellationDoesNotThrow() + { + using (var source = OpenFixture()) + { + source.AllEvents += data => source.StopProcessing(); + Assert.False(source.Process()); + } + } + + private static ETWTraceEventSource OpenFixture() + { + return new ETWTraceEventSource(Path.Combine("inputs", "Regression", "SelfDescribingSingleEvent.etl")); + } + } +} From 824801f1557df98ede306273a1a004965020b473 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:37:24 -0700 Subject: [PATCH 29/41] Retire pre-Windows 8.1 filtering fallbacks --- src/PerfView/CommandProcessor.cs | 12 +- .../Platform/OperatingSystemVersionTests.cs | 7 + src/TraceEvent/TraceEventNativeMethods.cs | 1 - src/TraceEvent/TraceEventSession.cs | 192 +++++++----------- 4 files changed, 83 insertions(+), 129 deletions(-) diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index 3c07a963f..7c685a550 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -699,13 +699,9 @@ public void Start(CommandLineArgs parsedArgs) if (parsedArgs.TplEvents != TplEtwProviderTraceEventParser.Keywords.None) { // Used to determine what is going on with tasks. - var netTaskStacks = stacksEnabled; - if (TraceEventProviderOptions.FilteringSupported) - { - // This turns on stacks only for TaskScheduled (7) TaskWaitSend (10) and AwaitTaskContinuationScheduled (12) - netTaskStacks = options.Clone(); - netTaskStacks.EventIDStacksToEnable = new List(3) { 7, 10, 12 }; - } + // This turns on stacks only for TaskScheduled (7) TaskWaitSend (10) and AwaitTaskContinuationScheduled (12) + var netTaskStacks = options.Clone(); + netTaskStacks.EventIDStacksToEnable = new List(3) { 7, 10, 12 }; EnableUserProvider(userModeSession, ".NETTasks", TplEtwProviderTraceEventParser.ProviderGuid, parsedArgs.ClrEventLevel, (ulong)parsedArgs.TplEvents, @@ -3297,7 +3293,7 @@ private void DoClrRundownForSession(string fileName, string sessionName, Command } TraceEventProviderOptions options = null; - if (parsedArgs.FocusProcess != null && TraceEventProviderOptions.FilteringSupported) + if (parsedArgs.FocusProcess != null) { options = new TraceEventProviderOptions(); int processId; diff --git a/src/TraceEvent/TraceEvent.Tests/Platform/OperatingSystemVersionTests.cs b/src/TraceEvent/TraceEvent.Tests/Platform/OperatingSystemVersionTests.cs index cffbbc05a..899d067ce 100644 --- a/src/TraceEvent/TraceEvent.Tests/Platform/OperatingSystemVersionTests.cs +++ b/src/TraceEvent/TraceEvent.Tests/Platform/OperatingSystemVersionTests.cs @@ -1,6 +1,8 @@ using Microsoft.Diagnostics.Utilities; +using Microsoft.Diagnostics.Tracing.Session; using System; using System.ComponentModel; +using System.Runtime.InteropServices; using Xunit; namespace TraceEventTests @@ -40,5 +42,10 @@ public void CurrentHostPassedModuleInitialization() Assert.True(OperatingSystemVersion.IsSupported); } + [Fact] + public void EtwFilteringIsOnlyAvailableOnWindows() + { + Assert.Equal(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), TraceEventProviderOptions.FilteringSupported); + } } } diff --git a/src/TraceEvent/TraceEventNativeMethods.cs b/src/TraceEvent/TraceEventNativeMethods.cs index a651eadca..76e351a7a 100644 --- a/src/TraceEvent/TraceEventNativeMethods.cs +++ b/src/TraceEvent/TraceEventNativeMethods.cs @@ -648,7 +648,6 @@ internal static int EnableTraceEx2( } // Values for ENABLE_TRACE_PARAMETERS.Version - internal const uint ENABLE_TRACE_PARAMETERS_VERSION = 1; internal const uint ENABLE_TRACE_PARAMETERS_VERSION_2 = 2; // Introduced in Windows 8.1 // Values for ENABLE_TRACE_PARAMETERS.EnableProperty diff --git a/src/TraceEvent/TraceEventSession.cs b/src/TraceEvent/TraceEventSession.cs index abaf55c15..f4691907d 100644 --- a/src/TraceEvent/TraceEventSession.cs +++ b/src/TraceEvent/TraceEventSession.cs @@ -338,94 +338,89 @@ public bool EnableProvider(Guid providerGuid, TraceEventLevel providerLevel = Tr { if (valueData != null) { - // This one must be first so it works pre-8.1 filterDescrPtr[curDescrIdx].Ptr = providerDataPtr; filterDescrPtr[curDescrIdx].Size = valueDataSize; filterDescrPtr[curDescrIdx].Type = (int)valueDataType; curDescrIdx++; } - bool etwFilteringSupported = TraceEventProviderOptions.FilteringSupported; - if (etwFilteringSupported) + if (options.ProcessIDFilter != null && 0 < options.ProcessIDFilter.Count) { - if (options.ProcessIDFilter != null && 0 < options.ProcessIDFilter.Count) + int* pids = stackalloc int[options.ProcessIDFilter.Count]; + for (int i = 0; i < options.ProcessIDFilter.Count; i++) { - int* pids = stackalloc int[options.ProcessIDFilter.Count]; - for (int i = 0; i < options.ProcessIDFilter.Count; i++) - { - pids[i] = options.ProcessIDFilter[i]; - } + pids[i] = options.ProcessIDFilter[i]; + } - filterDescrPtr[curDescrIdx].Ptr = (byte*)pids; - filterDescrPtr[curDescrIdx].Size = options.ProcessIDFilter.Count * sizeof(int); - filterDescrPtr[curDescrIdx].Type = TraceEventNativeMethods.EVENT_FILTER_TYPE_PID; - curDescrIdx++; + filterDescrPtr[curDescrIdx].Ptr = (byte*)pids; + filterDescrPtr[curDescrIdx].Size = options.ProcessIDFilter.Count * sizeof(int); + filterDescrPtr[curDescrIdx].Type = TraceEventNativeMethods.EVENT_FILTER_TYPE_PID; + curDescrIdx++; + } + if (options.ProcessNameFilter != null && 0 < options.ProcessNameFilter.Count) + { + int charCount = 0; + for (int i = 0; i < options.ProcessNameFilter.Count; i++) + { + charCount += options.ProcessNameFilter[i].Length + 1; // +1 for the separate or the null terminator. } - if (options.ProcessNameFilter != null && 0 < options.ProcessNameFilter.Count) + + byte* namesBuffer = stackalloc byte[charCount * 2]; // *2 because it is unicode. + char* namesPtr = (char*)namesBuffer; + // Fill in the names, ; separating them. + for (int i = 0; i < options.ProcessNameFilter.Count; i++) { - int charCount = 0; - for (int i = 0; i < options.ProcessNameFilter.Count; i++) + if (i != 0) { - charCount += options.ProcessNameFilter[i].Length + 1; // +1 for the separate or the null terminator. + *namesPtr++ = ';'; } - byte* namesBuffer = stackalloc byte[charCount * 2]; // *2 because it is unicode. - char* namesPtr = (char*)namesBuffer; - // Fill in the names, ; separating them. - for (int i = 0; i < options.ProcessNameFilter.Count; i++) + string name = options.ProcessNameFilter[i]; + for (int j = 0; j < name.Length; j++) { - if (i != 0) - { - *namesPtr++ = ';'; - } - - string name = options.ProcessNameFilter[i]; - for (int j = 0; j < name.Length; j++) - { - *namesPtr++ = name[j]; - } + *namesPtr++ = name[j]; } - *namesPtr++ = '\0'; - filterDescrPtr[curDescrIdx].Ptr = namesBuffer; - Debug.Assert(&filterDescrPtr[curDescrIdx].Ptr[charCount * 2] == (byte*)namesPtr); - filterDescrPtr[curDescrIdx].Size = charCount * 2; // *2 because it is unicode. - filterDescrPtr[curDescrIdx].Type = TraceEventNativeMethods.EVENT_FILTER_TYPE_EXECUTABLE_NAME; - if (filterDescrPtr[curDescrIdx].Size >= 1024) - { - throw new ArgumentException("ProcessNameFilters too large."); - } - - curDescrIdx++; - } - - var eventIdsBufferSize = ComputeEventIdsBufferSize(options.EventIDsToEnable); - if (0 < eventIdsBufferSize) - { - var eventIds = stackalloc byte[eventIdsBufferSize]; - ComputeEventIds(&filterDescrPtr[curDescrIdx++], eventIds, eventIdsBufferSize, - options.EventIDsToEnable, true, TraceEventNativeMethods.EVENT_FILTER_TYPE_EVENT_ID); - } - eventIdsBufferSize = ComputeEventIdsBufferSize(options.EventIDsToDisable); - if (0 < eventIdsBufferSize) - { - var eventIds = stackalloc byte[eventIdsBufferSize]; - ComputeEventIds(&filterDescrPtr[curDescrIdx++], eventIds, eventIdsBufferSize, - options.EventIDsToDisable, false, TraceEventNativeMethods.EVENT_FILTER_TYPE_EVENT_ID); } - eventIdsBufferSize = ComputeEventIdsBufferSize(options.EventIDStacksToEnable); - if (0 < eventIdsBufferSize) + *namesPtr++ = '\0'; + filterDescrPtr[curDescrIdx].Ptr = namesBuffer; + Debug.Assert(&filterDescrPtr[curDescrIdx].Ptr[charCount * 2] == (byte*)namesPtr); + filterDescrPtr[curDescrIdx].Size = charCount * 2; // *2 because it is unicode. + filterDescrPtr[curDescrIdx].Type = TraceEventNativeMethods.EVENT_FILTER_TYPE_EXECUTABLE_NAME; + if (filterDescrPtr[curDescrIdx].Size >= 1024) { - var eventIds = stackalloc byte[eventIdsBufferSize]; - ComputeEventIds(&filterDescrPtr[curDescrIdx++], eventIds, eventIdsBufferSize, - options.EventIDStacksToEnable, true, TraceEventNativeMethods.EVENT_FILTER_TYPE_STACKWALK); - } - eventIdsBufferSize = ComputeEventIdsBufferSize(options.EventIDStacksToDisable); - if (0 < eventIdsBufferSize) - { - var eventIds = stackalloc byte[eventIdsBufferSize]; - ComputeEventIds(&filterDescrPtr[curDescrIdx++], eventIds, eventIdsBufferSize, - options.EventIDStacksToDisable, false, TraceEventNativeMethods.EVENT_FILTER_TYPE_STACKWALK); + throw new ArgumentException("ProcessNameFilters too large."); } + + curDescrIdx++; + } + + var eventIdsBufferSize = ComputeEventIdsBufferSize(options.EventIDsToEnable); + if (0 < eventIdsBufferSize) + { + var eventIds = stackalloc byte[eventIdsBufferSize]; + ComputeEventIds(&filterDescrPtr[curDescrIdx++], eventIds, eventIdsBufferSize, + options.EventIDsToEnable, true, TraceEventNativeMethods.EVENT_FILTER_TYPE_EVENT_ID); + } + eventIdsBufferSize = ComputeEventIdsBufferSize(options.EventIDsToDisable); + if (0 < eventIdsBufferSize) + { + var eventIds = stackalloc byte[eventIdsBufferSize]; + ComputeEventIds(&filterDescrPtr[curDescrIdx++], eventIds, eventIdsBufferSize, + options.EventIDsToDisable, false, TraceEventNativeMethods.EVENT_FILTER_TYPE_EVENT_ID); + } + eventIdsBufferSize = ComputeEventIdsBufferSize(options.EventIDStacksToEnable); + if (0 < eventIdsBufferSize) + { + var eventIds = stackalloc byte[eventIdsBufferSize]; + ComputeEventIds(&filterDescrPtr[curDescrIdx++], eventIds, eventIdsBufferSize, + options.EventIDStacksToEnable, true, TraceEventNativeMethods.EVENT_FILTER_TYPE_STACKWALK); + } + eventIdsBufferSize = ComputeEventIdsBufferSize(options.EventIDStacksToDisable); + if (0 < eventIdsBufferSize) + { + var eventIds = stackalloc byte[eventIdsBufferSize]; + ComputeEventIds(&filterDescrPtr[curDescrIdx++], eventIds, eventIdsBufferSize, + options.EventIDStacksToDisable, false, TraceEventNativeMethods.EVENT_FILTER_TYPE_STACKWALK); } Debug.Assert(curDescrIdx <= MaxDesc); if (curDescrIdx == 0) @@ -435,7 +430,7 @@ public bool EnableProvider(Guid providerGuid, TraceEventLevel providerLevel = Tr TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS parameters = new TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS(); - parameters.Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION; + parameters.Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION_2; parameters.FilterDescCount = curDescrIdx; parameters.EnableFilterDesc = filterDescrPtr; @@ -452,16 +447,6 @@ public bool EnableProvider(Guid providerGuid, TraceEventLevel providerLevel = Tr parameters.EnableProperty |= TraceEventNativeMethods.EVENT_ENABLE_PROPERTY_SOURCE_CONTAINER_TRACKING; } - if (etwFilteringSupported) // If we are on 8.1 we can use the newer API. - { - parameters.Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION_2; - } - else - { - Debug.Assert(curDescrIdx <= 1); - Debug.Assert(filterDescrPtr == null || -100 <= filterDescrPtr[0].Type); // We are not using any of the Win8.1 defined types. - } - uint eventControlCode = (valueDataType == ControllerCommand.SendManifest ? TraceEventNativeMethods.EVENT_CONTROL_CODE_CAPTURE_STATE : TraceEventNativeMethods.EVENT_CONTROL_CODE_ENABLE_PROVIDER); @@ -810,7 +795,7 @@ public void DisableProvider(Guid providerGuid) { lock (this) { - var parameters = new TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS { Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION }; + var parameters = new TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS { Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION_2 }; int hr = TraceEventNativeMethods.EnableTraceEx2( m_SessionHandle, providerGuid, TraceEventNativeMethods.EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0, 0, 0, EnableProviderTimeoutMSec, parameters); @@ -1011,8 +996,7 @@ public void SetFileName(string newName) /// EventSources will re-dump their manifest on this command. /// This API is OK to call from one thread while Process() is being run on another /// - /// This routine only works Win7 and above, since previous versions don't have this concept. The providers also has - /// to support it. + /// The provider must support capture-state requests. /// /// /// The GUID that identifies the provider to send the CaptureState command to @@ -1031,7 +1015,7 @@ public void CaptureState(Guid providerGuid, ulong matchAnyKeywords = ulong.MaxVa var parameters = new TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS(); var filter = new TraceEventNativeMethods.EVENT_FILTER_DESCRIPTOR(); - parameters.Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION; + parameters.Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION_2; byte[] asArray = data as byte[]; if (data is int) @@ -1077,6 +1061,7 @@ public void CaptureState(Guid providerGuid, ulong matchAnyKeywords = ulong.MaxVa if (asArray != null) { parameters.EnableFilterDesc = &filter; + parameters.FilterDescCount = 1; filter.Type = filterType; filter.Size = asArray.Length; filter.Ptr = filterDataPtr; @@ -2856,50 +2841,17 @@ public TraceEventProviderOptions Clone() // Payload Filters not implemented yet. /// - /// This return true on OS version beyond 8.1 (windows Version 6.3). It means most of the - /// per-event filtering is supported. + /// Returns true on Windows, where supported hosts provide ETW per-event filtering. + /// Returns false on non-Windows platforms. /// public static bool FilteringSupported { get { - if (!s_IsEtwFilteringSupported.HasValue) - { - var ret = false; - - // For Windows Versions above windows 8, OSVersion lies and returns 6.2 (window 8) even though - // the windows version is higher. We have to try harder to figure out whether we are windows 8 or something - // later. Currently we look at the file version number of an OS DLL. - // There is probably a better way. - var winDir = Environment.GetEnvironmentVariable("WinDir"); - var kernel32 = Path.Combine(winDir, @"system32\Kernel32.dll"); - if (File.Exists(kernel32)) - { - using (var kernel32PE = new PEFile.PEFile(kernel32)) - { - var versionInfo = kernel32PE.GetFileVersionInfo(); - if (versionInfo != null) - { - // versionInfo.FileVersion is now the real version number we want but it is a string, not a - // number. Our tests is if version number bigger than 6.3 (as a string) or a two or more digit - // major version. - if (string.Compare("6.3", versionInfo.FileVersion) <= 0 || 2 <= versionInfo.FileVersion.IndexOf('.')) - { - ret = true; - } - } - } - } - s_IsEtwFilteringSupported = ret; - } - return s_IsEtwFilteringSupported.Value; + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows); } } - /// - /// This is the backing field for the lazily-computed property. - /// - private static bool? s_IsEtwFilteringSupported; } From fd17789ba95a2e176b14d725721867c06d2377ed Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:42:29 -0700 Subject: [PATCH 30/41] Remove dead version scaffolding --- src/TraceEvent/ETWKernelControl.cs | 40 ------------------------------ 1 file changed, 40 deletions(-) diff --git a/src/TraceEvent/ETWKernelControl.cs b/src/TraceEvent/ETWKernelControl.cs index 970a2fc53..f1b12751d 100644 --- a/src/TraceEvent/ETWKernelControl.cs +++ b/src/TraceEvent/ETWKernelControl.cs @@ -528,46 +528,6 @@ private static void SetImageTracingFlags(string sessionName, string exeFileName, private static string KernelSessionName { get { return "NT Kernel Logger"; } } private static Guid ProviderGuid = new Guid(unchecked((int)0x9e814aad), unchecked((short)0x3204), unchecked((short)0x11d2), 0x9a, 0x82, 0x00, 0x60, 0x08, 0xa8, 0x69, 0x39); - //// Code borrowed from CoreFX System.PlatformDetection.Windows to allow targeting netstandard1.6 - //[StructLayout(LayoutKind.Sequential)] - //private struct RTL_OSVERSIONINFOEX - //{ - // internal uint dwOSVersionInfoSize; - // internal uint dwMajorVersion; - // internal uint dwMinorVersion; - // internal uint dwBuildNumber; - // internal uint dwPlatformId; - // [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - // internal string szCSDVersion; - //} - - //// Code borrowed from CoreFX System.PlatformDetection.Windows to allow targeting netstandard1.6 - //[DllImport("ntdll.dll")] - //private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation); - - //// Code borrowed from CoreFX System.PlatformDetection.Windows to allow targeting netstandard1.6 - //private static bool IsWin8orNewer() - //{ - // RTL_OSVERSIONINFOEX osvi = new RTL_OSVERSIONINFOEX(); - // osvi.dwOSVersionInfoSize = (uint)Marshal.SizeOf(osvi); - // return osvi.dwMajorVersion * 10 + osvi.dwMinorVersion >= 62; - //} - - ///// - ///// These keywords are can't be passed to the OS. - ///// - //private static KernelKeywords NonOSKeywords - //{ - // get - // { - - // var ret = (KernelKeywords)unchecked((int)0xf84c8000); - // if (IsWin8orNewer()) - // ret &= ~KernelKeywords.VAMap; - // return ret; - // } - //} - private static bool s_KernelTraceControlLoaded; #endregion } From f0c982e2ceb8f75003f8e834a3fe7c1b333296f6 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:43:44 -0700 Subject: [PATCH 31/41] Retire Windows 7 tracing samples --- src/TraceEvent/Samples/00_AllSamples.cs | 2 - .../Samples/31_KernelAndClrMonitor.cs | 5 +- src/TraceEvent/Samples/32_KernelAndClrFile.cs | 6 +- .../Samples/33_KernelAndClrMonitorWin7.cs | 204 ------------------ .../Samples/34_KernelAndClrFileWin7.cs | 182 ---------------- 5 files changed, 4 insertions(+), 395 deletions(-) delete mode 100644 src/TraceEvent/Samples/33_KernelAndClrMonitorWin7.cs delete mode 100644 src/TraceEvent/Samples/34_KernelAndClrFileWin7.cs diff --git a/src/TraceEvent/Samples/00_AllSamples.cs b/src/TraceEvent/Samples/00_AllSamples.cs index 93ac1d3f7..6d2d382cf 100644 --- a/src/TraceEvent/Samples/00_AllSamples.cs +++ b/src/TraceEvent/Samples/00_AllSamples.cs @@ -51,8 +51,6 @@ public static void Run() ModuleLoadMonitor.Run(); Debugger.Break(); KernelAndClrMonitor.Run(); Debugger.Break(); KernelAndClrFile.Run(); Debugger.Break(); - KernelAndClrMonitorWin7.Run(); Debugger.Break(); - KernelAndClrFileWin7.Run(); Debugger.Break(); SimpleTraceLog.Run(); Debugger.Break(); TraceLogMonitor.Run(); Debugger.Break(); SimpleFileRelogger.Run(); Debugger.Break(); diff --git a/src/TraceEvent/Samples/31_KernelAndClrMonitor.cs b/src/TraceEvent/Samples/31_KernelAndClrMonitor.cs index 0e777443b..4b5cc1577 100644 --- a/src/TraceEvent/Samples/31_KernelAndClrMonitor.cs +++ b/src/TraceEvent/Samples/31_KernelAndClrMonitor.cs @@ -7,9 +7,8 @@ using System.Threading; /* README FIRST */ -// This shows you how to listen to both Kernel and non-Kernel (in this case the CLR) events on Windows 8. -// This is significantly easier than on Win7 (which is shown in 33_KernelAndClrMonitorWin7.cs) because -// a single session can have both kernel and non-kernel providers. +// This shows how to listen to both kernel and non-kernel (in this case the CLR) events +// in a single session on supported Windows hosts. // namespace TraceEventSamples { diff --git a/src/TraceEvent/Samples/32_KernelAndClrFile.cs b/src/TraceEvent/Samples/32_KernelAndClrFile.cs index 1dcac3288..d4af26f1a 100644 --- a/src/TraceEvent/Samples/32_KernelAndClrFile.cs +++ b/src/TraceEvent/Samples/32_KernelAndClrFile.cs @@ -7,9 +7,8 @@ using System.Threading; /* README FIRST */ -// This shows you how to listen to both Kernel and non-Kernel (in this case the CLR) events on Windows 8. -// This is significantly easier than on Win7 (which is shown in 34_KernelAndClrFileWin7.cs) because -// a single session can have both kernel and non-kernel providers. +// This shows how to record both kernel and non-kernel (in this case the CLR) events +// in a single session on supported Windows hosts. // namespace TraceEventSamples { @@ -164,4 +163,3 @@ private static void Print(TraceEvent data) } } } - diff --git a/src/TraceEvent/Samples/33_KernelAndClrMonitorWin7.cs b/src/TraceEvent/Samples/33_KernelAndClrMonitorWin7.cs deleted file mode 100644 index 0691c7a69..000000000 --- a/src/TraceEvent/Samples/33_KernelAndClrMonitorWin7.cs +++ /dev/null @@ -1,204 +0,0 @@ -using Microsoft.Diagnostics.Tracing; -using Microsoft.Diagnostics.Tracing.Parsers; -using Microsoft.Diagnostics.Tracing.Session; -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -/* README FIRST */ -// Until Windows 8, kernel events can only be turned on in a session called 'NT Kernel Logger' and no -// other providers except the kernel provider can be in such a session. Thus if you wish a trace -// that has both kernel and non-kernel events you must have two session. In the code below we show -// how to do this in the real time case. Basically you simply have two session, one for the -// kernel events and one for everything else. -// -// This requires the program to be multi-threaded, since there needs to be a thread to listen for -// each session. This is the main complication of doing this. -// -// Note also that since each session's 'flushing schedule' will be different the events from the -// two session will be NOT necessarily serviced in chronological order (all users events and all -// kernel events will come in order, but user and kernel events may not be in order). -// -// As mentioned on Windows 8 you can use a single session and thus avoid these complexities. In addition -// event before Windows 8 there are a number of kernel events that are available as non-kernel -// providers. Do a -// logman query providers -// To see all providers that the OS provides, and look for ones with the Microsoft-Windows-Kernel prefix. -// While these providers have 'Kernel' in their name they are not subject to the restrictions mentioned -// above, and thus can be used with other user-mode providers. In particular the Microsoft-Windows-Kernel-Process -// provider can give you the process, thread, and image load events which are some of the most likely -// kernel events you may want. -// -namespace TraceEventSamples -{ - public class KernelAndClrMonitorWin7 - { - /// - /// Where all the output goes. - /// - private static TextWriter Out = AllSamples.Out; - - public static void Run() - { - var monitoringTimeSec = 10; - - Out.WriteLine("******************** KernelAndClrMonitor DEMO (Win7) ********************"); - Out.WriteLine("Printing both Kernel and CLR (user mode) events simultaneously"); - Out.WriteLine("The monitor will run for a maximum of {0} seconds", monitoringTimeSec); - Out.WriteLine("Press Ctrl-C to stop monitoring early."); - Out.WriteLine(); - Out.WriteLine("Start a .NET program to see some events!"); - Out.WriteLine(); - if (TraceEventSession.IsElevated() != true) - { - Out.WriteLine("Must be elevated (Admin) to run this program."); - Debugger.Break(); - return; - } - - // Set up Ctrl-C to stop both user mode and kernel mode sessions - Console.CancelKeyPress += (object sender, ConsoleCancelEventArgs cancelArgs) => - { - StopSessions(); - cancelArgs.Cancel = true; - }; - - // Note that because there are different sessions, the events may not be printed - // in time order since the different sessions may flush their buffers at different - // times. If you care, you must buffer the events and order them by event - // timestamp. Note that the timestamps however ARE accurate. - Out.WriteLine("Setup up threads to process the events"); - - // start processing kernel events on a thread pool thread - var task1 = Task.Run(() => - { - // Note that TraceEventSession and EtwTraceEventParser are IN GENERAL NOT THREADSAFE, - // Normally this is not a hardship you just set up the session TraceDispacher on one - // thread. It is OK to call a session's Dispose() and 'Enable and Disable provider APIS - // from another thread, but things associated with ETWTraceEventSource and TraceEventParsers - // should all be on the same thread. - Out.WriteLine("Kernel event Thread Starting"); - Out.WriteLine("Enabling Image load, thread and process kernel events."); - using (s_kernelSession = new TraceEventSession(KernelTraceEventParser.KernelSessionName)) - { - // Enable the events we care about for the kernel in the kernel session - // For this instant the session will buffer any incoming events. - // If you only have to run on Win8 systems you can use one session for both. - // Here we turn in process, thread and Image load events. - s_kernelSession.EnableKernelProvider( - KernelTraceEventParser.Keywords.ImageLoad | - KernelTraceEventParser.Keywords.Process | - KernelTraceEventParser.Keywords.Thread); - - // You should do all processing from a single source on a single thread. - // Thus call calls to .Source, or Process() should be on the same thread. - s_kernelSession.Source.Kernel.All += Print; -#if DEBUG - // in debug builds it is useful to see any unhandled events because they could be bugs. - s_kernelSession.Source.UnhandledEvents += Print; -#endif - // process events until Ctrl-C is pressed - - Out.WriteLine("Waiting on kernel events."); - s_kernelSession.Source.Process(); - } - Out.WriteLine("Thread 1 dieing"); - }); - - // start processing CLR events on a thread pool thread - var task2 = Task.Run(() => - { - // Note that TraceEventSession and EtwTraceEventParser are IN GENERAL NOT THREADSAFE, - // Normally this is not a hardship you just set up the session TraceDispacher on one - // thread. It is OK to call a session's Dispose() and 'Enable and Disable provider APIS - // from another thread, but things associated with ETWTraceEventSource and TraceEventParsers - // should all be on the same thread. - using (s_userSession = new TraceEventSession("MonitorKernelAndClrEventsSession")) - { - Out.WriteLine("Enabling CLR GC and Exception events."); - // Enable the events we care about for the CLR (in the user session). - // unlike the kernel session, you can call EnableProvider on other things too. - // For this instant the ;session will buffer any incoming events. - s_userSession.EnableProvider( - ClrTraceEventParser.ProviderGuid, - TraceEventLevel.Informational, - (ulong)(ClrTraceEventParser.Keywords.GC | ClrTraceEventParser.Keywords.Exception)); - - // s_userSession.Source.Clr.GCHeapStats += (GCHeapStatsTraceData data) => Out.WriteLine(" ", data.GenerationSize0); - - Out.WriteLine("User event Thread Starting"); - s_userSession.Source.Clr.All += Print; -#if DEBUG - // in debug builds it is useful to see any unhandled events because they could be bugs. - s_userSession.Source.UnhandledEvents += Print; -#endif - // process events until Ctrl-C is pressed or timeout expires - Out.WriteLine("Waiting on user events."); - s_userSession.Source.Process(); - } - Out.WriteLine("Thread 2 dieing"); - }); - - // Set up a timer to stop processing after monitoringTimeSec - var timer = new Timer(delegate (object state) - { - Out.WriteLine("Stopped Monitoring after {0} sec", monitoringTimeSec); - StopSessions(); - }, null, monitoringTimeSec * 1000, Timeout.Infinite); - - // Wait until tasks are complete - Out.WriteLine("Waiting for processing tasks to complete"); - Task.WaitAll(task1, task2); - Out.WriteLine("Monitoring stopped"); - timer.Dispose(); - } - - private static bool s_stopping = false; - - private static void StopSessions() - { - s_stopping = true; - Out.WriteLine("Ensuring all ETW sessions are stopped."); - if (s_kernelSession != null) - { - s_kernelSession.Dispose(); - } - - if (s_userSession != null) - { - s_userSession.Dispose(); - } - } - - /// - /// Print data. Note that this method is called FROM DIFFERNET THREADS which means you need to properly - /// lock any read-write data you access. It turns out Out.Writeline is already thread safe so - /// there is nothing I have to do in this case. - /// - private static void Print(TraceEvent data) - { - if (s_stopping) // Ctrl-C will stop the sessions, but buffered events may still come in, ignore these. - { - return; - } - - // There are a lot of data collection start on entry that I don't want to see (but often they are quite handy - if (data.Opcode == TraceEventOpcode.DataCollectionStart) - { - return; - } - - Out.WriteLine(data.ToString()); - if (data is UnhandledTraceEvent) - { - Out.WriteLine(data.Dump()); - } - } - - private static TraceEventSession s_userSession; - private static TraceEventSession s_kernelSession; - } -} - diff --git a/src/TraceEvent/Samples/34_KernelAndClrFileWin7.cs b/src/TraceEvent/Samples/34_KernelAndClrFileWin7.cs deleted file mode 100644 index 7037ff7a3..000000000 --- a/src/TraceEvent/Samples/34_KernelAndClrFileWin7.cs +++ /dev/null @@ -1,182 +0,0 @@ -using Microsoft.Diagnostics.Tracing; -using Microsoft.Diagnostics.Tracing.Parsers; -using Microsoft.Diagnostics.Tracing.Session; -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; - -/* README FIRST */ -// Until Windows 8, kernel events can only be turned on in a session called 'NT Kernel Logger' and no -// other providers except the kernel provider can be in such a session. Thus if you wish a trace -// that has both kernel and non-kernel events you must have two session. In the code below we show -// how to do this in the file case. Basically you simply have two session, one for the -// kernel events and one for everything else. This means that you generate to files, one for -// kernel events and one for everything else (in our case the CLR events). You then can 'merge' -// the files as a post-processing step. -// -// As mentioned on Windows 8 you can use a single session and thus avoid these complexities. In addition -// event before Windows 8 there are a number of kernel events that are available as non-kernel -// providers. Do a -// logman query providers -// To see all providers that the OS provides, and look for ones with the Microsoft-Windows-Kernel prefix. -// While these providers have 'Kernel' in their name they are not subject to the restrictions mentioned -// above, and thus can be used with other user-mode providers. In particular the Microsoft-Windows-Kernel-Process -// provider can give you the process, thread, and image load events which are some of the most likely -// kernel events you may want. -// -namespace TraceEventSamples -{ - public class KernelAndClrFileWin7 - { - /// - /// Where all the output goes. - /// - private static TextWriter Out = AllSamples.Out; - - public static void Run() - { - Out.WriteLine("******************** KernelAndClrFile DEMO ********************"); - string dataFileName = "output.etl"; - DataCollection(dataFileName); - DataProcessing(dataFileName); - } - - /// - /// Turning on providers and creating files - /// - private static void DataCollection(string dataFileName) - { - Out.WriteLine("Collecting 10 seconds of kernel and CLR events to a file, and then printing."); - Out.WriteLine(); - Out.WriteLine("Start a .NET program while monitoring to see some events!"); - Out.WriteLine(); - if (TraceEventSession.IsElevated() != true) - { - Out.WriteLine("Must be elevated (Admin) to run this program."); - Debugger.Break(); - return; - } - - string kernelDataFileName = Path.ChangeExtension(dataFileName, ".kernel.etl"); - - // Create one user mode session and one kernel mode session - Out.WriteLine("Creating two raw files, one with kernel events and one with clr events."); - using (var userSession = new TraceEventSession("MonitorKernelAndClrEventsSession", dataFileName)) - using (var kernelSession = new TraceEventSession(KernelTraceEventParser.KernelSessionName, kernelDataFileName)) - { - // Set up Ctrl-C to stop both user mode and kernel mode sessions - Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs cancelArgs) - { - Out.WriteLine("Ensuring all ETW sessions are stopped."); - kernelSession.Stop(true); // true means don't throw on error - userSession.Stop(true); // true means don't throw on error - // Since we don't cancel the Ctrl-C we will terminate the process as normal for Ctrl-C - Out.WriteLine("OnCtrl C handler ending."); - }; - - // Enable the events we care about for the kernel in the kernel session - // For this instant the session will buffer any incoming events. - kernelSession.EnableKernelProvider( - KernelTraceEventParser.Keywords.ImageLoad | - KernelTraceEventParser.Keywords.Process | - KernelTraceEventParser.Keywords.Thread); - - // Enable the events we care about for the CLR (in the user session). - // unlike the kernel session, you can call EnableProvider on other things too. - // For this instant the session will buffer any incoming events. - userSession.EnableProvider( - ClrTraceEventParser.ProviderGuid, - TraceEventLevel.Informational, - (ulong)(ClrTraceEventParser.Keywords.Default)); - - Out.WriteLine("Collecting data for 10 seconds (run a .Net program to generate events)."); - Thread.Sleep(10000); - - Out.WriteLine("Stopping sessions"); - } // Using clauses will ensure that session are disposed (and thus stopped) before Main returns. - - Out.WriteLine("Merging the raw files into a single '{0}' file.", dataFileName); - TraceEventSession.MergeInPlace(dataFileName, Out); - Out.WriteLine("Merge complete."); - } - - /// - /// Processing the data in a particular file. - /// - private static void DataProcessing(string dataFileName) - { - Out.WriteLine("Opening the output file and printing the results."); - using (var source = new ETWTraceEventSource(dataFileName)) - { - if (source.EventsLost != 0) - { - Out.WriteLine("WARNING: there were {0} lost events", source.EventsLost); - } - - // Set up callbacks to - source.Clr.All += Print; - source.Kernel.All += Print; - - // When you merge a file, some 'symbol' events are injected into the trace. - // To avoid these showing up as 'unknown' add the parser for these. This - // also shows how you hook up a TraceEventParser that is not support by - // properties on the source itself (like CLR, and kernel) - var symbolParser = new SymbolTraceEventParser(source); - symbolParser.All += Print; - -#if DEBUG - // The callback above will only be called for events the parser recognizes (in the case of Kernel and CLR parsers) - // It is sometimes useful to see the other events that are not otherwise being handled. The source knows about these and you - // can ask the source to send them to you like this. - source.UnhandledEvents += delegate(TraceEvent data) - { - if ((int)data.ID == 0xFFFE) // The EventSource manifest events show up as unhandled, filter them out. - return; - - // To avoid 'rundown' events that happen in the beginning and end of the trace filter out things during those times - if (data.TimeStampRelativeMSec < 1000 || 9000 < data.TimeStampRelativeMSec) - return; - - Out.WriteLine("GOT UNHANDLED EVENT: " + data.Dump()); - }; -#endif - - // go into a loop processing events can calling the callbacks. This will return when the all the events - // In the file are processed, or the StopProcessing() call is made. - source.Process(); - Out.WriteLine("Done Processing."); - } - } - - /// - /// Print data. Note that this method is called FROM DIFFERNET THREADS which means you need to properly - /// lock any read-write data you access. It turns out Out.Writeline is already thread safe so - /// there is nothing I have to do in this case. - /// - private static void Print(TraceEvent data) - { - // There are a lot of data collection start on entry that I don't want to see (but often they are quite handy - if (data.Opcode == TraceEventOpcode.DataCollectionStart || data.Opcode == TraceEventOpcode.DataCollectionStop) - { - return; - } - - // Merging inject some 'symbol' events that are not that interesting so we ignore those too. - if (data.ProviderGuid == SymbolTraceEventParser.ProviderGuid) - { - return; - } - - // To avoid 'rundown' events that happen in the beginning and end of the trace filter out things during those times - if (data.TimeStampRelativeMSec < 1000 || 9000 < data.TimeStampRelativeMSec) - { - return; - } - - Out.WriteLine(data.ToString()); - } - } -} - - From 8a047e79c49e0dd804e0cf3242fd411b6bf6e4cc Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:45:49 -0700 Subject: [PATCH 32/41] Update samples for Windows 10 --- src/TraceEvent/Samples/30_MonitorLoads.cs | 9 +++------ src/TraceEvent/Samples/31_KernelAndClrMonitor.cs | 11 ++--------- src/TraceEvent/Samples/32_KernelAndClrFile.cs | 10 ++-------- src/TraceEvent/Samples/40_SimpleTraceLog.cs | 5 ++--- src/TraceEvent/Samples/41_TraceLogMonitor.cs | 13 ++----------- src/TraceEvent/Samples/50_SimpleFileRelogger.cs | 2 +- src/TraceEvent/Samples/51_SimpleMonitorRelogger.cs | 8 +------- 7 files changed, 13 insertions(+), 45 deletions(-) diff --git a/src/TraceEvent/Samples/30_MonitorLoads.cs b/src/TraceEvent/Samples/30_MonitorLoads.cs index a313621ae..f1bbc0f20 100644 --- a/src/TraceEvent/Samples/30_MonitorLoads.cs +++ b/src/TraceEvent/Samples/30_MonitorLoads.cs @@ -54,12 +54,9 @@ public static void Run() return; } - // Start the session as a real time monitoring session, - // Before windows 8, there is a restriction that if you wanted kernel events you must name your session - // 'NT Kernel Logger' (the value of KernelSessionName) and there can only be one such session and no - // other ETW providers can be enabled for that session (thus you need two sessions if you want both - // kernel and non-kernel events (fixed in Win 8). We want this to work on Win 7 so we live with those - // restrictions. + // This sample uses the special 'NT Kernel Logger' session, which accepts only kernel events. + // Creating it replaces any existing session with that name. Use a separately named session + // as in KernelAndClrMonitor to collect kernel and non-kernel events together. using (TraceEventSession session = new TraceEventSession(KernelTraceEventParser.KernelSessionName)) { /* BY DEFAULT ETW SESSIONS SURVIVE THE DEATH OF THE PROESS THAT CREATES THEM! */ diff --git a/src/TraceEvent/Samples/31_KernelAndClrMonitor.cs b/src/TraceEvent/Samples/31_KernelAndClrMonitor.cs index 4b5cc1577..dd5f52e8e 100644 --- a/src/TraceEvent/Samples/31_KernelAndClrMonitor.cs +++ b/src/TraceEvent/Samples/31_KernelAndClrMonitor.cs @@ -23,13 +23,7 @@ public static void Run() { var monitoringTimeSec = 10; - if (Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor < 62) - { - Out.WriteLine("This demo only works on Win8 / Win 2012 an above)"); - return; - } - - Out.WriteLine("******************** KernelAndClrMonitor DEMO (Win 8) ********************"); + Out.WriteLine("******************** KernelAndClrMonitor DEMO ********************"); Out.WriteLine("Printing both Kernel and CLR (user mode) events simultaneously"); Out.WriteLine("The monitor will run for a maximum of {0} seconds", monitoringTimeSec); Out.WriteLine("Press Ctrl-C to stop monitoring early."); @@ -67,13 +61,12 @@ public static void Run() }, null, monitoringTimeSec * 1000, Timeout.Infinite); // Create the new session to receive the events. - // Because we are on Win 8 this single session can handle both kernel and non-kernel providers. + // This single session handles both kernel and non-kernel providers. using (session = new TraceEventSession("MonitorKernelAndClrEventsSession")) { // Enable the events we care about for the kernel // For this instant the session will buffer any incoming events. // Enabling kernel events must be done before anything else. - // This will fail on Win7. Out.WriteLine("Enabling Image load, Process and Thread events."); session.EnableKernelProvider( KernelTraceEventParser.Keywords.ImageLoad | diff --git a/src/TraceEvent/Samples/32_KernelAndClrFile.cs b/src/TraceEvent/Samples/32_KernelAndClrFile.cs index d4af26f1a..074855cb1 100644 --- a/src/TraceEvent/Samples/32_KernelAndClrFile.cs +++ b/src/TraceEvent/Samples/32_KernelAndClrFile.cs @@ -21,12 +21,6 @@ public class KernelAndClrFile public static void Run() { - if (Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor < 62) - { - Out.WriteLine("This demo only works on Win8 / Win 2012 an above)"); - return; - } - Out.WriteLine("******************** KernelAndClrFile DEMO ********************"); string dataFileName = "output.etl"; DataCollection(dataFileName); @@ -49,7 +43,7 @@ private static void DataCollection(string dataFileName) return; } - // Create one user mode session and one kernel mode session + // Create one session for both user-mode and kernel events. Out.WriteLine("Creating a file mode session"); using (var session = new TraceEventSession("MonitorKernelAndClrEventsSession", dataFileName)) { @@ -64,7 +58,7 @@ private static void DataCollection(string dataFileName) // Enable the events we care about for the kernel in the kernel session // For this instant the session will buffer any incoming events. - // THis has to be first, and it will fail if you are not on Win8. + // Enable the kernel provider before other providers. session.EnableKernelProvider( KernelTraceEventParser.Keywords.ImageLoad | KernelTraceEventParser.Keywords.Process | diff --git a/src/TraceEvent/Samples/40_SimpleTraceLog.cs b/src/TraceEvent/Samples/40_SimpleTraceLog.cs index 6fe88ae0b..88e4f61f0 100644 --- a/src/TraceEvent/Samples/40_SimpleTraceLog.cs +++ b/src/TraceEvent/Samples/40_SimpleTraceLog.cs @@ -142,9 +142,8 @@ private static void CollectData(string eventSourceName, string dataFileName) // Enable my provider, you can call many of these on the same session to get events from other providers // Turn on the eventSource given its name. - // Note we turn on Verbose level all keywords (ulong.MaxValue == 0xFFF....) and turn on stacks for - // this provider (for all events, until Windows 8.1 you can only turn on stacks for every event - // for a particular provider or no stacks) + // Enable Verbose level, all keywords, and stacks for all events from this provider. + // EventIDStacksToEnable can instead select individual events for stack collection. var options = new TraceEventProviderOptions() { StacksEnabled = true }; var restarted = session.EnableProvider(eventSourceName, TraceEventLevel.Verbose, ulong.MaxValue, options); if (restarted) // Generally you don't bother with this warning, but for the demo we do. diff --git a/src/TraceEvent/Samples/41_TraceLogMonitor.cs b/src/TraceEvent/Samples/41_TraceLogMonitor.cs index e3d001c28..0ec59b16e 100644 --- a/src/TraceEvent/Samples/41_TraceLogMonitor.cs +++ b/src/TraceEvent/Samples/41_TraceLogMonitor.cs @@ -31,9 +31,7 @@ public static void Run() Out.WriteLine("This program Shows how to use the real-time support in TraceLog"); Out.WriteLine("We do this by showing how to monitor exceptions in real time "); Out.WriteLine(); - Out.WriteLine("This code depends on a Feature of Windows 8.1 (combined user and kernel sessions)"); - Out.WriteLine("It will work on Win7 machines, however win7 can have only one kernel session"); - Out.WriteLine("so it will disrupt any use of the kernel session on that OS. "); + Out.WriteLine("This code uses a combined user and kernel session."); Out.WriteLine(); Out.WriteLine("Note that this support is currently experimental and subject to change"); Out.WriteLine(); @@ -41,11 +39,6 @@ public static void Run() Out.WriteLine("Run some managed code (ideally that has exceptions) while the monitor is running."); Out.WriteLine(); - if (Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor < 62) - { - Out.WriteLine("This demo will preempt any use of the kernel provider. "); - } - TraceEventSession session = null; // Set up Ctrl-C to stop both user mode and kernel mode sessions @@ -69,14 +62,12 @@ public static void Run() Timer timer = null; // Create the new session to receive the events. - // Because we are on Win 8 this single session can handle both kernel and non-kernel providers. + // This single session handles both kernel and non-kernel providers. using (session = new TraceEventSession("TraceLogSession")) { // Enable the events we care about for the kernel // For this instant the session will buffer any incoming events. // Enabling kernel events must be done before anything else. - // Note that on Win7 it will turn on the one and only NT Kernel Session, and thus interrupt any kernel session in progress. - // On WIn8 you get a new session (like you would expect). // // Note that if you turn on the KernelTraceEventParser.Keywords.Profile, you can also get stacks for CPU sampling // (every millisecond). (You can use the traceLogSource.Kernel.PerfInfoSample callback). diff --git a/src/TraceEvent/Samples/50_SimpleFileRelogger.cs b/src/TraceEvent/Samples/50_SimpleFileRelogger.cs index 1b5fb7a93..859aaa3a3 100644 --- a/src/TraceEvent/Samples/50_SimpleFileRelogger.cs +++ b/src/TraceEvent/Samples/50_SimpleFileRelogger.cs @@ -119,7 +119,7 @@ private static void DataCollection(string dataFileName) // Enable the events we care about for the kernel in the kernel session // For this instant the session will buffer any incoming events. - // This has to be first, and it will fail if you are not on Win8. + // Enable the kernel provider before other providers. session.EnableKernelProvider( KernelTraceEventParser.Keywords.ImageLoad | KernelTraceEventParser.Keywords.Process | diff --git a/src/TraceEvent/Samples/51_SimpleMonitorRelogger.cs b/src/TraceEvent/Samples/51_SimpleMonitorRelogger.cs index e7b0dc55f..78550b43e 100644 --- a/src/TraceEvent/Samples/51_SimpleMonitorRelogger.cs +++ b/src/TraceEvent/Samples/51_SimpleMonitorRelogger.cs @@ -28,12 +28,6 @@ public static void Run() { int monitoringTimeSec = 10; - if (Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor < 62) - { - Out.WriteLine("This demo only works on Win8 / Win 2012 an above)"); - return; - } - // Today you have to be Admin to turn on ETW events (anyone can write ETW events). if (!(TraceEventSession.IsElevated() ?? false)) { @@ -72,7 +66,7 @@ public static void Run() { // Enable the events we care about for the kernel in the kernel session // For this instant the session will buffer any incoming events. - // This has to be first, and it will fail if you are not on Win8. + // Enable the kernel provider before other providers. session.EnableKernelProvider( KernelTraceEventParser.Keywords.ImageLoad | KernelTraceEventParser.Keywords.Process | From 8547d43f920dfa8b52248986d7b8d905d35e2abc Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:56:33 -0700 Subject: [PATCH 33/41] Remove EventRegister's WinRT OS gate --- src/related/EventRegister/Program.cs | 87 +++++++++---------- src/related/EventRegister/UsersGuide.htm | 4 + .../EventRegister/eventRegister.csproj | 3 + 3 files changed, 50 insertions(+), 44 deletions(-) diff --git a/src/related/EventRegister/Program.cs b/src/related/EventRegister/Program.cs index f7cb3a055..51afbd438 100644 --- a/src/related/EventRegister/Program.cs +++ b/src/related/EventRegister/Program.cs @@ -12,6 +12,7 @@ using System.Diagnostics.Eventing; using System.Collections.ObjectModel; using Microsoft.Diagnostics.Tracing; +using Microsoft.Diagnostics.Utilities; #if TARGET_FRAMEWORK_4_5_OR_HIGHER using System.Runtime.InteropServices.WindowsRuntime; @@ -140,6 +141,13 @@ internal static string GenerateManifest(Type eventSourceType, string manifestDll static int Main() { + return CommandLineUtilities.RunConsoleMainWithExceptionProcessing(Run); + } + + static int Run() + { + OperatingSystemVersion.EnsureSupported(); + #if USE_EVENTSOURCE_REFLECTION // If this app does not take a dependency on a specific EventSource implementation we'll need to use the EventSource // type that represents the base type of the custom event source that needs to be registered. To accomplish this we @@ -155,7 +163,7 @@ static int Main() if (AppDomain.CurrentDomain.FriendlyName != workerDomainFriendlyName) { // See code:#CommandLineDefinitions for Command line defintions - CommandLine commandLine = new CommandLine(); + CommandLine workerCommandLine = new CommandLine(); var ads = new AppDomainSetup(); // ads.PrivateBinPath = Path.GetDirectoryName(Path.GetFullPath(commandLine.DllPath)); ads.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; @@ -165,47 +173,43 @@ static int Main() // ads.ConfigurationFile = configName; var workerDom = AppDomain.CreateDomain(workerDomainFriendlyName, null, ads, new System.Security.PermissionSet(System.Security.Permissions.PermissionState.Unrestricted)); // make sure the worker domain is aware of the additional paths to probe when resolving the EventSource base type - if (commandLine.ReferencePath != null) - workerDom.SetData("ReferencePath", Environment.ExpandEnvironmentVariables(commandLine.ReferencePath)); + if (workerCommandLine.ReferencePath != null) + workerDom.SetData("ReferencePath", Environment.ExpandEnvironmentVariables(workerCommandLine.ReferencePath)); return workerDom.ExecuteAssembly(typeof(Program).Assembly.Location); } - else #endif - return CommandLineUtilities.RunConsoleMainWithExceptionProcessing(delegate + // See code:#CommandLineDefinitions for Command line defintions + CommandLine commandLine = new CommandLine(); + List> regDlls; + EventSourceReflectionProxy.ManifestGenerator = commandLine.ManifestGenerator; + switch (commandLine.Command) { - // See code:#CommandLineDefinitions for Command line defintions - CommandLine commandLine = new CommandLine(); - List> regDlls; - EventSourceReflectionProxy.ManifestGenerator = commandLine.ManifestGenerator; - switch (commandLine.Command) - { - // Create the XML description in the data directory "will be called - case CommandLine.CommandType.DumpManifest: - var manifests = CreateManifestsFromManagedDll(commandLine.DllPath, commandLine.ManifestPrefix); - if (manifests.Count == 0) - Console.WriteLine("Info: No event source classes " + (commandLine.ForceAll ? "" : "needing registration ") + "found in " + commandLine.DllPath); - break; - case CommandLine.CommandType.CompileManifest: - CompileManifest(commandLine.ManPath, System.IO.Path.ChangeExtension(commandLine.ManPath, ".dll")); - break; - case CommandLine.CommandType.DumpRegDlls: - regDlls = CreateRegDllsFromManagedDll(commandLine.DllPath, commandLine.ForceAll, commandLine.ManifestPrefix); - if (regDlls.Count == 0) - Console.WriteLine("Info: No event source classes " + (commandLine.ForceAll ? "" : "needing registration ") + "found in " + commandLine.DllPath); - break; - case CommandLine.CommandType.Install: - regDlls = RegisterManagedDll(commandLine.DllPath, false, commandLine.ManifestPrefix); - if (regDlls.Count == 0) - Console.WriteLine("Info: No event source classes " + (commandLine.ForceAll ? "" : "needing registration ") + "found in " + commandLine.DllPath); - break; - case CommandLine.CommandType.Uninstall: - regDlls = RegisterManagedDll(commandLine.DllPath, true, commandLine.ManifestPrefix); - if (regDlls.Count == 0) - Console.WriteLine("Info: No event source classes " + (commandLine.ForceAll ? "" : "needing registration ") + "found in " + commandLine.DllPath); - break; - } - return 0; - }); + // Create the XML description in the data directory "will be called + case CommandLine.CommandType.DumpManifest: + var manifests = CreateManifestsFromManagedDll(commandLine.DllPath, commandLine.ManifestPrefix); + if (manifests.Count == 0) + Console.WriteLine("Info: No event source classes " + (commandLine.ForceAll ? "" : "needing registration ") + "found in " + commandLine.DllPath); + break; + case CommandLine.CommandType.CompileManifest: + CompileManifest(commandLine.ManPath, System.IO.Path.ChangeExtension(commandLine.ManPath, ".dll")); + break; + case CommandLine.CommandType.DumpRegDlls: + regDlls = CreateRegDllsFromManagedDll(commandLine.DllPath, commandLine.ForceAll, commandLine.ManifestPrefix); + if (regDlls.Count == 0) + Console.WriteLine("Info: No event source classes " + (commandLine.ForceAll ? "" : "needing registration ") + "found in " + commandLine.DllPath); + break; + case CommandLine.CommandType.Install: + regDlls = RegisterManagedDll(commandLine.DllPath, false, commandLine.ManifestPrefix); + if (regDlls.Count == 0) + Console.WriteLine("Info: No event source classes " + (commandLine.ForceAll ? "" : "needing registration ") + "found in " + commandLine.DllPath); + break; + case CommandLine.CommandType.Uninstall: + regDlls = RegisterManagedDll(commandLine.DllPath, true, commandLine.ManifestPrefix); + if (regDlls.Count == 0) + Console.WriteLine("Info: No event source classes " + (commandLine.ForceAll ? "" : "needing registration ") + "found in " + commandLine.DllPath); + break; + } + return 0; } // These correspond to command line commands @@ -370,11 +374,7 @@ private static List GetEventSources(string dllPath) { AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_ReflectionOnlyAssemblyResolve; - if (System.Environment.OSVersion.Version >= new Version(6, 2, 0, 0)) - { - // on Win8+ support winmd assembly resolution - SubscribeToWinRTReflectionOnlyNamespaceResolve(); - } + SubscribeToWinRTReflectionOnlyNamespaceResolve(); assembly = Assembly.ReflectionOnlyLoadFrom(dllPath); @@ -705,4 +705,3 @@ public enum CommandType public string ManifestGenerator = "builtin"; // "base", "path_to_assm_containing_EventSource_type" public bool ForceAll; }; - diff --git a/src/related/EventRegister/UsersGuide.htm b/src/related/EventRegister/UsersGuide.htm index 5d2da6ffd..23222ba62 100644 --- a/src/related/EventRegister/UsersGuide.htm +++ b/src/related/EventRegister/UsersGuide.htm @@ -27,6 +27,10 @@

implemented using managed code and either the System.Diagnostics.Tracing.EventSource class or the NuGet-shipped Microsoft.Diagnostics.Tracing.EventSource.

+

+ EventRegister requires Windows 10 or Windows Server 2016 or later. + Its target-assembly reflection and WinRT metadata resolution support are unchanged. +

A Quick Summary of Event Tracing for Windows (ETW)

diff --git a/src/related/EventRegister/eventRegister.csproj b/src/related/EventRegister/eventRegister.csproj index d51307096..4abad0f93 100644 --- a/src/related/EventRegister/eventRegister.csproj +++ b/src/related/EventRegister/eventRegister.csproj @@ -46,6 +46,9 @@ + + Library\Util\OperatingSystemVersion.cs + From 80d4756b4e24fc60e8dbeb06b14da0f2b461fdc1 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 11:59:21 -0700 Subject: [PATCH 34/41] Refresh supported-platform guidance --- README.md | 4 + documentation/Downloading.md | 10 ++- documentation/TraceEvent/TraceEventLibrary.md | 3 - .../TraceEvent/TraceEventProgrammersGuide.md | 12 +-- src/PerfView/CommandLineArgs.cs | 2 +- src/PerfView/SupportFiles/UsersGuide.htm | 86 +++---------------- .../Parsers/KernelTraceEventParser.cs | 6 +- src/TraceEvent/Samples/ReadMe.txt | 4 +- src/TraceEvent/TraceEventSession.cs | 7 +- 9 files changed, 39 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 8c10de3fa..16ad33a08 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ PerfView requires .NET Framework 4.7.2 or later, which is widely available for a PerfView and TraceEvent require Windows 10 or Windows Server 2016 or later when running on Windows. +There is no native ARM64 PerfView application build. PerfViewCollect requires the matching +x64 .NET runtime, whose supported Windows releases may be more restrictive. +Historical trace files and older target runtimes remain supported for analysis. + ### Are you here about the TraceEvent Library? PerfView is built on a library called Microsoft.Diagnostics.Tracing.TraceEvent, that knows how to both collect and parse Event Tracing for Windows (ETW) and EventPipe (.NET Core trace) data. Thus if there is any information that PerfView collects and processes that you would like to manipulate yourself programmatically, you would probably be interested in the [TraceEvent Library Documentation](documentation/TraceEvent/TraceEventLibrary.md) diff --git a/documentation/Downloading.md b/documentation/Downloading.md index 695e64ac9..eff2bdf6f 100644 --- a/documentation/Downloading.md +++ b/documentation/Downloading.md @@ -4,6 +4,11 @@ PerfView is a free profiling tool from Microsoft. This page tells you how to g See the [PerfView Overview](https://github.com/Microsoft/perfview#perfview-overview) for general information about PerfView. +PerfView is an x64 application requiring Windows 10 or Windows Server 2016 or later +and .NET Framework 4.7.2 or later. It can collect from both x86 and x64 target processes. +Windows-provided x64 emulation may be used where available; there is no native ARM64 +PerfView application build. + # PerfView Releases The [PerfView GitHub Releases page](https://github.com/Microsoft/perfview/releases) is now the official @@ -50,8 +55,8 @@ This confirms that software is authentic. You can also do this by running the si # Additional Release Information The link above allows you to quickly download PerfView.exe which is what you need 95% of the time. -If you wish to get other files associated with the release (e.g. the 64 bit version, or -the debugging symbols), or wish to download older releases (not recommended), you can do +If you wish to get other files associated with the release (e.g. debugging symbols), +or wish to download older releases (not recommended), you can do so by visiting the [PerfView GitHub Releases](https://github.com/Microsoft/perfview/releases). @@ -60,4 +65,3 @@ The Microsoft Download Site for PerfView has been retired as the official download site for PerfView. It has been replaced by [PerfView GitHub Releases page](https://github.com/Microsoft/perfview/releases) referenced above. - diff --git a/documentation/TraceEvent/TraceEventLibrary.md b/documentation/TraceEvent/TraceEventLibrary.md index f5677af5b..af5671e2b 100644 --- a/documentation/TraceEvent/TraceEventLibrary.md +++ b/documentation/TraceEvent/TraceEventLibrary.md @@ -47,9 +47,6 @@ Older traces and target runtimes can still be analyzed on supported hosts. To see more complete samples that use the APIs in more sophisticated (but not too sophisticated ways). In the [Samples directory](https://github.com/Microsoft/perfview/tree/main/src/TraceEvent/Samples) here are one or two page samples doing interesting things (collecting data, parsing from files or in real time, transforming one ETL file to another etc). Each sample is independent of the others. -One way of getting the samples is to walk through the step by step guide in [Vance's Walkthough on TraceEvent](https://blogs.msdn.microsoft.com/vancem/2014/03/15/walk-through-getting-started-with-etw-traceevent-nuget-samples-package/). This walkthrough uses TraceEvent Samples package, however this code is old (but still completely relevant -the APIs have not changed). These samples are exactly the samples in Github mentioned above. - The easiest way to build the latest samples is to simply clone the [PerfView Repository](https://github.com/Microsoft/perfview) and and build it. The samples are in the _TraceEventSamples_ project in the PerfView solution. Simply set this project to be your _Startup Project_ (right click on it in Solution Explorer -> Set as Startup Project) and run it (F5). By default will run all the samples. ## Release Notes for the Microsoft.Diagnostics.Tracing.TraceEvent library diff --git a/documentation/TraceEvent/TraceEventProgrammersGuide.md b/documentation/TraceEvent/TraceEventProgrammersGuide.md index d705ded65..790ed49e1 100644 --- a/documentation/TraceEvent/TraceEventProgrammersGuide.md +++ b/documentation/TraceEvent/TraceEventProgrammersGuide.md @@ -436,7 +436,7 @@ Capabilities include: * The ability to turn on event providers selectively using ETW 'Keywords' and verbosity 'Levels'. You can also pass additional arguments to your provider which `EventSource` sources can pick up. In that way you can create very sophisticated filtering specification as well as execute simple commands (e.g. force a GC, flush the working set, and etc.). * The ability to enumerate the ETW providers on the system as well as in a particular process, and the ability to determine what ETW groups (Keywords) you can turn on. * Ability to take ETL files and merge them together into a single file. -* Ability to read an ETL file or real time session and write an ETL file from it, filtering it or adding new events (Windows 8 only). +* Ability to read an ETL file or real time session and write an ETL file from it, filtering it or adding new events. * The ability to capture stack traces when events are being logged. * The ability to convert the stacks to symbolic form both for .NET, Jscript, as well as native code. * The ability to store events in a new format (ETLX) that allows the events to be accessed efficiently in a random fashion as well as to enumerate the events backwards as well as forwards, and to efficiently represent the stack information. @@ -456,7 +456,7 @@ Capabilities include: * Network I/O. * Every packet (with compete data) that comes on or off the network (network sniffer). * Every system call. - * Sampling of processor CPU counters (instructions executed, branch mispredicts, cache misses, ...) (Windows 8 only). + * Sampling of processor CPU counters (instructions executed, branch mispredicts, cache misses, ...). * Remote procedure calls. * How the machine is configured (disk, memory, CPUs, ...). * Access CLR (.NET Runtime) events, including: @@ -481,13 +481,13 @@ You can also get a reasonably good idea of what is possible by taking a look at Unfortunately, there are some limitations in ETW that sometimes block it from being used in scenarios where it would otherwise be a natural fit. They are listed here for emphasis. -* You send commands to providers on a machine wide basis. Thus you can't target particular processes (however if you own the event provider code you can pass it extra information as arguments to 'enable' command to the provider and have your provider implement logic to ignore 'enable' commands not intended for it). (Fixed in Windows 8.1). +* Provider control is machine-wide, but `TraceEventProviderOptions.ProcessIDFilter` and `ProcessNameFilter` can limit collected events to selected processes on supported Windows hosts. * Because commands are machine wide and thus give you access to all information on the system, you have to be Elevated (Admin) to turn an ETW session on or off. * By design the communication between the controllers and the providers is 'fire and forget'. Thus ETW is not intended to be a general purpose cross process communication mechanism. Don't try to use it as such. * In real time mode, events are buffered and there is at least a second or so delay (typically 3 sec) between the firing of the event and the reception by the session (to allow events to be delivered in efficient clumps of many events). -* Before Windows 8, there could only one kernel session. Thus using kernel mode events for 'monitoring' scenarios was problematic because any other tools that used kernel sessions were likely to interfere by overriding the single Kernel model event logging session. +* Named sessions can combine kernel and user providers. The special `NT Kernel Logger` session remains kernel-only; reusing its name can interfere with another tool using that session. * In general scenarios having multiple controllers (sessions) controlling the same providers is dangerous. It can be done in some cases, but there is a significant potential for interference between the sessions. -* The file format is private, and before Windows 8 could be quite space inefficient (it compresses 8-to-1). Files can get big fast. +* ETL files can get large. Compression is available on supported Windows hosts; historical uncompressed traces may be substantially larger. * Logging more than 10K events/sec will load the system noticeably (5%). Logging more frequently than 10K/sec should be avoided if possible. Logging 1M events/sec will completely swamp a typical machine. ## Next Step: Code Samples @@ -827,7 +827,7 @@ Below are the steps in converting logging an event with a stack to a resolved sy 1. When the event is logged, the ETW system tries to crawl the stack at runtime. However this can fail for various reasons: 1. On 32-bit machines the crawler assumes the compiler stores unwinding information (EBP frames) on the stack. If the compile does not do this the stack 'breaks' (can't be unwound) and you lose any frames 'toward thread start'. - 2. On 64-bit processes on 64-bit machines the crawler needs 'unwind' information. For native code this is stored in the EXE, but for Just In Time (JIT) compiled code on Systems **before Windows 8 (or Win2012 server)** the ETW system did not know how to find this unwind information and break at the first frame with JIT compiled code. This is the most common reason for stack breakage, but will diminish machines are upgraded to new OSes. + 2. For 64-bit processes, the crawler needs unwind information. Historical traces collected **before Windows 8 (or Windows Server 2012)** may have stacks broken at JIT-compiled frames because ETW could not locate that information. This old-host limitation is not a restriction on current collection, but reading an old trace cannot recover frames that were never recorded. If either of these happen there is nothing wrong with your code as it is an issue with the APP or with the ETW infrastructure. PerfView should have the same problem. diff --git a/src/PerfView/CommandLineArgs.cs b/src/PerfView/CommandLineArgs.cs index 1fbee896f..01e989fdb 100644 --- a/src/PerfView/CommandLineArgs.cs +++ b/src/PerfView/CommandLineArgs.cs @@ -287,7 +287,7 @@ private void SetupCommandLine(CommandLineParser parser) parser.DefineOptionalQualifier("BufferSizeMB", ref BufferSizeMB, "The size the buffers (in MB) the OS should use to store events waiting to be written to disk."); parser.DefineOptionalQualifier("CircularMB", ref CircularMB, "Do Circular logging with a file size in MB."); parser.DefineOptionalQualifier("InMemoryCircularBuffer", ref InMemoryCircularBuffer, "Keeps the circular buffer in memory until the session is stopped."); - parser.DefineOptionalQualifier("StackCompression", ref StackCompression, "Use stack compression (only on Win 8+) to make collected file smaller."); + parser.DefineOptionalQualifier("StackCompression", ref StackCompression, "Use stack compression to make the collected file smaller."); parser.DefineOptionalQualifier("LbrSources", ref LastBranchRecordingSources, $"Turn on LBR sampling from these sources (comma-separated numeric hex values with 0x prefix or 'PmcInterrupt'). At most {TraceEventSession.GetMaxLastBranchRecordingSources()} sources are supported."); parser.DefineOptionalQualifier( diff --git a/src/PerfView/SupportFiles/UsersGuide.htm b/src/PerfView/SupportFiles/UsersGuide.htm index 32f9a7e4e..f53c2c661 100644 --- a/src/PerfView/SupportFiles/UsersGuide.htm +++ b/src/PerfView/SupportFiles/UsersGuide.htm @@ -64,15 +64,18 @@

way, right clicking allows you to discover what PerfView's can do for you.

- PerfView is a V4.6.2 .NET application.   Thus you need to have installed - a V4.6.2 .NET Runtime on the machine which you actually run PerfView.   - On Windows 10 and Windows Server 2016 has .NET V4.6.2. - On other supported OS you can install .NET 4.6.2 from standalone installer. PerfView is not supported - on Win2K3 or WinXP.    While PerfView itself needs a V4.6.2 runtime, - it can collect data on processes that use V2.0 and v4.0 runtimes. On machines that don't - have V4.6.2 or later of the .NET runtime installed, it is also possible to collect ETL data - with another tool (e.g. XPERF or PerfMonitor) and then copy data file to a machine - with V4.6.2 and view it with PerfView. + PerfView requires Windows 10 or Windows Server 2016 or later, an x64 execution + environment, and .NET Framework 4.7.2 or later. Windows-provided x64 emulation + may be used on ARM64 hosts; there is no native ARM64 PerfView application build. + PerfView and PerfViewCollect are x64 applications, but collection from x86 target + processes is still supported. PerfViewCollect requires the matching x64 .NET + runtime, whose supported Windows releases may be more restrictive. +

+

+ The host requirements do not retire historical trace formats or older target runtimes. + PerfView can still analyze traces containing processes using V2.0 and V4.0 .NET + runtimes, and traces collected on older systems with other tools can be copied + to a supported host for analysis.

What can PerfView do for you? @@ -8264,21 +8267,6 @@

for operating system code or for .NET Runtime code, but may occur for 3rd party code. -
  • - In a 32 bit process on a 64 bit Windows 7 or Windows Server 2008 there is a bug - in which stacks are uniformly dropped in some sessions.  The good news is that - it only happens intermittently.   Thus if you collect the data again, - it is likely to sidestep this bug.   This should be fixed in Windows 8. -
  • -
  • - In a 64 bit process, ETW relies on a different mechanism to walk the stack.  - In this mechanism the compiler generates 'unwind information'.    - Currently this ETW mechanism does not work properly for dynamically generated code - (as generated by the .NET runtime JIT compiler).  This causes stacks to be - broken at the first JIT compiled method on the stack (you see the JIT compile method, - but no callers of that method).    This issue is fixed on Window - 8 but not in previous OS versions.  -
  • Asynchronous activities.   Stack crawling is a 'best effort' service.   If the sample is taken at a time where it would be impossible to do logging safely, @@ -8287,54 +8275,6 @@

    crawling is simply aborted. 

  • -

    Working around 64 bit stack breaks:

    -

    - If you are profiling a 64 bit process there is pretty good chance that you are being - affected by scenario (2) above.    There are three workarounds to - broken stacks in that instance -

    -
      -
    1. - NGEN the application.   The failures occur at JIT compiled code.  - If you NGEN - the application, JIT compilation will not be necessary and the broken stacks will - disappear.   To NGEN your application simply type  -

      - C:\Windows\Microsoft.NET\Framework64\v4.0.30319\NGen install YourApp.exe. -

      - You will have to repeat this every time your application is recompiled. If your - code is called from a server, you need to NGEN all the DLLs that are important to - you (same command line as above). -

      - For server applications there is often not a main EXE that you can pass to the NGEN - command above, however you can NGEN particular DLLs using the same syntax (NGEN - install DLLPATH). If you don't know that path names to your DLLs you can find them - by going to the 'Events' view and selecting the 'ModuleLoad' and 'ModuleDCStop' - events as well as the 'ModuleILPath' and 'ModuleNativePath' columns. Any DLL without - a 'ModuleNativePath' is a candidate for NGEN. -

      -
    2. -
    3. - Switch to 32 bit.   If your code is pure managed code, then it can run - both as a 32 or a 64 bit process.  By switching use a 32 bit process, you avoids - the problem.   This does not work if you took dependencies native code - that only exists for 64 bit.    You can convert your application - to run 32 bit by using the CorFlags - utility that comes are part of the .NET SDK.   - It also comes are part of Visual Studio (open - the VS command prompt).   To switch simply type CorFlags /32bit+ YourApp.exe. - You will have to repeat this every time your application is recompiled. -

      - For ASP.NET applications you can set it so that your page is loaded in a 32 bit - process by following the instruction in - this blog -

      -
    4. -
    5. - Perform only a bottom-up analysis.   Even with many broken stacks, there - is a lot of information in the profile, and a 'bottom-up' analysis is possible.  -
    6. -

    @@ -8523,7 +8463,7 @@

    • - Help: On Windows 7, if you drag a top level + Help: If you drag a top level window to the left or right margin of the desktop, it will expand automatically to fill exactly half the desktop.  Use this feature to drag the StackWindow to take up the left half and the help to take up the right.  Now you can read diff --git a/src/TraceEvent/Parsers/KernelTraceEventParser.cs b/src/TraceEvent/Parsers/KernelTraceEventParser.cs index dd618d5f4..c4d0c2468 100644 --- a/src/TraceEvent/Parsers/KernelTraceEventParser.cs +++ b/src/TraceEvent/Parsers/KernelTraceEventParser.cs @@ -140,7 +140,7 @@ public enum Keywords /// VirtualAlloc = 0x004000, /// - /// Log mapping of files into memory (Win8 and above Only) + /// Log mapping of files into memory. /// Generally low volume. /// VAMap = 0x8000, @@ -198,11 +198,11 @@ public enum Keywords // These are ones that I have made up // All = 0x07B3FFFF, so 4'0000, 8'0000, 40'0000, and F000'00000 are free. /// - /// Turn on PMC (Precise Machine Counter) events. Only Win 8 + /// Turn on PMC (Precise Machine Counter) events. /// PMCProfile = unchecked((int)0x80000000), /// - /// Kernel reference set events (like XPERF ReferenceSet). Fully works only on Win 8. + /// Kernel reference set events (like XPERF ReferenceSet). /// ReferenceSet = 0x40000000, /// diff --git a/src/TraceEvent/Samples/ReadMe.txt b/src/TraceEvent/Samples/ReadMe.txt index 527b01a4a..330d9a0c0 100644 --- a/src/TraceEvent/Samples/ReadMe.txt +++ b/src/TraceEvent/Samples/ReadMe.txt @@ -4,6 +4,9 @@ **** QUICK START +These ETW samples require Windows 10 or Windows Server 2016 or later. +Samples that start collection also require the privileges described in their source. + To run all the samples, simply have your main program call TraceEventSamples.AllSamples.Run(); @@ -40,4 +43,3 @@ By default the output goes to Console.Out but you can redirect it to another TextWriter by setting AllSamples.Out. This is useful for GUI Apps. ****************************************************************************** - diff --git a/src/TraceEvent/TraceEventSession.cs b/src/TraceEvent/TraceEventSession.cs index f4691907d..92578f65f 100644 --- a/src/TraceEvent/TraceEventSession.cs +++ b/src/TraceEvent/TraceEventSession.cs @@ -1078,11 +1078,8 @@ public void CaptureState(Guid providerGuid, ulong matchAnyKeywords = ulong.MaxVa // These properties can be set both before and after a provider has been enabled in the session. /// - /// When you issue a EnableProvider command, on windows 7 and above it can be done synchronously (that is you know that because - /// the EnableProvider returned that the provider actually got the command). However synchronous behavior means that - /// you may wait forever. This is the time EnableProvider waits until it gives up. Setting this - /// to 0 means asynchronous (fire and forget). The default is 10000 (wait 10 seconds) - /// Before windows 7 EnableProvider is always asynchronous. + /// The time in milliseconds EnableProvider waits for the provider to receive the command. + /// Setting this to 0 means asynchronous (fire and forget). The default is 10000 (wait 10 seconds). /// public int EnableProviderTimeoutMSec { get; set; } /// From f834c39d061270f9db311225394caa4b8ed2e480 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Fri, 2 Oct 2026 12:04:41 -0700 Subject: [PATCH 35/41] Dispose the invalid-handle test fixture --- .../TraceEvent.Tests/Parsing/ETWTraceEventSourceTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/TraceEvent/TraceEvent.Tests/Parsing/ETWTraceEventSourceTests.cs b/src/TraceEvent/TraceEvent.Tests/Parsing/ETWTraceEventSourceTests.cs index c23ecc79f..fc56c7867 100644 --- a/src/TraceEvent/TraceEvent.Tests/Parsing/ETWTraceEventSourceTests.cs +++ b/src/TraceEvent/TraceEvent.Tests/Parsing/ETWTraceEventSourceTests.cs @@ -16,7 +16,6 @@ public void InvalidHandleReportsTheNativeError() var field = typeof(ETWTraceEventSource).GetField("handles", BindingFlags.NonPublic | BindingFlags.Instance); var handles = (TraceEventNativeMethods.SafeTraceHandle[])field.GetValue(source); handles[0].Dispose(); - handles[0] = new TraceEventNativeMethods.SafeTraceHandle(ulong.MaxValue); var error = Assert.Throws(() => source.Process()); Assert.Equal(unchecked((int)0x80070006), error.HResult); } From 8bfcba76e5caded94458f434b4d2c1516046ffb8 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Tue, 6 Oct 2026 13:53:09 -0700 Subject: [PATCH 36/41] Fix compiler typo in TraceEvent guide --- documentation/TraceEvent/TraceEventProgrammersGuide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/TraceEvent/TraceEventProgrammersGuide.md b/documentation/TraceEvent/TraceEventProgrammersGuide.md index 790ed49e1..a5e933ad6 100644 --- a/documentation/TraceEvent/TraceEventProgrammersGuide.md +++ b/documentation/TraceEvent/TraceEventProgrammersGuide.md @@ -826,7 +826,7 @@ Below are the steps in converting logging an event with a stack to a resolved sy 1. When the event is logged, the ETW system tries to crawl the stack at runtime. However this can fail for various reasons: - 1. On 32-bit machines the crawler assumes the compiler stores unwinding information (EBP frames) on the stack. If the compile does not do this the stack 'breaks' (can't be unwound) and you lose any frames 'toward thread start'. + 1. On 32-bit machines the crawler assumes the compiler stores unwinding information (EBP frames) on the stack. If the compiler does not do this the stack 'breaks' (can't be unwound) and you lose any frames 'toward thread start'. 2. For 64-bit processes, the crawler needs unwind information. Historical traces collected **before Windows 8 (or Windows Server 2012)** may have stacks broken at JIT-compiled frames because ETW could not locate that information. This old-host limitation is not a restriction on current collection, but reading an old trace cannot recover frames that were never recorded. If either of these happen there is nothing wrong with your code as it is an issue with the APP or with the ETW infrastructure. PerfView should have the same problem. From 4120c138defa7eea6d281a590d92c8e602e0ed6a Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Tue, 6 Oct 2026 14:01:36 -0700 Subject: [PATCH 37/41] Clarify ARM64 trace capture support --- documentation/Downloading.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/documentation/Downloading.md b/documentation/Downloading.md index eff2bdf6f..75c748c08 100644 --- a/documentation/Downloading.md +++ b/documentation/Downloading.md @@ -5,9 +5,7 @@ See the [PerfView Overview](https://github.com/Microsoft/perfview#perfview-overv about PerfView. PerfView is an x64 application requiring Windows 10 or Windows Server 2016 or later -and .NET Framework 4.7.2 or later. It can collect from both x86 and x64 target processes. -Windows-provided x64 emulation may be used where available; there is no native ARM64 -PerfView application build. +and .NET Framework 4.7.2 or later. It can collect from x86 and x64 target processes. It can also be used to capture traces on arm64 hardware. # PerfView Releases @@ -64,4 +62,3 @@ so by visiting the [PerfView GitHub Releases](https://github.com/Microsoft/perfv The Microsoft Download Site for PerfView has been retired as the official download site for PerfView. It has been replaced by [PerfView GitHub Releases page](https://github.com/Microsoft/perfview/releases) referenced above. - From 3ee63ae61878fae83ae5ff0fbd713b13f9b79075 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Tue, 6 Oct 2026 14:10:59 -0700 Subject: [PATCH 38/41] Link latest download in release information Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- documentation/Downloading.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/Downloading.md b/documentation/Downloading.md index 75c748c08..ebd1353eb 100644 --- a/documentation/Downloading.md +++ b/documentation/Downloading.md @@ -52,7 +52,8 @@ This confirms that software is authentic. You can also do this by running the si # Additional Release Information -The link above allows you to quickly download PerfView.exe which is what you need 95% of the time. +The [aka.ms/perfview/latest](https://aka.ms/perfview/latest) shortcut allows you to quickly download +PerfView.exe which is what you need 95% of the time. If you wish to get other files associated with the release (e.g. debugging symbols), or wish to download older releases (not recommended), you can do so by visiting the [PerfView GitHub Releases](https://github.com/Microsoft/perfview/releases). From 7be4deaf0c0c77592699366792a976c43da7cf14 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Tue, 6 Oct 2026 14:16:35 -0700 Subject: [PATCH 39/41] Clarify Windows package identity Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/HeapDump/GCHeapDumper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/HeapDump/GCHeapDumper.cs b/src/HeapDump/GCHeapDumper.cs index d62d411d8..e8a71515c 100644 --- a/src/HeapDump/GCHeapDumper.cs +++ b/src/HeapDump/GCHeapDumper.cs @@ -684,7 +684,8 @@ private void ResumeProcessIfNecessary(int processID) throw new HeapDumpException("Could not find process with ID " + processID, HR.CouldNotFindProcessId); } - // Resume packaged applications before collection. + // Resume packaged applications (apps with Windows package identity) before collection. + // See https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/package-identity-overview var fullPackageName = PackageUtil.FullPackageNameForProcess(process); if (fullPackageName != null) { From 598c3db441a0699636e4fab5334047289b83c051 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Tue, 6 Oct 2026 16:04:30 -0700 Subject: [PATCH 40/41] Remove constant PerfView architecture helpers --- src/PerfView.Tests/StartupTests.cs | 1 - src/PerfView/App.cs | 2 +- src/PerfView/CommandProcessor.cs | 8 ++++---- src/PerfView/Utilities/SupportFiles.cs | 26 ++++---------------------- 4 files changed, 9 insertions(+), 28 deletions(-) diff --git a/src/PerfView.Tests/StartupTests.cs b/src/PerfView.Tests/StartupTests.cs index 83f221a74..9f2e10037 100644 --- a/src/PerfView.Tests/StartupTests.cs +++ b/src/PerfView.Tests/StartupTests.cs @@ -17,7 +17,6 @@ public void ApplicationAndTestHostAreX64() [Fact] public void HostResourcesUseX64AndKeepBothHelpers() { - Assert.Equal("amd64", global::Utilities.SupportFiles.ProcessArchitectureDirectory); var resources = typeof(Startup).Assembly.GetManifestResourceNames(); Assert.Contains(@".\runtimes\win-x64\native\WebView2Loader.dll", resources); Assert.DoesNotContain(@".\runtimes\win-x86\native\WebView2Loader.dll", resources); diff --git a/src/PerfView/App.cs b/src/PerfView/App.cs index f008318aa..4ee57612f 100755 --- a/src/PerfView/App.cs +++ b/src/PerfView/App.cs @@ -568,7 +568,7 @@ public static void RelaunchIfNeeded(string[] args) var srcTraceEventPdb = Path.Combine(Path.GetDirectoryName(exe), "TraceEvent.pdb"); if (File.Exists(srcTraceEventPdb)) { - var dstTraceEventPdb = Path.Combine(SupportFiles.SupportFileDir, SupportFiles.ProcessArchitectureDirectory, "TraceEvent.pdb"); + var dstTraceEventPdb = Path.Combine(SupportFiles.SupportFileDir, "amd64", "TraceEvent.pdb"); File.Copy(srcTraceEventPdb, dstTraceEventPdb); } diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index 7c685a550..6f132e38e 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -2476,7 +2476,7 @@ private static void CreateSolution(string slnFile, List projectFiles) private static void InstallETWClrProfiler(TextWriter log, int profilerKeywords) { log.WriteLine("Ensuring that the .NET CLR Profiler is installed."); - var profilerDll = Path.Combine(SupportFiles.SupportFileDir, SupportFiles.ProcessArchitectureDirectory, "EtwClrProfiler.dll"); + var profilerDll = Path.Combine(SupportFiles.SupportFileDir, "amd64", "EtwClrProfiler.dll"); if (File.Exists(profilerDll)) { log.WriteLine("Profiler DLL to load is {0}", profilerDll); @@ -2484,13 +2484,13 @@ private static void InstallETWClrProfiler(TextWriter log, int profilerKeywords) using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) using (RegistryKey key = hklm.CreateSubKey(s_dotNetKey)) { - InsertEtwClrProfilerKeys(key, "COR", profilerDll, profilerKeywords, SupportFiles.ProcessArch.ToString(), log); - InsertEtwClrProfilerKeys(key, "CORECLR", profilerDll, profilerKeywords, SupportFiles.ProcessArch.ToString(), log); + InsertEtwClrProfilerKeys(key, "COR", profilerDll, profilerKeywords, "Amd64", log); + InsertEtwClrProfilerKeys(key, "CORECLR", profilerDll, profilerKeywords, "Amd64", log); } } else { - log.WriteLine("ERROR do not have a ETWClrProfiler.dll for architecture {0}", SupportFiles.ProcessArch); + log.WriteLine("ERROR do not have a ETWClrProfiler.dll for architecture Amd64"); } // The x64 host also profiles x86 targets through the 32-bit registry view. diff --git a/src/PerfView/Utilities/SupportFiles.cs b/src/PerfView/Utilities/SupportFiles.cs index f8407ab95..57e0cac2b 100755 --- a/src/PerfView/Utilities/SupportFiles.cs +++ b/src/PerfView/Utilities/SupportFiles.cs @@ -87,14 +87,14 @@ public static bool UnpackResourcesIfNeeded(bool force = false) } // Also look in processor specific location - fileName = Path.Combine(SupportFileDir, ProcessArchitectureDirectory, simpleName + ".dll"); + fileName = Path.Combine(SupportFileDir, "amd64", simpleName + ".dll"); if (File.Exists(fileName)) { return System.Reflection.Assembly.LoadFrom(fileName); } // And look for an exe (we need this for HeapDump.exe) - fileName = Path.Combine(SupportFileDir, ProcessArchitectureDirectory, simpleName + ".exe"); + fileName = Path.Combine(SupportFileDir, "amd64", simpleName + ".exe"); if (File.Exists(fileName)) { return System.Reflection.Assembly.LoadFrom(fileName); @@ -214,32 +214,14 @@ public static string ExePath } } - /// - /// Gets the architecture of the current process. PerfView is built and runs only as amd64, - /// including when running under x64 emulation on ARM64 hardware. - /// - public static ProcessorArchitecture ProcessArch - { - get { return ProcessorArchitecture.Amd64; } - } - - /// - /// Gets the name of the directory containing compiled binaries (DLLs) which have the same architecture as the - /// currently executing process. PerfView is built and runs only as amd64, so this directory is always amd64. - /// - public static string ProcessArchitectureDirectory - { - get { return "amd64"; } - } - /// /// If you need to load an unmanaged DLL that is part of your distribution - /// This routine will do the load library using the correct architecture + /// This routine loads it from the amd64 directory for the x64 host. /// /// public static void LoadNative(string relativePath) { - var archPath = Path.Combine(ProcessArchitectureDirectory, relativePath); + var archPath = Path.Combine("amd64", relativePath); var fullPath = Path.Combine(SupportFileDir, archPath); var ret = LoadLibrary(fullPath); if (ret == IntPtr.Zero) From 5ec234d22b9a99b2aa67e4011100b8d6eaa6b890 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Tue, 6 Oct 2026 16:20:44 -0700 Subject: [PATCH 41/41] Centralize the host architecture directory --- src/PerfView.Tests/StartupTests.cs | 1 + src/PerfView/App.cs | 4 ++-- src/PerfView/CommandProcessor.cs | 2 +- src/PerfView/Utilities/SupportFiles.cs | 11 ++++++++--- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/PerfView.Tests/StartupTests.cs b/src/PerfView.Tests/StartupTests.cs index 9f2e10037..d94dd7ff4 100644 --- a/src/PerfView.Tests/StartupTests.cs +++ b/src/PerfView.Tests/StartupTests.cs @@ -17,6 +17,7 @@ public void ApplicationAndTestHostAreX64() [Fact] public void HostResourcesUseX64AndKeepBothHelpers() { + Assert.Equal("amd64", global::Utilities.SupportFiles.HostArchitectureDirectory); var resources = typeof(Startup).Assembly.GetManifestResourceNames(); Assert.Contains(@".\runtimes\win-x64\native\WebView2Loader.dll", resources); Assert.DoesNotContain(@".\runtimes\win-x86\native\WebView2Loader.dll", resources); diff --git a/src/PerfView/App.cs b/src/PerfView/App.cs index 4ee57612f..b22ff8ec0 100755 --- a/src/PerfView/App.cs +++ b/src/PerfView/App.cs @@ -465,7 +465,7 @@ public static bool Unpack() // We have two versions of HeapDump.exe, and they each need their own copy of Microsoft.Diagnostics.Runtime.dll // so copy this dll to the other architecture. var fromDir = Path.Combine(SupportFiles.SupportFileDir, "x86"); - var toDir = Path.Combine(SupportFiles.SupportFileDir, "amd64"); + var toDir = Path.Combine(SupportFiles.SupportFileDir, SupportFiles.HostArchitectureDirectory); var fromFile = Path.Combine(fromDir, "Microsoft.Diagnostics.Runtime.dll"); if (Directory.Exists(toDir) && File.Exists(fromFile)) { @@ -568,7 +568,7 @@ public static void RelaunchIfNeeded(string[] args) var srcTraceEventPdb = Path.Combine(Path.GetDirectoryName(exe), "TraceEvent.pdb"); if (File.Exists(srcTraceEventPdb)) { - var dstTraceEventPdb = Path.Combine(SupportFiles.SupportFileDir, "amd64", "TraceEvent.pdb"); + var dstTraceEventPdb = Path.Combine(SupportFiles.SupportFileDir, SupportFiles.HostArchitectureDirectory, "TraceEvent.pdb"); File.Copy(srcTraceEventPdb, dstTraceEventPdb); } diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index 6f132e38e..30abd70b9 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -2476,7 +2476,7 @@ private static void CreateSolution(string slnFile, List projectFiles) private static void InstallETWClrProfiler(TextWriter log, int profilerKeywords) { log.WriteLine("Ensuring that the .NET CLR Profiler is installed."); - var profilerDll = Path.Combine(SupportFiles.SupportFileDir, "amd64", "EtwClrProfiler.dll"); + var profilerDll = Path.Combine(SupportFiles.SupportFileDir, SupportFiles.HostArchitectureDirectory, "EtwClrProfiler.dll"); if (File.Exists(profilerDll)) { log.WriteLine("Profiler DLL to load is {0}", profilerDll); diff --git a/src/PerfView/Utilities/SupportFiles.cs b/src/PerfView/Utilities/SupportFiles.cs index 57e0cac2b..d4eb6d1ea 100755 --- a/src/PerfView/Utilities/SupportFiles.cs +++ b/src/PerfView/Utilities/SupportFiles.cs @@ -46,6 +46,11 @@ namespace Utilities /// internal static class SupportFiles { + /// + /// The support-file directory for the x64 PerfView host, including under emulation on ARM64. + /// + public const string HostArchitectureDirectory = "amd64"; + /// /// Unpacks any resource that beginning with a .\ (so it looks like a relative path name) /// Such resources are unpacked into their relative position in SupportFileDir. @@ -87,14 +92,14 @@ public static bool UnpackResourcesIfNeeded(bool force = false) } // Also look in processor specific location - fileName = Path.Combine(SupportFileDir, "amd64", simpleName + ".dll"); + fileName = Path.Combine(SupportFileDir, HostArchitectureDirectory, simpleName + ".dll"); if (File.Exists(fileName)) { return System.Reflection.Assembly.LoadFrom(fileName); } // And look for an exe (we need this for HeapDump.exe) - fileName = Path.Combine(SupportFileDir, "amd64", simpleName + ".exe"); + fileName = Path.Combine(SupportFileDir, HostArchitectureDirectory, simpleName + ".exe"); if (File.Exists(fileName)) { return System.Reflection.Assembly.LoadFrom(fileName); @@ -221,7 +226,7 @@ public static string ExePath /// public static void LoadNative(string relativePath) { - var archPath = Path.Combine("amd64", relativePath); + var archPath = Path.Combine(HostArchitectureDirectory, relativePath); var fullPath = Path.Combine(SupportFileDir, archPath); var ret = LoadLibrary(fullPath); if (ret == IntPtr.Zero)