use metrics::{KeyName, Label, SharedString, histogram};
use std::time::Instant;
#[must_use = "DurationGuard must be bound to a variable; otherwise it records elapsed=0"]
pub struct DurationGuard {
name: KeyName,
labels: Vec<Label>,
started_at: Instant,
armed: bool,
}
impl DurationGuard {
pub fn new(name: impl Into<KeyName>, labels: Vec<Label>) -> Self {
Self {
name: name.into(),
labels,
started_at: Instant::now(),
armed: true,
}
}
pub fn disarm(&mut self) {
self.armed = false;
}
pub fn with_connector(
name: impl Into<KeyName>,
pipeline: SharedString,
row: SharedString,
connector: SharedString,
) -> Self {
Self::new(
name,
vec![
Label::new("pipeline", pipeline),
Label::new("row", row),
Label::new("connector", connector),
],
)
}
}
impl Drop for DurationGuard {
fn drop(&mut self) {
if !self.armed {
return;
}
let elapsed = self.started_at.elapsed().as_secs_f64();
histogram!(self.name.clone(), self.labels.clone()).record(elapsed);
}
}
#[cfg(test)]
mod tests {
use super::*;
use metrics::SharedString;
use metrics_util::debugging::DebugValue;
use std::thread;
use std::time::Duration;
use crate::observability::decorator::source_tests::{LOCK, snapshotter};
#[test]
fn records_sample_on_drop() {
let _g = LOCK.lock().unwrap_or_else(|e| e.into_inner());
let snap = snapshotter();
{
let _guard = DurationGuard::with_connector(
"test_duration_records_sample",
SharedString::const_str("p"),
SharedString::const_str("r"),
SharedString::const_str("c"),
);
thread::sleep(Duration::from_millis(2));
}
let snapshot = snap.snapshot();
let found = snapshot.into_vec().into_iter().any(|(key, _u, _d, value)| {
key.key().name() == "test_duration_records_sample"
&& matches!(
value,
DebugValue::Histogram(samples)
if samples.first().map(|s| s.into_inner()).unwrap_or(0.0) > 0.0
)
});
assert!(
found,
"expected a histogram sample > 0 on test_duration_records_sample"
);
}
#[test]
fn records_sample_when_dropped_early() {
let _g = LOCK.lock().unwrap_or_else(|e| e.into_inner());
let snap = snapshotter();
{
let _guard = DurationGuard::with_connector(
"test_duration_drop_early",
SharedString::const_str("p"),
SharedString::const_str("r"),
SharedString::const_str("c"),
);
}
let snapshot = snap.snapshot();
let found = snapshot
.into_vec()
.into_iter()
.any(|(key, _u, _d, _v)| key.key().name() == "test_duration_drop_early");
assert!(
found,
"expected a histogram entry for test_duration_drop_early"
);
}
}