faucet_cli/schedule/
metrics.rs1use chrono::{DateTime, Utc};
6use metrics::{counter, describe_counter, describe_gauge, describe_histogram, gauge, histogram};
7use std::time::Duration;
8
9pub fn describe() {
13 describe_counter!(
14 "faucet_schedule_runs_total",
15 "Scheduled pipeline runs, by outcome (ok|err|skipped)."
16 );
17 describe_counter!(
18 "faucet_schedule_overlaps_total",
19 "Scheduler ticks that overlapped an in-flight run, by policy (skip|queue|forbid)."
20 );
21 describe_gauge!(
22 "faucet_schedule_next_tick_unix_seconds",
23 "Unix timestamp of the next scheduled tick."
24 );
25 describe_gauge!(
26 "faucet_schedule_runs_in_flight",
27 "Scheduled runs currently executing (0 or 1)."
28 );
29 describe_gauge!(
30 "faucet_schedule_consecutive_failures",
31 "Consecutive failed scheduled runs since the last success."
32 );
33 describe_gauge!(
34 "faucet_schedule_heartbeat_unix_seconds",
35 "Unix timestamp the scheduler loop last ran (alert if it stalls)."
36 );
37 describe_gauge!(
38 "faucet_schedule_last_run_started_unix_seconds",
39 "Unix timestamp the most recent run started."
40 );
41 describe_gauge!(
42 "faucet_schedule_last_run_completed_unix_seconds",
43 "Unix timestamp the most recent run completed."
44 );
45 describe_gauge!(
46 "faucet_schedule_last_run_duration_seconds",
47 "Wall-clock duration of the most recent run."
48 );
49 describe_histogram!(
50 "faucet_schedule_run_lateness_seconds",
51 "How late each run started relative to its scheduled tick."
52 );
53}
54
55pub fn run_outcome(pipeline: &str, outcome: &'static str) {
57 counter!("faucet_schedule_runs_total", "pipeline" => pipeline.to_string(), "outcome" => outcome)
58 .increment(1);
59}
60
61pub fn overlap(pipeline: &str, policy: &'static str) {
63 counter!("faucet_schedule_overlaps_total", "pipeline" => pipeline.to_string(), "policy" => policy)
64 .increment(1);
65}
66
67pub fn next_tick(pipeline: &str, when: DateTime<Utc>) {
68 gauge!("faucet_schedule_next_tick_unix_seconds", "pipeline" => pipeline.to_string())
69 .set(when.timestamp() as f64);
70}
71
72pub fn in_flight(pipeline: &str, n: u64) {
73 gauge!("faucet_schedule_runs_in_flight", "pipeline" => pipeline.to_string()).set(n as f64);
74}
75
76pub fn consecutive_failures(pipeline: &str, n: u64) {
77 gauge!("faucet_schedule_consecutive_failures", "pipeline" => pipeline.to_string())
78 .set(n as f64);
79}
80
81pub fn heartbeat(pipeline: &str, now: DateTime<Utc>) {
82 gauge!("faucet_schedule_heartbeat_unix_seconds", "pipeline" => pipeline.to_string())
83 .set(now.timestamp() as f64);
84}
85
86pub fn last_run_started(pipeline: &str, when: DateTime<Utc>) {
87 gauge!("faucet_schedule_last_run_started_unix_seconds", "pipeline" => pipeline.to_string())
88 .set(when.timestamp() as f64);
89}
90
91pub fn last_run_completed(pipeline: &str, when: DateTime<Utc>) {
92 gauge!("faucet_schedule_last_run_completed_unix_seconds", "pipeline" => pipeline.to_string())
93 .set(when.timestamp() as f64);
94}
95
96pub fn last_run_duration(pipeline: &str, d: Duration) {
97 gauge!("faucet_schedule_last_run_duration_seconds", "pipeline" => pipeline.to_string())
98 .set(d.as_secs_f64());
99}
100
101pub fn lateness(pipeline: &str, late: chrono::Duration) {
103 let secs = (late.num_milliseconds() as f64 / 1000.0).max(0.0);
104 histogram!("faucet_schedule_run_lateness_seconds", "pipeline" => pipeline.to_string())
105 .record(secs);
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use metrics::with_local_recorder;
112 use metrics_util::debugging::{DebugValue, DebuggingRecorder};
113
114 fn gauge_value(
117 snapshot: metrics_util::debugging::Snapshot,
118 name: &str,
119 pipeline: &str,
120 ) -> Option<f64> {
121 snapshot
122 .into_vec()
123 .into_iter()
124 .find_map(|(key, _u, _d, v)| {
125 let k = key.key();
126 let labelled = k
127 .labels()
128 .any(|l| l.key() == "pipeline" && l.value() == pipeline);
129 if k.name() == name && labelled {
130 match v {
131 DebugValue::Gauge(g) => Some(g.into_inner()),
132 _ => None,
133 }
134 } else {
135 None
136 }
137 })
138 }
139
140 #[test]
141 fn in_flight_sets_gauge() {
142 let recorder = DebuggingRecorder::new();
143 let snap = recorder.snapshotter();
144 with_local_recorder(&recorder, || {
145 in_flight("p", 1);
146 in_flight("p", 0);
147 });
148 assert_eq!(
149 gauge_value(snap.snapshot(), "faucet_schedule_runs_in_flight", "p"),
150 Some(0.0),
151 "in_flight(0) must leave the gauge at 0"
152 );
153 }
154
155 #[test]
156 fn consecutive_failures_sets_gauge() {
157 let recorder = DebuggingRecorder::new();
158 let snap = recorder.snapshotter();
159 with_local_recorder(&recorder, || {
160 consecutive_failures("p", 0);
161 });
162 assert_eq!(
163 gauge_value(snap.snapshot(), "faucet_schedule_consecutive_failures", "p"),
164 Some(0.0),
165 "consecutive_failures(0) must register the series at 0"
166 );
167 }
168
169 #[test]
172 fn startup_preemit_registers_both_gauges_at_zero() {
173 let recorder = DebuggingRecorder::new();
174 let snap = recorder.snapshotter();
175 with_local_recorder(&recorder, || {
176 describe();
177 in_flight("p", 0);
178 consecutive_failures("p", 0);
179 });
180 assert_eq!(
181 gauge_value(snap.snapshot(), "faucet_schedule_runs_in_flight", "p"),
182 Some(0.0),
183 "runs_in_flight must exist at 0 from startup"
184 );
185 assert_eq!(
186 gauge_value(snap.snapshot(), "faucet_schedule_consecutive_failures", "p"),
187 Some(0.0),
188 "consecutive_failures must exist at 0 from startup"
189 );
190 }
191}