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