High-performance real-time monitoring and metrics collection library for .NET applications. Track system resources, network activity, operation throughput, latency percentiles, and thread pool metrics with minimal overhead. Includes ASP.NET Core integration.
$ dotnet add package Berberis.StatsReportersA high-performance .NET library for real-time monitoring and metrics collection in ASP.NET Core applications. Track system resources, network activity, and custom operation metrics with minimal overhead.
dotnet add package Berberis.StatsReporters
Or via Package Manager:
Install-Package Berberis.StatsReporters
using Berberis.StatsReporters;
var builder = WebApplication.CreateBuilder(args);
// Register the stats reporter factory
builder.Services.AddSingleton<IStatsReporterFactory, StatsReporterFactory>();
var app = builder.Build();
// Get the factory and map stats endpoints
var statsFactory = app.Services.GetRequiredService<IStatsReporterFactory>();
app.MapStats(statsFactory, prefix: "/api/"); // Optional prefix
app.Run();
This exposes the following endpoints:
GET /api/stats/systeminfo - Static system informationGET /api/stats/systemstats - Real-time system metricsGET /api/stats/networkinfo - Network adapter informationGET /api/stats/networkstats - Network bandwidth metricsGET /api/stats/reporters - List all custom reportersGET /api/stats/reporters/{source} - Get stats for a specific reporterAlternative: You can also use app.UseStats(statsFactory, prefix: "/api/") if you prefer the UseEndpoints pattern.
public class MyService
{
private readonly StatsReporter _stats;
public MyService(IStatsReporterFactory factory)
{
_stats = factory.GetOrCreateReporter("MyService.ProcessData");
}
public async Task ProcessDataAsync(byte[] data)
{
var start = _stats.Start();
// Your operation here
await DoWorkAsync(data);
// Track the operation with byte count
_stats.Stop(start, data.Length);
}
}
If you have pre-calculated metrics, you can record them directly:
public class BatchProcessor
{
private readonly StatsReporter _stats;
public BatchProcessor(IStatsReporterFactory factory)
{
_stats = factory.GetOrCreateReporter("BatchProcessor");
}
public void RecordBatchMetrics(long itemsProcessed, float totalServiceTimeMs, long totalBytes)
{
// Record pre-calculated metrics
_stats.Record(
units: itemsProcessed,
serviceTimeMs: totalServiceTimeMs,
bytes: totalBytes
);
}
}
Track latencies and get percentile statistics:
public class ApiHandler
{
private readonly ServiceTimeTracker _tracker;
public ApiHandler()
{
_tracker = new ServiceTimeTracker(
ewmaWindowSize: 100,
percentileOptions: new[]
{
new PercentileOptions(50), // p50 (median)
new PercentileOptions(95), // p95
new PercentileOptions(99), // p99
new PercentileOptions(99.9f) // p99.9
}
);
}
public async Task<Response> HandleRequestAsync()
{
var start = ServiceTimeTracker.GetTicks();
var response = await ProcessRequestAsync();
_tracker.RecordServiceTime(start);
return response;
}
public ServiceTimeStats GetStats()
{
return _tracker.GetStats(reset: false);
// Returns: IntervalMs, ProcessRate, IntervalMessages, TotalProcessedMessages,
// AvgServiceTimeMs, MinServiceTimeMs, MaxServiceTimeMs, PercentileValues[]
}
}
Measure how long items wait in the thread pool queue:
public class ThreadPoolMonitor
{
private readonly ThreadPoolLatencyTracker _latencyTracker;
public ThreadPoolMonitor()
{
// Results are cached for 5 seconds by default
_latencyTracker = new ThreadPoolLatencyTracker(numberOfMeasurements: 10_000);
// Or customize cache duration
// _latencyTracker = new ThreadPoolLatencyTracker(10_000, cacheDuration: TimeSpan.FromSeconds(10));
// Or disable caching entirely
// _latencyTracker = new ThreadPoolLatencyTracker(10_000, cacheDuration: TimeSpan.Zero);
}
public void CheckLatency()
{
// Concurrent calls within cache window return the same cached result
var latencyStats = _latencyTracker.Measure();
Console.WriteLine($"Thread pool latency:");
Console.WriteLine($" Median: {latencyStats.MedianMs:F2}ms");
Console.WriteLine($" P90: {latencyStats.P90Ms:F2}ms");
Console.WriteLine($" P99: {latencyStats.P99Ms:F2}ms");
Console.WriteLine($" P99.99: {latencyStats.P99_99Ms:F2}ms");
}
}
Caching Behavior:
Measure() calls during an active measurement wait and receive the same resultTimeSpan.Zero to disable caching and measure on every callpublic class SystemMonitor
{
private readonly SystemStatsReporter _systemStats;
public SystemMonitor()
{
// Pass true to enable thread pool latency tracking
_systemStats = new SystemStatsReporter(measureThreadPoolLatency: true);
}
public void PrintSystemInfo()
{
var info = _systemStats.SystemInfo;
Console.WriteLine($"CPU Cores: {info["CPU Cores"]}");
Console.WriteLine($"GC Server Mode: {info["GC Server Mode"]}");
Console.WriteLine($"Process ID: {info["Process Id"]}");
}
public void MonitorResources()
{
var stats = _systemStats.GetStats();
Console.WriteLine($"CPU Usage: {stats.CpuUsagePercent:F1}%");
Console.WriteLine($"Working Set: {stats.WorkingSetBytes / 1024 / 1024} MB");
Console.WriteLine($"GC Memory: {stats.GcTotalMemory / 1024 / 1024} MB");
Console.WriteLine($"Thread Count: {stats.ThreadCount}");
Console.WriteLine($"Thread Pool: {stats.ThreadPoolThreadCount}");
Console.WriteLine($"GC Gen0: {stats.Gc0} collections");
}
}
public class NetworkMonitor
{
private readonly NetworkStatsReporter _networkStats;
public NetworkMonitor()
{
_networkStats = new NetworkStatsReporter();
}
public void PrintNetworkInfo()
{
foreach (var adapter in _networkStats.NetworkInfo)
{
Console.WriteLine($"Adapter: {adapter["Name"]}");
Console.WriteLine($" Type: {adapter["Type"]}");
Console.WriteLine($" Status: {adapter["Status"]}");
Console.WriteLine($" Speed: {adapter["Speed"]} bps");
}
}
public void MonitorBandwidth()
{
var stats = _networkStats.GetStats();
var primary = stats.PrimaryAdapter;
Console.WriteLine($"Download: {primary.RcvBytesPerSecond / 1024 / 1024:F2} MB/s");
Console.WriteLine($"Upload: {primary.SndBytesPerSecond / 1024 / 1024:F2} MB/s");
// Per-adapter stats
foreach (var (name, adapterStats) in stats.AdapterStats)
{
Console.WriteLine($"{name}: ↓{adapterStats.RcvBytesPerSecond / 1024:F0} KB/s " +
$"↑{adapterStats.SndBytesPerSecond / 1024:F0} KB/s");
}
}
}
The factory manages multiple reporters and provides lifecycle management:
public class ApplicationMetrics
{
private readonly IStatsReporterFactory _factory;
public ApplicationMetrics(IStatsReporterFactory factory)
{
_factory = factory;
}
public void TrackOperation(string operationName, Action operation)
{
var reporter = _factory.GetOrCreateReporter(operationName);
var start = reporter.Start();
operation();
reporter.Stop(start);
}
public void PrintAllStats()
{
foreach (var reporterName in _factory.ListReporters())
{
var stats = _factory.GetReporterStats(reporterName);
Console.WriteLine($"{reporterName}:");
Console.WriteLine($" Rate: {stats.MessagesPerSecond:F1} ops/s");
Console.WriteLine($" Avg Time: {stats.AvgServiceTime:F2} ms");
Console.WriteLine($" Total: {stats.TotalMessages}");
}
}
}
Enable manual GC collection for debugging (use with caution in production):
app.MapGCOperations(prefix: "/api/");
// Exposes: GET /api/dbg/gccollect
// Alternative using UseEndpoints pattern:
app.UseGCOperations(prefix: "/api/");
public readonly struct Stats
{
public readonly float IntervalMs; // Measurement window in ms
public readonly float MessagesPerSecond; // Operations per second
public readonly float TotalMessages; // Total operations counted
public readonly float BytesPerSecond; // Throughput in bytes/s
public readonly float TotalBytes; // Total bytes processed
public readonly float AvgServiceTime; // Average latency in ms
}
Includes CPU usage, memory, GC stats, thread pool metrics, lock contention, and more.
public readonly struct ServiceTimeStats
{
public readonly float IntervalMs; // Measurement window in ms
public readonly float ProcessRate; // Operations per second
public readonly long IntervalMessages; // Messages in this interval
public readonly long TotalProcessedMessages; // Total messages processed
public readonly float AvgServiceTimeMs; // Average latency
public readonly float MinServiceTimeMs; // Minimum latency
public readonly float MaxServiceTimeMs; // Maximum latency
public readonly (float percentile, float value)[] PercentileValues; // Configured percentiles
}
See LICENSE file in the repository.
Contributions are welcome! Please submit issues and pull requests on GitHub.