/
InProcessExecutor.cs
151 lines (130 loc) · 5.87 KB
/
InProcessExecutor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Threading;
using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains.Parameters;
using BenchmarkDotNet.Toolchains.Results;
using JetBrains.Annotations;
namespace BenchmarkDotNet.Toolchains.InProcess
{
/// <summary>
/// Implementation of <see cref="IExecutor" /> for in-process benchmarks.
/// </summary>
[PublicAPI]
[SuppressMessage("ReSharper", "ArrangeBraces_using")]
public class InProcessExecutor : IExecutor
{
private static readonly TimeSpan UnderDebuggerTimeout = TimeSpan.FromDays(1);
/// <summary> Default timeout for in-process benchmarks. </summary>
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(5);
/// <summary>Initializes a new instance of the <see cref="InProcessExecutor" /> class.</summary>
/// <param name="timeout">Timeout for the run.</param>
/// <param name="codegenMode">Describes how benchmark action code is generated.</param>
/// <param name="logOutput"><c>true</c> if the output should be logged.</param>
public InProcessExecutor(TimeSpan timeout, BenchmarkActionCodegen codegenMode, bool logOutput)
{
if (timeout == TimeSpan.Zero)
timeout = DefaultTimeout;
ExecutionTimeout = timeout;
CodegenMode = codegenMode;
LogOutput = logOutput;
}
/// <summary>Timeout for the run.</summary>
/// <value>The timeout for the run.</value>
public TimeSpan ExecutionTimeout { get; }
/// <summary>Describes how benchmark action code is generated.</summary>
/// <value>Benchmark action code generation mode.</value>
public BenchmarkActionCodegen CodegenMode { get; }
/// <summary>Gets a value indicating whether the output should be logged.</summary>
/// <value><c>true</c> if the output should be logged; otherwise, <c>false</c>.</value>
public bool LogOutput { get; }
/// <summary>Executes the specified benchmark.</summary>
public ExecuteResult Execute(ExecuteParameters executeParameters)
{
// TODO: preallocate buffer for output (no direct logging)?
var hostLogger = LogOutput ? executeParameters.Logger : NullLogger.Instance;
var host = new InProcessHost(executeParameters.Benchmark, hostLogger, executeParameters.Diagnoser, executeParameters.Config);
int exitCode = -1;
var runThread = new Thread(() => exitCode = ExecuteCore(host, executeParameters.Benchmark, executeParameters.Logger));
#if !NETCOREAPP1_1
if (executeParameters.Benchmark.Target.Method.GetCustomAttributes<STAThreadAttribute>(false).Any())
{
runThread.SetApartmentState(ApartmentState.STA);
}
#endif
runThread.IsBackground = true;
var timeout = HostEnvironmentInfo.GetCurrent().HasAttachedDebugger ? UnderDebuggerTimeout : ExecutionTimeout;
runThread.Start();
if (!runThread.Join((int)timeout.TotalMilliseconds))
throw new InvalidOperationException(
$"Benchmark {executeParameters.Benchmark.DisplayInfo} takes to long to run. " +
"Prefer to use out-of-process toolchains for long-running benchmarks.");
return GetExecutionResult(host.RunResults, exitCode, executeParameters.Logger);
}
private int ExecuteCore(IHost host, Benchmark benchmark, ILogger logger)
{
int exitCode = -1;
var process = Process.GetCurrentProcess();
var oldPriority = process.PriorityClass;
var oldAffinity = process.TryGetAffinity();
#if !NETCOREAPP1_1
var thread = Thread.CurrentThread;
var oldThreadPriority = thread.Priority;
#endif
var affinity = benchmark.Job.ResolveValueAsNullable(EnvMode.AffinityCharacteristic);
try
{
process.TrySetPriority(ProcessPriorityClass.High, logger);
#if !NETCOREAPP1_1
thread.TrySetPriority(ThreadPriority.Highest, logger);
#endif
if (affinity != null)
{
process.TrySetAffinity(affinity.Value, logger);
}
exitCode = InProcessRunner.Run(host, benchmark, CodegenMode);
}
catch (Exception ex)
{
logger.WriteLineError($"// ! {GetType().Name}, exception: {ex}");
}
finally
{
process.TrySetPriority(oldPriority, logger);
#if !NETCOREAPP1_1
thread.TrySetPriority(oldThreadPriority, logger);
#endif
if (affinity != null && oldAffinity != null)
{
process.TrySetAffinity(oldAffinity.Value, logger);
}
}
return exitCode;
}
private ExecuteResult GetExecutionResult(RunResults runResults, int exitCode, ILogger logger)
{
if (exitCode != 0)
{
return new ExecuteResult(true, exitCode, Array.Empty<string>(), Array.Empty<string>());
}
var lines = new List<string>();
foreach (var measurement in runResults.GetMeasurements())
{
lines.Add(measurement.ToOutputLine());
}
lines.Add(runResults.GCStats.WithTotalOperations(runResults.TotalOperationsCount).ToOutputLine());
return new ExecuteResult(true, 0, lines.ToArray(), Array.Empty<string>());
}
}
}