Skip to main content

scirs2_core/profiling/
prometheus_metrics.rs

1//! # Prometheus Metrics Export for SciRS2 v0.2.0
2//!
3//! This module provides Prometheus metrics collection and export capabilities.
4//! It enables monitoring of SciRS2 applications with industry-standard tools.
5//!
6//! # Features
7//!
8//! - **Custom Metrics**: Counters, gauges, histograms, and summaries
9//! - **Automatic Registration**: Automatic metrics registration with global registry
10//! - **HTTP Export**: Expose metrics via HTTP endpoint
11//! - **Performance Counters**: Track computation performance
12//! - **Memory Metrics**: Monitor memory usage
13//!
14//! # Example
15//!
16//! ```rust,no_run
17//! use scirs2_core::profiling::prometheus_metrics::{MetricsRegistry, register_counter};
18//!
19//! // Create a counter
20//! let counter = register_counter(
21//!     "scirs2_operations_total",
22//!     "Total number of operations"
23//! ).expect("Failed to register counter");
24//!
25//! // Increment the counter
26//! counter.inc();
27//!
28//! // Export metrics
29//! let metrics = MetricsRegistry::gather();
30//! println!("{}", metrics);
31//! ```
32
33#[cfg(feature = "profiling_prometheus")]
34use crate::CoreResult;
35#[cfg(feature = "profiling_prometheus")]
36use prometheus::{
37    Counter, CounterVec, Encoder, Gauge, GaugeVec, Histogram, HistogramOpts, HistogramVec, Opts,
38    Registry, TextEncoder,
39};
40#[cfg(feature = "profiling_prometheus")]
41use std::sync::Arc;
42
43/// Global metrics registry
44#[cfg(feature = "profiling_prometheus")]
45static REGISTRY: once_cell::sync::Lazy<Arc<Registry>> =
46    once_cell::sync::Lazy::new(|| Arc::new(Registry::new()));
47
48/// Metrics registry wrapper
49#[cfg(feature = "profiling_prometheus")]
50pub struct MetricsRegistry;
51
52#[cfg(feature = "profiling_prometheus")]
53impl MetricsRegistry {
54    /// Get the global registry
55    pub fn global() -> Arc<Registry> {
56        REGISTRY.clone()
57    }
58
59    /// Gather all metrics in Prometheus text format
60    pub fn gather() -> String {
61        let encoder = TextEncoder::new();
62        let metric_families = REGISTRY.gather();
63        let mut buffer = Vec::new();
64
65        encoder
66            .encode(&metric_families, &mut buffer)
67            .expect("Failed to encode metrics");
68
69        String::from_utf8(buffer).expect("Failed to convert metrics to string")
70    }
71
72    /// Reset all metrics
73    pub fn reset() {
74        // Create a new registry (note: this doesn't actually reset, just for API compatibility)
75        // In practice, individual metrics should be reset
76    }
77}
78
79/// Register a counter metric
80#[cfg(feature = "profiling_prometheus")]
81pub fn register_counter(name: &str, help: &str) -> CoreResult<Counter> {
82    let opts = Opts::new(name, help);
83    let counter = Counter::with_opts(opts).map_err(|e| {
84        crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
85            "Failed to create counter: {}",
86            e
87        )))
88    })?;
89
90    REGISTRY.register(Box::new(counter.clone())).map_err(|e| {
91        crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
92            "Failed to register counter: {}",
93            e
94        )))
95    })?;
96
97    Ok(counter)
98}
99
100/// Register a counter vector metric
101#[cfg(feature = "profiling_prometheus")]
102pub fn register_counter_vec(
103    name: &str,
104    help: &str,
105    label_names: &[&str],
106) -> CoreResult<CounterVec> {
107    let opts = Opts::new(name, help);
108    let counter_vec = CounterVec::new(opts, label_names).map_err(|e| {
109        crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
110            "Failed to create counter vec: {}",
111            e
112        )))
113    })?;
114
115    REGISTRY
116        .register(Box::new(counter_vec.clone()))
117        .map_err(|e| {
118            crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
119                "Failed to register counter vec: {}",
120                e
121            )))
122        })?;
123
124    Ok(counter_vec)
125}
126
127/// Register a gauge metric
128#[cfg(feature = "profiling_prometheus")]
129pub fn register_gauge(name: &str, help: &str) -> CoreResult<Gauge> {
130    let opts = Opts::new(name, help);
131    let gauge = Gauge::with_opts(opts).map_err(|e| {
132        crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
133            "Failed to create gauge: {}",
134            e
135        )))
136    })?;
137
138    REGISTRY.register(Box::new(gauge.clone())).map_err(|e| {
139        crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
140            "Failed to register gauge: {}",
141            e
142        )))
143    })?;
144
145    Ok(gauge)
146}
147
148/// Register a gauge vector metric
149#[cfg(feature = "profiling_prometheus")]
150pub fn register_gauge_vec(name: &str, help: &str, label_names: &[&str]) -> CoreResult<GaugeVec> {
151    let opts = Opts::new(name, help);
152    let gauge_vec = GaugeVec::new(opts, label_names).map_err(|e| {
153        crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
154            "Failed to create gauge vec: {}",
155            e
156        )))
157    })?;
158
159    REGISTRY
160        .register(Box::new(gauge_vec.clone()))
161        .map_err(|e| {
162            crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
163                "Failed to register gauge vec: {}",
164                e
165            )))
166        })?;
167
168    Ok(gauge_vec)
169}
170
171/// Register a histogram metric
172#[cfg(feature = "profiling_prometheus")]
173pub fn register_histogram(name: &str, help: &str, buckets: Vec<f64>) -> CoreResult<Histogram> {
174    let opts = HistogramOpts::new(name, help).buckets(buckets);
175    let histogram = Histogram::with_opts(opts).map_err(|e| {
176        crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
177            "Failed to create histogram: {}",
178            e
179        )))
180    })?;
181
182    REGISTRY
183        .register(Box::new(histogram.clone()))
184        .map_err(|e| {
185            crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
186                "Failed to register histogram: {}",
187                e
188            )))
189        })?;
190
191    Ok(histogram)
192}
193
194/// Register a histogram vector metric
195#[cfg(feature = "profiling_prometheus")]
196pub fn register_histogram_vec(
197    name: &str,
198    help: &str,
199    label_names: &[&str],
200    buckets: Vec<f64>,
201) -> CoreResult<HistogramVec> {
202    let opts = HistogramOpts::new(name, help).buckets(buckets);
203    let histogram_vec = HistogramVec::new(opts, label_names).map_err(|e| {
204        crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
205            "Failed to create histogram vec: {}",
206            e
207        )))
208    })?;
209
210    REGISTRY
211        .register(Box::new(histogram_vec.clone()))
212        .map_err(|e| {
213            crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
214                "Failed to register histogram vec: {}",
215                e
216            )))
217        })?;
218
219    Ok(histogram_vec)
220}
221
222/// Standard buckets for latency metrics (in seconds)
223#[cfg(feature = "profiling_prometheus")]
224pub fn latency_buckets() -> Vec<f64> {
225    vec![
226        0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
227    ]
228}
229
230/// Standard buckets for size metrics (in bytes)
231#[cfg(feature = "profiling_prometheus")]
232pub fn size_buckets() -> Vec<f64> {
233    vec![
234        1024.0,
235        10_240.0,
236        102_400.0,
237        1_048_576.0,
238        10_485_760.0,
239        104_857_600.0,
240        1_073_741_824.0,
241    ]
242}
243
244/// Pre-defined SciRS2 metrics
245#[cfg(feature = "profiling_prometheus")]
246pub struct SciRS2Metrics {
247    /// Total number of operations
248    pub operations_total: CounterVec,
249    /// Operation duration histogram
250    pub operation_duration: HistogramVec,
251    /// Active operations gauge
252    pub active_operations: GaugeVec,
253    /// Memory usage gauge
254    pub memory_usage_bytes: Gauge,
255    /// Array size histogram
256    pub array_size_bytes: Histogram,
257    /// Error counter
258    pub errors_total: CounterVec,
259}
260
261#[cfg(feature = "profiling_prometheus")]
262impl SciRS2Metrics {
263    /// Create and register standard SciRS2 metrics
264    pub fn register() -> CoreResult<Self> {
265        let operations_total = register_counter_vec(
266            "scirs2_operations_total",
267            "Total number of operations",
268            &["operation", "module"],
269        )?;
270
271        let operation_duration = register_histogram_vec(
272            "scirs2_operation_duration_seconds",
273            "Duration of operations in seconds",
274            &["operation", "module"],
275            latency_buckets(),
276        )?;
277
278        let active_operations = register_gauge_vec(
279            "scirs2_active_operations",
280            "Number of active operations",
281            &["operation", "module"],
282        )?;
283
284        let memory_usage_bytes =
285            register_gauge("scirs2_memory_usage_bytes", "Current memory usage in bytes")?;
286
287        let array_size_bytes = register_histogram(
288            "scirs2_array_size_bytes",
289            "Size of arrays in bytes",
290            size_buckets(),
291        )?;
292
293        let errors_total = register_counter_vec(
294            "scirs2_errors_total",
295            "Total number of errors",
296            &["error_type", "module"],
297        )?;
298
299        Ok(Self {
300            operations_total,
301            operation_duration,
302            active_operations,
303            memory_usage_bytes,
304            array_size_bytes,
305            errors_total,
306        })
307    }
308}
309
310/// Timer for measuring operation duration
311#[cfg(feature = "profiling_prometheus")]
312pub struct PrometheusTimer {
313    histogram: Histogram,
314    start: std::time::Instant,
315}
316
317#[cfg(feature = "profiling_prometheus")]
318impl PrometheusTimer {
319    /// Start a new timer
320    pub fn start(histogram: Histogram) -> Self {
321        Self {
322            histogram,
323            start: std::time::Instant::now(),
324        }
325    }
326
327    /// Stop the timer and record the duration
328    pub fn stop(self) {
329        let duration = self.start.elapsed();
330        self.histogram.observe(duration.as_secs_f64());
331    }
332}
333
334#[cfg(feature = "profiling_prometheus")]
335impl Drop for PrometheusTimer {
336    fn drop(&mut self) {
337        let duration = self.start.elapsed();
338        self.histogram.observe(duration.as_secs_f64());
339    }
340}
341
342/// Macro for timing an operation with Prometheus
343#[macro_export]
344#[cfg(feature = "profiling_prometheus")]
345macro_rules! prometheus_time {
346    ($histogram:expr, $body:block) => {{
347        let _timer = $crate::profiling::prometheus_metrics::PrometheusTimer::start($histogram);
348        $body
349    }};
350}
351
352/// Stub implementations when profiling_prometheus feature is disabled
353#[cfg(not(feature = "profiling_prometheus"))]
354pub struct MetricsRegistry;
355
356#[cfg(not(feature = "profiling_prometheus"))]
357impl MetricsRegistry {
358    pub fn gather() -> String {
359        String::new()
360    }
361}
362
363#[cfg(test)]
364#[cfg(feature = "profiling_prometheus")]
365mod tests {
366    use super::*;
367
368    #[test]
369    fn test_register_counter() {
370        let counter = register_counter("test_counter", "Test counter");
371        assert!(counter.is_ok());
372    }
373
374    #[test]
375    fn test_register_gauge() {
376        let gauge = register_gauge("test_gauge", "Test gauge");
377        assert!(gauge.is_ok());
378    }
379
380    #[test]
381    fn test_register_histogram() {
382        let histogram = register_histogram("test_histogram", "Test histogram", latency_buckets());
383        assert!(histogram.is_ok());
384    }
385
386    #[test]
387    fn test_scirs2_metrics() {
388        let metrics = SciRS2Metrics::register();
389        assert!(metrics.is_ok());
390
391        if let Ok(m) = metrics {
392            m.operations_total
393                .with_label_values(&["test", "core"])
394                .inc();
395            m.memory_usage_bytes.set(1024.0);
396            m.array_size_bytes.observe(2048.0);
397        }
398    }
399
400    #[test]
401    fn test_prometheus_timer() {
402        let histogram = register_histogram("test_timer", "Test timer", latency_buckets())
403            .expect("Failed to register histogram");
404
405        let timer = PrometheusTimer::start(histogram);
406        std::thread::sleep(std::time::Duration::from_millis(10));
407        timer.stop();
408    }
409
410    #[test]
411    fn test_metrics_gather() {
412        let _counter = register_counter("gather_test", "Test counter").expect("Failed to register");
413        let metrics = MetricsRegistry::gather();
414        assert!(!metrics.is_empty());
415    }
416}