use crate::event::payload::{SupervisorEvent, What};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SupervisorMetricName {
RestartTotal,
ChildState,
ChildUptimeSeconds,
BackoffSeconds,
HealthcheckLatencySeconds,
MeltdownTotal,
ShutdownDurationSeconds,
EventLagTotal,
ConfigVersion,
}
impl SupervisorMetricName {
pub fn as_str(&self) -> &'static str {
match self {
Self::RestartTotal => "supervisor_restart_total",
Self::ChildState => "supervisor_child_state",
Self::ChildUptimeSeconds => "supervisor_child_uptime_seconds",
Self::BackoffSeconds => "supervisor_backoff_seconds",
Self::HealthcheckLatencySeconds => "supervisor_healthcheck_latency_seconds",
Self::MeltdownTotal => "supervisor_meltdown_total",
Self::ShutdownDurationSeconds => "supervisor_shutdown_duration_seconds",
Self::EventLagTotal => "supervisor_event_lag_total",
Self::ConfigVersion => "supervisor_config_version",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MetricSample {
pub name: String,
pub value: f64,
pub labels: BTreeMap<String, String>,
}
impl MetricSample {
pub fn new(name: SupervisorMetricName, value: f64, labels: BTreeMap<String, String>) -> Self {
Self {
name: name.as_str().to_owned(),
value,
labels,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MetricLabelError {
pub key: String,
pub reason: String,
}
#[derive(Debug, Clone)]
pub struct MetricsFacade {
pub max_label_value_len: usize,
}
impl MetricsFacade {
pub fn new() -> Self {
Self {
max_label_value_len: 96,
}
}
pub fn validate_label(&self, key: &str, value: &str) -> Result<(), MetricLabelError> {
if !allowed_label_key(key) {
return Err(MetricLabelError {
key: key.to_owned(),
reason: "label key is not allowed".to_owned(),
});
}
if value.len() > self.max_label_value_len {
return Err(MetricLabelError {
key: key.to_owned(),
reason: "label value is too long".to_owned(),
});
}
if value.contains('\n') {
return Err(MetricLabelError {
key: key.to_owned(),
reason: "label value contains a newline".to_owned(),
});
}
Ok(())
}
pub fn samples_for_event(&self, event: &SupervisorEvent) -> Vec<MetricSample> {
match &event.what {
What::ChildRestarted { .. } => vec![MetricSample::new(
SupervisorMetricName::RestartTotal,
1.0,
labels_for_event(event),
)],
What::BackoffScheduled { delay_ms } => vec![MetricSample::new(
SupervisorMetricName::BackoffSeconds,
*delay_ms as f64 / 1000.0,
labels_for_event(event),
)],
What::Meltdown { .. } => vec![MetricSample::new(
SupervisorMetricName::MeltdownTotal,
1.0,
labels_for_event(event),
)],
What::SubscriberLagged { missed } => vec![MetricSample::new(
SupervisorMetricName::EventLagTotal,
*missed as f64,
labels_for_event(event),
)],
_ => Vec::new(),
}
}
}
impl Default for MetricsFacade {
fn default() -> Self {
Self::new()
}
}
fn allowed_label_key(key: &str) -> bool {
matches!(
key,
"supervisor_path" | "child_id" | "state" | "decision" | "failure_category"
)
}
fn labels_for_event(event: &SupervisorEvent) -> BTreeMap<String, String> {
let mut labels = BTreeMap::new();
labels.insert(
"supervisor_path".to_owned(),
event.r#where.supervisor_path.to_string(),
);
if let Some(child_id) = &event.r#where.child_id {
labels.insert("child_id".to_owned(), child_id.to_string());
}
if let Some(policy) = &event.policy {
labels.insert("decision".to_owned(), policy.decision.clone());
}
labels
}