Skip to content

Commit 2bb9047

Browse files
authored
Support an argument to resume MicroBenchmarks from a previous run (dotnet#2378)
* Support a --resume argument to resume a MicroBenchmarks run from previous results * Show existing results in the output
1 parent 1d2e14c commit 2bb9047

File tree

5 files changed

+122
-19
lines changed

5 files changed

+122
-19
lines changed

Diff for: scripts/benchmarks_ci.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,22 @@ def __is_valid_datetime(dt: str) -> str:
172172
action='store_true',
173173
help='Attempts to run the benchmarks without building.',
174174
)
175+
parser.add_argument(
176+
'--resume',
177+
dest='resume',
178+
required=False,
179+
default=False,
180+
action='store_true',
181+
help='Resume a previous run from existing benchmark results',
182+
)
175183
parser.add_argument(
176184
'--skip-logger-setup',
177185
dest='skip_logger_setup',
178186
required=False,
179187
default=False,
180188
action='store_true',
181-
help='Skips the logger setup, for cases when invoked by another script that already sets logging up')
189+
help='Skips the logger setup, for cases when invoked by another script that already sets logging up',
190+
)
182191

183192
return parser
184193

Diff for: scripts/benchmarks_monthly.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,13 @@ def add_arguments(parser: ArgumentParser) -> ArgumentParser:
7575
'--no-clean',
7676
dest='no_clean',
7777
action='store_true',
78-
help='Do not clean the SDK and results directories before execution')
78+
help='Do not clean the SDK installations before execution')
79+
80+
parser.add_argument(
81+
'--resume',
82+
dest='resume',
83+
action='store_true',
84+
help='Resume a previous run from existing benchmark results')
7985

8086
parser.add_argument(
8187
'--dry-run',
@@ -131,25 +137,28 @@ def log(text: str):
131137
resultsPath = os.path.join(rootPath, 'artifacts', 'bin', 'MicroBenchmarks', 'Release', moniker, 'BenchmarkDotNet.Artifacts', 'results')
132138

133139
if not args.no_clean:
134-
# Delete any preexisting SDK and results, which allows
140+
# Delete any preexisting SDK installations, which allows
135141
# multiple versions to be run from a single command
136142
if os.path.isdir(sdkPath):
137143
log('rmdir -r ' + sdkPath)
138144

139145
if not args.dry_run:
140146
shutil.rmtree(sdkPath)
141147

148+
benchmarkArgs = ['--skip-logger-setup', '--filter', args.filter, '--architecture', args.architecture, '-f', version['tfm']]
149+
150+
if 'build' in version:
151+
benchmarkArgs += ['--dotnet-versions', version['build']]
152+
153+
if args.resume:
154+
benchmarkArgs += ['--resume']
155+
else:
142156
if os.path.isdir(resultsPath):
143157
log('rmdir -r ' + resultsPath)
144158

145159
if not args.dry_run:
146160
shutil.rmtree(resultsPath)
147161

148-
benchmarkArgs = ['--skip-logger-setup', '--filter', args.filter, '--architecture', args.architecture, '-f', version['tfm']]
149-
150-
if 'build' in version:
151-
benchmarkArgs += ['--dotnet-versions', version['build']]
152-
153162
if args.bdn_arguments:
154163
if version['tfm'].startswith('nativeaot'):
155164
benchmarkArgs += ['--bdn-arguments', args.bdn_arguments + ' --ilCompilerVersion ' + version['ilc']]

Diff for: scripts/micro_benchmarks.py

+2
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ def __get_benchmarkdotnet_arguments(framework: str, args: tuple) -> list:
243243
]
244244
if args.filter:
245245
run_args += ['--filter'] + args.filter
246+
if args.resume:
247+
run_args += ['--resume']
246248

247249
# Extra BenchmarkDotNet cli arguments.
248250
if args.bdn_arguments:

Diff for: src/benchmarks/micro/Program.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,18 @@ static int Main(string[] args)
2323
List<string> categoryExclusionFilterValue;
2424
Dictionary<string, string> parameterFilterValue;
2525
bool getDiffableDisasm;
26+
bool resumeRun;
2627

2728
// Parse and remove any additional parameters that we need that aren't part of BDN
28-
try {
29+
try
30+
{
2931
argsList = CommandLineOptions.ParseAndRemoveIntParameter(argsList, "--partition-count", out partitionCount);
3032
argsList = CommandLineOptions.ParseAndRemoveIntParameter(argsList, "--partition-index", out partitionIndex);
3133
argsList = CommandLineOptions.ParseAndRemoveStringsParameter(argsList, "--exclusion-filter", out exclusionFilterValue);
3234
argsList = CommandLineOptions.ParseAndRemoveStringsParameter(argsList, "--category-exclusion-filter", out categoryExclusionFilterValue);
3335
argsList = CommandLineOptions.ParseAndRemovePairsParameter(argsList, "--parameter-filter", out parameterFilterValue);
3436
CommandLineOptions.ParseAndRemoveBooleanParameter(argsList, "--disasm-diff", out getDiffableDisasm);
37+
CommandLineOptions.ParseAndRemoveBooleanParameter(argsList, "--resume", out resumeRun);
3538

3639
CommandLineOptions.ValidatePartitionParameters(partitionCount, partitionIndex);
3740
}
@@ -52,7 +55,8 @@ static int Main(string[] args)
5255
exclusionFilterValue: exclusionFilterValue,
5356
categoryExclusionFilterValue: categoryExclusionFilterValue,
5457
parameterFilterValue: parameterFilterValue,
55-
getDiffableDisasm: getDiffableDisasm)
58+
getDiffableDisasm: getDiffableDisasm,
59+
resumeRun: resumeRun)
5660
.AddValidator(new NoWasmValidator(Categories.NoWASM)))
5761
.ToExitCode();
5862
}

Diff for: src/harness/BenchmarkDotNet.Extensions/RecommendedConfig.cs

+88-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
using System.Collections.Immutable;
2-
using System.IO;
3-
using BenchmarkDotNet.Columns;
1+
using BenchmarkDotNet.Columns;
42
using BenchmarkDotNet.Configs;
53
using BenchmarkDotNet.Diagnosers;
4+
using BenchmarkDotNet.Exporters;
65
using BenchmarkDotNet.Exporters.Json;
7-
using Perfolizer.Horology;
86
using BenchmarkDotNet.Jobs;
7+
using BenchmarkDotNet.Loggers;
98
using BenchmarkDotNet.Reports;
10-
using System.Collections.Generic;
9+
using Newtonsoft.Json;
10+
using Perfolizer.Horology;
1111
using Reporting;
12-
using BenchmarkDotNet.Loggers;
13-
using System.Linq;
14-
using BenchmarkDotNet.Exporters;
1512
using System;
13+
using System.Collections.Generic;
14+
using System.Collections.Immutable;
15+
using System.Diagnostics;
16+
using System.IO;
17+
using System.Linq;
1618

1719
namespace BenchmarkDotNet.Extensions
1820
{
@@ -27,7 +29,8 @@ public static IConfig Create(
2729
List<string> categoryExclusionFilterValue = null,
2830
Dictionary<string, string> parameterFilterValue = null,
2931
Job job = null,
30-
bool getDiffableDisasm = false)
32+
bool getDiffableDisasm = false,
33+
bool resumeRun = false)
3134
{
3235
if (job is null)
3336
{
@@ -39,6 +42,12 @@ public static IConfig Create(
3942
.DontEnforcePowerPlan(); // make sure BDN does not try to enforce High Performance power plan on Windows
4043
}
4144

45+
if (resumeRun)
46+
{
47+
exclusionFilterValue ??= new List<string>();
48+
exclusionFilterValue.AddRange(GetBenchmarksToResume(artifactsPath));
49+
}
50+
4251
var config = ManualConfig.CreateEmpty()
4352
.WithBuildTimeout(TimeSpan.FromMinutes(15)) // for slow machines
4453
.AddLogger(ConsoleLogger.Default) // log output to console
@@ -83,5 +92,75 @@ private static DisassemblyDiagnoser CreateDisassembler()
8392
exportHtml: false,
8493
exportCombinedDisassemblyReport: false,
8594
exportDiff: false));
95+
96+
private static IEnumerable<string> GetBenchmarksToResume(DirectoryInfo artifacts)
97+
{
98+
if (!artifacts.Exists)
99+
return new string[0];
100+
101+
// Get all existing report files, of any export type; order by descending filename length to avoid rename collisions
102+
var existingBenchmarks = artifacts.GetFiles($"*-report-*", SearchOption.AllDirectories)
103+
.OrderByDescending(resultFile => resultFile.FullName.Length)
104+
.SelectMany(resultFile =>
105+
{
106+
var reportFileName = resultFile.FullName;
107+
108+
// Prepend the report name with -resume, potentially multiple times if multiple reports for the same
109+
// benchmarks exist, so that they don't collide with one another. But don't unnecessarily prepend
110+
// -resume multiple times.
111+
if (!reportFileName.Contains("-resume-report-") || File.Exists(reportFileName.Replace("-resume-report-", "-report-")))
112+
{
113+
var resumeFileName = reportFileName.Replace("-report-", "-resume-report-");
114+
File.Move(reportFileName, resumeFileName);
115+
116+
reportFileName = resumeFileName;
117+
}
118+
119+
// For JSON reports, load the data to get the benchmarks that have already been reported
120+
if (reportFileName.EndsWith(".json"))
121+
{
122+
try
123+
{
124+
var result = JsonConvert.DeserializeObject<BdnResult>(File.ReadAllText(reportFileName));
125+
var benchmarks = result.Benchmarks.Select(benchmark =>
126+
{
127+
var nameParts = new[] { benchmark.Namespace, benchmark.Type, benchmark.Method };
128+
return string.Join(".", nameParts.Where(part => !string.IsNullOrEmpty(part)));
129+
}).Distinct();
130+
131+
return benchmarks;
132+
}
133+
catch (JsonSerializationException)
134+
{
135+
}
136+
}
137+
138+
return new string[0];
139+
});
140+
141+
if (existingBenchmarks.Any())
142+
{
143+
Console.WriteLine($"// Found {existingBenchmarks.Count()} existing result(s) to be skipped:");
144+
145+
foreach (var benchmark in existingBenchmarks.OrderBy(b => b))
146+
{
147+
Console.WriteLine($"// ***** {benchmark}");
148+
}
149+
}
150+
151+
return existingBenchmarks;
152+
}
153+
154+
private class Benchmark
155+
{
156+
public string Namespace { get; set; }
157+
public string Type { get; set; }
158+
public string Method { get; set; }
159+
}
160+
161+
private class BdnResult
162+
{
163+
public List<Benchmark> Benchmarks { get; set; }
164+
}
86165
}
87166
}

0 commit comments

Comments
 (0)