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 directory::{
113 DirectoryMetricOperation, DirectoryMetricOutcome, DirectoryMetricReason,
114 DirectoryMetrics,
115 },
116 intent::{
117 IntentMetricOperation, IntentMetricOutcome, IntentMetricReason,
118 IntentMetricSurface, IntentMetrics,
119 },
120 platform_call::{
121 PlatformCallMetricMode, PlatformCallMetricOutcome, PlatformCallMetricReason,
122 PlatformCallMetricSurface, PlatformCallMetrics,
123 },
124 pool::{PoolMetricOperation, PoolMetricOutcome, PoolMetricReason, PoolMetrics},
125 replay::{
126 ReplayMetricOperation, ReplayMetricOutcome, ReplayMetricReason, ReplayMetrics,
127 },
128 scaling::{
129 ScalingMetricOperation, ScalingMetricOutcome, ScalingMetricReason, ScalingMetrics,
130 },
131 wasm_store::{
132 WasmStoreMetricOperation, WasmStoreMetricOutcome, WasmStoreMetricReason,
133 WasmStoreMetricSource, WasmStoreMetrics,
134 },
135 },
136 };
137
138 #[test]
139 fn page_sorts_metric_rows_before_paginating() {
140 metrics::reset_for_tests();
141
142 AccessMetrics::increment("zeta", AccessMetricKind::Auth, "caller_is_root");
143 AccessMetrics::increment("alpha", AccessMetricKind::Guard, "app_allows_updates");
144
145 let page = MetricsQuery::page(
146 MetricsKind::Security,
147 PageRequest {
148 limit: 1,
149 offset: 0,
150 },
151 );
152
153 assert_eq!(page.total, 2);
154 assert_eq!(
155 page.entries[0].labels,
156 ["access", "alpha", "guard", "app_allows_updates"]
157 );
158
159 let page = MetricsQuery::page(
160 MetricsKind::Security,
161 PageRequest {
162 limit: 1,
163 offset: 1,
164 },
165 );
166
167 assert_eq!(page.total, 2);
168 assert_eq!(
169 page.entries[0].labels,
170 ["access", "zeta", "auth", "caller_is_root"]
171 );
172 }
173
174 #[test]
175 fn page_sorts_auth_metric_family_before_paginating() {
176 metrics::reset_for_tests();
177
178 AuthMetrics::record(
179 AuthMetricSurface::Session,
180 AuthMetricOperation::Session,
181 AuthMetricOutcome::Completed,
182 AuthMetricReason::Created,
183 );
184 AuthMetrics::record(
185 AuthMetricSurface::Attestation,
186 AuthMetricOperation::Verify,
187 AuthMetricOutcome::Failed,
188 AuthMetricReason::VerifyFailed,
189 );
190
191 assert_first_metric_labels(
192 MetricsKind::Security,
193 ["auth", "attestation", "verify", "failed", "verify_failed"],
194 );
195 }
196
197 #[test]
198 fn page_sorts_new_multi_label_metric_families_before_paginating() {
199 metrics::reset_for_tests();
200
201 record_multi_label_sort_metrics();
202
203 assert_first_metric_labels(
204 MetricsKind::Core,
205 ["canister_ops", "create", "app", "started", "ok"],
206 );
207 assert_first_metric_labels(
208 MetricsKind::Storage,
209 [
210 "wasm_store",
211 "chunk_upload",
212 "bootstrap",
213 "skipped",
214 "cache_hit",
215 ],
216 );
217 assert_first_metric_labels(
218 MetricsKind::Placement,
219 ["cascade", "child_send", "state", "failed", "send_failed"],
220 );
221 assert_first_metric_labels(
222 MetricsKind::Runtime,
223 ["intent", "call", "capacity_check", "failed", "capacity"],
224 );
225 assert_first_metric_labels(
226 MetricsKind::Platform,
227 ["platform_call", "generic", "bounded_wait", "started", "ok"],
228 );
229 assert_first_metric_labels(
230 MetricsKind::Security,
231 ["replay", "check", "completed", "fresh"],
232 );
233 }
234
235 fn record_multi_label_sort_metrics() {
237 CanisterOpsMetrics::record(
238 CanisterOpsMetricOperation::Upgrade,
239 &CanisterRole::new("worker"),
240 CanisterOpsMetricOutcome::Completed,
241 CanisterOpsMetricReason::Ok,
242 );
243 CanisterOpsMetrics::record(
244 CanisterOpsMetricOperation::Create,
245 &CanisterRole::new("app"),
246 CanisterOpsMetricOutcome::Started,
247 CanisterOpsMetricReason::Ok,
248 );
249 WasmStoreMetrics::record(
250 WasmStoreMetricOperation::SourceResolve,
251 WasmStoreMetricSource::Store,
252 WasmStoreMetricOutcome::Completed,
253 WasmStoreMetricReason::Ok,
254 );
255 WasmStoreMetrics::record(
256 WasmStoreMetricOperation::ChunkUpload,
257 WasmStoreMetricSource::Bootstrap,
258 WasmStoreMetricOutcome::Skipped,
259 WasmStoreMetricReason::CacheHit,
260 );
261 CascadeMetrics::record(
262 CascadeMetricOperation::RootFanout,
263 CascadeMetricSnapshot::Topology,
264 CascadeMetricOutcome::Completed,
265 CascadeMetricReason::Ok,
266 );
267 CascadeMetrics::record(
268 CascadeMetricOperation::ChildSend,
269 CascadeMetricSnapshot::State,
270 CascadeMetricOutcome::Failed,
271 CascadeMetricReason::SendFailed,
272 );
273 DirectoryMetrics::record(
274 DirectoryMetricOperation::Resolve,
275 DirectoryMetricOutcome::Started,
276 DirectoryMetricReason::Ok,
277 );
278 DirectoryMetrics::record(
279 DirectoryMetricOperation::Classify,
280 DirectoryMetricOutcome::Completed,
281 DirectoryMetricReason::PendingFresh,
282 );
283 PoolMetrics::record(
284 PoolMetricOperation::ImportQueued,
285 PoolMetricOutcome::Skipped,
286 PoolMetricReason::AlreadyPresent,
287 );
288 PoolMetrics::record(
289 PoolMetricOperation::CreateEmpty,
290 PoolMetricOutcome::Completed,
291 PoolMetricReason::Ok,
292 );
293 record_replay_sort_metrics();
294 record_intent_sort_metrics();
295 record_platform_call_sort_metrics();
296 ScalingMetrics::record(
297 ScalingMetricOperation::CreateWorker,
298 ScalingMetricOutcome::Completed,
299 ScalingMetricReason::Ok,
300 );
301 ScalingMetrics::record(
302 ScalingMetricOperation::BootstrapPool,
303 ScalingMetricOutcome::Skipped,
304 ScalingMetricReason::TargetSatisfied,
305 );
306 }
307
308 #[cfg(feature = "sharding")]
309 #[test]
310 fn page_sorts_sharding_metric_family_before_paginating() {
311 metrics::reset_for_tests();
312
313 ShardingMetrics::record(
314 ShardingMetricOperation::PlanAssign,
315 ShardingMetricOutcome::Completed,
316 ShardingMetricReason::ExistingCapacity,
317 );
318 ShardingMetrics::record(
319 ShardingMetricOperation::BootstrapPool,
320 ShardingMetricOutcome::Skipped,
321 ShardingMetricReason::TargetSatisfied,
322 );
323
324 assert_first_metric_labels(
325 MetricsKind::Placement,
326 ["sharding", "bootstrap_pool", "skipped", "target_satisfied"],
327 );
328 }
329
330 #[test]
331 fn sample_query_returns_value_and_current_counter() {
332 let sample = MetricsQuery::sample_query("ok");
333
334 assert_eq!(sample.value, "ok");
335 assert_eq!(sample.local_instructions, 0);
336 }
337
338 fn assert_first_metric_labels<const N: usize>(kind: MetricsKind, expected: [&str; N]) {
340 let page = MetricsQuery::page(
341 kind,
342 PageRequest {
343 limit: 1,
344 offset: 0,
345 },
346 );
347
348 assert!(page.total > 0);
349 assert_eq!(page.entries[0].labels, expected);
350 }
351
352 fn record_intent_sort_metrics() {
354 IntentMetrics::record(
355 IntentMetricSurface::Pool,
356 IntentMetricOperation::Reserve,
357 IntentMetricOutcome::Completed,
358 IntentMetricReason::Ok,
359 );
360 IntentMetrics::record(
361 IntentMetricSurface::Call,
362 IntentMetricOperation::CapacityCheck,
363 IntentMetricOutcome::Failed,
364 IntentMetricReason::Capacity,
365 );
366 }
367
368 fn record_platform_call_sort_metrics() {
370 PlatformCallMetrics::record(
371 PlatformCallMetricSurface::Management,
372 PlatformCallMetricMode::Update,
373 PlatformCallMetricOutcome::Failed,
374 PlatformCallMetricReason::Infra,
375 );
376 PlatformCallMetrics::record(
377 PlatformCallMetricSurface::Generic,
378 PlatformCallMetricMode::BoundedWait,
379 PlatformCallMetricOutcome::Started,
380 PlatformCallMetricReason::Ok,
381 );
382 }
383
384 fn record_replay_sort_metrics() {
386 ReplayMetrics::record(
387 ReplayMetricOperation::Reserve,
388 ReplayMetricOutcome::Failed,
389 ReplayMetricReason::Capacity,
390 );
391 ReplayMetrics::record(
392 ReplayMetricOperation::Check,
393 ReplayMetricOutcome::Completed,
394 ReplayMetricReason::Fresh,
395 );
396 }
397}