use prometheus::{Counter, CounterVec, Encoder, Gauge, GaugeVec, Opts, Registry, TextEncoder};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
pub struct Metrics {
registry: Arc<Registry>,
running_servers: Arc<Gauge>,
collection_servers: Arc<GaugeVec>,
task_restarts: Arc<CounterVec>,
task_failures: Arc<CounterVec>,
task_total_started: Arc<Counter>,
server_count: Arc<Mutex<usize>>,
collection_counts: Arc<Mutex<HashMap<String, usize>>>,
}
impl Default for Metrics {
fn default() -> Self {
Self::new()
}
}
impl Metrics {
pub fn new() -> Self {
let registry = Arc::new(Registry::new());
let running_servers = Arc::new(
Gauge::with_opts(Opts::new(
"mrcon_running_servers_total",
"Total number of running connector servers",
))
.expect("Failed to create running_servers gauge"),
);
let collection_servers = Arc::new(
GaugeVec::new(
Opts::new(
"mrcon_collection_servers",
"Number of connector servers per collection",
),
&["collection", "database"],
)
.expect("Failed to create collection_servers gauge"),
);
let task_restarts = Arc::new(
CounterVec::new(
Opts::new(
"mrcon_task_restarts_total",
"Total number of task restarts per collection",
),
&["collection", "database", "reason"],
)
.expect("Failed to create task_restarts counter"),
);
let task_failures = Arc::new(
CounterVec::new(
Opts::new(
"mrcon_task_failures_total",
"Total number of task failures per collection",
),
&["collection", "database", "error_type"],
)
.expect("Failed to create task_failures counter"),
);
let task_total_started = Arc::new(
Counter::with_opts(Opts::new(
"mrcon_tasks_started_total",
"Total number of tasks started since server startup",
))
.expect("Failed to create task_total_started counter"),
);
registry
.register(Box::new((*running_servers).clone()))
.expect("Failed to register running_servers metric");
registry
.register(Box::new((*collection_servers).clone()))
.expect("Failed to register collection_servers metric");
registry
.register(Box::new((*task_restarts).clone()))
.expect("Failed to register task_restarts metric");
registry
.register(Box::new((*task_failures).clone()))
.expect("Failed to register task_failures metric");
registry
.register(Box::new((*task_total_started).clone()))
.expect("Failed to register task_total_started metric");
Self {
registry,
running_servers,
collection_servers,
task_restarts,
task_failures,
task_total_started,
server_count: Arc::new(Mutex::new(0)),
collection_counts: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn increment_servers(&self) {
let mut count = self.server_count.lock().unwrap();
*count += 1;
self.running_servers.set(*count as f64);
}
pub fn decrement_servers(&self) {
let mut count = self.server_count.lock().unwrap();
if *count > 0 {
*count -= 1;
}
self.running_servers.set(*count as f64);
}
pub fn set_server_count(&self, count: usize) {
let mut current_count = self.server_count.lock().unwrap();
*current_count = count;
self.running_servers.set(count as f64);
}
pub fn increment_collection_server(&self, collection: &str, database: &str) {
let key = format!("{}:{}", database, collection);
let mut counts = self.collection_counts.lock().unwrap();
let count = counts.entry(key).or_insert(0);
*count += 1;
self.collection_servers
.with_label_values(&[collection, database])
.set(*count as f64);
}
pub fn decrement_collection_server(&self, collection: &str, database: &str) {
let key = format!("{}:{}", database, collection);
let mut counts = self.collection_counts.lock().unwrap();
if let Some(count) = counts.get_mut(&key) {
if *count > 0 {
*count -= 1;
}
self.collection_servers
.with_label_values(&[collection, database])
.set(*count as f64);
}
}
pub fn set_collection_server_count(&self, collection: &str, database: &str, count: usize) {
let key = format!("{}:{}", database, collection);
let mut counts = self.collection_counts.lock().unwrap();
counts.insert(key, count);
self.collection_servers
.with_label_values(&[collection, database])
.set(count as f64);
}
pub fn record_task_restart(&self, collection: &str, database: &str, reason: &str) {
self.task_restarts
.with_label_values(&[collection, database, reason])
.inc();
}
pub fn record_task_failure(&self, collection: &str, database: &str, error_type: &str) {
self.task_failures
.with_label_values(&[collection, database, error_type])
.inc();
}
pub fn record_task_start(&self) {
self.task_total_started.inc();
}
pub fn get_server_count(&self) -> usize {
*self.server_count.lock().unwrap()
}
pub fn get_collection_server_count(&self, collection: &str, database: &str) -> usize {
let key = format!("{}:{}", database, collection);
let counts = self.collection_counts.lock().unwrap();
*counts.get(&key).unwrap_or(&0)
}
pub fn export(&self) -> Result<String, prometheus::Error> {
let encoder = TextEncoder::new();
let metric_families = self.registry.gather();
let mut buffer = Vec::new();
encoder.encode(&metric_families, &mut buffer)?;
Ok(String::from_utf8_lossy(&buffer).to_string())
}
pub fn registry(&self) -> Arc<Registry> {
self.registry.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metrics_creation() {
let metrics = Metrics::new();
assert_eq!(metrics.get_server_count(), 0);
}
#[test]
fn test_server_count() {
let metrics = Metrics::new();
metrics.increment_servers();
assert_eq!(metrics.get_server_count(), 1);
metrics.increment_servers();
assert_eq!(metrics.get_server_count(), 2);
metrics.decrement_servers();
assert_eq!(metrics.get_server_count(), 1);
metrics.set_server_count(5);
assert_eq!(metrics.get_server_count(), 5);
}
#[test]
fn test_collection_server_count() {
let metrics = Metrics::new();
metrics.increment_collection_server("users", "mydb");
assert_eq!(metrics.get_collection_server_count("users", "mydb"), 1);
metrics.increment_collection_server("users", "mydb");
assert_eq!(metrics.get_collection_server_count("users", "mydb"), 2);
metrics.decrement_collection_server("users", "mydb");
assert_eq!(metrics.get_collection_server_count("users", "mydb"), 1);
metrics.set_collection_server_count("orders", "mydb", 3);
assert_eq!(metrics.get_collection_server_count("orders", "mydb"), 3);
}
#[test]
fn test_export() {
let metrics = Metrics::new();
metrics.set_server_count(2);
metrics.set_collection_server_count("users", "mydb", 1);
metrics.record_task_restart("users", "mydb", "mongo_error");
metrics.record_task_failure("users", "mydb", "connection_failed");
metrics.record_task_start();
let export = metrics.export().unwrap();
assert!(export.contains("mrcon_running_servers_total"));
assert!(export.contains("mrcon_collection_servers"));
assert!(export.contains("mrcon_task_restarts_total"));
assert!(export.contains("mrcon_task_failures_total"));
assert!(export.contains("mrcon_tasks_started_total"));
}
}