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