1#[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#[cfg(feature = "profiling_prometheus")]
45static REGISTRY: once_cell::sync::Lazy<Arc<Registry>> =
46 once_cell::sync::Lazy::new(|| Arc::new(Registry::new()));
47
48#[cfg(feature = "profiling_prometheus")]
50pub struct MetricsRegistry;
51
52#[cfg(feature = "profiling_prometheus")]
53impl MetricsRegistry {
54 pub fn global() -> Arc<Registry> {
56 REGISTRY.clone()
57 }
58
59 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 pub fn reset() {
74 }
77}
78
79#[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#[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#[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#[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#[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#[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#[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#[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#[cfg(feature = "profiling_prometheus")]
246pub struct SciRS2Metrics {
247 pub operations_total: CounterVec,
249 pub operation_duration: HistogramVec,
251 pub active_operations: GaugeVec,
253 pub memory_usage_bytes: Gauge,
255 pub array_size_bytes: Histogram,
257 pub errors_total: CounterVec,
259}
260
261#[cfg(feature = "profiling_prometheus")]
262impl SciRS2Metrics {
263 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#[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 pub fn start(histogram: Histogram) -> Self {
321 Self {
322 histogram,
323 start: std::time::Instant::now(),
324 }
325 }
326
327 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_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#[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}