1use crate::{
8 dto::{
9 metrics::{MetricEntry, MetricsKind, QueryPerfSample},
10 page::{Page, PageRequest},
11 },
12 ops::runtime::metrics,
13 perf,
14 workflow::view::paginate::paginate_vec,
15};
16
17pub struct MetricsQuery;
25
26impl MetricsQuery {
27 #[must_use]
29 pub fn page(kind: MetricsKind, page: PageRequest) -> Page<MetricEntry> {
30 Self::page_entries(metrics::entries(kind), page)
31 }
32
33 #[must_use]
34 pub fn core(page: PageRequest) -> Page<MetricEntry> {
35 Self::page_entries(metrics::core_entries(), page)
36 }
37
38 #[must_use]
39 pub fn placement(page: PageRequest) -> Page<MetricEntry> {
40 Self::page_entries(metrics::placement_entries(), page)
41 }
42
43 #[must_use]
44 pub fn platform(page: PageRequest) -> Page<MetricEntry> {
45 Self::page_entries(metrics::platform_entries(), page)
46 }
47
48 #[must_use]
49 pub fn runtime(page: PageRequest) -> Page<MetricEntry> {
50 Self::page_entries(metrics::runtime_entries(), page)
51 }
52
53 #[must_use]
54 pub fn security(page: PageRequest) -> Page<MetricEntry> {
55 Self::page_entries(metrics::security_entries(), page)
56 }
57
58 #[must_use]
59 pub fn storage(page: PageRequest) -> Page<MetricEntry> {
60 Self::page_entries(metrics::storage_entries(), page)
61 }
62
63 fn page_entries(mut entries: Vec<MetricEntry>, page: PageRequest) -> Page<MetricEntry> {
64 entries.sort_by(|a, b| {
65 a.labels
66 .cmp(&b.labels)
67 .then_with(|| a.principal.cmp(&b.principal))
68 });
69
70 paginate_vec(entries, page)
71 }
72
73 #[must_use]
75 pub fn sample_query<T>(value: T) -> QueryPerfSample<T> {
76 QueryPerfSample {
77 value,
78 local_instructions: perf::perf_counter(),
79 }
80 }
81}
82
83#[cfg(test)]
88mod tests {
89 use super::*;
90 #[cfg(feature = "sharding")]
91 use crate::ops::runtime::metrics::sharding::{
92 ShardingMetricOperation, ShardingMetricOutcome, ShardingMetricReason, ShardingMetrics,
93 };
94 use crate::{
95 ids::{AccessMetricKind, CanisterRole},
96 ops::runtime::metrics::{
97 self,
98 access::AccessMetrics,
99 auth::{
100 AuthMetricOperation, AuthMetricOutcome, AuthMetricReason, AuthMetricSurface,
101 AuthMetrics,
102 },
103 canister_ops::{
104 CanisterOpsMetricOperation, CanisterOpsMetricOutcome, CanisterOpsMetricReason,
105 CanisterOpsMetrics,
106 },
107 cascade::{
108 CascadeMetricOperation, CascadeMetricOutcome, CascadeMetricReason,
109 CascadeMetricSnapshot, CascadeMetrics,
110 },
111 directory::{
112 DirectoryMetricOperation, DirectoryMetricOutcome, DirectoryMetricReason,
113 DirectoryMetrics,
114 },
115 intent::{
116 IntentMetricOperation, IntentMetricOutcome, IntentMetricReason,
117 IntentMetricSurface, IntentMetrics,
118 },
119 platform_call::{
120 PlatformCallMetricMode, PlatformCallMetricOutcome, PlatformCallMetricReason,
121 PlatformCallMetricSurface, PlatformCallMetrics,
122 },
123 pool::{PoolMetricOperation, PoolMetricOutcome, PoolMetricReason, PoolMetrics},
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, "app_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", "app_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", "call", "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::Upgrade,
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 DirectoryMetrics::record(
273 DirectoryMetricOperation::Resolve,
274 DirectoryMetricOutcome::Started,
275 DirectoryMetricReason::Ok,
276 );
277 DirectoryMetrics::record(
278 DirectoryMetricOperation::Classify,
279 DirectoryMetricOutcome::Completed,
280 DirectoryMetricReason::PendingFresh,
281 );
282 PoolMetrics::record(
283 PoolMetricOperation::ImportQueued,
284 PoolMetricOutcome::Skipped,
285 PoolMetricReason::AlreadyPresent,
286 );
287 PoolMetrics::record(
288 PoolMetricOperation::CreateEmpty,
289 PoolMetricOutcome::Completed,
290 PoolMetricReason::Ok,
291 );
292 record_replay_sort_metrics();
293 record_intent_sort_metrics();
294 record_platform_call_sort_metrics();
295 ScalingMetrics::record(
296 ScalingMetricOperation::CreateWorker,
297 ScalingMetricOutcome::Completed,
298 ScalingMetricReason::Ok,
299 );
300 ScalingMetrics::record(
301 ScalingMetricOperation::BootstrapPool,
302 ScalingMetricOutcome::Skipped,
303 ScalingMetricReason::TargetSatisfied,
304 );
305 }
306
307 #[cfg(feature = "sharding")]
308 #[test]
309 fn page_sorts_sharding_metric_family_before_paginating() {
310 metrics::reset_for_tests();
311
312 ShardingMetrics::record(
313 ShardingMetricOperation::PlanAssign,
314 ShardingMetricOutcome::Completed,
315 ShardingMetricReason::ExistingCapacity,
316 );
317 ShardingMetrics::record(
318 ShardingMetricOperation::BootstrapPool,
319 ShardingMetricOutcome::Skipped,
320 ShardingMetricReason::TargetSatisfied,
321 );
322
323 assert_first_metric_labels(
324 MetricsKind::Placement,
325 ["sharding", "bootstrap_pool", "skipped", "target_satisfied"],
326 );
327 }
328
329 #[test]
330 fn sample_query_returns_value_and_current_counter() {
331 let sample = MetricsQuery::sample_query("ok");
332
333 assert_eq!(sample.value, "ok");
334 assert_eq!(sample.local_instructions, 0);
335 }
336
337 fn assert_first_metric_labels<const N: usize>(kind: MetricsKind, expected: [&str; N]) {
339 let page = MetricsQuery::page(
340 kind,
341 PageRequest {
342 limit: 1,
343 offset: 0,
344 },
345 );
346
347 assert!(page.total > 0);
348 assert_eq!(page.entries[0].labels, expected);
349 }
350
351 fn record_intent_sort_metrics() {
353 IntentMetrics::record(
354 IntentMetricSurface::Pool,
355 IntentMetricOperation::Reserve,
356 IntentMetricOutcome::Completed,
357 IntentMetricReason::Ok,
358 );
359 IntentMetrics::record(
360 IntentMetricSurface::Call,
361 IntentMetricOperation::CapacityCheck,
362 IntentMetricOutcome::Failed,
363 IntentMetricReason::Capacity,
364 );
365 }
366
367 fn record_platform_call_sort_metrics() {
369 PlatformCallMetrics::record(
370 PlatformCallMetricSurface::Management,
371 PlatformCallMetricMode::Update,
372 PlatformCallMetricOutcome::Failed,
373 PlatformCallMetricReason::Infra,
374 );
375 PlatformCallMetrics::record(
376 PlatformCallMetricSurface::Generic,
377 PlatformCallMetricMode::BoundedWait,
378 PlatformCallMetricOutcome::Started,
379 PlatformCallMetricReason::Ok,
380 );
381 }
382
383 fn record_replay_sort_metrics() {
385 ReplayMetrics::record(
386 ReplayMetricOperation::Reserve,
387 ReplayMetricOutcome::Failed,
388 ReplayMetricReason::Capacity,
389 );
390 ReplayMetrics::record(
391 ReplayMetricOperation::Check,
392 ReplayMetricOutcome::Completed,
393 ReplayMetricReason::Fresh,
394 );
395 }
396}