# Diagnosers and Exporters in BenchmarkDotNet: Extending Telemetry and Reporting

> Learn how to extend BenchmarkDotNet telemetry and reporting with diagnosers for diagnostics and exporters for shareable results like Markdown, HTML, CSV, and JSON.

- Repository: [.NET Platform/skills](https://github.com/dotnet/skills)
- Tags: deep-dive
- Published: 2026-07-11

---

**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`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/src/BenchmarkDotNet/Config/ManualConfig.cs). The following example enables memory allocation tracking:

```csharp
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:

```csharp
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`](https://github.com/dotnet/skills/blob/main/src/BenchmarkDotNet/Diagnosers/MemoryDiagnoser.cs) | Core implementation that hooks into .NET GC to capture allocations. |
| **IDiagnoser Interface** | [`src/BenchmarkDotNet/Diagnosers/IDiagnoser.cs`](https://github.com/dotnet/skills/blob/main/src/BenchmarkDotNet/Diagnosers/IDiagnoser.cs) | Defines the lifecycle hooks (`Initialize`, `BeforeMainRun`, `GetResults`, etc.). |
| **MarkdownExporter** | [`src/BenchmarkDotNet/Exporters/MarkdownExporter.cs`](https://github.com/dotnet/skills/blob/main/src/BenchmarkDotNet/Exporters/MarkdownExporter.cs) | Generates markdown reports with tables and code fences. |
| **HtmlExporter** | [`src/BenchmarkDotNet/Exporters/HtmlExporter.cs`](https://github.com/dotnet/skills/blob/main/src/BenchmarkDotNet/Exporters/HtmlExporter.cs) | Produces styled HTML pages with interactive tables. |
| **IExporter Interface** | [`src/BenchmarkDotNet/Exporters/IExporter.cs`](https://github.com/dotnet/skills/blob/main/src/BenchmarkDotNet/Exporters/IExporter.cs) | Contract that all exporters implement for result serialization. |
| **ManualConfig** | [`src/BenchmarkDotNet/Config/ManualConfig.cs`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/MemoryDiagnoser.cs) and [`MarkdownExporter.cs`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/MarkdownExporter.cs), [`HtmlExporter.cs`](https://github.com/dotnet/skills/blob/main/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.