Repository navigation
Metrics
Metrics use System.Diagnostics.Metrics from the core library. You do not need an extra package.
Note for advanced users: The library's metric abstractions map to
System.Diagnostics.Metrics(SDM) types as follows: Counter =UpDownCounter<long>, Meter =Counter<long>(monotonic), Histogram =Histogram<long>, Timer =Histogram<double>(unit: ms), Gauge =ObservableGauge<double>. Some metric backends display the SDM type name rather than the library's abstraction name.
Metrics are on by default. The library records these types:
- Counter: state tracking (up/down counts)
- Meter: rate measurement (events per second)
- Histogram: distribution of values
- Timer: duration tracking
- Gauge: observable values through lambda functions
All metrics are published under the DotNetWorkQueue meter name (PascalCase as registered, but some backends normalize it to lowercase). Each metric is prefixed with dotnetworkqueue.{queueName} (e.g. dotnetworkqueue.myqueue.Serializer.SendMessagesMeter). Some backends convert dots to underscores (e.g. dotnetworkqueue_myqueue_Serializer_SendMessagesMeter).
| Metric | Type | What it tracks |
|---|---|---|
SendTimer |
Timer | Time to send a single message |
SendAsyncTimer |
Timer | Time to send a single message (async) |
SendBatchTimer |
Timer | Time to send a batch |
SendMessagesMeter |
Meter | Messages sent (rate) |
SendMessagesErrorMeter |
Meter | Send failures (rate) |
HandleTimer |
Timer | Time in user message handler |
HandleCompiledMethodTimer |
Timer | Time in compiled LINQ handler |
HandleDynamicMethodTimer |
Timer | Time in dynamic LINQ handler |
CommitCounter |
Counter | Messages committed after processing |
RollbackCounter |
Counter | Messages rolled back |
MessageFailedProcessingErrorMeter |
Meter | Messages that failed and moved to error |
MessageFailedProcessingRetryMeter |
Meter | Messages that failed and will retry |
PoisonHandleMeter |
Meter | Poison messages handled |
ResetTimer (heartbeat context) |
Timer | Time to reset stale heartbeats |
ResetCounter (heartbeat context) |
Counter | Heartbeat resets performed |
ClearMessages.ResetTimer |
Timer | Time to clear expired messages |
ClearMessages.ResetCounter |
Counter | Expired messages cleared |
SendTimer (heartbeat context) |
Timer | Time to send a heartbeat update |
ConvertMessageToBytesTimer |
Timer | Serialization time |
ConvertBytesToMessageTimer |
Timer | Deserialization time |
ConvertMessageToBytesHistogram |
Histogram | Serialized message size (bytes) |
CompileActionTimer |
Timer | LINQ expression compile time |
LinqActionCacheHitCounter |
Counter | LINQ cache hits |
LinqActionCacheMissCounter |
Counter | LINQ cache misses |
BytesToMessageTimer |
Timer | Interceptor decode time |
MessageToBytesTimer |
Timer | Interceptor encode time |
WaitingOnTaskCounter |
Counter | Tasks waiting for a free thread (scheduler) |
DotNetWorkQueue does not ship an exporter. Use OpenTelemetry.Metrics with whatever backend you prefer.
One option is to export metrics to Prometheus through the ASP.NET Core exporter and show them in Grafana.
1. Add NuGet packages:
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCore
2. Configure in Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddMeter("DotNetWorkQueue"); // capture all queue metrics
metrics.AddPrometheusExporter();
});
var app = builder.Build();
app.MapPrometheusScrapingEndpoint(); // exposes /metrics
app.Run();3. Scrape from Prometheus. Add this to prometheus.yml:
scrape_configs:
- job_name: 'dotnetworkqueue'
scrape_interval: 15s
static_configs:
- targets: ['localhost:5000'] # your app's host:port4. Query in Prometheus. Go to http://localhost:9090 and type dotnetworkqueue_. Autocomplete then shows all queue metrics.
Note: OTLP export converts dots to underscores in some backends. Metric names may appear as
dotnetworkqueue_myqueue_Serializer_ConvertMessageToBytesHistogram_Bytes_bucket. All DotNetWorkQueue metrics share thedotnetworkqueueprefix.
5. Grafana (optional). The samples repo includes a pre-built Grafana dashboard. It works with any Prometheus-compatible data source.
To import it:
- Add your metrics backend as a data source in Grafana
- Go to Dashboards → New → Import → Upload JSON file
- Select
grafana-dashboard.jsonfrom the samples repo root - Pick your data source when prompted
- The queue name dropdown auto-populates from available metrics
The dashboard includes panels for message throughput (sent, received, committed, rollbacks, errors, retries), send/receive rates, error and retry rates, message handler duration, commits and rollbacks over time, heartbeat and maintenance counters, serialization time and message sizes, and interceptor encode/decode time.

This screenshot is hosted in the samples repo. If the image fails to load, visit the samples repo directly.
For backends that accept OTLP (Prometheus, Grafana Cloud, Azure Monitor, Datadog, etc.):
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddMeter("DotNetWorkQueue");
metrics.AddOtlpExporter(options =>
{
// Prometheus OTLP endpoint (requires --web.enable-otlp-receiver flag)
options.Endpoint = new Uri("http://localhost:9090/api/v1/otlp/v1/metrics");
options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf;
});
});For gRPC-based OTLP collectors (Jaeger, OpenTelemetry Collector), use http://localhost:4317 with the default gRPC protocol instead.
Use this exporter to check that DotNetWorkQueue emits metrics:
dotnet add package OpenTelemetry.Exporter.Console
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddMeter("DotNetWorkQueue");
metrics.AddConsoleExporter();
});If you do not use WebApplication, create the meter provider directly:
using OpenTelemetry;
using OpenTelemetry.Metrics;
var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter("DotNetWorkQueue")
.AddPrometheusHttpListener(options =>
{
options.UriPrefixes = new[] { "http://localhost:9464/" };
})
.Build();
// ... run your consumer/producer ...
// Dispose when shutting down
meterProvider.Dispose();This starts an HTTP listener on port 9464 that Prometheus can scrape.
You can also read metric snapshots directly without an exporter:
// From a consumer queue
var metrics = container.GetInstance<IMetrics>();
var snapshot = metrics.GetCollectedMetrics();
foreach (var counter in snapshot.Counters)
Console.WriteLine($"{counter.Key}: {counter.Value}");
foreach (var meter in snapshot.Meters)
Console.WriteLine($"{meter.Key}: {meter.Value}");Note: GetCollectedMetrics() returns cumulative counter and meter totals. For time-series data and rate calculations, use an OpenTelemetry exporter with a proper metrics backend.
The samples repo includes working examples of OpenTelemetry configuration for both tracing and metrics export. See the SampleShared/Injectors.cs file for how to wire up OpenTelemetry providers.
If you do not configure an OpenTelemetry exporter, the library still records metrics internally. The overhead of the System.Diagnostics.Metrics instrumentation alone is negligible.
For any issues please use the GitHub issues