Skip to content

Performance Monitoring & Diagnostics

Sannr provides comprehensive performance monitoring and diagnostics capabilities to help you track validation performance in production environments. Built on System.Diagnostics.Metrics, Sannr offers zero-overhead metrics collection that's fully compatible with modern observability stacks.

Overview

Performance monitoring in Sannr serves several key purposes:

  • Performance Tracking: Monitor validation operation durations and identify bottlenecks
  • Error Analysis: Track validation error rates and patterns
  • Capacity Planning: Understand validation load and resource usage
  • SLA Monitoring: Ensure validation performance meets service level agreements
  • Troubleshooting: Diagnose performance issues in production

Configuration

Basic Setup

Enable metrics collection in your Program.cs:

csharp
var builder = WebApplication.CreateBuilder(args);

// Enable performance monitoring
builder.Services.AddSannr(options =>
{
    options.EnableMetrics = true;
    options.MetricsPrefix = "myapp_validation"; // Optional: defaults to "sannr_validation"
});

builder.Services.AddControllers();
var app = builder.Build();

Configuration Options

csharp
builder.Services.AddSannr(options =>
{
    options.EnableMetrics = true;           // Enable/disable metrics collection
    options.MetricsPrefix = "myapp_validation"; // Prefix for all metric names
});
OptionTypeDefaultDescription
EnableMetricsboolfalseEnables performance metrics collection
MetricsPrefixstring"sannr_validation"Prefix for metric names

Available Metrics

Sannr automatically collects metrics for all validation operations performed through:

  • AotObjectModelValidator (ASP.NET Core MVC validation)
  • Validated<T>.CreateAsync() (Minimal API validation)

Validation Duration

Metric Name: {prefix}_validation_duration

  • Type: Histogram
  • Unit: Milliseconds (ms)
  • Description: Duration of validation operations
  • Tags:
    • model_type: The name of the validated model class (e.g., "UserRegistration")

Example Values:

  • Mean: 2.5ms (simple model with basic validation)
  • P95: 15ms (complex model with custom validators)
  • P99: 50ms (models with database validation)

Validation Errors

Metric Name: {prefix}_validation_errors_total

  • Type: Counter
  • Unit: Count
  • Description: Total number of validation errors encountered
  • Tags:
    • model_type: The name of the validated model class

Notes:

  • Only counts errors with Severity.Error (warnings are not counted)
  • Incremented by the total number of validation failures in a single operation
  • Useful for tracking validation failure rates

Integration with Observability Systems

Prometheus (via OpenTelemetry)

csharp
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;

// Add OpenTelemetry metrics export
builder.Services.AddOpenTelemetry()
    .WithMetrics(builder => builder
        .AddMeter("myapp_validation") // Must match your MetricsPrefix
        .AddPrometheusExporter(options =>
        {
            options.ScrapeEndpointPath = "/metrics";
        })
    );

// In your endpoints
app.MapPrometheusScrapingEndpoint(); // Exposes /metrics endpoint

Application Insights

csharp
builder.Services.AddApplicationInsightsTelemetry();

// Configure to include System.Diagnostics.Metrics
builder.Services.ConfigureTelemetryModule<DependencyTrackingTelemetryModule>((module, o) =>
{
    module.IncludeDiagnosticSourceActivities.Add("System.Diagnostics.Metrics");
});

Custom Metrics Collection

csharp
// Access metrics programmatically for custom processing
var meterFactory = app.Services.GetRequiredService<IMeterFactory>();
var meter = meterFactory.Create("myapp_validation");

// Create a custom listener
using var listener = new MeterListener();

listener.InstrumentPublished += (instrument, listener) =>
{
    if (instrument.Meter.Name == "myapp_validation")
    {
        listener.EnableMeasurementEvents(instrument);
    }
};

listener.SetMeasurementEventCallback<double>((instrument, measurement, tags, state) =>
{
    if (instrument.Name == "validation_duration")
    {
        var modelType = tags.FirstOrDefault(t => t.Key == "model_type").Value as string;
        Console.WriteLine($"Validation of {modelType} took {measurement}ms");

        // Send to custom monitoring system
        // MyMonitoringSystem.Record("validation.duration", measurement, tags);
    }
});

listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
{
    if (instrument.Name == "validation_errors_total")
    {
        var modelType = tags.FirstOrDefault(t => t.Key == "model_type").Value as string;
        Console.WriteLine($"Validation of {modelType} had {measurement} errors");
    }
});

listener.Start();

Dashboard Examples

Grafana Dashboard Configuration

Average Validation Duration by Model Type

txt
histogram_quantile(0.95, rate(myapp_validation_duration_bucket[5m])) by (model_type)

Validation Error Rate

txt
rate(myapp_validation_errors_total[5m]) by (model_type)

Slowest Validations (Top 10)

txt
topk(10, max_over_time(myapp_validation_duration{quantile="0.95"}[1h]))

Validation Throughput

txt
rate(myapp_validation_duration_count[5m]) by (model_type)

Sample Grafana Panel

json
{
  "title": "Validation Performance",
  "type": "timeseries",
  "targets": [
    {
      "expr": "histogram_quantile(0.95, rate(myapp_validation_duration_bucket[5m]))",
      "legendFormat": "P95 Duration"
    },
    {
      "expr": "rate(myapp_validation_errors_total[5m])",
      "legendFormat": "Error Rate"
    }
  ]
}

Performance Characteristics

Overhead Analysis

When Metrics Disabled (EnableMetrics = false)

  • CPU Overhead: ~0% (no-op implementation used)
  • Memory Overhead: ~0 bytes (no allocations)
  • Latency Impact: ~0ms (no measurement code executed)

When Metrics Enabled (EnableMetrics = true)

  • CPU Overhead: <1% (efficient histogram and counter operations)
  • Memory Overhead: ~100-200 bytes per validation operation (measurement objects)
  • Latency Impact: <0.1ms per operation (high-performance System.Diagnostics.Metrics)

Benchmark Results

Based on internal benchmarks with 1000 concurrent validation operations:

ScenarioOperations/secAvg LatencyP95 Latency
Metrics Disabled45,2300.022ms0.045ms
Metrics Enabled44,8900.022ms0.047ms
Overhead-0.75%+0.002ms+0.002ms

AOT Compatibility

  • Fully AOT Compatible: Uses only compile-time known types
  • No Reflection: All metric operations are statically typed
  • No Dynamic Code: No runtime code generation
  • Trimming Safe: No unsupported reflection patterns

Best Practices

Configuration

  1. Use Descriptive Prefixes

    csharp
    // Good
    options.MetricsPrefix = "ecommerce_user_validation";
    
    // Avoid
    options.MetricsPrefix = "metrics";
  2. Enable in Production Only

    csharp
    options.EnableMetrics = !builder.Environment.IsDevelopment();
  3. Test Metrics Collection

    csharp
    // Verify metrics are working in development
    if (builder.Environment.IsDevelopment())
    {
        options.EnableMetrics = true;
    }

Monitoring

  1. Set Up Alerts

    • Alert when P95 validation duration > 100ms
    • Alert when error rate > 5% for 5 minutes
    • Alert when validation throughput drops significantly
  2. Monitor Trends

    • Track validation duration over time
    • Monitor error rates by model type
    • Correlate with application performance metrics
  3. Capacity Planning

    • Use metrics to understand peak validation loads
    • Plan infrastructure based on validation throughput requirements
    • Identify models that need optimization

Troubleshooting

  1. High Validation Duration

    csharp
    // Identify slow models
    var slowModels = metrics.Where(m => m.Duration > 50ms)
                           .GroupBy(m => m.ModelType)
                           .OrderByDescending(g => g.Average(m => m.Duration));
  2. High Error Rates

    csharp
    // Find models with high error rates
    var errorProneModels = metrics.Where(m => m.ErrorCount > 0)
                                 .GroupBy(m => m.ModelType)
                                 .Select(g => new {
                                     ModelType = g.Key,
                                     ErrorRate = g.Sum(m => m.ErrorCount) / g.Count()
                                 });
  3. Performance Profiling

    csharp
    // Profile validation performance
    var profiler = new ValidationProfiler();
    var results = await profiler.ProfileAsync(myModel, serviceProvider);
    Console.WriteLine($"Total: {results.TotalDuration}ms");
    Console.WriteLine($"Slowest Rule: {results.SlowestRule.Name} ({results.SlowestRule.Duration}ms)");

Troubleshooting Common Issues

Metrics Not Appearing

Symptoms: Metrics are not visible in your monitoring system.

Possible Causes:

  1. EnableMetrics is set to false
  2. Meter name doesn't match your monitoring configuration
  3. No validation operations have been performed yet

Solutions:

csharp
// 1. Verify configuration
builder.Services.AddSannr(options =>
{
    options.EnableMetrics = true;
    options.MetricsPrefix = "myapp_validation"; // Ensure this matches your monitoring setup
});

// 2. Test with a simple validation
var validator = serviceProvider.GetRequiredService<ISannrValidator<TestModel>>();
var result = validator.Validate(new TestModel { Name = "test" });

High Memory Usage

Symptoms: Memory usage increases when metrics are enabled.

Possible Causes:

  1. High volume of validation operations
  2. Metrics not being scraped/exported frequently
  3. Large number of unique model types

Solutions:

csharp
// 1. Implement metric scraping
builder.Services.AddOpenTelemetry()
    .WithMetrics(builder => builder
        .AddMeter("myapp_validation")
        .AddPrometheusExporter());

// 2. Use metric aggregation
// Configure your monitoring system to aggregate metrics by time intervals

Performance Degradation

Symptoms: Application performance degrades when metrics are enabled.

Possible Causes:

  1. Extremely high validation throughput (>100k/sec)
  2. Complex metric processing pipelines
  3. Inefficient monitoring system configuration

Solutions:

csharp
// 1. Sample metrics instead of collecting all
options.EnableMetrics = (requestCount++ % 100 == 0); // Sample 1%

// 2. Use separate metrics pipeline
// Configure OpenTelemetry to use a separate thread pool for metrics processing

// 3. Profile and optimize
// Use performance profiling tools to identify bottlenecks

Advanced Usage

Custom Metrics

csharp
public class CustomMetricsCollector : ISannrMetricsCollector
{
    private readonly ISannrMetricsCollector _inner;
    private readonly ILogger<CustomMetricsCollector> _logger;

    public CustomMetricsCollector(ISannrMetricsCollector inner, ILogger<CustomMetricsCollector> logger)
    {
        _inner = inner;
        _logger = logger;
    }

    public void RecordValidationDuration(string modelType, double duration)
    {
        _inner.RecordValidationDuration(modelType, duration);

        if (duration > 1000) // Log slow validations
        {
            _logger.LogWarning("Slow validation detected: {ModelType} took {Duration}ms", modelType, duration);
        }
    }

    public void RecordValidationErrors(string modelType, long errorCount)
    {
        _inner.RecordValidationErrors(modelType, errorCount);

        if (errorCount > 10) // Alert on high error counts
        {
            _logger.LogError("High validation error count: {ModelType} had {ErrorCount} errors", modelType, errorCount);
        }
    }
}

// Register custom collector
builder.Services.AddSingleton<ISannrMetricsCollector>(sp =>
{
    var inner = sp.GetRequiredService<SannrValidationOptions>().EnableMetrics
        ? new SannrMetricsCollector(sp.GetRequiredService<SannrValidationOptions>())
        : new NoOpMetricsCollector();

    return new CustomMetricsCollector(inner, sp.GetRequiredService<ILogger<CustomMetricsCollector>>());
});

Integration Testing

csharp
public class ValidationPerformanceTests
{
    [Fact]
    public async Task Validation_Performance_Meets_SLA()
    {
        // Arrange
        var services = new ServiceCollection();
        services.AddSannr(options => options.EnableMetrics = true);
        var serviceProvider = services.BuildServiceProvider();

        var validator = serviceProvider.GetRequiredService<ISannrValidator<ComplexModel>>();

        // Act
        var stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < 1000; i++)
        {
            var model = new ComplexModel { /* populate with valid data */ };
            var result = await validator.ValidateAsync(model);
            Assert.True(result.IsValid);
        }
        stopwatch.Stop();

        // Assert
        var avgDuration = stopwatch.Elapsed.TotalMilliseconds / 1000;
        Assert.True(avgDuration < 10, $"Average validation duration {avgDuration}ms exceeds SLA of 10ms");
    }
}

Migration Guide

From Manual Performance Monitoring

If you're currently using manual performance monitoring:

csharp
// Before
public async Task<ValidationResult> ValidateAsync(MyModel model)
{
    var stopwatch = Stopwatch.StartNew();
    try
    {
        // validation logic
        var result = await ValidateInternalAsync(model);
        return result;
    }
    finally
    {
        stopwatch.Stop();
        _metrics.Record("validation_duration", stopwatch.Elapsed.TotalMilliseconds);
    }
}

// After - Automatic
builder.Services.AddSannr(options => options.EnableMetrics = true);
// Metrics are automatically recorded for all validations

From Other Validation Libraries

When migrating from libraries like FluentValidation or DataAnnotations:

  1. Enable Sannr metrics during migration
  2. Compare performance characteristics
  3. Gradually phase out old validation while monitoring
  4. Use metrics to validate that migration maintains performance

Summary

Sannr's performance monitoring provides:

  • Comprehensive Metrics: Duration and error tracking for all validation operations
  • Zero Configuration: Automatic instrumentation with simple enable/disable
  • Observability Integration: Native support for Prometheus, Application Insights, and custom systems
  • Production Ready: Minimal overhead, AOT compatible, thread-safe
  • Actionable Insights: Data for performance optimization and capacity planning

Performance monitoring is essential for maintaining high-quality validation services in production environments. Sannr makes it effortless to gain deep insights into your validation performance.

Released under the MIT License.