faucet_core/observability/
timer.rs1use metrics::{KeyName, Label, SharedString, histogram};
5use std::time::Instant;
6
7#[must_use = "DurationGuard must be bound to a variable; otherwise it records elapsed=0"]
11pub struct DurationGuard {
12 name: KeyName,
13 labels: Vec<Label>,
14 started_at: Instant,
15 armed: bool,
16}
17
18impl DurationGuard {
19 pub fn new(name: impl Into<KeyName>, labels: Vec<Label>) -> Self {
20 Self {
21 name: name.into(),
22 labels,
23 started_at: Instant::now(),
24 armed: true,
25 }
26 }
27
28 pub fn disarm(&mut self) {
33 self.armed = false;
34 }
35
36 pub fn with_connector(
38 name: impl Into<KeyName>,
39 pipeline: SharedString,
40 row: SharedString,
41 connector: SharedString,
42 ) -> Self {
43 Self::new(
44 name,
45 vec![
46 Label::new("pipeline", pipeline),
47 Label::new("row", row),
48 Label::new("connector", connector),
49 ],
50 )
51 }
52}
53
54impl Drop for DurationGuard {
55 fn drop(&mut self) {
56 if !self.armed {
57 return;
58 }
59 let elapsed = self.started_at.elapsed().as_secs_f64();
60 histogram!(self.name.clone(), self.labels.clone()).record(elapsed);
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use metrics::SharedString;
68 use metrics_util::debugging::DebugValue;
69 use std::thread;
70 use std::time::Duration;
71
72 use crate::observability::decorator::source_tests::{LOCK, snapshotter};
78
79 #[test]
80 fn records_sample_on_drop() {
81 let _g = LOCK.lock().unwrap_or_else(|e| e.into_inner());
82 let snap = snapshotter();
83 {
84 let _guard = DurationGuard::with_connector(
85 "test_duration_records_sample",
86 SharedString::const_str("p"),
87 SharedString::const_str("r"),
88 SharedString::const_str("c"),
89 );
90 thread::sleep(Duration::from_millis(2));
91 }
92 let snapshot = snap.snapshot();
93 let found = snapshot.into_vec().into_iter().any(|(key, _u, _d, value)| {
94 key.key().name() == "test_duration_records_sample"
95 && matches!(
96 value,
97 DebugValue::Histogram(samples)
98 if samples.first().map(|s| s.into_inner()).unwrap_or(0.0) > 0.0
99 )
100 });
101 assert!(
102 found,
103 "expected a histogram sample > 0 on test_duration_records_sample"
104 );
105 }
106
107 #[test]
108 fn records_sample_when_dropped_early() {
109 let _g = LOCK.lock().unwrap_or_else(|e| e.into_inner());
112 let snap = snapshotter();
113 {
114 let _guard = DurationGuard::with_connector(
115 "test_duration_drop_early",
116 SharedString::const_str("p"),
117 SharedString::const_str("r"),
118 SharedString::const_str("c"),
119 );
120 }
121 let snapshot = snap.snapshot();
122 let found = snapshot
123 .into_vec()
124 .into_iter()
125 .any(|(key, _u, _d, _v)| key.key().name() == "test_duration_drop_early");
126 assert!(
127 found,
128 "expected a histogram entry for test_duration_drop_early"
129 );
130 }
131}