//! Metrics Collection Overhead Benchmarks //! //! Validates metrics performance targets: //! - Observation overhead: <5us per metric //! - Registry size impact: O(1) lookup //! - Cardinality performance: >1000 unique labels //! - Aggregation overhead: <100us per aggregation //! //! Critical for ensuring observability doesn't impact trading latency. use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::time::Duration; /// Mock metrics registry struct MetricsRegistry { counters: Arc>>, gauges: Arc>>, histograms: Arc>>>, } impl MetricsRegistry { fn new() -> Self { Self { counters: Arc::new(Mutex::new(HashMap::new())), gauges: Arc::new(Mutex::new(HashMap::new())), histograms: Arc::new(Mutex::new(HashMap::new())), } } fn observe_counter(&self, name: String, value: f64) { let mut counters = self.counters.lock().unwrap(); *counters.entry(name).or_insert(0.0) += value; } fn observe_gauge(&self, name: String, value: f64) { let mut gauges = self.gauges.lock().unwrap(); gauges.insert(name, value); } fn observe_histogram(&self, name: String, value: f64) { let mut histograms = self.histograms.lock().unwrap(); histograms.entry(name).or_default().push(value); } } /// Benchmark metric observation overhead fn bench_observation_overhead(c: &mut Criterion) { let mut group = c.benchmark_group("observation_overhead"); group.throughput(Throughput::Elements(1)); let registry = MetricsRegistry::new(); group.bench_function("counter_increment", |b| { b.iter(|| { registry.observe_counter("requests_total".to_string(), 1.0); black_box(®istry) }); }); group.bench_function("gauge_set", |b| { b.iter(|| { registry.observe_gauge("queue_size".to_string(), 42.0); black_box(®istry) }); }); group.bench_function("histogram_observe", |b| { b.iter(|| { registry.observe_histogram("request_duration_ms".to_string(), 15.5); black_box(®istry) }); }); group.finish(); } /// Benchmark registry lookup performance fn bench_registry_lookup(c: &mut Criterion) { let mut group = c.benchmark_group("registry_lookup"); for num_metrics in &[10, 100, 1000, 10000] { group.bench_with_input( BenchmarkId::new("metrics", num_metrics), num_metrics, |b, &count| { let registry = MetricsRegistry::new(); // Pre-populate registry for i in 0..count { registry.observe_counter(format!("metric_{}", i), 1.0); } b.iter(|| { // Lookup random metric let metric_name = format!("metric_{}", count / 2); registry.observe_counter(metric_name, 1.0); black_box(®istry) }); }, ); } group.finish(); } /// Benchmark label cardinality impact fn bench_label_cardinality(c: &mut Criterion) { let mut group = c.benchmark_group("label_cardinality"); for num_labels in &[1, 5, 10, 20] { group.bench_with_input( BenchmarkId::new("labels", num_labels), num_labels, |b, &labels| { b.iter(|| { let mut label_map = HashMap::new(); for i in 0..labels { label_map.insert(format!("label_{}", i), format!("value_{}", i)); } black_box(("request_latency", label_map, 42.0_f64, 0_u64)) }); }, ); } group.finish(); } /// Benchmark metric aggregation fn bench_aggregation(c: &mut Criterion) { let mut group = c.benchmark_group("metric_aggregation"); for sample_count in &[100, 1000, 10000] { group.throughput(Throughput::Elements(*sample_count as u64)); group.bench_with_input( BenchmarkId::new("samples", sample_count), sample_count, |b, &count| { b.iter_batched( || { // Generate sample data (0..count).map(|i| i as f64).collect::>() }, |samples| { // Calculate percentiles let mut sorted = samples.clone(); sorted.sort_by(|a, b| a.partial_cmp(b).unwrap()); let p50 = sorted[count / 2]; let p95 = sorted[(count * 95) / 100]; let p99 = sorted[(count * 99) / 100]; black_box((p50, p95, p99)) }, criterion::BatchSize::SmallInput, ); }, ); } group.finish(); } /// Benchmark concurrent metric updates fn bench_concurrent_updates(c: &mut Criterion) { let mut group = c.benchmark_group("concurrent_updates"); for num_threads in &[1, 2, 4, 8] { group.bench_with_input( BenchmarkId::new("threads", num_threads), num_threads, |b, &threads| { b.iter(|| { let registry = Arc::new(MetricsRegistry::new()); let mut handles = vec![]; for t in 0..threads { let reg = Arc::clone(®istry); let handle = std::thread::spawn(move || { for i in 0..100 { reg.observe_counter(format!("counter_{}_{}", t, i), 1.0); } }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } black_box(registry) }); }, ); } group.finish(); } /// Benchmark histogram bucket operations fn bench_histogram_buckets(c: &mut Criterion) { let mut group = c.benchmark_group("histogram_buckets"); for num_buckets in &[10, 50, 100] { group.bench_with_input( BenchmarkId::new("buckets", num_buckets), num_buckets, |b, &buckets| { b.iter(|| { // Simulate finding appropriate bucket let value = 42.5; let bucket_boundaries: Vec = (0..buckets).map(|i| (i as f64) * 10.0).collect(); let bucket = bucket_boundaries .iter() .position(|&b| value < b) .unwrap_or(buckets - 1); black_box(bucket) }); }, ); } group.finish(); } criterion_group! { name = metrics_benchmarks; config = Criterion::default() .measurement_time(Duration::from_secs(10)) .sample_size(1000) .warm_up_time(Duration::from_secs(2)) .with_plots(); targets = bench_observation_overhead, bench_registry_lookup, bench_label_cardinality, bench_aggregation, bench_concurrent_updates, bench_histogram_buckets } criterion_main!(metrics_benchmarks); #[cfg(test)] mod metrics_validation { #[allow(unused_imports)] use super::*; #[allow(unused_imports)] use std::time::{Duration, Instant}; #[test] fn validate_observation_overhead() { let registry = MetricsRegistry::new(); let iterations = 100_000_u128; let start = Instant::now(); for i in 0..iterations { registry.observe_counter("test_counter".to_string(), i as f64); } let elapsed = start.elapsed(); let avg_overhead_ns = elapsed.as_nanos() / iterations; let avg_overhead_us = avg_overhead_ns / 1000; println!( "Average observation overhead: {}ns ({}us)", avg_overhead_ns, avg_overhead_us ); // Target: <5us = 5000ns assert!( avg_overhead_ns < 5000, "Observation overhead exceeds 5us target: {}ns", avg_overhead_ns ); } #[test] fn validate_registry_scalability() { let registry = MetricsRegistry::new(); // Add many metrics for i in 0..10000 { registry.observe_counter(format!("metric_{}", i), 1.0); } // Measure lookup time with large registry let start = Instant::now(); for _ in 0..1000 { registry.observe_counter("metric_5000".to_string(), 1.0); } let elapsed = start.elapsed(); let avg_lookup_ns = elapsed.as_nanos() / 1000; let metric_count = registry.counters.lock().unwrap().len() + registry.gauges.lock().unwrap().len() + registry.histograms.lock().unwrap().len(); println!( "Registry with {} metrics, avg lookup: {}ns", metric_count, avg_lookup_ns ); // Should maintain O(1) performance assert!( avg_lookup_ns < 10000, "Registry lookup degraded with size: {}ns", avg_lookup_ns ); } #[test] fn validate_label_cardinality() { let max_labels = 20; let iterations = 10_000_u128; let start = Instant::now(); for _ in 0..iterations { let mut labels = HashMap::new(); for i in 0..max_labels { labels.insert(format!("label_{}", i), format!("value_{}", i)); } black_box(labels); } let elapsed = start.elapsed(); let avg_time_ns = elapsed.as_nanos() / iterations; println!( "{} labels per metric, avg creation time: {}ns", max_labels, avg_time_ns ); // Should handle high cardinality efficiently assert!( avg_time_ns < 50000, "Label processing too slow: {}ns", avg_time_ns ); } #[test] fn validate_aggregation_performance() { let sample_count = 10000; let samples: Vec = (0..sample_count).map(|i| i as f64).collect(); let start = Instant::now(); let mut sorted = samples.clone(); sorted.sort_by(|a, b| a.partial_cmp(b).unwrap()); let _p50 = sorted[sample_count / 2]; let _p95 = sorted[(sample_count * 95) / 100]; let _p99 = sorted[(sample_count * 99) / 100]; let elapsed = start.elapsed(); println!("Aggregated {} samples in {:?}", sample_count, elapsed); // Target: <100us for aggregation assert!( elapsed < Duration::from_micros(100), "Aggregation too slow: {:?}", elapsed ); } }