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