use core_types::{HealthStatus, MetricsProvider, MetricsSnapshot};
pub struct MetricTimeSeries {
name: String,
capacity: usize,
samples: std::collections::VecDeque<f64>,
}
impl MetricTimeSeries {
pub fn new(name: impl Into<String>, capacity: usize) -> Self {
Self {
name: name.into(),
capacity: capacity.max(1),
samples: std::collections::VecDeque::new(),
}
}
pub fn push(&mut self, value: f64) {
if self.samples.len() >= self.capacity {
self.samples.pop_front();
}
self.samples.push_back(value);
}
pub fn name(&self) -> &str {
&self.name
}
pub fn len(&self) -> usize {
self.samples.len()
}
pub fn is_empty(&self) -> bool {
self.samples.is_empty()
}
pub fn last(&self) -> Option<f64> {
self.samples.back().copied()
}
pub fn avg(&self) -> Option<f64> {
if self.samples.is_empty() {
return None;
}
Some(self.samples.iter().sum::<f64>() / self.samples.len() as f64)
}
pub fn max(&self) -> Option<f64> {
self.samples.iter().cloned().reduce(f64::max)
}
pub fn min(&self) -> Option<f64> {
self.samples.iter().cloned().reduce(f64::min)
}
}
pub struct ThresholdGauge {
series: MetricTimeSeries,
unit: &'static str,
warn_threshold: f64,
crit_threshold: f64,
}
impl ThresholdGauge {
pub fn new(name: impl Into<String>, unit: &'static str, warn: f64, crit: f64) -> Self {
Self {
series: MetricTimeSeries::new(name, 60),
unit,
warn_threshold: warn,
crit_threshold: crit,
}
}
pub fn push(&mut self, value: f64) {
self.series.push(value);
}
}
impl MetricsProvider for ThresholdGauge {
fn collect(&self) -> Vec<MetricsSnapshot> {
let mut out = Vec::new();
if let Some(v) = self.series.last() {
out.push(MetricsSnapshot::gauge(
self.series.name().to_string(),
v,
self.unit,
));
}
if let Some(avg) = self.series.avg() {
out.push(MetricsSnapshot::gauge(
format!("{}.avg", self.series.name()),
avg,
self.unit,
));
}
out
}
fn health(&self) -> HealthStatus {
match self.series.last() {
None => HealthStatus::Healthy,
Some(v) if v >= self.crit_threshold => HealthStatus::Unhealthy {
reason: format!(
"{} = {:.2} >= crit {:.2}",
self.series.name(),
v,
self.crit_threshold
),
},
Some(v) if v >= self.warn_threshold => HealthStatus::Degraded {
reason: format!(
"{} = {:.2} >= warn {:.2}",
self.series.name(),
v,
self.warn_threshold
),
},
_ => HealthStatus::Healthy,
}
}
}