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::ApplicationSession,
179 AuthMetricOperation::Establish,
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 [
193 "auth",
194 "application_session",
195 "establish",
196 "completed",
197 "created",
198 ],
199 );
200 }
201
202 #[test]
203 fn page_sorts_new_multi_label_metric_families_before_paginating() {
204 metrics::reset_for_tests();
205
206 record_multi_label_sort_metrics();
207
208 assert_first_metric_labels(
209 MetricsKind::Core,
210 ["canister_ops", "create", "app", "started", "ok"],
211 );
212 assert_first_metric_labels(
213 MetricsKind::Storage,
214 [
215 "wasm_store",
216 "chunk_upload",
217 "bootstrap",
218 "skipped",
219 "cache_hit",
220 ],
221 );
222 assert_first_metric_labels(
223 MetricsKind::Placement,
224 ["cascade", "child_send", "state", "failed", "send_failed"],
225 );
226 assert_first_metric_labels(
227 MetricsKind::Runtime,
228 ["intent", "local", "capacity_check", "failed", "capacity"],
229 );
230 assert_first_metric_labels(
231 MetricsKind::Platform,
232 ["platform_call", "generic", "bounded_wait", "started", "ok"],
233 );
234 assert_first_metric_labels(
235 MetricsKind::Security,
236 ["replay", "check", "completed", "fresh"],
237 );
238 }
239
240 fn record_multi_label_sort_metrics() {
242 CanisterOpsMetrics::record(
243 CanisterOpsMetricOperation::Install,
244 &CanisterRole::new("worker"),
245 CanisterOpsMetricOutcome::Completed,
246 CanisterOpsMetricReason::Ok,
247 );
248 CanisterOpsMetrics::record(
249 CanisterOpsMetricOperation::Create,
250 &CanisterRole::new("app"),
251 CanisterOpsMetricOutcome::Started,
252 CanisterOpsMetricReason::Ok,
253 );
254 WasmStoreMetrics::record(
255 WasmStoreMetricOperation::SourceResolve,
256 WasmStoreMetricSource::Store,
257 WasmStoreMetricOutcome::Completed,
258 WasmStoreMetricReason::Ok,
259 );
260 WasmStoreMetrics::record(
261 WasmStoreMetricOperation::ChunkUpload,
262 WasmStoreMetricSource::Bootstrap,
263 WasmStoreMetricOutcome::Skipped,
264 WasmStoreMetricReason::CacheHit,
265 );
266 CascadeMetrics::record(
267 CascadeMetricOperation::RootFanout,
268 CascadeMetricSnapshot::Topology,
269 CascadeMetricOutcome::Completed,
270 CascadeMetricReason::Ok,
271 );
272 CascadeMetrics::record(
273 CascadeMetricOperation::ChildSend,
274 CascadeMetricSnapshot::State,
275 CascadeMetricOutcome::Failed,
276 CascadeMetricReason::SendFailed,
277 );
278 PlacementIndexMetrics::record(
279 PlacementIndexMetricOperation::Resolve,
280 PlacementIndexMetricOutcome::Started,
281 PlacementIndexMetricReason::Ok,
282 );
283 PlacementIndexMetrics::record(
284 PlacementIndexMetricOperation::Classify,
285 PlacementIndexMetricOutcome::Completed,
286 PlacementIndexMetricReason::PendingFresh,
287 );
288 record_replay_sort_metrics();
289 record_intent_sort_metrics();
290 record_platform_call_sort_metrics();
291 ScalingMetrics::record(
292 ScalingMetricOperation::CreateWorker,
293 ScalingMetricOutcome::Completed,
294 ScalingMetricReason::Ok,
295 );
296 ScalingMetrics::record(
297 ScalingMetricOperation::BootstrapPool,
298 ScalingMetricOutcome::Skipped,
299 ScalingMetricReason::TargetSatisfied,
300 );
301 }
302
303 #[cfg(feature = "sharding")]
304 #[test]
305 fn page_sorts_sharding_metric_family_before_paginating() {
306 metrics::reset_for_tests();
307
308 ShardingMetrics::record(
309 ShardingMetricOperation::PlanAssign,
310 ShardingMetricOutcome::Completed,
311 ShardingMetricReason::ExistingCapacity,
312 );
313 ShardingMetrics::record(
314 ShardingMetricOperation::BootstrapPool,
315 ShardingMetricOutcome::Skipped,
316 ShardingMetricReason::TargetSatisfied,
317 );
318
319 assert_first_metric_labels(
320 MetricsKind::Placement,
321 ["sharding", "bootstrap_pool", "skipped", "target_satisfied"],
322 );
323 }
324
325 #[test]
326 fn sample_query_returns_value_and_current_counter() {
327 let sample = MetricsQuery::sample_query("ok");
328
329 assert_eq!(sample.value, "ok");
330 assert_eq!(sample.local_instructions, 0);
331 }
332
333 fn assert_first_metric_labels<const N: usize>(kind: MetricsKind, expected: [&str; N]) {
335 let page = MetricsQuery::page(
336 kind,
337 PageRequest {
338 limit: 1,
339 offset: 0,
340 },
341 );
342
343 assert!(page.total > 0);
344 assert_eq!(page.entries[0].labels, expected);
345 }
346
347 fn record_intent_sort_metrics() {
349 IntentMetrics::record(
350 IntentMetricSurface::ReceiptBacked,
351 IntentMetricOperation::Reserve,
352 IntentMetricOutcome::Completed,
353 IntentMetricReason::Ok,
354 );
355 IntentMetrics::record(
356 IntentMetricSurface::Local,
357 IntentMetricOperation::CapacityCheck,
358 IntentMetricOutcome::Failed,
359 IntentMetricReason::Capacity,
360 );
361 }
362
363 fn record_platform_call_sort_metrics() {
365 PlatformCallMetrics::record(
366 PlatformCallMetricSurface::Management,
367 PlatformCallMetricMode::Update,
368 PlatformCallMetricOutcome::Failed,
369 PlatformCallMetricReason::Infra,
370 );
371 PlatformCallMetrics::record(
372 PlatformCallMetricSurface::Generic,
373 PlatformCallMetricMode::BoundedWait,
374 PlatformCallMetricOutcome::Started,
375 PlatformCallMetricReason::Ok,
376 );
377 }
378
379 fn record_replay_sort_metrics() {
381 ReplayMetrics::record(
382 ReplayMetricOperation::Reserve,
383 ReplayMetricOutcome::Failed,
384 ReplayMetricReason::Capacity,
385 );
386 ReplayMetrics::record(
387 ReplayMetricOperation::Check,
388 ReplayMetricOutcome::Completed,
389 ReplayMetricReason::Fresh,
390 );
391 }
392}