Skip to content

Commit 6ca6c66

Browse files
authored
don't use diagnosers when running the benchmark has failed (#1903)
* add test * when OverheadJitting succeeds and WorkloadJitting fails, we need to stop the benchmarking * perform the before parsing the diagnoser stats * don't try to run the diagnostics if benchmarking has failed * edge case: the benchmark fails, but not immediately * don't try to parse to stats if there is nothing to parse
1 parent 4b88216 commit 6ca6c66

File tree

2 files changed

+82
-10
lines changed

2 files changed

+82
-10
lines changed

src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -436,21 +436,32 @@ private static (bool success, List<ExecuteResult> executeResults, GcStats gcStat
436436
.Where(r => r.IterationMode != IterationMode.Unknown)
437437
.ToArray();
438438

439-
if (!measurements.Any())
439+
if (!measurements.Any(measurement => measurement.IsWorkload()))
440440
{
441441
// Something went wrong during the benchmark, don't bother doing more runs
442442
logger.WriteLineError("No more Benchmark runs will be launched as NO measurements were obtained from the previous run!");
443443
success = false;
444444
break;
445445
}
446446

447+
if (!success && benchmarkCase.Config.Options.IsSet(ConfigOptions.StopOnFirstError))
448+
{
449+
break;
450+
}
451+
447452
if (useDiagnoser)
448453
{
449454
if (benchmarkCase.Config.HasMemoryDiagnoser())
450-
gcStats = GcStats.Parse(executeResult.Data.Last(line => !string.IsNullOrEmpty(line) && line.StartsWith(GcStats.ResultsLinePrefix)));
455+
{
456+
string resultsLine = executeResult.Data.LastOrDefault(line => !string.IsNullOrEmpty(line) && line.StartsWith(GcStats.ResultsLinePrefix));
457+
gcStats = resultsLine is not null ? GcStats.Parse(resultsLine) : default;
458+
}
451459

452460
if (benchmarkCase.Config.HasThreadingDiagnoser())
453-
threadingStats = ThreadingStats.Parse(executeResult.Data.Last(line => !string.IsNullOrEmpty(line) && line.StartsWith(ThreadingStats.ResultsLinePrefix)));
461+
{
462+
string resultsLine = executeResult.Data.LastOrDefault(line => !string.IsNullOrEmpty(line) && line.StartsWith(ThreadingStats.ResultsLinePrefix));
463+
threadingStats = resultsLine is not null ? ThreadingStats.Parse(resultsLine) : default;
464+
}
454465

455466
metrics.AddRange(
456467
noOverheadCompositeDiagnoser.ProcessResults(
@@ -465,17 +476,12 @@ private static (bool success, List<ExecuteResult> executeResults, GcStats gcStat
465476
double percent = overheadApprox / workloadApprox * 100;
466477
launchCount = (int)Math.Round(Math.Max(2, 2 + (percent - 1) / 3)); // an empirical formula
467478
}
468-
469-
if (!success && benchmarkCase.Config.Options.IsSet(ConfigOptions.StopOnFirstError))
470-
{
471-
break;
472-
}
473479
}
474480
logger.WriteLine();
475481

476482
// Do a "Diagnostic" run, but DISCARD the results, so that the overhead of Diagnostics doesn't skew the overall results
477483
var extraRunCompositeDiagnoser = benchmarkCase.Config.GetCompositeDiagnoser(benchmarkCase, Diagnosers.RunMode.ExtraRun);
478-
if (extraRunCompositeDiagnoser != null)
484+
if (extraRunCompositeDiagnoser != null && success)
479485
{
480486
logger.WriteLineInfo("// Run, Diagnostic");
481487

@@ -499,7 +505,7 @@ private static (bool success, List<ExecuteResult> executeResults, GcStats gcStat
499505
}
500506

501507
var separateLogicCompositeDiagnoser = benchmarkCase.Config.GetCompositeDiagnoser(benchmarkCase, Diagnosers.RunMode.SeparateLogic);
502-
if (separateLogicCompositeDiagnoser != null)
508+
if (separateLogicCompositeDiagnoser != null && success)
503509
{
504510
logger.WriteLineInfo("// Run, Diagnostic [SeparateLogic]");
505511

tests/BenchmarkDotNet.IntegrationTests/ExceptionHandlingTests.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using System.Linq;
23
using BenchmarkDotNet.Attributes;
34
using BenchmarkDotNet.Configs;
5+
using BenchmarkDotNet.Diagnosers;
46
using BenchmarkDotNet.Jobs;
57
using BenchmarkDotNet.Loggers;
68
using BenchmarkDotNet.Toolchains.InProcess.Emit;
@@ -111,5 +113,69 @@ public void Bar1() { }
111113
[Benchmark]
112114
public void Bar2() => throw new Exception();
113115
}
116+
117+
[Theory]
118+
[InlineData(false)]
119+
[InlineData(true)]
120+
public void StopOnFirstErrorIsRespected(bool value)
121+
{
122+
var config = ManualConfig.CreateEmpty()
123+
.AddJob(Job.Dry)
124+
.AddDiagnoser(MemoryDiagnoser.Default) // crucial to repro the bug
125+
.WithOption(ConfigOptions.StopOnFirstError, value);
126+
127+
var summary = CanExecute<MoreThanOneNonThrowingBenchmark>(config, fullValidation: false);
128+
129+
if (value)
130+
{
131+
Assert.Equal(1, summary.Reports.Count(report => !report.Success));
132+
}
133+
else
134+
{
135+
Assert.Equal(3, summary.Reports.Count(report => report.Success));
136+
Assert.Equal(4, summary.Reports.Count(report => !report.Success));
137+
}
138+
}
139+
140+
public class MoreThanOneNonThrowingBenchmark
141+
{
142+
[Benchmark] public void Ok1() { }
143+
[Benchmark] public void Throw1() => throw new Exception(BenchmarkExceptionMessage);
144+
[Benchmark] public void Ok2() { }
145+
[Benchmark] public void Throw2() => throw new Exception(BenchmarkExceptionMessage);
146+
[Benchmark] public void Ok3() { }
147+
[Benchmark] public void Throw3() => throw new Exception(BenchmarkExceptionMessage);
148+
[Benchmark] public void Throw4() => throw new Exception(BenchmarkExceptionMessage);
149+
}
150+
151+
[Fact]
152+
public void DiagnosersCantCrashRunnerWhenTheyHaveNothingToParse()
153+
{
154+
var config = ManualConfig.CreateEmpty()
155+
.AddJob(Job.Dry.WithIterationCount(10))
156+
.AddDiagnoser(MemoryDiagnoser.Default); // crucial to repro the bug;
157+
158+
var summary = CanExecute<ThrowButNotImmediately>(config, fullValidation: false);
159+
160+
var benchmarkReport = summary.Reports.Single();
161+
Assert.Equal(default, benchmarkReport.GcStats);
162+
}
163+
164+
public class ThrowButNotImmediately
165+
{
166+
private int iterationCount;
167+
168+
[IterationSetup]
169+
public void Setup() => ++iterationCount;
170+
171+
[Benchmark]
172+
public void Throwing()
173+
{
174+
if (iterationCount >= 5)
175+
{
176+
throw new Exception();
177+
}
178+
}
179+
}
114180
}
115181
}

0 commit comments

Comments
 (0)