Skip to content

Commit 7c996d1

Browse files
committed
Added BeforeExtraIteration and AfterExtraIteration signals.
Refactored `ThreadingDiagnoser` and `ExceptionDiagnoser` to implement `IInProcessDiagnoser` using the new signals.
1 parent 7cad308 commit 7c996d1

29 files changed

+259
-353
lines changed

src/BenchmarkDotNet/Code/CodeGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ internal static string Generate(BuildPartition buildPartition)
5656
.Replace("$LoadArguments$", GetLoadArguments(benchmark))
5757
.Replace("$PassArguments$", passArguments)
5858
.Replace("$EngineFactoryType$", GetEngineFactoryTypeName(benchmark))
59-
.Replace("$MeasureExtraStats$", buildInfo.Config.HasExtraStatsDiagnoser() ? "true" : "false")
59+
.Replace("$RunExtraIteration$", buildInfo.Config.HasExtraIterationDiagnoser(benchmark) ? "true" : "false")
6060
.Replace("$DisassemblerEntryMethodName$", DisassemblerConstants.DisassemblerEntryMethodName)
6161
.Replace("$WorkloadMethodCall$", provider.GetWorkloadMethodCall(passArguments))
6262
.Replace("$InProcessDiagnoserRouters$", GetInProcessDiagnoserRouters(buildInfo))

src/BenchmarkDotNet/Configs/ImmutableConfig.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,15 @@ internal ImmutableConfig(
114114

115115
public bool HasMemoryDiagnoser() => diagnosers.OfType<MemoryDiagnoser>().Any();
116116

117-
public bool HasThreadingDiagnoser() => diagnosers.Contains(ThreadingDiagnoser.Default);
118-
119-
public bool HasExceptionDiagnoser() => diagnosers.Contains(ExceptionDiagnoser.Default);
120-
121117
internal bool HasPerfCollectProfiler() => diagnosers.OfType<PerfCollectProfiler>().Any();
122118

123119
internal bool HasDisassemblyDiagnoser() => diagnosers.OfType<DisassemblyDiagnoser>().Any();
124120

125-
public bool HasExtraStatsDiagnoser() => HasMemoryDiagnoser() || HasThreadingDiagnoser() || HasExceptionDiagnoser();
121+
public bool HasExtraIterationDiagnoser(BenchmarkCase benchmarkCase) => HasMemoryDiagnoser() || diagnosers.Any(d => d.GetRunMode(benchmarkCase) == RunMode.ExtraIteration);
126122

127-
public IDiagnoser? GetCompositeDiagnoser(BenchmarkCase benchmarkCase, RunMode runMode)
123+
public IDiagnoser? GetCompositeDiagnoser(BenchmarkCase benchmarkCase, Func<RunMode, bool> runModeComparer)
128124
{
129-
var diagnosersForGivenMode = diagnosers.Where(diagnoser => diagnoser.GetRunMode(benchmarkCase) == runMode).ToImmutableHashSet();
125+
var diagnosersForGivenMode = diagnosers.Where(diagnoser => runModeComparer(diagnoser.GetRunMode(benchmarkCase))).ToImmutableHashSet();
130126

131127
return diagnosersForGivenMode.Any() ? new CompositeDiagnoser(diagnosersForGivenMode) : null;
132128
}

src/BenchmarkDotNet/Diagnosers/CompositeDiagnoser.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public void Handle(BenchmarkSignal signal)
8080

8181
foreach (var router in routers)
8282
{
83-
if (router.runMode == runMode)
83+
if (router.ShouldHandle(runMode))
8484
{
8585
router.handler.Handle(signal, parameters);
8686
}
@@ -93,7 +93,7 @@ public void Handle(BenchmarkSignal signal)
9393

9494
foreach (var router in routers)
9595
{
96-
if (router.runMode != runMode)
96+
if (!router.ShouldHandle(runMode))
9797
{
9898
continue;
9999
}

src/BenchmarkDotNet/Diagnosers/DiagnoserResults.cs

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,16 @@
77

88
namespace BenchmarkDotNet.Diagnosers
99
{
10-
public class DiagnoserResults
10+
public class DiagnoserResults(BenchmarkCase benchmarkCase, ExecuteResult executeResult, BuildResult buildResult)
1111
{
12-
public DiagnoserResults(BenchmarkCase benchmarkCase, ExecuteResult executeResult, BuildResult buildResult)
13-
{
14-
BenchmarkCase = benchmarkCase;
15-
TotalOperations = executeResult.Measurements.Where(measurement => measurement.IsWorkload()).Sum(m => m.Operations);
16-
GcStats = executeResult.GcStats;
17-
ThreadingStats = executeResult.ThreadingStats;
18-
BuildResult = buildResult;
19-
ExceptionFrequency = executeResult.ExceptionFrequency;
20-
Measurements = executeResult.Measurements;
21-
}
12+
public BenchmarkCase BenchmarkCase { get; } = benchmarkCase;
2213

23-
public BenchmarkCase BenchmarkCase { get; }
14+
public long TotalOperations { get; } = executeResult.Measurements.Where(measurement => measurement.IsWorkload()).Sum(m => m.Operations);
2415

25-
public long TotalOperations { get; }
16+
public GcStats GcStats { get; } = executeResult.GcStats;
2617

27-
public GcStats GcStats { get; }
18+
public BuildResult BuildResult { get; } = buildResult;
2819

29-
public ThreadingStats ThreadingStats { get; }
30-
31-
public double ExceptionFrequency { get; }
32-
33-
public BuildResult BuildResult { get; }
34-
35-
public IReadOnlyList<Measurement> Measurements { get; }
20+
public IReadOnlyList<Measurement> Measurements { get; } = executeResult.Measurements;
3621
}
3722
}

src/BenchmarkDotNet/Diagnosers/ExceptionDiagnoser.cs

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,47 +7,55 @@
77
using BenchmarkDotNet.Reports;
88
using BenchmarkDotNet.Running;
99
using BenchmarkDotNet.Validators;
10+
using JetBrains.Annotations;
1011
using System;
1112
using System.Collections.Generic;
13+
using System.ComponentModel;
1214
using System.Linq;
15+
using System.Runtime.ExceptionServices;
16+
using System.Threading;
1317

1418
namespace BenchmarkDotNet.Diagnosers
1519
{
16-
public class ExceptionDiagnoser : IDiagnoser
20+
public class ExceptionDiagnoser(ExceptionDiagnoserConfig config) : IInProcessDiagnoser
1721
{
18-
public static readonly ExceptionDiagnoser Default = new ExceptionDiagnoser(new ExceptionDiagnoserConfig(displayExceptionsIfZeroValue: true));
22+
public static readonly ExceptionDiagnoser Default = new(new ExceptionDiagnoserConfig(displayExceptionsIfZeroValue: true));
1923

20-
public ExceptionDiagnoser(ExceptionDiagnoserConfig config) => Config = config;
24+
private readonly Dictionary<BenchmarkCase, long> results = [];
2125

22-
public ExceptionDiagnoserConfig Config { get; }
26+
public ExceptionDiagnoserConfig Config { get; } = config;
2327

24-
public IEnumerable<string> Ids => new[] { nameof(ExceptionDiagnoser) };
28+
public IEnumerable<string> Ids => [nameof(ExceptionDiagnoser)];
2529

26-
public IEnumerable<IExporter> Exporters => Array.Empty<IExporter>();
30+
public IEnumerable<IExporter> Exporters => [];
2731

28-
public IEnumerable<IAnalyser> Analysers => Array.Empty<IAnalyser>();
32+
public IEnumerable<IAnalyser> Analysers => [];
2933

3034
public void DisplayResults(ILogger logger) { }
3135

32-
public RunMode GetRunMode(BenchmarkCase benchmarkCase) => RunMode.NoOverhead;
36+
public RunMode GetRunMode(BenchmarkCase benchmarkCase) => RunMode.ExtraIteration;
3337

3438
public void Handle(HostSignal signal, DiagnoserActionParameters parameters) { }
3539

36-
public IEnumerable<Metric> ProcessResults(DiagnoserResults results)
40+
public IEnumerable<Metric> ProcessResults(DiagnoserResults diagnoserResults)
3741
{
38-
yield return new Metric(new ExceptionsFrequencyMetricDescriptor(Config), results.ExceptionFrequency);
42+
if (results.TryGetValue(diagnoserResults.BenchmarkCase, out var exceptionsCount))
43+
{
44+
double totalOperations = diagnoserResults.Measurements.First(m => m.IterationStage == IterationStage.Extra).Operations;
45+
yield return new Metric(new ExceptionsFrequencyMetricDescriptor(Config), exceptionsCount / totalOperations);
46+
}
3947
}
4048

41-
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters) => Enumerable.Empty<ValidationError>();
49+
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters) => [];
4250

43-
internal class ExceptionsFrequencyMetricDescriptor : IMetricDescriptor
44-
{
45-
public ExceptionDiagnoserConfig Config { get; }
46-
public ExceptionsFrequencyMetricDescriptor(ExceptionDiagnoserConfig config = null)
47-
{
48-
Config = config;
49-
}
51+
void IInProcessDiagnoser.DeserializeResults(BenchmarkCase benchmarkCase, string serializedResults)
52+
=> results.Add(benchmarkCase, long.Parse(serializedResults));
5053

54+
InProcessDiagnoserHandlerData IInProcessDiagnoser.GetHandlerData(BenchmarkCase benchmarkCase)
55+
=> new(typeof(ExceptionDiagnoserInProcessHandler), null);
56+
57+
internal class ExceptionsFrequencyMetricDescriptor(ExceptionDiagnoserConfig config) : IMetricDescriptor
58+
{
5159
public string Id => "ExceptionFrequency";
5260
public string DisplayName => Column.Exceptions;
5361
public string Legend => "Exceptions thrown per single operation";
@@ -57,12 +65,37 @@ public ExceptionsFrequencyMetricDescriptor(ExceptionDiagnoserConfig config = nul
5765
public bool TheGreaterTheBetter => false;
5866
public int PriorityInCategory => 0;
5967
public bool GetIsAvailable(Metric metric)
68+
=> config?.DisplayExceptionsIfZeroValue == true || metric.Value > 0;
69+
}
70+
}
71+
72+
[UsedImplicitly]
73+
[EditorBrowsable(EditorBrowsableState.Never)]
74+
public sealed class ExceptionDiagnoserInProcessHandler : IInProcessDiagnoserHandler
75+
{
76+
private long exceptionsCount;
77+
78+
void IInProcessDiagnoserHandler.Handle(BenchmarkSignal signal, InProcessDiagnoserActionArgs args)
79+
{
80+
switch (signal)
6081
{
61-
if (Config == null)
62-
return metric.Value > 0;
63-
else
64-
return Config.DisplayExceptionsIfZeroValue || metric.Value > 0;
82+
case BenchmarkSignal.BeforeExtraIteration:
83+
AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException;
84+
break;
85+
case BenchmarkSignal.AfterExtraIteration:
86+
AppDomain.CurrentDomain.FirstChanceException -= OnFirstChanceException;
87+
break;
6588
}
6689
}
90+
91+
void IInProcessDiagnoserHandler.Initialize(string? serializedConfig) { }
92+
93+
string IInProcessDiagnoserHandler.SerializeResults()
94+
=> exceptionsCount.ToString();
95+
96+
private void OnFirstChanceException(object sender, FirstChanceExceptionEventArgs e)
97+
{
98+
Interlocked.Increment(ref exceptionsCount);
99+
}
67100
}
68101
}

src/BenchmarkDotNet/Diagnosers/InProcessDiagnoserRouter.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,9 @@ internal static InProcessDiagnoserRouter Create(IInProcessDiagnoser diagnoser, B
3333
runMode = diagnoser.GetRunMode(benchmarkCase)
3434
};
3535
}
36+
37+
internal readonly bool ShouldHandle(RunMode runMode)
38+
=> this.runMode == runMode
39+
// ExtraIteration is merged with NoOverhead, so we need to check it explicitly.
40+
|| (runMode == RunMode.NoOverhead && this.runMode == RunMode.ExtraIteration);
3641
}

src/BenchmarkDotNet/Diagnosers/RunMode.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ public enum RunMode : byte
1414
/// </summary>
1515
ExtraRun,
1616
/// <summary>
17+
/// needs a single extra iteration of the benchmark
18+
/// </summary>
19+
ExtraIteration,
20+
/// <summary>
1721
/// no overhead, can be executed without extra run
1822
/// </summary>
1923
NoOverhead,

0 commit comments

Comments
 (0)