Skip to content

Commit 7d83758

Browse files
Serg046adamsitnik
andauthored
Issue #1736: Add ExceptionDiagnoser (#2169)
Co-authored-by: Adam Sitnik <[email protected]>
1 parent 0f7eb25 commit 7d83758

File tree

20 files changed

+276
-17
lines changed

20 files changed

+276
-17
lines changed

docs/articles/configs/diagnosers.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ The current Diagnosers are:
3737
It is a cross-platform profiler that allows profile .NET code on every platform - Windows, Linux, macOS.
3838
Please see Wojciech Nagórski's [blog post](https://wojciechnagorski.com/2020/04/cross-platform-profiling-.net-code-with-benchmarkdotnet/) for all the details.
3939
- Threading Diagnoser (`ThreadingDiagnoser`) - .NET Core 3.0+ diagnoser that reports some Threading statistics.
40+
- Exception Diagnoser (`ExceptionDiagnoser`) - a diagnoser that reports the frequency of exceptions thrown during the operation.
4041

4142
## Usage
4243

@@ -59,6 +60,7 @@ private class Config : ManualConfig
5960
Add(new InliningDiagnoser());
6061
Add(new EtwProfiler());
6162
Add(ThreadingDiagnoser.Default);
63+
Add(ExceptionDiagnoser.Default);
6264
}
6365
}
6466
```
@@ -72,6 +74,7 @@ You can also use one of the following attributes (apply it on a class that conta
7274
[ConcurrencyVisualizerProfiler]
7375
[NativeMemoryProfiler]
7476
[ThreadingDiagnoser]
77+
[ExceptionDiagnoser]
7578
```
7679

7780
In BenchmarkDotNet, 1kB = 1024B, 1MB = 1024kB, and so on. The column Gen X means number of GC collections per 1000 operations for that generation.
@@ -123,3 +126,5 @@ In BenchmarkDotNet, 1kB = 1024B, 1MB = 1024kB, and so on. The column Gen X means
123126
[!include[IntroNativeMemory](../samples/IntroNativeMemory.md)]
124127

125128
[!include[IntroThreadingDiagnoser](../samples/IntroThreadingDiagnoser.md)]
129+
130+
[!include[IntroExceptionDiagnoser](../samples/IntroExceptionDiagnoser.md)]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
uid: BenchmarkDotNet.Samples.IntroExceptionDiagnoser
3+
---
4+
5+
## Sample: IntroExceptionDiagnoser
6+
7+
The `ExceptionDiagnoser` uses [AppDomain.FirstChanceException](https://learn.microsoft.com/en-us/dotnet/api/system.appdomain.firstchanceexception) API to report:
8+
9+
* Exception frequency: The number of exceptions thrown during the operations divided by the number of operations.
10+
11+
### Source code
12+
13+
[!code-csharp[IntroExceptionDiagnoser.cs](../../../samples/BenchmarkDotNet.Samples/IntroExceptionDiagnoser.cs)]
14+
15+
### Output
16+
17+
| Method | Mean | Error | StdDev | Exception frequency |
18+
|----------------------- |---------:|----------:|----------:|--------------------:|
19+
| ThrowExceptionRandomly | 4.936 us | 0.1542 us | 0.4499 us | 0.1381 |
20+
21+
### Links
22+
23+
* @docs.diagnosers
24+
* The permanent link to this sample: @BenchmarkDotNet.Samples.IntroExceptionDiagnoser
25+
26+
---
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using BenchmarkDotNet.Attributes;
2+
using System;
3+
4+
namespace BenchmarkDotNet.Samples
5+
{
6+
[ExceptionDiagnoser]
7+
public class IntroExceptionDiagnoser
8+
{
9+
[Benchmark]
10+
public void ThrowExceptionRandomly()
11+
{
12+
try
13+
{
14+
if (new Random().Next(0, 5) > 1)
15+
{
16+
throw new Exception();
17+
}
18+
}
19+
catch { }
20+
}
21+
}
22+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using BenchmarkDotNet.Configs;
2+
using BenchmarkDotNet.Diagnosers;
3+
using System;
4+
5+
namespace BenchmarkDotNet.Attributes
6+
{
7+
[AttributeUsage(AttributeTargets.Class)]
8+
public class ExceptionDiagnoserAttribute : Attribute, IConfigSource
9+
{
10+
public IConfig Config { get; }
11+
12+
public ExceptionDiagnoserAttribute() => Config = ManualConfig.CreateEmpty().AddDiagnoser(ExceptionDiagnoser.Default);
13+
}
14+
}

src/BenchmarkDotNet/Columns/Column.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public static class Column
5555
public const string CompletedWorkItems = "Completed Work Items";
5656
public const string LockContentions = "Lock Contentions";
5757
public const string CodeSize = "Code Size";
58+
public const string Exceptions = "Exceptions";
5859

5960
//Characteristics:
6061
public const string Id = "Id";

src/BenchmarkDotNet/Configs/ImmutableConfig.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,11 @@ internal ImmutableConfig(
106106

107107
public bool HasThreadingDiagnoser() => diagnosers.Contains(ThreadingDiagnoser.Default);
108108

109+
public bool HasExceptionDiagnoser() => diagnosers.Contains(ExceptionDiagnoser.Default);
110+
109111
internal bool HasPerfCollectProfiler() => diagnosers.OfType<PerfCollectProfiler>().Any();
110112

111-
public bool HasExtraStatsDiagnoser() => HasMemoryDiagnoser() || HasThreadingDiagnoser();
113+
public bool HasExtraStatsDiagnoser() => HasMemoryDiagnoser() || HasThreadingDiagnoser() || HasExceptionDiagnoser();
112114

113115
public IDiagnoser GetCompositeDiagnoser(BenchmarkCase benchmarkCase, RunMode runMode)
114116
{

src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ public class CommandLineOptions
3737
[Option('t', "threading", Required = false, Default = false, HelpText = "Prints threading statistics")]
3838
public bool UseThreadingDiagnoser { get; set; }
3939

40+
[Option("exceptions", Required = false, Default = false, HelpText = "Prints exception statistics")]
41+
public bool UseExceptionDiagnoser { get; set; }
42+
4043
[Option('d', "disasm", Required = false, Default = false, HelpText = "Gets disassembly of benchmarked code")]
4144
public bool UseDisassemblyDiagnoser
4245
{

src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ private static IConfig CreateConfig(CommandLineOptions options, IConfig globalCo
213213
config.AddDiagnoser(MemoryDiagnoser.Default);
214214
if (options.UseThreadingDiagnoser)
215215
config.AddDiagnoser(ThreadingDiagnoser.Default);
216+
if (options.UseExceptionDiagnoser)
217+
config.AddDiagnoser(ExceptionDiagnoser.Default);
216218
if (options.UseDisassemblyDiagnoser)
217219
config.AddDiagnoser(new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig(
218220
maxDepth: options.DisassemblerRecursiveDepth,

src/BenchmarkDotNet/Diagnosers/DiagnoserResults.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public DiagnoserResults(BenchmarkCase benchmarkCase, ExecuteResult executeResult
1515
GcStats = executeResult.GcStats;
1616
ThreadingStats = executeResult.ThreadingStats;
1717
BuildResult = buildResult;
18+
ExceptionFrequency = executeResult.ExceptionFrequency;
1819
}
1920

2021
public BenchmarkCase BenchmarkCase { get; }
@@ -25,6 +26,8 @@ public DiagnoserResults(BenchmarkCase benchmarkCase, ExecuteResult executeResult
2526

2627
public ThreadingStats ThreadingStats { get; }
2728

29+
public double ExceptionFrequency { get; }
30+
2831
public BuildResult BuildResult { get; }
2932
}
3033
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using BenchmarkDotNet.Analysers;
2+
using BenchmarkDotNet.Columns;
3+
using BenchmarkDotNet.Engines;
4+
using BenchmarkDotNet.Exporters;
5+
using BenchmarkDotNet.Loggers;
6+
using BenchmarkDotNet.Reports;
7+
using BenchmarkDotNet.Running;
8+
using BenchmarkDotNet.Validators;
9+
using System;
10+
using System.Collections.Generic;
11+
using System.Linq;
12+
13+
namespace BenchmarkDotNet.Diagnosers
14+
{
15+
public class ExceptionDiagnoser : IDiagnoser
16+
{
17+
public static readonly ExceptionDiagnoser Default = new ExceptionDiagnoser();
18+
19+
private ExceptionDiagnoser() { }
20+
21+
public IEnumerable<string> Ids => new[] { nameof(ExceptionDiagnoser) };
22+
23+
public IEnumerable<IExporter> Exporters => Array.Empty<IExporter>();
24+
25+
public IEnumerable<IAnalyser> Analysers => Array.Empty<IAnalyser>();
26+
27+
public void DisplayResults(ILogger logger) { }
28+
29+
public RunMode GetRunMode(BenchmarkCase benchmarkCase) => RunMode.NoOverhead;
30+
31+
public void Handle(HostSignal signal, DiagnoserActionParameters parameters) { }
32+
33+
public IEnumerable<Metric> ProcessResults(DiagnoserResults results)
34+
{
35+
yield return new Metric(ExceptionsFrequencyMetricDescriptor.Instance, results.ExceptionFrequency);
36+
}
37+
38+
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters) => Enumerable.Empty<ValidationError>();
39+
40+
private class ExceptionsFrequencyMetricDescriptor : IMetricDescriptor
41+
{
42+
internal static readonly IMetricDescriptor Instance = new ExceptionsFrequencyMetricDescriptor();
43+
44+
public string Id => "ExceptionFrequency";
45+
public string DisplayName => Column.Exceptions;
46+
public string Legend => "Exceptions thrown per single operation";
47+
public string NumberFormat => "#0.0000";
48+
public UnitType UnitType => UnitType.Dimensionless;
49+
public string Unit => "Count";
50+
public bool TheGreaterTheBetter => false;
51+
public int PriorityInCategory => 0;
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)