Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement WakeLock so that the system does not enter sleep #2670

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/articles/guides/console-args.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ dotnet run -c Release -- --filter * --runtimes net6.0 net8.0 --statisticalTest 5
* `--platform` the Platform that should be used. If not specified, the host process platform is used (default). AnyCpu/X86/X64/Arm/Arm64/LoongArch64.
* `--runOncePerIteration` run the benchmark exactly once per iteration.
* `--buildTimeout` build timeout in seconds.
* `--wakeLock` prevents the system from entering sleep or turning off the display. None/System/Display.
* `--wasmEngine` full path to a java script engine used to run the benchmarks, used by Wasm toolchain.
* `--wasmMainJS` path to the test-main.js file used by Wasm toolchain. Mandatory when using \"--runtimes wasm\"
* `--expose_wasm` arguments for the JavaScript engine used by Wasm toolchain.
Expand Down
32 changes: 32 additions & 0 deletions docs/articles/samples/IntroWakeLock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
uid: BenchmarkDotNet.Samples.IntroWakeLock
---

## Sample: IntroWakeLock

Running benchmarks may sometimes take enough time such that the system enters sleep or turns off the display.

Using a WakeLock prevents the Windows system doing so.

### Source code

[!code-csharp[IntroWakeLock.cs](../../../samples/BenchmarkDotNet.Samples/IntroWakeLock.cs)]

### Command line

```
--wakeLock None
```
```
--wakeLock System
```
```
--wakeLock Display
```

### Links

* @BenchmarkDotNet.Attributes.WakeLockAttribute
* The permanent link to this sample: @BenchmarkDotNet.Samples.IntroWakeLock

---
2 changes: 2 additions & 0 deletions docs/articles/samples/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@
href: IntroTailcall.md
- name: IntroVisualStudioProfiler
href: IntroVisualStudioProfiler.md
- name: IntroWakeLock
href: IntroWakeLock.md
- name: IntroWasm
href: IntroWasm.md
- name: IntroUnicode
Expand Down
30 changes: 30 additions & 0 deletions samples/BenchmarkDotNet.Samples/IntroWakeLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using System;
using System.Threading;

// *** Attribute Style applied to Assembly ***
[assembly: WakeLock(WakeLockType.System)]

namespace BenchmarkDotNet.Samples;

// *** Attribute Style ***
[WakeLock(WakeLockType.Display)]
public class IntroWakeLock
{
[Benchmark]
public void LongRunning() => Thread.Sleep(TimeSpan.FromSeconds(10));
}

// *** Object Style ***
[Config(typeof(Config))]
public class IntroWakeLockObjectStyle
{
private class Config : ManualConfig
{
public Config() => WakeLock = WakeLockType.System;
}

[Benchmark]
public void LongRunning() => Thread.Sleep(TimeSpan.FromSeconds(10));
}
18 changes: 18 additions & 0 deletions src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using BenchmarkDotNet.Configs;
using System;

namespace BenchmarkDotNet.Attributes
{
/// <summary>
/// Placing a <see cref="WakeLockAttribute"/> on your assembly or class controls whether the
/// Windows system enters sleep or turns off the display while benchmarks run.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
public sealed class WakeLockAttribute : Attribute, IConfigSource
{
public WakeLockAttribute(WakeLockType wakeLockType) =>
Config = ManualConfig.CreateEmpty().WithWakeLock(wakeLockType);

public IConfig Config { get; }
}
}
1 change: 1 addition & 0 deletions src/BenchmarkDotNet/Configs/DebugConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public abstract class DebugConfig : IConfig
public SummaryStyle SummaryStyle => SummaryStyle.Default;
public ConfigUnionRule UnionRule => ConfigUnionRule.Union;
public TimeSpan BuildTimeout => DefaultConfig.Instance.BuildTimeout;
public WakeLockType WakeLock => WakeLockType.None;

public string ArtifactsPath => null; // DefaultConfig.ArtifactsPath will be used if the user does not specify it in explicit way

Expand Down
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/Configs/DefaultConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ public IEnumerable<IValidator> GetValidators()

public TimeSpan BuildTimeout => TimeSpan.FromSeconds(120);

public WakeLockType WakeLock => WakeLockType.System;
leonvandermeer marked this conversation as resolved.
Show resolved Hide resolved

public string ArtifactsPath
{
get
Expand Down
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/Configs/IConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public interface IConfig
/// </summary>
TimeSpan BuildTimeout { get; }

public WakeLockType WakeLock { get; }

/// <summary>
/// Collect any errors or warnings when composing the configuration
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/BenchmarkDotNet/Configs/ImmutableConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ internal ImmutableConfig(
SummaryStyle summaryStyle,
ConfigOptions options,
TimeSpan buildTimeout,
WakeLockType wakeLock,
IReadOnlyList<Conclusion> configAnalysisConclusion)
{
columnProviders = uniqueColumnProviders;
Expand All @@ -78,6 +79,7 @@ internal ImmutableConfig(
SummaryStyle = summaryStyle;
Options = options;
BuildTimeout = buildTimeout;
WakeLock = wakeLock;
ConfigAnalysisConclusion = configAnalysisConclusion;
}

Expand All @@ -89,6 +91,7 @@ internal ImmutableConfig(
public ICategoryDiscoverer CategoryDiscoverer { get; }
public SummaryStyle SummaryStyle { get; }
public TimeSpan BuildTimeout { get; }
public WakeLockType WakeLock { get; }

public IEnumerable<IColumnProvider> GetColumnProviders() => columnProviders;
public IEnumerable<IExporter> GetExporters() => exporters;
Expand Down
1 change: 1 addition & 0 deletions src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public static ImmutableConfig Create(IConfig source)
source.SummaryStyle ?? SummaryStyle.Default,
source.Options,
source.BuildTimeout,
source.WakeLock,
configAnalyse.AsReadOnly()
);
}
Expand Down
15 changes: 15 additions & 0 deletions src/BenchmarkDotNet/Configs/ManualConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class ManualConfig : IConfig
[PublicAPI] public ICategoryDiscoverer CategoryDiscoverer { get; set; }
[PublicAPI] public SummaryStyle SummaryStyle { get; set; }
[PublicAPI] public TimeSpan BuildTimeout { get; set; } = DefaultConfig.Instance.BuildTimeout;
[PublicAPI] public WakeLockType WakeLock { get; set; } = DefaultConfig.Instance.WakeLock;

public IReadOnlyList<Conclusion> ConfigAnalysisConclusion => emptyConclusion;

Expand Down Expand Up @@ -109,6 +110,12 @@ public ManualConfig WithBuildTimeout(TimeSpan buildTimeout)
return this;
}

public ManualConfig WithWakeLock(WakeLockType wakeLockType)
{
WakeLock = wakeLockType;
return this;
}

[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method will soon be removed, please start using .AddColumn() instead.")]
public void Add(params IColumn[] newColumns) => AddColumn(newColumns);
Expand Down Expand Up @@ -273,6 +280,7 @@ public void Add(IConfig config)
columnHidingRules.AddRange(config.GetColumnHidingRules());
Options |= config.Options;
BuildTimeout = GetBuildTimeout(BuildTimeout, config.BuildTimeout);
WakeLock = GetWakeLock(WakeLock, config.WakeLock);
}

/// <summary>
Expand Down Expand Up @@ -327,5 +335,12 @@ private static TimeSpan GetBuildTimeout(TimeSpan current, TimeSpan other)
=> current == DefaultConfig.Instance.BuildTimeout
? other
: TimeSpan.FromMilliseconds(Math.Max(current.TotalMilliseconds, other.TotalMilliseconds));

private static WakeLockType GetWakeLock(WakeLockType current, WakeLockType other)
{
if (current == DefaultConfig.Instance.WakeLock) { return other; }
if (other == DefaultConfig.Instance.WakeLock) { return current; }
return current.CompareTo(other) > 0 ? current : other;
}
}
}
20 changes: 20 additions & 0 deletions src/BenchmarkDotNet/Configs/WakeLockType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace BenchmarkDotNet.Configs
{
public enum WakeLockType
{
/// <summary>
/// Allows the system to enter sleep and/or turn off the display while benchmarks are running.
/// </summary>
None,

/// <summary>
/// Forces the system to be in the working state while benchmarks are running.
/// </summary>
System,

/// <summary>
/// Forces the display to be on while benchmarks are running.
/// </summary>
Display
}
}
4 changes: 4 additions & 0 deletions src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.ConsoleArguments.ListBenchmarks;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
Expand Down Expand Up @@ -179,6 +180,9 @@ public bool UseDisassemblyDiagnoser
[Option("buildTimeout", Required = false, HelpText = "Build timeout in seconds.")]
public int? TimeOutInSeconds { get; set; }

[Option("wakeLock", Required = false, HelpText = "Prevents the system from entering sleep or turning off the display. None/System/Display.")]
public WakeLockType? WakeLock { get; set; }

[Option("stopOnFirstError", Required = false, Default = false, HelpText = "Stop on first error.")]
public bool StopOnFirstError { get; set; }

Expand Down
3 changes: 3 additions & 0 deletions src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,9 @@ private static IConfig CreateConfig(CommandLineOptions options, IConfig globalCo
if (options.TimeOutInSeconds.HasValue)
config.WithBuildTimeout(TimeSpan.FromSeconds(options.TimeOutInSeconds.Value));

if (options.WakeLock.HasValue)
config.WithWakeLock(options.WakeLock.Value);

return config;
}

Expand Down
1 change: 1 addition & 0 deletions src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos)
using (var streamLogger = new StreamLogger(GetLogFileStreamWriter(benchmarkRunInfos, logFilePath)))
{
var compositeLogger = CreateCompositeLogger(benchmarkRunInfos, streamLogger);
using var wakeLock = WakeLock.Request(WakeLock.GetWakeLockType(benchmarkRunInfos), "BenchmarkDotNet Running Benchmarks", streamLogger);
var eventProcessor = new CompositeEventProcessor(benchmarkRunInfos);

eventProcessor.OnStartValidationStage();
Expand Down
77 changes: 77 additions & 0 deletions src/BenchmarkDotNet/Running/WakeLock.PInvoke.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace BenchmarkDotNet.Running;

internal partial class WakeLock
{
private static class PInvoke
{
public static SafePowerHandle PowerCreateRequest(string reason)
{
REASON_CONTEXT context = new REASON_CONTEXT()
{
Version = POWER_REQUEST_CONTEXT_VERSION,
Flags = POWER_REQUEST_CONTEXT_FLAGS.POWER_REQUEST_CONTEXT_SIMPLE_STRING,
SimpleReasonString = reason
};
SafePowerHandle safePowerHandle = PowerCreateRequest(context);
if (safePowerHandle.IsInvalid) { throw new Win32Exception(); }
leonvandermeer marked this conversation as resolved.
Show resolved Hide resolved
return safePowerHandle;
}

[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
private static extern SafePowerHandle PowerCreateRequest(REASON_CONTEXT Context);

public static void PowerSetRequest(SafePowerHandle safePowerHandle, POWER_REQUEST_TYPE requestType)
{
if (!InvokePowerSetRequest(safePowerHandle, requestType))
{
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();
}
}

[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);

private struct REASON_CONTEXT
{
public uint Version;

public POWER_REQUEST_CONTEXT_FLAGS Flags;

[MarshalAs(UnmanagedType.LPWStr)]
public string SimpleReasonString;
}

private const uint POWER_REQUEST_CONTEXT_VERSION = 0U;

private enum POWER_REQUEST_CONTEXT_FLAGS : uint
{
POWER_REQUEST_CONTEXT_DETAILED_STRING = 2U,
POWER_REQUEST_CONTEXT_SIMPLE_STRING = 1U,
}

public enum POWER_REQUEST_TYPE
{
PowerRequestDisplayRequired = 0,
leonvandermeer marked this conversation as resolved.
Show resolved Hide resolved
PowerRequestSystemRequired = 1,
PowerRequestAwayModeRequired = 2,
PowerRequestExecutionRequired = 3,
}
}
}
13 changes: 13 additions & 0 deletions src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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);
}
}
Loading
Loading