Skip to content

Commit 7fabd82

Browse files
committed
feat(metrics): pre-seed known channels at zero on startup
Without this, Prometheus only creates a series for cryptify_uploads_total {channel="X"} when the first upload from channel X lands. PromQL increase(...[1h]) over that series returns nothing because the earliest sample is already non-zero — dashboards under-report low-volume channels for an hour after each pod restart. Pre-seeds website, staging-website, outlook, thunderbird, api, unknown to 0 in Metrics::new(), so the full label set is visible from the first scrape and increase() can compute deltas honestly. The previous render-time "if empty, emit unknown=0" fallback is now effectively dead (the map is never empty after new()), but left in place defensively against any future code path that constructs Metrics via Default rather than new().
1 parent ffeedb6 commit 7fabd82

1 file changed

Lines changed: 35 additions & 4 deletions

File tree

src/metrics.rs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ use rocket::http::HeaderMap;
2020
/// Channel label used when no other source information is present.
2121
pub const CHANNEL_UNKNOWN: &str = "unknown";
2222

23+
/// Channels pre-seeded at value 0 on startup so dashboards see the full
24+
/// label set from the first scrape, rather than each channel popping into
25+
/// existence the first time a request from it lands. Without this, PromQL
26+
/// `increase()` over a window can read `0` for a channel whose first
27+
/// observed sample is already non-zero — see #102 follow-up discussion.
28+
pub const KNOWN_CHANNELS: &[&str] = &[
29+
"website",
30+
"staging-website",
31+
"outlook",
32+
"thunderbird",
33+
"api",
34+
CHANNEL_UNKNOWN,
35+
];
36+
2337
/// Header clients can set to identify themselves (`outlook`, `thunderbird`,
2438
/// `api`, ...). Leading whitespace is trimmed and the value is lowercased
2539
/// and restricted to `[a-z0-9_-]` so it cannot inject Prometheus syntax.
@@ -36,7 +50,16 @@ pub struct Metrics {
3650

3751
impl Metrics {
3852
pub fn new() -> Self {
39-
Self::default()
53+
let m = Self::default();
54+
{
55+
let mut uploads = m.uploads.lock().unwrap();
56+
let mut bytes = m.upload_bytes.lock().unwrap();
57+
for c in KNOWN_CHANNELS {
58+
uploads.insert((*c).to_string(), 0);
59+
bytes.insert((*c).to_string(), 0);
60+
}
61+
}
62+
m
4063
}
4164

4265
/// Record a successfully finalized upload.
@@ -325,11 +348,19 @@ mod tests {
325348
}
326349

327350
#[test]
328-
fn render_emits_zero_counters_when_empty() {
351+
fn render_preseeds_known_channels_at_zero() {
329352
let m = Metrics::new();
330353
let text = m.render();
331-
assert!(text.contains("cryptify_uploads_total{channel=\"unknown\"} 0"));
332-
assert!(text.contains("cryptify_upload_bytes_total{channel=\"unknown\"} 0"));
354+
for c in KNOWN_CHANNELS {
355+
assert!(
356+
text.contains(&format!("cryptify_uploads_total{{channel=\"{c}\"}} 0")),
357+
"missing zero-seed for uploads channel={c} in:\n{text}"
358+
);
359+
assert!(
360+
text.contains(&format!("cryptify_upload_bytes_total{{channel=\"{c}\"}} 0")),
361+
"missing zero-seed for upload_bytes channel={c} in:\n{text}"
362+
);
363+
}
333364
assert!(text.contains("cryptify_storage_bytes 0"));
334365
assert!(text.contains("cryptify_active_files 0"));
335366
assert!(text.contains("cryptify_expired_files_total 0"));

0 commit comments

Comments
 (0)