diff --git a/src/BenchmarkDotNet/Helpers/PowerManagementHelper.cs b/src/BenchmarkDotNet/Helpers/PowerManagementHelper.cs index e3d82dd517..6e3b8f7e43 100644 --- a/src/BenchmarkDotNet/Helpers/PowerManagementHelper.cs +++ b/src/BenchmarkDotNet/Helpers/PowerManagementHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Text; @@ -50,7 +51,7 @@ internal static string CurrentPlanFriendlyName internal static bool Set(Guid newPolicy) { - return PowerSetActiveScheme(IntPtr.Zero, ref newPolicy) == 0; + return PowerSetActiveScheme(IntPtr.Zero, ref newPolicy) == 0; } [DllImport("powrprof.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] @@ -61,5 +62,34 @@ internal static bool Set(Guid newPolicy) [DllImport("powrprof.dll", ExactSpelling = true)] private static extern uint PowerGetActiveScheme(IntPtr UserRootPowerKey, ref IntPtr ActivePolicyGuid); + + [DllImport("powrprof.dll", SetLastError = true)] + private static extern uint PowerEnumerate(IntPtr RootPowerKey, IntPtr SchemeGuid, IntPtr SubGroupOfPowerSettingsGuid, uint AccessFlags, uint Index, ref Guid Buffer, ref uint BufferSize); + + internal static IEnumerable EnumerateAllPlanGuids() + { + const uint ACCESS_SCHEME = 16; + uint index = 0; + while (true) + { + Guid schemeGuid = Guid.Empty; + uint size = (uint)Marshal.SizeOf(typeof(Guid)); + uint res = PowerEnumerate(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, ACCESS_SCHEME, index, ref schemeGuid, ref size); + if (res != 0) + break; + yield return schemeGuid; + index++; + } + } + + internal static bool PlanExists(Guid planGuid) + { + foreach (var guid in EnumerateAllPlanGuids()) + { + if (guid == planGuid) + return true; + } + return false; + } } -} +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs index 8ed930c1f4..e461fe89be 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs @@ -232,8 +232,24 @@ private static Summary Run(BenchmarkRunInfo benchmarkRunInfo, { var benchmark = benchmarks[i]; - powerManagementApplier.ApplyPerformancePlan(benchmark.Job.Environment.PowerPlanMode - ?? benchmark.Job.ResolveValue(EnvironmentMode.PowerPlanModeCharacteristic, EnvironmentResolver.Instance).GetValueOrDefault()); + bool userExplicitlySetPlan = benchmark.Job.HasValue(EnvironmentMode.PowerPlanModeCharacteristic); + + var requestedPlan = benchmark.Job.Environment.PowerPlanMode + ?? benchmark.Job.ResolveValue(EnvironmentMode.PowerPlanModeCharacteristic, EnvironmentResolver.Instance).GetValueOrDefault(); + + // If the plan is not explicitly set and would downgrade Ultimate, skip changing + if (!userExplicitlySetPlan && requestedPlan == PowerManagementApplier.Map(PowerPlan.HighPerformance)) + { + var ultimateGuid = PowerManagementApplier.Map(PowerPlan.UltimatePerformance); + var currentPlan = PowerManagementHelper.CurrentPlan; + if (currentPlan.HasValue && currentPlan.Value == ultimateGuid) + { + logger.WriteLineInfo("Already on Ultimate Performance; not switching to High Performance since no plan explicitly set in Job."); + continue; + } + } + + powerManagementApplier.ApplyPerformancePlan(requestedPlan); var info = buildResults[benchmark]; var buildResult = info.buildResult; diff --git a/src/BenchmarkDotNet/Running/PowerManagementApplier.cs b/src/BenchmarkDotNet/Running/PowerManagementApplier.cs index f48d95d61e..69ede4a22d 100644 --- a/src/BenchmarkDotNet/Running/PowerManagementApplier.cs +++ b/src/BenchmarkDotNet/Running/PowerManagementApplier.cs @@ -76,6 +76,20 @@ private void ApplyPlanByGuid(Guid guid) isInitialized = true; } + Guid currentActivePlan = (Guid)PowerManagementHelper.CurrentPlan; + Guid ultimatePerformanceGuid = PowerPlansDict[PowerPlan.UltimatePerformance]; + Guid highPerformanceGuid = PowerPlansDict[PowerPlan.HighPerformance]; + + if (currentActivePlan == ultimatePerformanceGuid && guid == highPerformanceGuid) + { + if (!PowerManagementHelper.PlanExists(highPerformanceGuid)) + { + logger.WriteLineInfo("Cannot setup High Performance power plan - plan doesn't exist on this system."); + return; + } + logger.WriteLineInfo("Changing from Ultimate Performance to explicitly configured High Performance plan."); + } + if (PowerManagementHelper.Set(guid)) { powerPlanChanged = true; @@ -83,7 +97,9 @@ private void ApplyPlanByGuid(Guid guid) logger.WriteLineInfo($"Setup power plan (GUID: {guid} FriendlyName: {powerPlanFriendlyName})"); } else + { logger.WriteLineError($"Cannot setup power plan (GUID: {guid})"); + } } catch (Exception ex) { @@ -91,4 +107,4 @@ private void ApplyPlanByGuid(Guid guid) } } } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.IntegrationTests/PowerManagementApplierTests.cs b/tests/BenchmarkDotNet.IntegrationTests/PowerManagementApplierTests.cs index 69eed9a234..a6cbdad72f 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/PowerManagementApplierTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/PowerManagementApplierTests.cs @@ -1,5 +1,7 @@ -using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Environments; using BenchmarkDotNet.Helpers; +using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; using BenchmarkDotNet.Tests.Loggers; using BenchmarkDotNet.Tests.XUnit; @@ -42,5 +44,38 @@ public void TestPowerPlanShouldNotChange() Assert.Equal(userPlan, PowerManagementHelper.CurrentPlan); } + + [FactEnvSpecific("Should keep power plan at Ultimate Performance if current power plan is Ultimate when a power plan is not specifically set", EnvRequirement.WindowsOnly)] + public void TestKeepingUltimatePowerPlan() + { + var ultimateGuid = PowerManagementApplier.Map(PowerPlan.UltimatePerformance); + var userPlan = PowerManagementHelper.CurrentPlan; + + if (!PowerManagementHelper.PlanExists(ultimateGuid)) + { + Output.WriteLine("Ultimate Performance plan does not exist or cannot be activated. Skipping test."); + return; + } + + PowerManagementHelper.Set(ultimateGuid); + + var job = Job.Default; + + var config = ManualConfig.CreateEmpty().AddJob(job); + + BenchmarkRunner.Run(config); + + Assert.Equal(ultimateGuid.ToString(), PowerManagementHelper.CurrentPlan.ToString()); + + PowerManagementHelper.Set(userPlan.Value); + } + + public class DummyBenchmark + { + [BenchmarkDotNet.Attributes.Benchmark] + public void DoNothing() + { + } + } } -} +} \ No newline at end of file