use std::sync::OnceLock;
use std::sync::atomic::{AtomicI64, Ordering};
use jiff::Timestamp;
use miette::{IntoDiagnostic, Result};
use prometheus::{Encoder, IntCounter, IntGauge, Registry, TextEncoder};
static REGISTRY: OnceLock<Registry> = OnceLock::new();
static ALERTS_LOADED: OnceLock<IntGauge> = OnceLock::new();
static ALERTS_SENT_TOTAL: OnceLock<IntCounter> = OnceLock::new();
static ALERTS_FAILED_TOTAL: OnceLock<IntCounter> = OnceLock::new();
static RELOADS_TOTAL: OnceLock<IntCounter> = OnceLock::new();
static LAST_ACTIVITY_GAUGE: OnceLock<IntGauge> = OnceLock::new();
static LAST_ACTIVITY: AtomicI64 = AtomicI64::new(0);
pub fn init_metrics() {
let registry = Registry::new();
let alerts_loaded = IntGauge::new(
"bes_alertd_alerts_loaded",
"Number of alerts currently loaded",
)
.expect("failed to create alerts_loaded metric");
let alerts_sent_total = IntCounter::new(
"bes_alertd_alerts_sent_total",
"Total number of alerts sent",
)
.expect("failed to create alerts_sent_total metric");
let alerts_failed_total = IntCounter::new(
"bes_alertd_alerts_failed_total",
"Total number of alerts that failed to send",
)
.expect("failed to create alerts_failed_total metric");
let reloads_total = IntCounter::new(
"bes_alertd_reloads_total",
"Total number of configuration reloads",
)
.expect("failed to create reloads_total metric");
let last_activity_gauge = IntGauge::new(
"bes_alertd_last_activity_unix",
"Unix timestamp of the last activity",
)
.expect("failed to create last_activity_gauge metric");
registry
.register(Box::new(alerts_loaded.clone()))
.expect("failed to register alerts_loaded metric");
registry
.register(Box::new(alerts_sent_total.clone()))
.expect("failed to register alerts_sent_total metric");
registry
.register(Box::new(alerts_failed_total.clone()))
.expect("failed to register alerts_failed_total metric");
registry
.register(Box::new(reloads_total.clone()))
.expect("failed to register reloads_total metric");
registry
.register(Box::new(last_activity_gauge.clone()))
.expect("failed to register last_activity_gauge metric");
REGISTRY.set(registry).expect("metrics already initialized");
ALERTS_LOADED
.set(alerts_loaded)
.expect("metrics already initialized");
ALERTS_SENT_TOTAL
.set(alerts_sent_total)
.expect("metrics already initialized");
ALERTS_FAILED_TOTAL
.set(alerts_failed_total)
.expect("metrics already initialized");
RELOADS_TOTAL
.set(reloads_total)
.expect("metrics already initialized");
LAST_ACTIVITY_GAUGE
.set(last_activity_gauge)
.expect("metrics already initialized");
}
pub fn set_alerts_loaded(count: usize) {
if let Some(metric) = ALERTS_LOADED.get() {
metric.set(count as i64);
}
}
pub fn inc_alerts_sent() {
if let Some(metric) = ALERTS_SENT_TOTAL.get() {
metric.inc();
}
}
pub fn inc_alerts_failed() {
if let Some(metric) = ALERTS_FAILED_TOTAL.get() {
metric.inc();
}
}
pub fn inc_reloads() {
if let Some(metric) = RELOADS_TOTAL.get() {
metric.inc();
}
}
pub fn record_activity() {
let now = Timestamp::now().as_second();
LAST_ACTIVITY.store(now, Ordering::Relaxed);
if let Some(metric) = LAST_ACTIVITY_GAUGE.get() {
metric.set(now);
}
}
pub fn last_activity_timestamp() -> i64 {
LAST_ACTIVITY.load(Ordering::Relaxed)
}
pub fn gather_metrics() -> Result<String> {
let registry = REGISTRY
.get()
.ok_or_else(|| miette::miette!("metrics not initialized"))?;
let metric_families = registry.gather();
let encoder = TextEncoder::new();
let mut buffer = Vec::new();
encoder
.encode(&metric_families, &mut buffer)
.into_diagnostic()?;
String::from_utf8(buffer).into_diagnostic()
}