use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tracing::debug;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MetricValue {
Counter(u64),
Gauge(f64),
Histogram(Vec<f64>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Metric {
pub name: String,
pub value: MetricValue,
pub labels: HashMap<String, String>,
pub timestamp: u64,
}
pub struct MetricsManager {
module_metrics: Arc<tokio::sync::RwLock<HashMap<String, Vec<Metric>>>>,
}
impl MetricsManager {
pub fn new() -> Self {
Self {
module_metrics: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
}
}
pub async fn report_metric(&self, module_id: String, metric: Metric) {
let mut metrics = self.module_metrics.write().await;
let module_metrics = metrics.entry(module_id.clone()).or_insert_with(Vec::new);
let metric_name = metric.name.clone();
let metric_key = format!("{}_{:?}", metric.name, metric.labels);
if let Some(existing) = module_metrics
.iter_mut()
.find(|m| format!("{}_{:?}", m.name, m.labels) == metric_key)
{
*existing = metric;
} else {
module_metrics.push(metric);
}
debug!("Module {} reported metric: {}", module_id, metric_name);
}
pub async fn get_module_metrics(&self, module_id: &str) -> Vec<Metric> {
let metrics = self.module_metrics.read().await;
metrics.get(module_id).cloned().unwrap_or_default()
}
pub async fn get_all_metrics(&self) -> HashMap<String, Vec<Metric>> {
let metrics = self.module_metrics.read().await;
metrics.clone()
}
pub async fn clear_module_metrics(&self, module_id: &str) {
let mut metrics = self.module_metrics.write().await;
metrics.remove(module_id);
debug!("Cleared metrics for module {}", module_id);
}
pub async fn format_prometheus(&self) -> String {
let metrics = self.module_metrics.read().await;
let mut output = String::new();
for (module_id, module_metrics) in metrics.iter() {
for metric in module_metrics {
let metric_name = format!("blvm_module_{}", metric.name.replace('-', "_"));
let labels_str = if metric.labels.is_empty() {
format!("module_id=\"{module_id}\"")
} else {
let mut label_parts = vec![format!("module_id=\"{}\"", module_id)];
for (key, value) in &metric.labels {
label_parts.push(format!("{}=\"{}\"", key.replace('-', "_"), value));
}
label_parts.join(",")
};
match &metric.value {
MetricValue::Counter(value) => {
output.push_str(&format!("# HELP {metric_name} Module metric counter\n"));
output.push_str(&format!("# TYPE {metric_name} counter\n"));
output.push_str(&format!("{metric_name}{{{labels_str}}} {value}\n"));
}
MetricValue::Gauge(value) => {
output.push_str(&format!("# HELP {metric_name} Module metric gauge\n"));
output.push_str(&format!("# TYPE {metric_name} gauge\n"));
output.push_str(&format!("{metric_name}{{{labels_str}}} {value}\n"));
}
MetricValue::Histogram(buckets) => {
output.push_str(&format!("# HELP {metric_name} Module metric histogram\n"));
output.push_str(&format!("# TYPE {metric_name} histogram\n"));
for (i, bucket_value) in buckets.iter().enumerate() {
output.push_str(&format!(
"{metric_name}{{{labels_str},le=\"{i}\"}} {bucket_value}\n"
));
}
}
}
}
}
output
}
}
impl Default for MetricsManager {
fn default() -> Self {
Self::new()
}
}