Diagnosers and Exporters in BenchmarkDotNet: Extending Telemetry and Reporting

Diagnosers and exporters in BenchmarkDotNet are plugin interfaces that capture runtime diagnostics (memory allocations, GC events, threading metrics) and serialize benchmark results into shareable formats (Markdown, HTML, CSV, JSON), respectively.

BenchmarkDotNet is the de facto standard library for micro-benchmarking in .NET applications. To move beyond simple execution time measurements, the framework provides two critical extensibility points: diagnosers for collecting additional runtime telemetry and exporters for formatting results. These components implement the IDiagnoser and IExporter interfaces and are discovered via reflection when the BenchmarkRunner initializes a configuration.

How Diagnosers Work in BenchmarkDotNet

Diagnosers attach to the benchmark execution pipeline through specific lifecycle hooks defined in src/BenchmarkDotNet/Diagnosers/IDiagnoser.cs. When the runner builds a benchmark job, it scans the IConfig for any IDiagnoser implementations and invokes their methods at precise moments during execution.

The Diagnoser Lifecycle Hooks

The IDiagnoser interface provides several methods that the infrastructure calls sequentially:

  • Initialize: Executes before any benchmark runs to allocate resources or set up event listeners (such as ETW or EventPipe subscriptions).
  • BeforeSetup / AfterSetup: Wrap the target benchmark's [GlobalSetup] and [IterationSetup] methods, allowing you to reset counters or state.
  • BeforeMainRun / AfterMainRun: Trigger immediately before and after the measured iteration loop, ideal for starting and stopping high-precision profiling.
  • GetResults: Called after the benchmark completes to return a DiagnoserResult that merges into the final report as additional columns.
  • Cleanup: Executes when the benchmark session ends to release resources and unsubscribe from events.

Built-in Diagnosers

BenchmarkDotNet ships with several ready-to-use diagnosers located in src/BenchmarkDotNet/Diagnosers/:

  • MemoryDiagnoser (implemented in MemoryDiagnoser.cs): Tracks total allocated bytes, allocation count, and GC-related metrics by hooking into .NET garbage collection events.
  • ThreadingDiagnoser: Captures thread-pool usage statistics and lock contention data.
  • EventPipeDiagnoser: Opens an EventPipe session to collect low-level runtime events, available for .NET 5 and later.

How Exporters Work in BenchmarkDotNet

Exporters implement the IExporter interface defined in src/BenchmarkDotNet/Exporters/IExporter.cs. After the benchmark run completes, the BenchmarkRunner gathers all IExporter instances from the configuration and passes them a Summary object containing the measured data plus any diagnoser results.

Each exporter serializes this data into its target representation and writes output files to the BenchmarkDotNet.Artifacts/results/ directory.

Available Exporter Formats

The library provides several built-in exporters for different consumption scenarios:

  • MarkdownExporter (src/BenchmarkDotNet/Exporters/MarkdownExporter.cs): Generates .md files with formatted tables and code fences, optimized for GitHub documentation and README files.
  • HtmlExporter (src/BenchmarkDotNet/Exporters/HtmlExporter.cs): Creates self-contained .html pages with interactive tables and styling, suitable for CI pipeline browsing.
  • CsvExporter: Produces comma-separated values for import into spreadsheets and data-analysis pipelines.
  • JsonExporter: Outputs structured JSON for programmatic consumption by custom tooling, dashboards, or downstream automation.

Configuring Diagnosers and Exporters in Practice

Adding a Diagnoser via ManualConfig

You attach diagnosers by extending ManualConfig from src/BenchmarkDotNet/Config/ManualConfig.cs. The following example enables memory allocation tracking:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Running;

public class MyConfig : ManualConfig
{
    public MyConfig()
    {
        AddDiagnoser(MemoryDiagnoser.Default);
    }
}

public class SampleBenchmarks
{
    [Benchmark]
    public int AllocateArray()
    {
        return new int[1000].Length;
    }
}

public class Program
{
    public static void Main()
    {
        var config = new MyConfig();
        BenchmarkRunner.Run<SampleBenchmarks>(config);
    }
}

Running this configuration produces a markdown report containing an additional Allocated column generated by the MemoryDiagnoser.GetResults method.

Customizing Exporter Output

To generate specific output formats, add exporters to your configuration. This example replaces the default Markdown output with CSV only:

using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Configs;

public class CsvOnlyConfig : ManualConfig
{
    public CsvOnlyConfig()
    {
        AddExporter(CsvExporter.Default);
        RemoveExporter(MarkdownExporter.Default);
    }
}

When running with CsvOnlyConfig, BenchmarkDotNet writes a .csv file to the artifacts folder instead of the default markdown report, as implemented in the CsvExporter.ExportToFile method.

Key Source Files for Diagnosers and Exporters

Component File Path Description
MemoryDiagnoser src/BenchmarkDotNet/Diagnosers/MemoryDiagnoser.cs Core implementation that hooks into .NET GC to capture allocations.
IDiagnoser Interface src/BenchmarkDotNet/Diagnosers/IDiagnoser.cs Defines the lifecycle hooks (Initialize, BeforeMainRun, GetResults, etc.).
MarkdownExporter src/BenchmarkDotNet/Exporters/MarkdownExporter.cs Generates markdown reports with tables and code fences.
HtmlExporter src/BenchmarkDotNet/Exporters/HtmlExporter.cs Produces styled HTML pages with interactive tables.
IExporter Interface src/BenchmarkDotNet/Exporters/IExporter.cs Contract that all exporters implement for result serialization.
ManualConfig src/BenchmarkDotNet/Config/ManualConfig.cs Configuration class showing how diagnosers and exporters are registered.

Summary

  • Diagnosers implement IDiagnoser to inject telemetry collection into the benchmark lifecycle at specific hooks like BeforeMainRun and AfterMainRun, producing DiagnoserResult objects that appear as additional columns.
  • Exporters implement IExporter to convert Summary objects into human-readable files, with built-in support for Markdown, HTML, CSV, and JSON formats.
  • Configuration occurs through ManualConfig using AddDiagnoser() and AddExporter() methods, allowing you to combine built-in and custom implementations.
  • Source implementations reside in src/BenchmarkDotNet/Diagnosers/ and src/BenchmarkDotNet/Exporters/, with key files including MemoryDiagnoser.cs and MarkdownExporter.cs.

Frequently Asked Questions

How do I enable memory allocation tracking in BenchmarkDotNet?

Add the MemoryDiagnoser to your configuration using AddDiagnoser(MemoryDiagnoser.Default) in a custom ManualConfig class, or apply the [MemoryDiagnoser] attribute directly to your benchmark class. This diagnoser hooks into the .NET GC to capture total allocated bytes and GC generation counts, displaying them as additional columns in the final report.

Can I create a custom exporter for BenchmarkDotNet?

Yes, implement the IExporter interface and override the ExportToLog and ExportToFile methods. Your implementation receives a Summary object containing all benchmark results and diagnoser data, which you can format into any proprietary format your organization requires, such as XML or proprietary binary formats.

Where does BenchmarkDotNet save exporter output files?

By default, exporters write files to the BenchmarkDotNet.Artifacts/results/ directory relative to your project root. Each exporter generates files with appropriate extensions (.md, .html, .csv, .json) based on the implementation in MarkdownExporter.cs, HtmlExporter.cs, and related classes.

What is the difference between a diagnoser and a column in BenchmarkDotNet?

A diagnoser is an active component that collects data during benchmark execution using lifecycle hooks like BeforeMainRun and AfterMainRun, while a column is a passive display element in the results table. Diagnosers produce data that typically appears as additional columns (like "Allocated" from MemoryDiagnoser), but the diagnoser itself performs the measurement work rather than just formatting existing data.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →