diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj index 751b75a5da..660e222453 100644 --- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj +++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj @@ -48,9 +48,17 @@ + + + all + runtime; build; native; contentfiles; analyzers + + true + + System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute diff --git a/src/BenchmarkDotNet/Detectors/OsDetector.cs b/src/BenchmarkDotNet/Detectors/OsDetector.cs index 1ccd86d26b..2f360d01f4 100644 --- a/src/BenchmarkDotNet/Detectors/OsDetector.cs +++ b/src/BenchmarkDotNet/Detectors/OsDetector.cs @@ -127,7 +127,6 @@ internal static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); #endif - [SupportedOSPlatformGuard("linux")] internal static bool IsLinux() => #if NET6_0_OR_GREATER @@ -170,4 +169,12 @@ internal static bool IsTvOS() => #else RuntimeInformation.IsOSPlatform(OSPlatform.Create("TVOS")); #endif + + [SupportedOSPlatformGuard("windows6.1")] + internal static bool IsWindows7OrLater() => +#if NET6_0_OR_GREATER + OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(6, 1); +#else + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version >= new Version(6, 1); +#endif } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs index 56c8aeee2e..114836c438 100644 --- a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs +++ b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs @@ -1,6 +1,10 @@ +using BenchmarkDotNet.Detectors; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Text.RegularExpressions; +using Windows.Win32; +using Windows.Win32.System.SystemInformation; namespace BenchmarkDotNet.Environments { @@ -35,13 +39,13 @@ public static class SystemMemory { try { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (OsDetector.IsWindows7OrLater()) return GetWindowsMemory(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + if (OsDetector.IsLinux()) return GetLinuxMemory(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + if (OsDetector.IsMacOS()) return GetMacMemory(); } catch (Exception) @@ -52,36 +56,13 @@ public static class SystemMemory return null; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - private class MEMORYSTATUSEX - { - public uint dwLength; - public uint dwMemoryLoad; - public ulong ullTotalPhys; - public ulong ullAvailPhys; - public ulong ullTotalPageFile; - public ulong ullAvailPageFile; - public ulong ullTotalVirtual; - public ulong ullAvailVirtual; - public ulong ullAvailExtendedVirtual; - - public MEMORYSTATUSEX() - { - dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX)); - } - } - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool GlobalMemoryStatusEx([In, Out] MEMORYSTATUSEX lpBuffer); - + [SupportedOSPlatform("windows5.1.2600")] private static PhysicalMemoryInfo? GetWindowsMemory() { - var memStatus = new MEMORYSTATUSEX(); - if (GlobalMemoryStatusEx(memStatus)) - { + var memStatus = new MEMORYSTATUSEX { dwLength = (uint)Marshal.SizeOf() }; + if (PInvoke.GlobalMemoryStatusEx(ref memStatus)) return new PhysicalMemoryInfo((long)memStatus.ullTotalPhys, (long)memStatus.ullAvailPhys); - } + return null; } diff --git a/src/BenchmarkDotNet/Helpers/PowerManagementHelper.cs b/src/BenchmarkDotNet/Helpers/PowerManagementHelper.cs index 62ca6d6004..f25a5579b7 100644 --- a/src/BenchmarkDotNet/Helpers/PowerManagementHelper.cs +++ b/src/BenchmarkDotNet/Helpers/PowerManagementHelper.cs @@ -1,64 +1,64 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using System.Text; +using System.Runtime.Versioning; +using Windows.Win32; +using Windows.Win32.Foundation; namespace BenchmarkDotNet.Helpers { [SuppressMessage("ReSharper", "InconsistentNaming")] + [SupportedOSPlatform("windows6.0.6000")] internal class PowerManagementHelper { - private const uint ErrorMoreData = 234; - private const uint SuccessCode = 0; - - internal static Guid? CurrentPlan + internal static unsafe Guid? CurrentPlan { get { - IntPtr activeGuidPtr = IntPtr.Zero; - uint res = PowerGetActiveScheme(IntPtr.Zero, ref activeGuidPtr); - if (res != SuccessCode) + WIN32_ERROR res = PInvoke.PowerGetActiveScheme(null, out var activePolicyGuidPtr); + if (res != WIN32_ERROR.NO_ERROR) return null; - return Marshal.PtrToStructure(activeGuidPtr); + var activePolicyGuid = *activePolicyGuidPtr; + + PInvoke.LocalFree((HLOCAL)activePolicyGuidPtr); + + return activePolicyGuid; } } - internal static string CurrentPlanFriendlyName + internal unsafe static string CurrentPlanFriendlyName { get { - uint buffSize = 0; - StringBuilder buffer = new StringBuilder(); - IntPtr activeGuidPtr = IntPtr.Zero; - uint res = PowerGetActiveScheme(IntPtr.Zero, ref activeGuidPtr); - if (res != SuccessCode) + WIN32_ERROR res = PInvoke.PowerGetActiveScheme(null, out var activeGuidPtr); + if (res != WIN32_ERROR.NO_ERROR) return ""; - res = PowerReadFriendlyName(IntPtr.Zero, activeGuidPtr, IntPtr.Zero, IntPtr.Zero, buffer, ref buffSize); - if (res == ErrorMoreData) + + Guid activeSchemaGuid = *activeGuidPtr; + PInvoke.LocalFree((HLOCAL)activeGuidPtr); + + Span buffer = stackalloc byte[260]; + uint bufferSize = (uint)buffer.Length; + + res = PInvoke.PowerReadFriendlyName(null, activeSchemaGuid, null, null, buffer, ref bufferSize); + if (res == WIN32_ERROR.ERROR_MORE_DATA) { - buffer.Capacity = (int)buffSize; - res = PowerReadFriendlyName(IntPtr.Zero, activeGuidPtr, - IntPtr.Zero, IntPtr.Zero, buffer, ref buffSize); + buffer = new byte[(int)bufferSize]; + res = PInvoke.PowerReadFriendlyName(null, activeSchemaGuid, null, null, buffer, ref bufferSize); + bufferSize = (uint)buffer.Length; } - if (res != SuccessCode) + + if (res != WIN32_ERROR.NO_ERROR) return ""; - return buffer.ToString(); + ReadOnlySpan chars = MemoryMarshal.Cast(buffer.Slice(0, (int)bufferSize)); + return chars.TrimEnd('\0').ToString(); // Trim null terminator of PWSTR. } } internal static bool Set(Guid newPolicy) { - return PowerSetActiveScheme(IntPtr.Zero, ref newPolicy) == 0; + return PInvoke.PowerSetActiveScheme(null, newPolicy) == WIN32_ERROR.NO_ERROR; } - - [DllImport("powrprof.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] - private static extern uint PowerReadFriendlyName(IntPtr RootPowerKey, IntPtr SchemeGuid, IntPtr SubGroupOfPowerSettingGuid, IntPtr PowerSettingGuid, StringBuilder Buffer, ref uint BufferSize); - - [DllImport("powrprof.dll", ExactSpelling = true)] - private static extern int PowerSetActiveScheme(IntPtr ReservedZero, ref Guid policyGuid); - - [DllImport("powrprof.dll", ExactSpelling = true)] - private static extern uint PowerGetActiveScheme(IntPtr UserRootPowerKey, ref IntPtr ActivePolicyGuid); } } diff --git a/src/BenchmarkDotNet/Helpers/TaskbarProgress.cs b/src/BenchmarkDotNet/Helpers/TaskbarProgress.cs index 5585dfbd49..953cf2d0fa 100644 --- a/src/BenchmarkDotNet/Helpers/TaskbarProgress.cs +++ b/src/BenchmarkDotNet/Helpers/TaskbarProgress.cs @@ -1,5 +1,7 @@ using BenchmarkDotNet.Detectors; using System.Runtime.InteropServices; +using Windows.Win32.System.Console; +using Windows.Win32; namespace BenchmarkDotNet.Helpers { @@ -15,9 +17,7 @@ internal enum TaskbarProgressState internal class TaskbarProgress : DisposeAtProcessTermination { - private static readonly bool OsVersionIsSupported = OsDetector.IsWindows() - // Must be windows 7 or greater - && Environment.OSVersion.Version >= new Version(6, 1); + private static readonly bool OsVersionIsSupported = OsDetector.IsWindows7OrLater(); private Com? com; private Terminal? terminal; @@ -97,9 +97,6 @@ private interface ITaskbarList3 [ComImport] private class TaskbarInstance { } - [DllImport("kernel32.dll")] - private static extern IntPtr GetConsoleWindow(); - private readonly ITaskbarList3 taskbarInstance; private readonly IntPtr consoleWindowHandle; @@ -113,7 +110,7 @@ private Com(IntPtr handle) { try { - IntPtr handle = GetConsoleWindow(); + IntPtr handle = PInvoke.GetConsoleWindow(); if (handle == IntPtr.Zero) { return null; @@ -142,49 +139,21 @@ internal void SetValue(uint progressValue) private sealed class Terminal { - [Flags] - private enum ConsoleModes : uint - { - ENABLE_PROCESSED_INPUT = 0x0001, - ENABLE_LINE_INPUT = 0x0002, - ENABLE_ECHO_INPUT = 0x0004, - ENABLE_WINDOW_INPUT = 0x0008, - ENABLE_MOUSE_INPUT = 0x0010, - ENABLE_INSERT_MODE = 0x0020, - ENABLE_QUICK_EDIT_MODE = 0x0040, - ENABLE_EXTENDED_FLAGS = 0x0080, - ENABLE_AUTO_POSITION = 0x0100, - - ENABLE_PROCESSED_OUTPUT = 0x0001, - ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002, - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004, - DISABLE_NEWLINE_AUTO_RETURN = 0x0008, - ENABLE_LVB_GRID_WORLDWIDE = 0x0010 - } - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out ConsoleModes lpMode); - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool SetConsoleMode(IntPtr hConsoleHandle, ConsoleModes dwMode); - [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr GetStdHandle(int nStdHandle); - private const int STD_OUTPUT_HANDLE = -11; - - private readonly IntPtr consoleHandle; + private readonly SafeHandle consoleHandle; private uint currentProgress; - private Terminal(IntPtr handle) + private Terminal(SafeHandle handle) => consoleHandle = handle; internal static Terminal? MaybeCreateInstanceAndSetInitialState(TaskbarProgressState initialTaskbarState) { - IntPtr handle = GetStdHandle(STD_OUTPUT_HANDLE); - if (handle == IntPtr.Zero) + var handle = PInvoke.GetStdHandle_SafeHandle(STD_HANDLE.STD_OUTPUT_HANDLE); + if (handle.IsInvalid) { return null; } - if (!GetConsoleMode(handle, out ConsoleModes previousConsoleMode) - || !SetConsoleMode(handle, ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes.ENABLE_PROCESSED_OUTPUT)) + if (!PInvoke.GetConsoleMode(handle, out CONSOLE_MODE previousConsoleMode) + || !PInvoke.SetConsoleMode(handle, CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING | CONSOLE_MODE.ENABLE_PROCESSED_OUTPUT)) { // If we failed to set virtual terminal processing mode, it is likely due to an older Windows version that does not support it, // or legacy console. In either case the TaskbarProgressCom will take care of the progress. See https://stackoverflow.com/a/44574463/5703407. @@ -193,16 +162,16 @@ private Terminal(IntPtr handle) } var terminal = new Terminal(handle); terminal.WriteStateSequence(initialTaskbarState); - SetConsoleMode(handle, previousConsoleMode); + PInvoke.SetConsoleMode(handle, previousConsoleMode); return terminal; } internal void SetState(TaskbarProgressState taskbarState) { - GetConsoleMode(consoleHandle, out ConsoleModes previousConsoleMode); - SetConsoleMode(consoleHandle, ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes.ENABLE_PROCESSED_OUTPUT); + PInvoke.GetConsoleMode(consoleHandle, out CONSOLE_MODE previousConsoleMode); + PInvoke.SetConsoleMode(consoleHandle, CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING | CONSOLE_MODE.ENABLE_PROCESSED_OUTPUT); WriteStateSequence(taskbarState); - SetConsoleMode(consoleHandle, previousConsoleMode); + PInvoke.SetConsoleMode(consoleHandle, previousConsoleMode); } private void WriteStateSequence(TaskbarProgressState taskbarState) @@ -238,10 +207,10 @@ internal void SetValue(uint progressValue) { currentProgress = progressValue; // Write progress sequence to console for Windows Terminal (https://github.com/microsoft/terminal/discussions/14268). - GetConsoleMode(consoleHandle, out ConsoleModes previousConsoleMode); - SetConsoleMode(consoleHandle, ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes.ENABLE_PROCESSED_OUTPUT); + PInvoke.GetConsoleMode(consoleHandle, out CONSOLE_MODE previousConsoleMode); + PInvoke.SetConsoleMode(consoleHandle, CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING | CONSOLE_MODE.ENABLE_PROCESSED_OUTPUT); WriteProgressSequence(progressValue); - SetConsoleMode(consoleHandle, previousConsoleMode); + PInvoke.SetConsoleMode(consoleHandle, previousConsoleMode); } private static void WriteProgressSequence(uint progressValue) diff --git a/src/BenchmarkDotNet/NativeMethods.json b/src/BenchmarkDotNet/NativeMethods.json new file mode 100644 index 0000000000..e0369e5437 --- /dev/null +++ b/src/BenchmarkDotNet/NativeMethods.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "allowMarshaling": false, + "multiTargetingFriendlyAPIs": true +} diff --git a/src/BenchmarkDotNet/NativeMethods.txt b/src/BenchmarkDotNet/NativeMethods.txt new file mode 100644 index 0000000000..2b98f025cd --- /dev/null +++ b/src/BenchmarkDotNet/NativeMethods.txt @@ -0,0 +1,14 @@ +PowerGetActiveScheme +PowerSetActiveScheme +PowerReadFriendlyName +GetConsoleWindow +GetConsoleMode +GetStdHandle +SetConsoleMode +CloseHandle +PowerCreateRequest +PowerClearRequest +PowerSetRequest +PowerGetActiveScheme +LocalFree +GlobalMemoryStatusEx diff --git a/src/BenchmarkDotNet/Running/PowerManagementApplier.cs b/src/BenchmarkDotNet/Running/PowerManagementApplier.cs index 8e61198c3e..b3b0fb32f5 100644 --- a/src/BenchmarkDotNet/Running/PowerManagementApplier.cs +++ b/src/BenchmarkDotNet/Running/PowerManagementApplier.cs @@ -46,26 +46,32 @@ internal void ApplyPerformancePlan(Guid id) private void ApplyUserPowerPlan() { - if (powerPlanChanged && OsDetector.IsWindows()) + if (!powerPlanChanged) + return; + + if (!OsDetector.IsWindows7OrLater()) + return; + + try { - try - { - if (userCurrentPowerPlan != null && PowerManagementHelper.Set(userCurrentPowerPlan.Value)) - { - powerPlanChanged = false; - var powerPlanFriendlyName = PowerManagementHelper.CurrentPlanFriendlyName; - logger.WriteLineInfo($"Successfully reverted power plan (GUID: {userCurrentPowerPlan.Value} FriendlyName: {powerPlanFriendlyName})"); - } - } - catch (Exception ex) + if (userCurrentPowerPlan != null && PowerManagementHelper.Set(userCurrentPowerPlan.Value)) { - logger.WriteLineError($"Cannot revert power plan (error message: {ex.Message})"); + powerPlanChanged = false; + var powerPlanFriendlyName = PowerManagementHelper.CurrentPlanFriendlyName; + logger.WriteLineInfo($"Successfully reverted power plan (GUID: {userCurrentPowerPlan.Value} FriendlyName: {powerPlanFriendlyName})"); } } + catch (Exception ex) + { + logger.WriteLineError($"Cannot revert power plan (error message: {ex.Message})"); + } } private void ApplyPlanByGuid(Guid guid) { + if (!OsDetector.IsWindows7OrLater()) + return; + try { if (isInitialized == false) diff --git a/src/BenchmarkDotNet/Running/WakeLock.PInvoke.cs b/src/BenchmarkDotNet/Running/WakeLock.PInvoke.cs index 4edbc43720..c041ba175f 100644 --- a/src/BenchmarkDotNet/Running/WakeLock.PInvoke.cs +++ b/src/BenchmarkDotNet/Running/WakeLock.PInvoke.cs @@ -1,109 +1,61 @@ +using Microsoft.Win32.SafeHandles; using System.ComponentModel; using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using Windows.Win32; +using Windows.Win32.System.Power; +using Windows.Win32.System.Threading; namespace BenchmarkDotNet.Running; internal partial class WakeLock { - private static class PInvoke + [SupportedOSPlatform("windows6.1")] + public static SafeFileHandle PowerCreateRequest(string reason) { - public static SafePowerHandle PowerCreateRequest(string reason) + IntPtr reasonPtr = Marshal.StringToHGlobalAuto(reason); + try { - IntPtr reasonPtr = Marshal.StringToHGlobalAuto(reason); - try + const uint POWER_REQUEST_CONTEXT_VERSION = 0U; + var context = new REASON_CONTEXT() { - REASON_CONTEXT context = new REASON_CONTEXT() + Version = POWER_REQUEST_CONTEXT_VERSION, + Flags = POWER_REQUEST_CONTEXT_FLAGS.POWER_REQUEST_CONTEXT_SIMPLE_STRING, + Reason = new REASON_CONTEXT._Reason_e__Union { - Version = POWER_REQUEST_CONTEXT_VERSION, - Flags = POWER_REQUEST_CONTEXT_FLAGS.POWER_REQUEST_CONTEXT_SIMPLE_STRING, - Reason = new REASON_CONTEXT.REASON_CONTEXT_UNION { SimpleReasonString = reasonPtr } + SimpleReasonString = new Windows.Win32.Foundation.PWSTR(reasonPtr) + }, + }; - }; - SafePowerHandle safePowerHandle = PowerCreateRequest(context); - if (safePowerHandle.IsInvalid) { throw new Win32Exception(); } - return safePowerHandle; - } - finally - { - if (reasonPtr != IntPtr.Zero) - { - Marshal.FreeHGlobal(reasonPtr); - } - } - } - - [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] - private static extern SafePowerHandle PowerCreateRequest(REASON_CONTEXT Context); + var safePowerHandle = PInvoke.PowerCreateRequest(context); - public static void PowerSetRequest(SafePowerHandle safePowerHandle, POWER_REQUEST_TYPE requestType) - { - if (!InvokePowerSetRequest(safePowerHandle, requestType)) - { + if (safePowerHandle.IsInvalid) throw new Win32Exception(); - } - } - - [DllImport("kernel32.dll", EntryPoint = "PowerSetRequest", ExactSpelling = true, SetLastError = true)] - private static extern bool InvokePowerSetRequest(SafePowerHandle PowerRequest, POWER_REQUEST_TYPE RequestType); - public static void PowerClearRequest(SafePowerHandle safePowerHandle, POWER_REQUEST_TYPE requestType) - { - if (!InvokePowerClearRequest(safePowerHandle, requestType)) - { - throw new Win32Exception(); - } + return safePowerHandle; } - - [DllImport("kernel32.dll", EntryPoint = "PowerClearRequest", ExactSpelling = true, SetLastError = true)] - private static extern bool InvokePowerClearRequest(SafePowerHandle PowerRequest, POWER_REQUEST_TYPE RequestType); - - [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] - public static extern bool CloseHandle(nint hObject); - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - private struct REASON_CONTEXT + finally { - public uint Version; - - public POWER_REQUEST_CONTEXT_FLAGS Flags; - - public REASON_CONTEXT_UNION Reason; - - [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] - public struct REASON_CONTEXT_UNION - { - [FieldOffset(0)] - public nint SimpleReasonString; - - // The DETAILED structure is not (yet) used, but needed for ARM CPUs, otherwise PowerCreateRequest fails, see #2745 - [FieldOffset(0)] - public DETAILED Detailed; - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct DETAILED - { - public nint LocalizedReasonModule; - public uint LocalizedReasonId; - public uint ReasonStringCount; - public nint ReasonStrings; - } - } + if (reasonPtr != IntPtr.Zero) + Marshal.FreeHGlobal(reasonPtr); } + } - private const uint POWER_REQUEST_CONTEXT_VERSION = 0U; + [SupportedOSPlatform("windows6.1")] + private static void PowerSetRequest(SafeFileHandle safePowerHandle, POWER_REQUEST_TYPE requestType) + { + if (PInvoke.PowerSetRequest(safePowerHandle, requestType)) + return; - private enum POWER_REQUEST_CONTEXT_FLAGS : uint - { - POWER_REQUEST_CONTEXT_DETAILED_STRING = 2U, - POWER_REQUEST_CONTEXT_SIMPLE_STRING = 1U, - } + throw new Win32Exception(); + } - public enum POWER_REQUEST_TYPE - { - PowerRequestDisplayRequired = 0, - PowerRequestSystemRequired = 1, - PowerRequestAwayModeRequired = 2, - PowerRequestExecutionRequired = 3, - } + [SupportedOSPlatform("windows6.1")] + private static void PowerClearRequest(SafeFileHandle safePowerHandle, POWER_REQUEST_TYPE requestType) + { + if (PInvoke.PowerClearRequest(safePowerHandle, requestType)) + return; + + throw new Win32Exception(); } -} \ No newline at end of file +} diff --git a/src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs b/src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs deleted file mode 100644 index eaa1f3b2b2..0000000000 --- a/src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.Win32.SafeHandles; - -namespace BenchmarkDotNet.Running; - -internal partial class WakeLock -{ - private sealed class SafePowerHandle : SafeHandleZeroOrMinusOneIsInvalid - { - private SafePowerHandle() : base(true) { } - - protected override bool ReleaseHandle() => PInvoke.CloseHandle(handle); - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Running/WakeLock.cs b/src/BenchmarkDotNet/Running/WakeLock.cs index 507958957b..5982172cad 100644 --- a/src/BenchmarkDotNet/Running/WakeLock.cs +++ b/src/BenchmarkDotNet/Running/WakeLock.cs @@ -2,26 +2,35 @@ using BenchmarkDotNet.Detectors; using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Loggers; +using Microsoft.Win32.SafeHandles; using System.ComponentModel; +using System.Runtime.Versioning; +using Windows.Win32.System.Power; namespace BenchmarkDotNet.Running; internal partial class WakeLock { public static WakeLockType GetWakeLockType(BenchmarkRunInfo[] benchmarkRunInfos) => - benchmarkRunInfos.Length == 0 ? WakeLockType.None : benchmarkRunInfos.Select(static i => i.Config.WakeLock).Max(); + benchmarkRunInfos.Length == 0 ? WakeLockType.None : benchmarkRunInfos.Max(static i => i.Config.WakeLock); + + public static IDisposable? Request(WakeLockType wakeLockType, string reason, ILogger logger) + { + if (wakeLockType == WakeLockType.None) + return null; - private static readonly bool OsVersionIsSupported = // Must be windows 7 or greater - OsDetector.IsWindows() && Environment.OSVersion.Version >= new Version(6, 1); + if (!OsDetector.IsWindows7OrLater()) + return null; - public static IDisposable? Request(WakeLockType wakeLockType, string reason, ILogger logger) => - wakeLockType == WakeLockType.None || !OsVersionIsSupported ? null : new WakeLockSentinel(wakeLockType, reason, logger); + return new WakeLockSentinel(wakeLockType, reason, logger); + } + [SupportedOSPlatform("windows6.1")] private class WakeLockSentinel : DisposeAtProcessTermination { private readonly WakeLockType wakeLockType; - private readonly SafePowerHandle? safePowerHandle; + private readonly SafeFileHandle? safePowerHandle; private readonly ILogger logger; public WakeLockSentinel(WakeLockType wakeLockType, string reason, ILogger logger) @@ -30,11 +39,11 @@ public WakeLockSentinel(WakeLockType wakeLockType, string reason, ILogger logger this.logger = logger; try { - safePowerHandle = PInvoke.PowerCreateRequest(reason); - PInvoke.PowerSetRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestSystemRequired); + safePowerHandle = PowerCreateRequest(reason); + PowerSetRequest(safePowerHandle, POWER_REQUEST_TYPE.PowerRequestSystemRequired); if (wakeLockType == WakeLockType.Display) { - PInvoke.PowerSetRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestDisplayRequired); + PowerSetRequest(safePowerHandle, POWER_REQUEST_TYPE.PowerRequestDisplayRequired); } } catch (Win32Exception ex) @@ -51,9 +60,9 @@ protected override void Dispose(bool exiting) { if (wakeLockType == WakeLockType.Display) { - PInvoke.PowerClearRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestDisplayRequired); + PowerClearRequest(safePowerHandle, POWER_REQUEST_TYPE.PowerRequestDisplayRequired); } - PInvoke.PowerClearRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestSystemRequired); + PowerClearRequest(safePowerHandle, POWER_REQUEST_TYPE.PowerRequestSystemRequired); } catch (Win32Exception ex) { diff --git a/tests/BenchmarkDotNet.IntegrationTests/PowerManagementApplierTests.cs b/tests/BenchmarkDotNet.IntegrationTests/PowerManagementApplierTests.cs index 0ed58915d5..bc9094407e 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/PowerManagementApplierTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/PowerManagementApplierTests.cs @@ -1,3 +1,4 @@ +using BenchmarkDotNet.Detectors; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Running; @@ -16,6 +17,9 @@ public PowerManagementApplierTests(ITestOutputHelper output) : base(output) { } [FactEnvSpecific("Setting high-performance plan is suitable only on Windows", EnvRequirement.WindowsOnly)] public void TestSettingAndRevertingBackGuid() { + if (!OsDetector.IsWindows7OrLater()) + return; + var userPlan = PowerManagementHelper.CurrentPlan; using var powerManagementApplier = new PowerManagementApplier(new OutputLogger(Output)); @@ -33,6 +37,9 @@ public void TestSettingAndRevertingBackGuid() [FactEnvSpecific("Setting high-performance plan is suitable only on Windows", EnvRequirement.WindowsOnly)] public void TestPowerPlanShouldNotChange() { + if (!OsDetector.IsWindows7OrLater()) + return; + var userPlan = PowerManagementHelper.CurrentPlan; using var powerManagementApplier = new PowerManagementApplier(new OutputLogger(Output));