veza/veza-common/src/metrics.rs
senke 0e7b6fede1 [T0-002] fix(rust): Corriger erreurs compilation Rust
- Conflit SQLx résolu (alignement sur version 0.7)
- build.rs configurés pour protoc dans chat/stream servers
- API Prometheus migrée vers HistogramOpts
- Traits Display/Debug corrigés (String au lieu de &dyn Display)
- API TOTP corrigée (totp-rs 5.4 avec Secret::Encoded)
- Layers tracing-subscriber corrigés (types conditionnels)
- VezaError/VezaResult exportés dans lib.rs
- TransactionProvider simplifié (retour void au lieu de Box<dyn>)
- VezaConfig contraint Serialize pour to_json()

Files: veza-common/Cargo.toml, veza-common/src/*.rs, veza-chat-server/Cargo.toml, veza-chat-server/build.rs, veza-stream-server/Cargo.toml, veza-stream-server/build.rs, VEZA_ROADMAP.json
Hours: 8 estimated, 3 actual
2026-01-04 01:44:20 +01:00

357 lines
13 KiB
Rust

//! Metrics utilities for Veza Rust services
//!
//! This module provides centralized metrics collection and reporting.
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use prometheus::{
Counter, CounterVec, Gauge, GaugeVec, Histogram, HistogramVec,
HistogramOpts, Registry, Encoder, TextEncoder, Opts,
};
use crate::{VezaError, VezaResult};
/// Metrics collector for Veza services
pub struct MetricsCollector {
registry: Registry,
counters: HashMap<String, Counter>,
counter_vecs: HashMap<String, CounterVec>,
gauges: HashMap<String, Gauge>,
gauge_vecs: HashMap<String, GaugeVec>,
histograms: HashMap<String, Histogram>,
histogram_vecs: HashMap<String, HistogramVec>,
}
impl MetricsCollector {
/// Create a new metrics collector
pub fn new() -> VezaResult<Self> {
let registry = Registry::new();
Ok(Self {
registry,
counters: HashMap::new(),
counter_vecs: HashMap::new(),
gauges: HashMap::new(),
gauge_vecs: HashMap::new(),
histograms: HashMap::new(),
histogram_vecs: HashMap::new(),
})
}
/// Register a counter
pub fn register_counter(&mut self, name: &str, help: &str) -> VezaResult<()> {
let counter = Counter::with_opts(Opts::new(name, help))
.map_err(|e| VezaError::Internal(format!("Failed to create counter {}: {}", name, e)))?;
self.registry.register(Box::new(counter.clone()))
.map_err(|e| VezaError::Internal(format!("Failed to register counter {}: {}", name, e)))?;
self.counters.insert(name.to_string(), counter);
Ok(())
}
/// Register a counter vector
pub fn register_counter_vec(&mut self, name: &str, help: &str, labels: &[&str]) -> VezaResult<()> {
let counter_vec = CounterVec::new(Opts::new(name, help), labels)
.map_err(|e| VezaError::Internal(format!("Failed to create counter vec {}: {}", name, e)))?;
self.registry.register(Box::new(counter_vec.clone()))
.map_err(|e| VezaError::Internal(format!("Failed to register counter vec {}: {}", name, e)))?;
self.counter_vecs.insert(name.to_string(), counter_vec);
Ok(())
}
/// Register a gauge
pub fn register_gauge(&mut self, name: &str, help: &str) -> VezaResult<()> {
let gauge = Gauge::with_opts(Opts::new(name, help))
.map_err(|e| VezaError::Internal(format!("Failed to create gauge {}: {}", name, e)))?;
self.registry.register(Box::new(gauge.clone()))
.map_err(|e| VezaError::Internal(format!("Failed to register gauge {}: {}", name, e)))?;
self.gauges.insert(name.to_string(), gauge);
Ok(())
}
/// Register a gauge vector
pub fn register_gauge_vec(&mut self, name: &str, help: &str, labels: &[&str]) -> VezaResult<()> {
let gauge_vec = GaugeVec::new(Opts::new(name, help), labels)
.map_err(|e| VezaError::Internal(format!("Failed to create gauge vec {}: {}", name, e)))?;
self.registry.register(Box::new(gauge_vec.clone()))
.map_err(|e| VezaError::Internal(format!("Failed to register gauge vec {}: {}", name, e)))?;
self.gauge_vecs.insert(name.to_string(), gauge_vec);
Ok(())
}
/// Register a histogram
pub fn register_histogram(&mut self, name: &str, help: &str, buckets: Option<Vec<f64>>) -> VezaResult<()> {
let mut opts = HistogramOpts::new(name, help);
if let Some(buckets) = buckets {
opts = opts.buckets(buckets);
}
let histogram = Histogram::with_opts(opts)
.map_err(|e| VezaError::Internal(format!("Failed to create histogram {}: {}", name, e)))?;
self.registry.register(Box::new(histogram.clone()))
.map_err(|e| VezaError::Internal(format!("Failed to register histogram {}: {}", name, e)))?;
self.histograms.insert(name.to_string(), histogram);
Ok(())
}
/// Register a histogram vector
pub fn register_histogram_vec(&mut self, name: &str, help: &str, labels: &[&str], buckets: Option<Vec<f64>>) -> VezaResult<()> {
let mut opts = HistogramOpts::new(name, help);
if let Some(buckets) = buckets {
opts = opts.buckets(buckets);
}
let histogram_vec = HistogramVec::new(opts, labels)
.map_err(|e| VezaError::Internal(format!("Failed to create histogram vec {}: {}", name, e)))?;
self.registry.register(Box::new(histogram_vec.clone()))
.map_err(|e| VezaError::Internal(format!("Failed to register histogram vec {}: {}", name, e)))?;
self.histogram_vecs.insert(name.to_string(), histogram_vec);
Ok(())
}
/// Increment a counter
pub fn increment_counter(&self, name: &str) -> VezaResult<()> {
if let Some(counter) = self.counters.get(name) {
counter.inc();
Ok(())
} else {
Err(VezaError::Internal(format!("Counter {} not found", name)))
}
}
/// Increment a counter by value
pub fn increment_counter_by(&self, name: &str, value: f64) -> VezaResult<()> {
if let Some(counter) = self.counters.get(name) {
counter.inc_by(value);
Ok(())
} else {
Err(VezaError::Internal(format!("Counter {} not found", name)))
}
}
/// Increment a counter vector
pub fn increment_counter_vec(&self, name: &str, labels: &[&str], value: f64) -> VezaResult<()> {
if let Some(counter_vec) = self.counter_vecs.get(name) {
counter_vec.with_label_values(labels).inc_by(value);
Ok(())
} else {
Err(VezaError::Internal(format!("Counter vec {} not found", name)))
}
}
/// Set a gauge value
pub fn set_gauge(&self, name: &str, value: f64) -> VezaResult<()> {
if let Some(gauge) = self.gauges.get(name) {
gauge.set(value);
Ok(())
} else {
Err(VezaError::Internal(format!("Gauge {} not found", name)))
}
}
/// Set a gauge vector value
pub fn set_gauge_vec(&self, name: &str, labels: &[&str], value: f64) -> VezaResult<()> {
if let Some(gauge_vec) = self.gauge_vecs.get(name) {
gauge_vec.with_label_values(labels).set(value);
Ok(())
} else {
Err(VezaError::Internal(format!("Gauge vec {} not found", name)))
}
}
/// Observe a histogram value
pub fn observe_histogram(&self, name: &str, value: f64) -> VezaResult<()> {
if let Some(histogram) = self.histograms.get(name) {
histogram.observe(value);
Ok(())
} else {
Err(VezaError::Internal(format!("Histogram {} not found", name)))
}
}
/// Observe a histogram vector value
pub fn observe_histogram_vec(&self, name: &str, labels: &[&str], value: f64) -> VezaResult<()> {
if let Some(histogram_vec) = self.histogram_vecs.get(name) {
histogram_vec.with_label_values(labels).observe(value);
Ok(())
} else {
Err(VezaError::Internal(format!("Histogram vec {} not found", name)))
}
}
/// Get metrics as string
pub fn get_metrics(&self) -> VezaResult<String> {
let encoder = TextEncoder::new();
let metric_families = self.registry.gather();
encoder.encode_to_string(&metric_families)
.map_err(|e| VezaError::Internal(format!("Failed to encode metrics: {}", e)))
}
}
impl Default for MetricsCollector {
fn default() -> Self {
Self::new().expect("Failed to create metrics collector")
}
}
/// Global metrics collector
pub static mut GLOBAL_METRICS: Option<Arc<MetricsCollector>> = None;
/// Initialize global metrics
pub fn init_global_metrics() -> VezaResult<()> {
let mut collector = MetricsCollector::new()?;
// Register common metrics
collector.register_counter("veza_requests_total", "Total number of requests")?;
collector.register_counter_vec("veza_requests_by_method", "Requests by HTTP method", &["method"])?;
collector.register_counter_vec("veza_requests_by_status", "Requests by HTTP status", &["status"])?;
collector.register_histogram("veza_request_duration_seconds", "Request duration in seconds", None)?;
collector.register_gauge("veza_active_connections", "Number of active connections")?;
collector.register_gauge("veza_active_users", "Number of active users")?;
collector.register_counter("veza_errors_total", "Total number of errors")?;
collector.register_counter_vec("veza_errors_by_type", "Errors by type", &["type"])?;
collector.register_counter("veza_messages_total", "Total number of messages")?;
collector.register_counter("veza_tracks_streamed_total", "Total number of tracks streamed")?;
collector.register_histogram("veza_message_size_bytes", "Message size in bytes", None)?;
collector.register_histogram("veza_track_duration_seconds", "Track duration in seconds", None)?;
unsafe {
GLOBAL_METRICS = Some(Arc::new(collector));
}
Ok(())
}
/// Get global metrics collector
pub fn get_global_metrics() -> VezaResult<Arc<MetricsCollector>> {
unsafe {
GLOBAL_METRICS
.as_ref()
.ok_or_else(|| VezaError::Internal("Global metrics not initialized".to_string()))
.map(|m| m.clone())
}
}
/// Increment request counter
pub fn increment_request_counter() -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.increment_counter("veza_requests_total")
}
/// Increment request counter by method
pub fn increment_request_counter_by_method(method: &str) -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.increment_counter_vec("veza_requests_by_method", &[method], 1.0)
}
/// Increment request counter by status
pub fn increment_request_counter_by_status(status: u16) -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.increment_counter_vec("veza_requests_by_status", &[&status.to_string()], 1.0)
}
/// Observe request duration
pub fn observe_request_duration(duration: Duration) -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.observe_histogram("veza_request_duration_seconds", duration.as_secs_f64())
}
/// Set active connections gauge
pub fn set_active_connections(count: f64) -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.set_gauge("veza_active_connections", count)
}
/// Set active users gauge
pub fn set_active_users(count: f64) -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.set_gauge("veza_active_users", count)
}
/// Increment error counter
pub fn increment_error_counter() -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.increment_counter("veza_errors_total")
}
/// Increment error counter by type
pub fn increment_error_counter_by_type(error_type: &str) -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.increment_counter_vec("veza_errors_by_type", &[error_type], 1.0)
}
/// Increment message counter
pub fn increment_message_counter() -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.increment_counter("veza_messages_total")
}
/// Increment tracks streamed counter
pub fn increment_tracks_streamed_counter() -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.increment_counter("veza_tracks_streamed_total")
}
/// Observe message size
pub fn observe_message_size(size: usize) -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.observe_histogram("veza_message_size_bytes", size as f64)
}
/// Observe track duration
pub fn observe_track_duration(duration: Duration) -> VezaResult<()> {
let metrics = get_global_metrics()?;
metrics.observe_histogram("veza_track_duration_seconds", duration.as_secs_f64())
}
/// Get all metrics as string
pub fn get_all_metrics() -> VezaResult<String> {
let metrics = get_global_metrics()?;
metrics.get_metrics()
}
/// Timer for measuring duration
pub struct Timer {
start: Instant,
metric_name: String,
}
impl Timer {
/// Start a new timer
pub fn start(metric_name: &str) -> Self {
Self {
start: Instant::now(),
metric_name: metric_name.to_string(),
}
}
/// Stop the timer and record the metric
pub fn stop(self) -> VezaResult<()> {
let duration = self.start.elapsed();
let metrics = get_global_metrics()?;
metrics.observe_histogram(&self.metric_name, duration.as_secs_f64())
}
}
/// Macro for timing operations
#[macro_export]
macro_rules! time_operation {
($metric_name:expr, $operation:expr) => {{
let timer = crate::metrics::Timer::start($metric_name);
let result = $operation;
timer.stop()?;
result
}};
}