1use crate::{
8 domain::metrics::MetricsKind,
9 dto::{
10 metrics::{MetricEntry, QueryPerfSample},
11 page::{Page, PageRequest},
12 },
13 ops::runtime::metrics,
14 perf,
15 workflow::view::paginate::paginate_vec,
16};
17
18pub struct MetricsQuery;
26
27impl MetricsQuery {
28 #[must_use]
30 pub fn page(kind: MetricsKind, page: PageRequest) -> Page<MetricEntry> {
31 Self::page_entries(metrics::entries(kind), page)
32 }
33
34 #[must_use]
35 pub fn core(page: PageRequest) -> Page<MetricEntry> {
36 Self::page_entries(metrics::core_entries(), page)
37 }
38
39 #[must_use]
40 pub fn placement(page: PageRequest) -> Page<MetricEntry> {
41 Self::page_entries(metrics::placement_entries(), page)
42 }
43
44 #[must_use]
45 pub fn platform(page: PageRequest) -> Page<MetricEntry> {
46 Self::page_entries(metrics::platform_entries(), page)
47 }
48
49 #[must_use]
50 pub fn runtime(page: PageRequest) -> Page<MetricEntry> {
51 Self::page_entries(metrics::runtime_entries(), page)
52 }
53
54 #[must_use]
55 pub fn security(page: PageRequest) -> Page<MetricEntry> {
56 Self::page_entries(metrics::security_entries(), page)
57 }
58
59 #[must_use]
60 pub fn storage(page: PageRequest) -> Page<MetricEntry> {
61 Self::page_entries(metrics::storage_entries(), page)
62 }
63
64 fn page_entries(mut entries: Vec<MetricEntry>, page: PageRequest) -> Page<MetricEntry> {
65 entries.sort_by(|a, b| {
66 a.labels
67 .cmp(&b.labels)
68 .then_with(|| a.principal.cmp(&b.principal))
69 });
70
71 paginate_vec(entries, page)
72 }
73
74 #[must_use]
76 pub fn sample_query<T>(value: T) -> QueryPerfSample<T> {
77 QueryPerfSample {
78 value,
79 local_instructions: perf::perf_counter(),
80 }
81 }
82}
83
84#[cfg(test)]
89mod tests {
90 use super::*;
91 #[cfg(feature = "sharding")]
92 use crate::ops::runtime::metrics::sharding::{
93 ShardingMetricOperation, ShardingMetricOutcome, ShardingMetricReason, ShardingMetrics,
94 };
95 use crate::{
96 ids::{AccessMetricKind, CanisterRole},
97 ops::runtime::metrics::{
98 self,
99 access::AccessMetrics,
100 auth::{
101 AuthMetricOperation, AuthMetricOutcome, AuthMetricReason, AuthMetricSurface,
102 AuthMetrics,
103 },
104 canister_ops::{
105 CanisterOpsMetricOperation, CanisterOpsMetricOutcome, CanisterOpsMetricReason,
106 CanisterOpsMetrics,
107 },
108 cascade::{
109 CascadeMetricOperation, CascadeMetricOutcome, CascadeMetricReason,
110 CascadeMetricSnapshot, CascadeMetrics,
111 },
112 intent::{
113 IntentMetricOperation, IntentMetricOutcome, IntentMetricReason,
114 IntentMetricSurface, IntentMetrics,
115 },
116 placement_index::{
117 PlacementIndexMetricOperation, PlacementIndexMetricOutcome,
118 PlacementIndexMetricReason, PlacementIndexMetrics,
119 },
120 platform_call::{
121 PlatformCallMetricMode, PlatformCallMetricOutcome, PlatformCallMetricReason,
122 PlatformCallMetricSurface, PlatformCallMetrics,
123 },
124 replay::{
125 ReplayMetricOperation, ReplayMetricOutcome, ReplayMetricReason, ReplayMetrics,
126 },
127 scaling::{
128 ScalingMetricOperation, ScalingMetricOutcome, ScalingMetricReason, ScalingMetrics,
129 },
130 wasm_store::{
131 WasmStoreMetricOperation, WasmStoreMetricOutcome, WasmStoreMetricReason,
132 WasmStoreMetricSource, WasmStoreMetrics,
133 },
134 },
135 };
136
137 #[test]
138 fn page_sorts_metric_rows_before_paginating() {
139 metrics::reset_for_tests();
140
141 AccessMetrics::increment("zeta", AccessMetricKind::Auth, "caller_is_root");
142 AccessMetrics::increment("alpha", AccessMetricKind::Guard, "fleet_allows_updates");
143
144 let page = MetricsQuery::page(
145 MetricsKind::Security,
146 PageRequest {
147 limit: 1,
148 offset: 0,
149 },
150 );
151
152 assert_eq!(page.total, 2);
153 assert_eq!(
154 page.entries[0].labels,
155 ["access", "alpha", "guard", "fleet_allows_updates"]
156 );
157
158 let page = MetricsQuery::page(
159 MetricsKind::Security,
160 PageRequest {
161 limit: 1,
162 offset: 1,
163 },
164 );
165
166 assert_eq!(page.total, 2);
167 assert_eq!(
168 page.entries[0].labels,
169 ["access", "zeta", "auth", "caller_is_root"]
170 );
171 }
172
173 #[test]
174 fn page_sorts_auth_metric_family_before_paginating() {
175 metrics::reset_for_tests();
176
177 AuthMetrics::record(
178 AuthMetricSurface::Session,
179 AuthMetricOperation::Session,
180 AuthMetricOutcome::Completed,
181 AuthMetricReason::Created,
182 );
183 AuthMetrics::record(
184 AuthMetricSurface::Attestation,
185 AuthMetricOperation::Verify,
186 AuthMetricOutcome::Failed,
187 AuthMetricReason::VerifyFailed,
188 );
189
190 assert_first_metric_labels(
191 MetricsKind::Security,
192 ["auth", "attestation", "verify", "failed", "verify_failed"],
193 );
194 }
195
196 #[test]
197 fn page_sorts_new_multi_label_metric_families_before_paginating() {
198 metrics::reset_for_tests();
199
200 record_multi_label_sort_metrics();
201
202 assert_first_metric_labels(
203 MetricsKind::Core,
204 ["canister_ops", "create", "app", "started", "ok"],
205 );
206 assert_first_metric_labels(
207 MetricsKind::Storage,
208 [
209 "wasm_store",
210 "chunk_upload",
211 "bootstrap",
212 "skipped",
213 "cache_hit",
214 ],
215 );
216 assert_first_metric_labels(
217 MetricsKind::Placement,
218 ["cascade", "child_send", "state", "failed", "send_failed"],
219 );
220 assert_first_metric_labels(
221 MetricsKind::Runtime,
222 ["intent", "local", "capacity_check", "failed", "capacity"],
223 );
224 assert_first_metric_labels(
225 MetricsKind::Platform,
226 ["platform_call", "generic", "bounded_wait", "started", "ok"],
227 );
228 assert_first_metric_labels(
229 MetricsKind::Security,
230 ["replay", "check", "completed", "fresh"],
231 );
232 }
233
234 fn record_multi_label_sort_metrics() {
236 CanisterOpsMetrics::record(
237 CanisterOpsMetricOperation::Install,
238 &CanisterRole::new("worker"),
239 CanisterOpsMetricOutcome::Completed,
240 CanisterOpsMetricReason::Ok,
241 );
242 CanisterOpsMetrics::record(
243 CanisterOpsMetricOperation::Create,
244 &CanisterRole::new("app"),
245 CanisterOpsMetricOutcome::Started,
246 CanisterOpsMetricReason::Ok,
247 );
248 WasmStoreMetrics::record(
249 WasmStoreMetricOperation::SourceResolve,
250 WasmStoreMetricSource::Store,
251 WasmStoreMetricOutcome::Completed,
252 WasmStoreMetricReason::Ok,
253 );
254 WasmStoreMetrics::record(
255 WasmStoreMetricOperation::ChunkUpload,
256 WasmStoreMetricSource::Bootstrap,
257 WasmStoreMetricOutcome::Skipped,
258 WasmStoreMetricReason::CacheHit,
259 );
260 CascadeMetrics::record(
261 CascadeMetricOperation::RootFanout,
262 CascadeMetricSnapshot::Topology,
263 CascadeMetricOutcome::Completed,
264 CascadeMetricReason::Ok,
265 );
266 CascadeMetrics::record(
267 CascadeMetricOperation::ChildSend,
268 CascadeMetricSnapshot::State,
269 CascadeMetricOutcome::Failed,
270 CascadeMetricReason::SendFailed,
271 );
272 PlacementIndexMetrics::record(
273 PlacementIndexMetricOperation::Resolve,
274 PlacementIndexMetricOutcome::Started,
275 PlacementIndexMetricReason::Ok,
276 );
277 PlacementIndexMetrics::record(
278 PlacementIndexMetricOperation::Classify,
279 PlacementIndexMetricOutcome::Completed,
280 PlacementIndexMetricReason::PendingFresh,
281 );
282 record_replay_sort_metrics();
283 record_intent_sort_metrics();
284 record_platform_call_sort_metrics();
285 ScalingMetrics::record(
286 ScalingMetricOperation::CreateWorker,
287 ScalingMetricOutcome::Completed,
288 ScalingMetricReason::Ok,
289 );
290 ScalingMetrics::record(
291 ScalingMetricOperation::BootstrapPool,
292 ScalingMetricOutcome::Skipped,
293 ScalingMetricReason::TargetSatisfied,
294 );
295 }
296
297 #[cfg(feature = "sharding")]
298 #[test]
299 fn page_sorts_sharding_metric_family_before_paginating() {
300 metrics::reset_for_tests();
301
302 ShardingMetrics::record(
303 ShardingMetricOperation::PlanAssign,
304 ShardingMetricOutcome::Completed,
305 ShardingMetricReason::ExistingCapacity,
306 );
307 ShardingMetrics::record(
308 ShardingMetricOperation::BootstrapPool,
309 ShardingMetricOutcome::Skipped,
310 ShardingMetricReason::TargetSatisfied,
311 );
312
313 assert_first_metric_labels(
314 MetricsKind::Placement,
315 ["sharding", "bootstrap_pool", "skipped", "target_satisfied"],
316 );
317 }
318
319 #[test]
320 fn sample_query_returns_value_and_current_counter() {
321 let sample = MetricsQuery::sample_query("ok");
322
323 assert_eq!(sample.value, "ok");
324 assert_eq!(sample.local_instructions, 0);
325 }
326
327 fn assert_first_metric_labels<const N: usize>(kind: MetricsKind, expected: [&str; N]) {
329 let page = MetricsQuery::page(
330 kind,
331 PageRequest {
332 limit: 1,
333 offset: 0,
334 },
335 );
336
337 assert!(page.total > 0);
338 assert_eq!(page.entries[0].labels, expected);
339 }
340
341 fn record_intent_sort_metrics() {
343 IntentMetrics::record(
344 IntentMetricSurface::ReceiptBacked,
345 IntentMetricOperation::Reserve,
346 IntentMetricOutcome::Completed,
347 IntentMetricReason::Ok,
348 );
349 IntentMetrics::record(
350 IntentMetricSurface::Local,
351 IntentMetricOperation::CapacityCheck,
352 IntentMetricOutcome::Failed,
353 IntentMetricReason::Capacity,
354 );
355 }
356
357 fn record_platform_call_sort_metrics() {
359 PlatformCallMetrics::record(
360 PlatformCallMetricSurface::Management,
361 PlatformCallMetricMode::Update,
362 PlatformCallMetricOutcome::Failed,
363 PlatformCallMetricReason::Infra,
364 );
365 PlatformCallMetrics::record(
366 PlatformCallMetricSurface::Generic,
367 PlatformCallMetricMode::BoundedWait,
368 PlatformCallMetricOutcome::Started,
369 PlatformCallMetricReason::Ok,
370 );
371 }
372
373 fn record_replay_sort_metrics() {
375 ReplayMetrics::record(
376 ReplayMetricOperation::Reserve,
377 ReplayMetricOutcome::Failed,
378 ReplayMetricReason::Capacity,
379 );
380 ReplayMetrics::record(
381 ReplayMetricOperation::Check,
382 ReplayMetricOutcome::Completed,
383 ReplayMetricReason::Fresh,
384 );
385 }
386}