Lyo.Metrics 1.0.0

dotnet add package Lyo.Metrics --version 1.0.0
                    
NuGet\Install-Package Lyo.Metrics -Version 1.0.0
                    
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="Lyo.Metrics" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Lyo.Metrics" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Lyo.Metrics" />
                    
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 Lyo.Metrics --version 1.0.0
                    
#r "nuget: Lyo.Metrics, 1.0.0"
                    
#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 Lyo.Metrics@1.0.0
                    
#: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=Lyo.Metrics&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Lyo.Metrics&version=1.0.0
                    
Install as a Cake Tool

Lyo.Metrics

A flexible, thread-safe metrics library for .NET applications with support for multiple metric types and implementations.

Features

  • Thread-Safe: Built with ConcurrentDictionary and proper locking mechanisms
  • Multiple Metric Types: Counters, Gauges, Histograms, Timings, Errors, Events
  • Multiple Implementations: In-memory, OpenTelemetry, and Null (for testing)
  • Memory Efficient: Bounded collections, automatic cleanup, configurable limits
  • Production Ready: Comprehensive error handling, overflow protection, resource management
  • Flexible Configuration: Sampling, tag validation, cleanup intervals
  • Dependency Injection: First-class support for .NET DI containers
  • Type Flexible: Accepts IConvertible for numeric values (int, long, float, decimal, etc.)

Examples

Subscribe to events

using Lyo.Metrics;

// Create a metrics service
var metrics = new MetricsService();

// Record a counter
metrics.IncrementCounter("requests.total");

// Record a counter with value
metrics.IncrementCounter("bytes.processed", 1024);

// Record a counter with tags
metrics.IncrementCounter("requests.total", tags: [("method", "GET"), ("status", "200")]);

// Record a gauge (current value)
metrics.RecordGauge("cache.size", 1500);

// Record timing using a timer
using (metrics.StartTimer("operation.duration"))
{
    // Your operation here
    await DoSomethingAsync();
}

// Record an error
try
{
    await ProcessDataAsync();
}
catch (Exception ex)
{
    metrics.RecordError("data.processing", ex);
}

Dependency Injection

using Lyo.Metrics;
using Microsoft.Extensions.DependencyInjection;

// Register metrics service
services.AddLyoMetrics();

// Or with custom configuration
services.AddLyoMetrics(options =>
{
    options.MaxEventQueueSize = 50000;
    options.SamplingRate = 0.1; // Sample 10% of metrics
    options.ValidateTags = true;
});

// Use in your services
public class MyService
{
    private readonly IMetrics _metrics;
    
    public MyService(IMetrics metrics)
    {
        _metrics = metrics;
    }
    
    public async Task ProcessAsync()
    {
        using (_metrics.StartTimer("my_service.process"))
        {
            _metrics.IncrementCounter("my_service.calls");
            // Your logic here
        }
    }
}

MetricsOptions

var options = new MetricsOptions
{
    // Maximum number of events to keep in the event queue
    MaxEventQueueSize = 10000,
    
    // Maximum number of values per histogram
    MaxHistogramValues = 1000,
    
    // Whether to throw exceptions on conversion errors
    ThrowOnConversionErrors = false,
    
    // Interval for cleaning up unused key locks (in minutes)
    KeyLockCleanupIntervalMinutes = 60,
    
    // Sampling rate (0.0 to 1.0)
    // 1.0 = record all metrics, 0.5 = record 50% of metrics
    SamplingRate = 1.0,
    
    // Whether to validate and sanitize tag keys/values
    ValidateTags = true,
    
    // Characters not allowed in tag keys/values
    InvalidTagCharacters = new HashSet<char> { '|', '=', '\n', '\r' }
};

var metrics = new MetricsService(options);

Dependency Injection Configuration

// Basic registration
services.AddLyoMetrics();

// With options
services.AddLyoMetrics(options =>
{
    options.MaxEventQueueSize = 50000;
    options.SamplingRate = 0.1;
});

// With options factory
services.AddLyoMetrics((serviceProvider, options) =>
{
    var config = serviceProvider.GetRequiredService<IConfiguration>();
    options.MaxEventQueueSize = config.GetValue<int>("Metrics:MaxEventQueueSize");
    options.SamplingRate = config.GetValue<double>("Metrics:SamplingRate");
});

// From configuration (binds to "MetricsOptions" section by default,
// validating on start via Options.ValidateOnStart())
services.AddLyoMetricsFromConfiguration(configuration);
// Or custom section name:
services.AddLyoMetricsFromConfiguration(configuration, configSectionName: "MyMetrics");

ASP.NET Core Integration

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddLyoMetrics(options =>
        {
            options.MaxEventQueueSize = 50000;
            options.SamplingRate = 1.0;
        });
        
        services.AddControllers();
    }
}

public class MyController : ControllerBase
{
    private readonly IMetrics _metrics;
    
    public MyController(IMetrics metrics)
    {
        _metrics = metrics;
    }
    
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        using (_metrics.StartTimer("api.get.duration"))
        {
            _metrics.IncrementCounter("api.requests", tags: [("endpoint", "get"), ("method", "GET")]);
            
            var result = await ProcessRequestAsync();
            
            _metrics.IncrementCounter("api.requests.success");
            return Ok(result);
        }
    }
}

Background Service Integration

public class MyBackgroundService : BackgroundService
{
    private readonly IMetrics _metrics;
    
    public MyBackgroundService(IMetrics metrics)
    {
        _metrics = metrics;
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using (_metrics.StartTimer("background.job.duration"))
            {
                try
                {
                    await ProcessJobAsync();
                    _metrics.IncrementCounter("background.job.success");
                }
                catch (Exception ex)
                {
                    _metrics.RecordError("background.job", ex);
                    _metrics.IncrementCounter("background.job.failure");
                }
            }
            
            await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        }
    }
}

Errors

try
{
    await ProcessDataAsync();
}
catch (Exception ex)
{
    metrics.RecordError("data.processing", ex);
    
    // With additional tags
    metrics.RecordError("data.processing", ex, tags: [("source", "api"), ("user_id", userId)]);
}

Events

// Simple event
metrics.RecordEvent("user.login");

// Event with value
metrics.RecordEvent("file.uploaded", fileSizeBytes);

// Event with tags
metrics.RecordEvent("user.login", tags: [("provider", "google")]);

Get Counter Value

var metrics = new MetricsService();

metrics.IncrementCounter("requests.total", tags: [("method", "GET")]);

var count = metrics.GetCounterValue("requests.total", tags: [("method", "GET")]);

Get Gauge Value

metrics.RecordGauge("cache.size", 1500);

var size = metrics.GetGaugeValue("cache.size");
if (size.HasValue)
{
    Console.WriteLine($"Cache size: {size.Value}");
}

Get Histogram

metrics.RecordHistogram("response.size", 1024);
metrics.RecordHistogram("response.size", 2048);
metrics.RecordHistogram("response.size", 4096);

var histogram = metrics.GetHistogram("response.size");
if (histogram != null)
{
    var min = histogram.Values.Min();
    var max = histogram.Values.Max();
    var avg = histogram.Values.Average();
    Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
}

Get Events

// Get events (default: last 1000)
var events = metrics.GetEvents(); // last 1000
var events100 = metrics.GetEvents(100);

foreach (var evt in events)
{
    Console.WriteLine($"{evt.Name}: {evt.Value} at {evt.Timestamp}");
}

Clear Metrics

metrics.Clear(); // Clears all counters, gauges, histograms, and events

Export Snapshot

var snapshot = metrics.Export();

Console.WriteLine($"Total metrics recorded: {snapshot.TotalMetricsRecorded}");
Console.WriteLine($"Counters: {snapshot.Counters.Count}");
Console.WriteLine($"Gauges: {snapshot.Gauges.Count}");
Console.WriteLine($"Histograms: {snapshot.Histograms.Count}");

// Serialize to JSON
var json = JsonSerializer.Serialize(snapshot);

1. Use Meaningful Metric Names

// Good
metrics.IncrementCounter("http.requests.total");
metrics.RecordGauge("cache.size_bytes");

// Bad
metrics.IncrementCounter("c1");
metrics.RecordGauge("x");

2. Use Tags for Dimensions

// Good - use tags for filtering/grouping
metrics.IncrementCounter("requests.total", tags: [("method", "GET"), ("status", "200"), ("endpoint", "/api/users")]);

// Bad - create separate metrics for each dimension
metrics.IncrementCounter("requests.get.200.users");
metrics.IncrementCounter("requests.get.200.products");

4. Use Sampling for High-Volume Metrics

var options = new MetricsOptions
{
    SamplingRate = 0.1 // Sample 10% of metrics
};

5. Use Timers for Operations

// Good - automatic timing
using (metrics.StartTimer("operation.duration"))
{
    await DoWorkAsync();
}

// Bad - manual timing (error-prone)
var sw = Stopwatch.StartNew();
try
{
    await DoWorkAsync();
}
finally
{
    sw.Stop();
    metrics.RecordTiming("operation.duration", sw.Elapsed);
}

6. Handle Errors Gracefully

try
{
    await ProcessDataAsync();
}
catch (Exception ex)
{
    metrics.RecordError("data.processing", ex, tags: [("source", "api")]);
    throw; // Re-throw if needed
}

Metric Types — Counters

Counters are monotonic values that only increase (or decrease). They're perfect for tracking totals, rates, and occurrences.

// Increment by 1 (default)
metrics.IncrementCounter("requests.total");

// Increment by specific value
metrics.IncrementCounter("bytes.processed", 1024);

// Decrement counter
metrics.DecrementCounter("items.in_queue", 5);

// With tags
metrics.IncrementCounter("requests.total", tags: [("method", "POST"), ("endpoint", "/api/users")]);

Metric Types — Gauges

Gauges represent a current value at a point in time. They're perfect for tracking current state like cache size, queue length, or memory usage.

// Record current value
metrics.RecordGauge("cache.size", 1500);

// Update gauge value
metrics.RecordGauge("memory.usage_mb", 512.5);

// With tags
metrics.RecordGauge("queue.length", 42, tags: [("queue_name", "email_queue")]);

Metric Types — Histograms

Histograms track the distribution of values. They're perfect for tracking response times, sizes, or any numeric distribution.

// Record a value
metrics.RecordHistogram("response.size_bytes", 2048);

// Record multiple values (they'll be aggregated)
metrics.RecordHistogram("response.size_bytes", 1024);
metrics.RecordHistogram("response.size_bytes", 4096);

// With tags
metrics.RecordHistogram("response.size_bytes", 2048, tags: [("endpoint", "/api/data")]);

Metric Types — Timings

Timings are a special case of histograms for measuring duration. Use the Timer class for automatic timing.

// Using StartTimer (recommended)
using (metrics.StartTimer("operation.duration"))
{
    await DoWorkAsync();
}

// Manual timing
var stopwatch = Stopwatch.StartNew();
await DoWorkAsync();
stopwatch.Stop();
metrics.RecordTiming("operation.duration", stopwatch.Elapsed);

// With tags
using (metrics.StartTimer("database.query", tags: [("table", "users")]))
{
    await QueryDatabaseAsync();
}

MetricsOptions

Configure the behavior of MetricsService:

Dependency Injection Configuration

The IServiceCollection registrations live in Lyo.Metrics.Extensions and cover: parameterless, Action<MetricsOptions>, Action<IServiceProvider, MetricsOptions>, Func<IServiceProvider, MetricsOptions>, and AddLyoMetricsFromConfiguration(IConfiguration, string configSectionName = "MetricsOptions"). AddNullMetrics() registers NullMetrics for the same IMetrics contract.

Implementations — MetricsService (In-Memory)

The default implementation that stores metrics in memory. Perfect for single-instance applications or development.

var metrics = new MetricsService();
// or
var metrics = new MetricsService(new MetricsOptions { ... });

Features:

  • Fast, in-memory storage
  • Thread-safe operations
  • Bounded collections
  • Automatic cleanup
  • Export to snapshot

Use when:

  • Single-instance applications
  • Development/testing
  • Simple metrics requirements
  • No need for distributed observability

Implementations — OpenTelemetryMetrics

Implementation that exports metrics to OpenTelemetry. Perfect for production deployments requiring distributed observability.

using Lyo.Metrics.OpenTelemetry;

services.AddLyoMetricsWithOpenTelemetry("MyApp.Metrics", configureMeterProvider: builder =>
{
    builder.AddConsoleExporter(); // For development
    builder.AddPrometheusExporter(); // For Prometheus scraping
    builder.AddOtlpExporter(options => // For OTLP collection
    {
        options.Endpoint = new Uri("http://otel-collector:4317");
    });
});

Features:

  • OpenTelemetry standard
  • Multiple exporters (Console, Prometheus, OTLP, etc.)
  • Distributed observability
  • Production-grade

Use when:

  • Production deployments
  • Multiple instances/services
  • Integration with monitoring systems (Prometheus, Grafana, etc.)
  • Need for distributed tracing/observability

See Lyo.Metrics.OpenTelemetry README for more details.

Implementations — NullMetrics

No-op implementation for testing or when metrics are optional. The class is a singleton (NullMetrics.Instance) with a private constructor; use the DI extension or the static instance directly.

services.AddNullMetrics();
// or
IMetrics metrics = NullMetrics.Instance;

Features:

  • Zero overhead
  • No exceptions
  • StartTimer returns default(MetricsTimer) — disposal is a no-op, so using (metrics.StartTimer(...)) allocates nothing

Use when:

  • Unit testing
  • Optional metrics
  • Disabling metrics without code changes

Statistics on histograms (MathExtensions)

Lyo.Metrics.MathExtensions bridges recorded histogram values into Lyo.Mathematics.Functions (StatisticsFunctions). The extensions hang off both HistogramData? (so they work on cached snapshots) and MetricsService (so they look up the histogram by name + tags), and return null / empty arrays for missing or empty histograms instead of throwing.

// On a HistogramData? (e.g. from snapshot.Histograms.Values or MetricsService.GetHistogram(...))
HistogramData? h = metrics.GetHistogram("latency.ms");
var stats = h.Describe(sample: true); // DescriptiveStatisticsResult?
var quartiles = h.Quartiles(); // QuartilesResult?
var iqr = h.InterquartileRange();
var p95 = h.Percentile(0.95);
var sma = h.MovingAverage(windowSize: 30);
var ema = h.ExponentialMovingAverage(smoothingFactor: 0.2);
var rollingStd = h.RollingStandardDeviation(windowSize: 30);
var rollingMed = h.RollingMedian(windowSize: 30);
var mad = h.MedianAbsoluteDeviation();
var z = h.LatestZScore();
var anomalousZ = h.IsLatestValueAnomalous(threshold: 3d);
var anomalousMad = h.IsLatestValueAnomalousByMad(threshold: 3.5d);
var ci95 = h.MeanConfidenceInterval(confidenceLevel: 0.95);
var pearson = h.PearsonCorrelation(other); // null if either is empty

// Tag-aware lookups directly on MetricsService
var p99 = metrics.GetHistogramPercentile("latency.ms", percentile: 0.99,
                                           tags: new[] { ("endpoint", "/api/users") });
var pcts = snapshot.GetHistogramPercentiles("latency.ms", 0.5, 0.9, 0.99);
var pearr = metrics.GetHistogramPearsonCorrelation(
                "service_a.latency", "service_b.latency");

Best Practices — 3. Limit Tag Cardinality

Avoid high-cardinality tags (like user IDs) that create too many unique metric combinations.

// Good - low cardinality
metrics.IncrementCounter("requests.total", tags: [("method", "GET"), ("status", "200")]); // Only a few values

// Bad - high cardinality
metrics.IncrementCounter("requests.total", tags: [("user_id", userId)]); // Thousands of unique values!

Thread Safety

All implementations are thread-safe and can be used concurrently from multiple threads:

// Safe to use from multiple threads
Parallel.ForEach(items, item =>
{
    metrics.IncrementCounter("items.processed");
});

Performance Considerations

  • Sampling: Use SamplingRate < 1.0 for high-volume metrics
  • Tag Cardinality: Limit the number of unique tag combinations
  • Histogram Size: Configure MaxHistogramValues appropriately
  • Event Queue: Limit MaxEventQueueSize based on memory constraints

Dependencies

Generated from ProjectReference / PackageReference (same model as docs/Lyo.ProjectGraph.html).

  • Lyo.Exceptions — (direct, lyo)
  • Microsoft.Extensions.DependencyInjection.Abstractions 10.0.5 — (direct, microsoft)
  • Microsoft.Extensions.Options.ConfigurationExtensions 10.0.5 — (direct, microsoft)
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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 is compatible.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (40)

Showing the top 5 NuGet packages that depend on Lyo.Metrics:

Package Downloads
Lyo.IO.Temp

Temp file and directory service with session tracking, logging, and metrics support.

Lyo.Compression

A production-ready .NET compression library providing efficient, thread-safe compression for the built-in BCL algorithms (GZip, Deflate, and on net10+ Brotli, ZLib). Additional algorithms (LZ4, LZMA, Snappier, Zstd, BZip2, XZ) ship as separate Lyo.Compression.* addon packages so consumers only pay for what they use.

Lyo.Cache

Cache service abstractions and local IMemoryCache implementation.

Lyo.MessageQueue

Message queue service interface and base implementation for asynchronous messaging.

Lyo.FileStorage

File storage service interface and base implementation for file operations.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 328 8/16/2026