canic_core/workflow/metrics/
query.rs1use crate::{
2 dto::{
3 metrics::{
4 AccessMetricEntry, AuthMetricEntry, AuthRolloutMetricEntry, CyclesFundingMetricEntry,
5 DelegationMetricEntry, EndpointHealth, HttpMetricEntry, IccMetricEntry, MetricsKind,
6 MetricsRequest, MetricsResponse, RootCapabilityMetricEntry, SystemMetricEntry,
7 TimerMetricEntry,
8 },
9 page::{Page, PageRequest},
10 },
11 ops::{
12 perf::PerfOps,
13 runtime::metrics::{
14 MetricsOps,
15 auth::typed_auth_metric_records,
16 mapper::{
17 AccessMetricEntryMapper, AuthMetricEntryMapper, AuthRolloutMetricEntryMapper,
18 CyclesFundingMetricEntryMapper, DelegationMetricEntryMapper, EndpointHealthMapper,
19 HttpMetricEntryMapper, IccMetricEntryMapper, RootCapabilityMetricEntryMapper,
20 SystemMetricEntryMapper, TimerMetricEntryMapper,
21 },
22 },
23 },
24 perf::PerfEntry,
25 workflow::view::paginate::paginate_vec,
26};
27
28pub struct MetricsQuery;
36
37impl MetricsQuery {
38 #[must_use]
39 pub fn dispatch(req: MetricsRequest) -> MetricsResponse {
40 match req.kind {
41 MetricsKind::System => MetricsResponse::System(Self::system_snapshot()),
42 MetricsKind::Icc => MetricsResponse::Icc(Self::icc_page(req.page)),
43 MetricsKind::Http => MetricsResponse::Http(Self::http_page(req.page)),
44 MetricsKind::Timer => MetricsResponse::Timer(Self::timer_page(req.page)),
45 MetricsKind::Access => MetricsResponse::Access(Self::access_page(req.page)),
46 MetricsKind::Auth => MetricsResponse::Auth(Self::auth_page(req.page)),
47 MetricsKind::AuthRollout => {
48 MetricsResponse::AuthRollout(Self::auth_rollout_page(req.page))
49 }
50 MetricsKind::Delegation => MetricsResponse::Delegation(Self::delegation_page(req.page)),
51 MetricsKind::RootCapability => {
52 MetricsResponse::RootCapability(Self::root_capability_page(req.page))
53 }
54 MetricsKind::CyclesFunding => {
55 MetricsResponse::CyclesFunding(Self::cycles_funding_page(req.page))
56 }
57 MetricsKind::Perf => MetricsResponse::Perf(Self::perf_page(req.page)),
58 MetricsKind::EndpointHealth => MetricsResponse::EndpointHealth(
59 Self::endpoint_health_page(req.page, Some(crate::protocol::CANIC_METRICS)),
60 ),
61 }
62 }
63
64 #[must_use]
65 pub fn system_snapshot() -> Vec<SystemMetricEntry> {
66 let snapshot = MetricsOps::system_snapshot();
67 let mut entries = SystemMetricEntryMapper::record_to_view(snapshot.entries);
68
69 entries.sort_by(|a, b| a.kind.cmp(&b.kind));
70
71 entries
72 }
73
74 #[must_use]
75 pub fn icc_page(page: PageRequest) -> Page<IccMetricEntry> {
76 let snapshot = MetricsOps::icc_snapshot();
77 let mut entries = IccMetricEntryMapper::record_to_view(snapshot.entries);
78
79 entries.sort_by(|a, b| {
80 a.target
81 .as_slice()
82 .cmp(b.target.as_slice())
83 .then_with(|| a.method.cmp(&b.method))
84 });
85
86 paginate_vec(entries, page)
87 }
88
89 #[must_use]
90 pub fn http_page(page: PageRequest) -> Page<HttpMetricEntry> {
91 let snapshot = MetricsOps::http_snapshot();
92 let mut entries = HttpMetricEntryMapper::record_to_view(snapshot.entries);
93
94 entries.sort_by(|a, b| a.method.cmp(&b.method).then_with(|| a.label.cmp(&b.label)));
95
96 paginate_vec(entries, page)
97 }
98
99 #[must_use]
100 pub fn timer_page(page: PageRequest) -> Page<TimerMetricEntry> {
101 let snapshot = MetricsOps::timer_snapshot();
102 let mut entries = TimerMetricEntryMapper::record_to_view(snapshot.entries);
103
104 entries.sort_by(|a, b| {
105 a.mode
106 .cmp(&b.mode)
107 .then_with(|| a.delay_ms.cmp(&b.delay_ms))
108 .then_with(|| a.label.cmp(&b.label))
109 });
110
111 paginate_vec(entries, page)
112 }
113
114 #[must_use]
115 pub fn access_page(page: PageRequest) -> Page<AccessMetricEntry> {
116 let snapshot = MetricsOps::access_snapshot();
117 let mut entries = AccessMetricEntryMapper::record_to_view(snapshot.entries);
118
119 entries.sort_by(|a, b| {
120 a.endpoint
121 .cmp(&b.endpoint)
122 .then_with(|| a.kind.cmp(&b.kind))
123 .then_with(|| a.predicate.cmp(&b.predicate))
124 });
125
126 paginate_vec(entries, page)
127 }
128
129 #[must_use]
130 pub fn auth_page(page: PageRequest) -> Page<AuthMetricEntry> {
131 let snapshot = MetricsOps::access_snapshot();
132 let mut entries =
133 AuthMetricEntryMapper::record_to_view(typed_auth_metric_records(snapshot.entries));
134
135 entries.sort_by(|a, b| {
136 a.endpoint
137 .cmp(&b.endpoint)
138 .then_with(|| a.predicate.cmp(&b.predicate))
139 });
140
141 paginate_vec(entries, page)
142 }
143
144 #[must_use]
145 pub fn auth_rollout_page(page: PageRequest) -> Page<AuthRolloutMetricEntry> {
146 let snapshot = MetricsOps::access_snapshot();
147 let entries = AuthRolloutMetricEntryMapper::record_to_view(typed_auth_metric_records(
148 snapshot.entries,
149 ));
150
151 paginate_vec(entries, page)
152 }
153
154 #[must_use]
155 pub fn delegation_page(page: PageRequest) -> Page<DelegationMetricEntry> {
156 let snapshot = MetricsOps::delegation_snapshot();
157 let mut entries = DelegationMetricEntryMapper::record_to_view(snapshot.entries);
158
159 entries.sort_by(|a, b| a.authority.as_slice().cmp(b.authority.as_slice()));
160
161 paginate_vec(entries, page)
162 }
163
164 #[must_use]
165 pub fn root_capability_page(page: PageRequest) -> Page<RootCapabilityMetricEntry> {
166 let snapshot = MetricsOps::root_capability_snapshot();
167 let mut entries = RootCapabilityMetricEntryMapper::record_to_view(snapshot.entries);
168
169 entries.sort_by(|a, b| {
170 a.capability
171 .cmp(&b.capability)
172 .then_with(|| a.event_type.cmp(&b.event_type))
173 .then_with(|| a.outcome.cmp(&b.outcome))
174 .then_with(|| a.proof_mode.cmp(&b.proof_mode))
175 });
176
177 paginate_vec(entries, page)
178 }
179
180 #[must_use]
181 pub fn cycles_funding_page(page: PageRequest) -> Page<CyclesFundingMetricEntry> {
182 let snapshot = MetricsOps::cycles_funding_snapshot();
183 let mut entries = CyclesFundingMetricEntryMapper::record_to_view(snapshot.entries);
184
185 entries.sort_by(|a, b| {
186 a.metric
187 .cmp(&b.metric)
188 .then_with(|| a.child_principal.cmp(&b.child_principal))
189 .then_with(|| a.reason.cmp(&b.reason))
190 });
191
192 paginate_vec(entries, page)
193 }
194
195 #[must_use]
196 pub fn perf_page(page: PageRequest) -> Page<PerfEntry> {
197 let snapshot = PerfOps::snapshot();
198 paginate_vec(snapshot.entries, page)
199 }
200
201 #[must_use]
202 pub fn endpoint_health_page(
203 page: PageRequest,
204 exclude_endpoint: Option<&str>,
205 ) -> Page<EndpointHealth> {
206 let snapshot = MetricsOps::endpoint_health_snapshot();
207 let mut entries = EndpointHealthMapper::record_to_view(
208 snapshot.attempts,
209 snapshot.results,
210 snapshot.access,
211 exclude_endpoint,
212 );
213
214 entries.sort_by(|a, b| a.endpoint.cmp(&b.endpoint));
215
216 paginate_vec(entries, page)
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use crate::{
224 dto::metrics::AuthRolloutMetricClass,
225 ids::AccessMetricKind,
226 ops::runtime::metrics::{
227 access::AccessMetrics,
228 auth::{
229 AuthMetricPredicate, AuthProofCacheUtilizationBucket,
230 DelegationInstallValidationFailureReason, DelegationProvisionRole,
231 VerifierProofCacheEvictionClass,
232 },
233 },
234 };
235
236 #[test]
237 fn auth_page_filters_non_auth_metrics() {
238 AccessMetrics::reset();
239
240 AccessMetrics::increment(
241 "auth_verifier",
242 AccessMetricKind::Auth,
243 AuthMetricPredicate::ProofMiss.as_str().as_ref(),
244 );
245 AccessMetrics::increment(
246 "auth_verifier",
247 AccessMetricKind::Auth,
248 AuthMetricPredicate::ProofMiss.as_str().as_ref(),
249 );
250 AccessMetrics::increment(
251 "canic_metrics",
252 AccessMetricKind::Guard,
253 "caller_is_controller",
254 );
255
256 let page = MetricsQuery::auth_page(PageRequest {
257 offset: 0,
258 limit: 10,
259 });
260
261 assert_eq!(page.entries.len(), 1);
262 assert_eq!(page.entries[0].endpoint, "auth_verifier");
263 assert_eq!(
264 page.entries[0].predicate,
265 AuthMetricPredicate::ProofMiss.as_str().as_ref()
266 );
267 assert_eq!(page.entries[0].count, 2);
268 }
269
270 #[test]
271 fn auth_rollout_page_groups_gate_and_operational_signals() {
272 AccessMetrics::reset();
273
274 AccessMetrics::increment(
275 "auth_verifier",
276 AccessMetricKind::Auth,
277 AuthMetricPredicate::ProofMiss.as_str().as_ref(),
278 );
279 AccessMetrics::increment(
280 "auth_verifier",
281 AccessMetricKind::Auth,
282 AuthMetricPredicate::ProofMismatch.as_str().as_ref(),
283 );
284 AccessMetrics::increment(
285 "auth_verifier",
286 AccessMetricKind::Auth,
287 AuthMetricPredicate::ProofCacheEviction {
288 class: VerifierProofCacheEvictionClass::Active,
289 }
290 .as_str()
291 .as_ref(),
292 );
293 AccessMetrics::increment(
294 "auth_verifier",
295 AccessMetricKind::Auth,
296 AuthMetricPredicate::ProofCacheUtilization {
297 bucket: AuthProofCacheUtilizationBucket::NinetyFiveToOneHundred,
298 }
299 .as_str()
300 .as_ref(),
301 );
302 AccessMetrics::increment(
303 "auth_signer",
304 AccessMetricKind::Auth,
305 AuthMetricPredicate::DelegationPushFailed {
306 role: DelegationProvisionRole::Verifier,
307 intent: crate::dto::auth::DelegationProofInstallIntent::Repair,
308 }
309 .as_str()
310 .as_ref(),
311 );
312 AccessMetrics::increment(
313 "auth_signer",
314 AccessMetricKind::Auth,
315 AuthMetricPredicate::DelegationInstallValidationFailed {
316 intent: crate::dto::auth::DelegationProofInstallIntent::Prewarm,
317 reason: DelegationInstallValidationFailureReason::VerifyProof,
318 }
319 .as_str()
320 .as_ref(),
321 );
322 AccessMetrics::increment(
323 "auth_verifier",
324 AccessMetricKind::Auth,
325 AuthMetricPredicate::ProofCacheEviction {
326 class: VerifierProofCacheEvictionClass::Cold,
327 }
328 .as_str()
329 .as_ref(),
330 );
331 AccessMetrics::increment(
332 "canic_metrics",
333 AccessMetricKind::Guard,
334 "caller_is_controller",
335 );
336
337 let page = MetricsQuery::auth_rollout_page(PageRequest {
338 offset: 0,
339 limit: 20,
340 });
341
342 assert_eq!(page.entries.len(), 7);
343 assert_eq!(
344 rollout_entry(&page, "proof_miss"),
345 Some((AuthRolloutMetricClass::HardGate, 1))
346 );
347 assert_eq!(
348 rollout_entry(&page, "proof_mismatch"),
349 Some((AuthRolloutMetricClass::HardGate, 1))
350 );
351 assert_eq!(
352 rollout_entry(&page, "active_proof_eviction"),
353 Some((AuthRolloutMetricClass::HardGate, 1))
354 );
355 assert_eq!(
356 rollout_entry(&page, "repair_failure"),
357 Some((AuthRolloutMetricClass::HardGate, 1))
358 );
359 assert_eq!(
360 rollout_entry(&page, "cache_saturation"),
361 Some((AuthRolloutMetricClass::HardGate, 1))
362 );
363 assert_eq!(
364 rollout_entry(&page, "cold_proof_eviction"),
365 Some((AuthRolloutMetricClass::Operational, 1))
366 );
367 assert_eq!(
368 rollout_entry(&page, "prewarm_failure"),
369 Some((AuthRolloutMetricClass::Operational, 1))
370 );
371 }
372
373 fn rollout_entry(
374 page: &Page<AuthRolloutMetricEntry>,
375 signal: &str,
376 ) -> Option<(AuthRolloutMetricClass, u64)> {
377 page.entries
378 .iter()
379 .find_map(|entry| (entry.signal == signal).then_some((entry.class, entry.count)))
380 }
381}