Berberis.StatsReporters 1.1.8

dotnet add package Berberis.StatsReporters --version 1.1.8
                    
NuGet\Install-Package Berberis.StatsReporters -Version 1.1.8
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Berberis.StatsReporters" Version="1.1.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Berberis.StatsReporters" Version="1.1.8" />
                    
Directory.Packages.props
<PackageReference Include="Berberis.StatsReporters" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Berberis.StatsReporters --version 1.1.8
                    
#r "nuget: Berberis.StatsReporters, 1.1.8"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Berberis.StatsReporters@1.1.8
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Berberis.StatsReporters&version=1.1.8
                    
Install as a Cake Addin
#tool nuget:?package=Berberis.StatsReporters&version=1.1.8
                    
Install as a Cake Tool

Berberis.StatsReporters

NuGet Version NuGet Downloads Build Status License .NET Version

A 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.

Installation

dotnet add package Berberis.StatsReporters

Or via Package Manager:

Install-Package Berberis.StatsReporters

Features

  • System Monitoring: CPU usage, memory consumption, GC statistics, thread pool metrics
  • Network Monitoring: Network interface statistics, bandwidth tracking per adapter
  • Custom Metrics: Track throughput, latency, and service times for any operation
  • Thread Pool Latency: Measure thread pool scheduling latency
  • Service Time Tracking: Track operation latencies with percentile calculations (p50, p95, p99)
  • ASP.NET Core Integration: Built-in HTTP endpoints to expose metrics
  • Thread-Safe: All reporters are designed for concurrent access

Quick Start

Basic Usage with ASP.NET Core

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 information
  • GET /api/stats/systemstats - Real-time system metrics
  • GET /api/stats/networkinfo - Network adapter information
  • GET /api/stats/networkstats - Network bandwidth metrics
  • GET /api/stats/reporters - List all custom reporters
  • GET /api/stats/reporters/{source} - Get stats for a specific reporter

Alternative: You can also use app.UseStats(statsFactory, prefix: "/api/") if you prefer the UseEndpoints pattern.

Tracking Custom Operations

Simple Operation Tracking

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);
    }
}

Recording Pre-Calculated Metrics

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
        );
    }
}

Service Time Tracking with Percentiles

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[]
    }
}

Thread Pool Latency Monitoring

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:

  • Results are cached for 5 seconds by default to prevent redundant measurements
  • Concurrent Measure() calls during an active measurement wait and receive the same result
  • Only one actual measurement occurs at a time (deduplication)
  • Pass TimeSpan.Zero to disable caching and measure on every call

System Monitoring

System Stats

public 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");
    }
}

Network Stats

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");
        }
    }
}

Advanced Features

Using the Stats Reporter Factory

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}");
        }
    }
}

GC Operations Endpoint

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/");

Stats Output

Stats Structure

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
}

System Stats Structure

Includes CPU usage, memory, GC stats, thread pool metrics, lock contention, and more.

Service Time Stats

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
}

Performance Considerations

  • All reporters use lock-free operations where possible (Interlocked operations)
  • Minimal allocation during tracking (struct-based stats)
  • Thread-safe by design
  • Negligible overhead for operation tracking (<100ns per operation)
  • Stats calculation is separate from tracking (pull-based model)

Use Cases

  • Production Monitoring: Track application health and performance
  • Load Testing: Measure throughput and latency under load
  • Debugging: Identify performance bottlenecks
  • SLA Monitoring: Track percentile latencies
  • Capacity Planning: Monitor resource utilization trends

Requirements

  • .NET 8.0 or higher
  • ASP.NET Core (for endpoint integration)

License

See LICENSE file in the repository.

Contributing

Contributions are welcome! Please submit issues and pull requests on GitHub.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Berberis.StatsReporters:

Package Downloads
ThreadPoolLatencyDemo

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.8 5,733 10/26/2025
1.1.7 174 10/24/2025
1.1.6 215 10/23/2025
1.1.5 19,293 6/5/2024
1.1.4 1,553 7/21/2023
1.1.3 268 7/21/2023
1.1.2 251 6/23/2023
1.1.1 3,255 6/19/2023
1.1.0 267 6/12/2023
1.0.9 5,819 12/18/2022
1.0.8 432 12/11/2022
1.0.7 408 12/11/2022
1.0.6 485 11/4/2022
1.0.5 591 6/27/2022
1.0.4 556 6/26/2022
1.0.3 539 6/24/2022
1.0.2 545 6/24/2022
1.0.1 559 6/24/2022
1.0.0 561 6/24/2022