use crate::component::InstrumentRef;
use crate::instruments::counter::Counter;
use crate::instruments::timer::Timer;
use crate::labels::Labels;
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MetricDetail {
Counts,
#[default]
Timers,
}
impl MetricDetail {
pub fn parse(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"counts" | "count" | "counter" | "counters" => Some(Self::Counts),
"timers" | "timer" | "timed" | "histogram" => Some(Self::Timers),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct MetricDetailConfig {
default: MetricDetail,
overrides: HashMap<String, MetricDetail>,
}
impl MetricDetailConfig {
pub fn new(default: MetricDetail) -> Self {
Self {
default,
overrides: HashMap::new(),
}
}
pub fn with_override(mut self, family: impl Into<String>, detail: MetricDetail) -> Self {
self.overrides.insert(family.into(), detail);
self
}
pub fn for_family(&self, family: &str) -> MetricDetail {
self.overrides.get(family).copied().unwrap_or(self.default)
}
pub fn default_detail(&self) -> MetricDetail {
self.default
}
}
pub enum OutcomeInstrument {
Counted(Arc<Counter>),
Timed(Arc<Timer>),
}
impl OutcomeInstrument {
pub fn new(labels: Labels, sigdigs: u8, detail: MetricDetail) -> Self {
match detail {
MetricDetail::Counts => Self::Counted(Arc::new(Counter::new(labels))),
MetricDetail::Timers => Self::Timed(Arc::new(Timer::with_sigdigs(labels, sigdigs))),
}
}
pub fn observe(&self, duration_nanos: u64) {
match self {
Self::Counted(c) => c.inc(),
Self::Timed(t) => t.record(duration_nanos),
}
}
pub fn count(&self) -> u64 {
match self {
Self::Counted(c) => c.get(),
Self::Timed(t) => t.count(),
}
}
pub fn labels(&self) -> &Labels {
match self {
Self::Counted(c) => c.labels(),
Self::Timed(t) => t.labels(),
}
}
pub fn is_timed(&self) -> bool {
matches!(self, Self::Timed(_))
}
pub fn instrument_ref(&self) -> InstrumentRef {
match self {
Self::Counted(c) => InstrumentRef::Counter(c.clone()),
Self::Timed(t) => InstrumentRef::Timer(t.clone()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counts_mode_is_counter_with_count() {
let i = OutcomeInstrument::new(
Labels::of("name", "result_success"),
3,
MetricDetail::Counts,
);
assert!(!i.is_timed());
i.observe(1234);
i.observe(5678);
assert_eq!(i.count(), 2);
}
#[test]
fn timed_mode_keeps_count_and_distribution() {
let i = OutcomeInstrument::new(
Labels::of("name", "result_success"),
3,
MetricDetail::Timers,
);
assert!(i.is_timed());
i.observe(1_000_000);
i.observe(2_000_000);
assert_eq!(i.count(), 2);
}
#[test]
fn detail_parse_and_config_override() {
assert_eq!(MetricDetail::parse("counts"), Some(MetricDetail::Counts));
assert_eq!(MetricDetail::parse("Timers"), Some(MetricDetail::Timers));
assert_eq!(MetricDetail::parse("nope"), None);
let cfg = MetricDetailConfig::new(MetricDetail::Timers)
.with_override("attempt_success", MetricDetail::Counts);
assert_eq!(cfg.for_family("attempt_success"), MetricDetail::Counts);
assert_eq!(cfg.for_family("result_success"), MetricDetail::Timers);
}
}