use metrics::{counter, describe_counter, describe_histogram, histogram};
use metrics_util::debugging::Snapshotter;
use monotony::MonotonicClock;
use ortho_config::OrthoError;
use std::{
error::Error,
sync::{Once, OnceLock},
};
#[path = "observability_recorder.rs"]
mod recorder;
use self::recorder::ConfigMetricsRecorder;
pub(crate) const CONFIG_LOAD_COUNTER: &str = "config_load_total";
pub(crate) const CONFIG_LOAD_DURATION: &str = "config_load_duration_seconds";
pub(crate) const DIAG_MODE_PHASE: &str = "diag_mode";
pub(crate) const MERGE_PHASE: &str = "merge";
pub(crate) const DIAG_MODE_OPERATION: &str = "diag_mode_resolution";
pub(crate) const MERGE_OPERATION: &str = "config_merge";
#[derive(Clone, Copy)]
pub(crate) enum ConfigLoadPhase {
DiagMode,
Merge,
}
impl ConfigLoadPhase {
const fn as_label(self) -> &'static str {
match self {
Self::DiagMode => DIAG_MODE_PHASE,
Self::Merge => MERGE_PHASE,
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum ConfigLoadOutcome {
Success,
Failure,
}
impl ConfigLoadOutcome {
const fn from_is_ok(is_ok: bool) -> Self {
if is_ok { Self::Success } else { Self::Failure }
}
const fn as_label(self) -> &'static str {
match self {
Self::Success => "success",
Self::Failure => "failure",
}
}
}
static SNAPSHOTTER: OnceLock<Snapshotter> = OnceLock::new();
static METRICS_INITIALIZED: Once = Once::new();
pub(crate) fn init_metrics() {
METRICS_INITIALIZED.call_once(|| {
let recorder = ConfigMetricsRecorder::new();
let snapshotter = recorder.snapshotter();
if metrics::set_global_recorder(recorder).is_ok() {
drop(SNAPSHOTTER.set(snapshotter));
}
});
}
pub(crate) fn emit_metrics_snapshot() {
if let Some(snapshotter) = SNAPSHOTTER.get() {
tracing::debug!(metrics = ?snapshotter.snapshot().into_vec(), "metrics snapshot");
}
}
pub(crate) fn record_config_load<T, E>(
phase: ConfigLoadPhase,
clock: &impl MonotonicClock,
load: impl FnOnce() -> Result<T, E>,
) -> Result<T, E> {
describe_config_metrics();
let started = clock.now();
let result = load();
let outcome = ConfigLoadOutcome::from_is_ok(result.is_ok());
counter!(
CONFIG_LOAD_COUNTER,
"phase" => phase.as_label(),
"outcome" => outcome.as_label()
)
.increment(1);
histogram!(CONFIG_LOAD_DURATION, "phase" => phase.as_label())
.record(clock.now().duration_since(started));
result
}
pub(crate) fn classify_error(err: &(dyn Error + 'static)) -> &'static str {
match err.downcast_ref::<OrthoError>() {
Some(OrthoError::File { .. }) => "io",
Some(OrthoError::Validation { .. }) => "validation",
_ => "parse",
}
}
fn describe_config_metrics() {
static DESCRIBE: Once = Once::new();
DESCRIBE.call_once(|| {
describe_counter!(
CONFIG_LOAD_COUNTER,
"Counts configuration-load outcomes by bounded phase and outcome."
);
describe_histogram!(
CONFIG_LOAD_DURATION,
"Measures configuration-load duration in seconds by bounded phase."
);
});
}
#[cfg(test)]
#[path = "observability_tests.rs"]
mod tests;