Skip to content

Commit 0f7eb25

Browse files
meliasMaykon Eliasadamsitnik
authored
Implement --resume support (#2164), fixes #1799
Co-authored-by: Maykon Elias <[email protected]> Co-authored-by: Adam Sitnik <[email protected]>
1 parent c02c3d8 commit 0f7eb25

File tree

5 files changed

+68
-2
lines changed

5 files changed

+68
-2
lines changed

src/BenchmarkDotNet/Configs/ConfigOptions.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ public enum ConfigOptions
4444
/// <summary>
4545
/// Performs apples-to-apples comparison for provided benchmarks and jobs. Experimental, will change in the near future!
4646
/// </summary>
47-
ApplesToApples = 1 << 8
47+
ApplesToApples = 1 << 8,
48+
/// <summary>
49+
/// Continue the execution if the last run was stopped.
50+
/// </summary>
51+
Resume = 1 << 9
4852
}
4953

5054
internal static class ConfigOptionsExtensions

src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs

+3
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ public bool UseDisassemblyDiagnoser
215215
[Option("noForcedGCs", Required = false, HelpText = "Specifying would not forcefully induce any GCs.")]
216216
public bool NoForcedGCs { get; set; }
217217

218+
[Option("resume", Required = false, Default = false, HelpText = "Continue the execution if the last run was stopped.")]
219+
public bool Resume { get; set; }
220+
218221
internal bool UserProvidedFilters => Filters.Any() || AttributeNames.Any() || AllCategories.Any() || AnyCategories.Any();
219222

220223
[Usage(ApplicationAlias = "")]

src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs

+1
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ private static IConfig CreateConfig(CommandLineOptions options, IConfig globalCo
245245
config.WithOption(ConfigOptions.LogBuildOutput, options.LogBuildOutput);
246246
config.WithOption(ConfigOptions.GenerateMSBuildBinLog, options.GenerateMSBuildBinLog);
247247
config.WithOption(ConfigOptions.ApplesToApples, options.ApplesToApples);
248+
config.WithOption(ConfigOptions.Resume, options.Resume);
248249

249250
if (options.MaxParameterColumnWidth.HasValue)
250251
config.WithSummaryStyle(SummaryStyle.Default.WithMaxParameterColumnWidth(options.MaxParameterColumnWidth.Value));

src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs

+44-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Collections.Immutable;
44
using System.IO;
55
using System.Linq;
6+
using System.Text.RegularExpressions;
67
using BenchmarkDotNet.Analysers;
78
using BenchmarkDotNet.Characteristics;
89
using BenchmarkDotNet.Columns;
@@ -42,6 +43,7 @@ internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos)
4243
var rootArtifactsFolderPath = GetRootArtifactsFolderPath(benchmarkRunInfos);
4344
var resultsFolderPath = GetResultsFolderPath(rootArtifactsFolderPath, benchmarkRunInfos);
4445
var logFilePath = Path.Combine(rootArtifactsFolderPath, title + ".log");
46+
var idToResume = GetIdToResume(rootArtifactsFolderPath, title, benchmarkRunInfos);
4547

4648
using (var streamLogger = new StreamLogger(GetLogFileStreamWriter(benchmarkRunInfos, logFilePath)))
4749
{
@@ -62,7 +64,7 @@ internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos)
6264
return new[] { Summary.ValidationFailed(title, resultsFolderPath, logFilePath) };
6365

6466
int totalBenchmarkCount = supportedBenchmarks.Sum(benchmarkInfo => benchmarkInfo.BenchmarksCases.Length);
65-
int benchmarksToRunCount = totalBenchmarkCount;
67+
int benchmarksToRunCount = totalBenchmarkCount - (idToResume + 1); // ids are indexed from 0
6668
compositeLogger.WriteLineHeader("// ***** BenchmarkRunner: Start *****");
6769
compositeLogger.WriteLineHeader($"// ***** Found {totalBenchmarkCount} benchmark(s) in total *****");
6870
var globalChronometer = Chronometer.Start();
@@ -84,6 +86,16 @@ internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos)
8486

8587
foreach (var benchmarkRunInfo in supportedBenchmarks) // we run them in the old order now using the new build artifacts
8688
{
89+
if (idToResume >= 0)
90+
{
91+
var benchmarkWithHighestIdForGivenType = benchmarkRunInfo.BenchmarksCases.Last();
92+
if (benchmarkToBuildResult[benchmarkWithHighestIdForGivenType].Id.Value <= idToResume)
93+
{
94+
compositeLogger.WriteLineInfo($"Skipping {benchmarkRunInfo.BenchmarksCases.Length} benchmark(s) defined by {benchmarkRunInfo.Type.GetCorrectCSharpTypeName()}.");
95+
continue;
96+
}
97+
}
98+
8799
var summary = Run(benchmarkRunInfo, benchmarkToBuildResult, resolver, compositeLogger, artifactsToCleanup,
88100
resultsFolderPath, logFilePath, totalBenchmarkCount, in runsChronometer, ref benchmarksToRunCount);
89101

@@ -678,5 +690,36 @@ private static void PrintValidationErrors(ILogger logger, IEnumerable<Validation
678690
logger.WriteLine();
679691
}
680692
}
693+
694+
private static int GetIdToResume(string rootArtifactsFolderPath, string currentLogFileName, BenchmarkRunInfo[] benchmarkRunInfos)
695+
{
696+
if (benchmarkRunInfos.Any(benchmark => benchmark.Config.Options.IsSet(ConfigOptions.Resume)))
697+
{
698+
var directoryInfo = new DirectoryInfo(rootArtifactsFolderPath);
699+
var logFilesExceptCurrent = directoryInfo
700+
.GetFiles($"{currentLogFileName.Split('-')[0]}*")
701+
.Where(file => Path.GetFileNameWithoutExtension(file.Name) != currentLogFileName)
702+
.ToArray();
703+
704+
if (logFilesExceptCurrent.Length > 0)
705+
{
706+
var previousRunLogFile = logFilesExceptCurrent
707+
.OrderByDescending(o => o.LastWriteTime)
708+
.First();
709+
710+
var regex = new Regex("--benchmarkId (.*?) in", RegexOptions.Compiled);
711+
foreach (var line in File.ReadLines(previousRunLogFile.FullName).Reverse())
712+
{
713+
var match = regex.Match(line);
714+
if (match.Success)
715+
{
716+
return int.Parse(match.Groups[1].Value);
717+
}
718+
}
719+
}
720+
}
721+
722+
return -1;
723+
}
681724
}
682725
}

tests/BenchmarkDotNet.IntegrationTests/BenchmarkSwitcherTest.cs

+15
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,21 @@ public void WhenUserCreatesStaticBenchmarkMethodWeDisplayAnError_FromAssembly()
332332
Assert.Contains("static", logger.GetLog());
333333
}
334334

335+
[Fact]
336+
public void WhenUserAddTheResumeAttributeAndRunTheBenchmarks()
337+
{
338+
var logger = new OutputLogger(Output);
339+
var config = ManualConfig.CreateEmpty().AddLogger(logger);
340+
341+
var types = new[] { typeof(WithDryAttributeAndCategory) };
342+
var switcher = new BenchmarkSwitcher(types);
343+
344+
// the first run should execute all benchmarks
345+
Assert.Single(switcher.Run(new[] { "--filter", "*WithDryAttributeAndCategory*" }, config));
346+
// resuming after succesfull run should run nothing
347+
Assert.Empty(switcher.Run(new[] { "--resume", "--filter", "*WithDryAttributeAndCategory*" }, config));
348+
}
349+
335350
private class UserInteractionMock : IUserInteraction
336351
{
337352
private readonly IReadOnlyList<Type> returnValue;

0 commit comments

Comments
 (0)