1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// Copyright (c) 2026 The NORA Authors
// SPDX-License-Identifier: MIT
//! Dashboard activity counters — a thin facade over the Prometheus registry,
//! which is the single source of truth (#626).
//!
//! Nothing is persisted. Earlier this module kept its own `AtomicU64` copy of
//! every counter and flushed a `metrics.json` to disk every 30s — constant idle
//! IOPS, and after a restart the UI showed the restored totals while `/metrics`
//! started from zero (divergence). The Prometheus counters already track exactly
//! the same events, so the dashboard now reads straight from them: no on-disk
//! copy, no periodic write, and the UI numbers can never disagree with
//! `/metrics`. Because Prometheus counters reset on process restart, the
//! dashboard figures are "since restart" (the UI labels them so, anchored by the
//! uptime shown alongside).
use crate::metrics::{CACHE_REQUESTS, DOWNLOADS_TOTAL, UPLOADS_TOTAL};
use crate::registry_type::RegistryType;
/// Registry names from `RegistryType` (single source of truth) for aggregation.
fn registry_names() -> Vec<&'static str> {
RegistryType::all().iter().map(|rt| rt.as_str()).collect()
}
/// Dashboard activity facade. Holds no state of its own — reads and writes both
/// go to the process-global Prometheus counters, so the UI and `/metrics` stay
/// in lock-step.
#[derive(Default)]
pub struct DashboardMetrics;
impl DashboardMetrics {
pub fn new() -> Self {
Self
}
// ---- writes: increment the Prometheus counter (the only store) ----
pub fn record_download(&self, registry: &str) {
DOWNLOADS_TOTAL.with_label_values(&[registry]).inc();
}
pub fn record_upload(&self, registry: &str) {
UPLOADS_TOTAL.with_label_values(&[registry]).inc();
}
pub fn record_cache_hit(&self, registry: &str) {
CACHE_REQUESTS.with_label_values(&[registry, "hit"]).inc();
}
pub fn record_cache_miss(&self, registry: &str) {
CACHE_REQUESTS.with_label_values(&[registry, "miss"]).inc();
}
// ---- reads: derived from the Prometheus counters (since restart) ----
pub fn get_registry_downloads(&self, registry: &str) -> u64 {
DOWNLOADS_TOTAL.with_label_values(&[registry]).get()
}
pub fn get_registry_uploads(&self, registry: &str) -> u64 {
UPLOADS_TOTAL.with_label_values(&[registry]).get()
}
pub fn downloads(&self) -> u64 {
registry_names()
.iter()
.map(|&r| DOWNLOADS_TOTAL.with_label_values(&[r]).get())
.sum()
}
pub fn uploads(&self) -> u64 {
registry_names()
.iter()
.map(|&r| UPLOADS_TOTAL.with_label_values(&[r]).get())
.sum()
}
pub fn cache_hits(&self) -> u64 {
registry_names()
.iter()
.map(|&r| CACHE_REQUESTS.with_label_values(&[r, "hit"]).get())
.sum()
}
pub fn cache_misses(&self) -> u64 {
registry_names()
.iter()
.map(|&r| CACHE_REQUESTS.with_label_values(&[r, "miss"]).get())
.sum()
}
pub fn cache_hit_rate(&self) -> f64 {
let hits = self.cache_hits();
let total = hits + self.cache_misses();
if total == 0 {
0.0
} else {
(hits as f64 / total as f64) * 100.0
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
// The Prometheus counters are process-global and shared across all tests, so
// exact-value assertions would race under cargo's parallel test runner. Each
// test therefore uses a UNIQUE registry label (deterministic on its own
// series) and asserts the DELTA, or asserts a monotone lower bound for the
// global aggregate (`>= before + n`) which is parallel-safe.
#[test]
fn record_download_increments_that_registry_series() {
let m = DashboardMetrics::new();
let reg = "test-dl-unique-a";
let before = m.get_registry_downloads(reg);
m.record_download(reg);
m.record_download(reg);
assert_eq!(m.get_registry_downloads(reg) - before, 2);
}
#[test]
fn record_upload_increments_that_registry_series() {
let m = DashboardMetrics::new();
let reg = "test-ul-unique-b";
let before = m.get_registry_uploads(reg);
m.record_upload(reg);
assert_eq!(m.get_registry_uploads(reg) - before, 1);
}
#[test]
fn downloads_aggregate_reflects_a_real_registry_record() {
// downloads() sums over RegistryType::all(); recording on a real registry
// must be included. `>= before + 1` is parallel-safe (other tests may add).
let m = DashboardMetrics::new();
let before = m.downloads();
m.record_download("docker");
assert!(m.downloads() >= before + 1);
}
#[test]
fn uploads_aggregate_reflects_a_real_registry_record() {
let m = DashboardMetrics::new();
let before = m.uploads();
m.record_upload("maven");
assert!(m.uploads() >= before + 1);
}
#[test]
fn cache_hit_rate_is_a_percentage_and_moves_with_hits() {
// Rate is global; assert the invariant (0..=100) and that a recorded hit
// keeps it strictly positive — without asserting an exact global value.
let m = DashboardMetrics::new();
m.record_cache_hit("docker");
let rate = m.cache_hit_rate();
assert!((0.0..=100.0).contains(&rate));
assert!(m.cache_hits() >= 1);
}
#[test]
fn unknown_registry_label_is_isolated() {
// A never-recorded unique label reads as 0 (its own series), independent
// of any other registry's traffic.
let m = DashboardMetrics::new();
assert_eq!(m.get_registry_downloads("test-never-recorded-zzz"), 0);
}
#[test]
fn record_is_stateless_across_instances() {
// Two facades share the same global counters (no per-instance state):
// recording via one is visible via the other.
let reg = "test-shared-instance-c";
let a = DashboardMetrics::new();
let b = DashboardMetrics::new();
let before = b.get_registry_downloads(reg);
a.record_download(reg);
assert_eq!(b.get_registry_downloads(reg) - before, 1);
}
}