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 pool::{PoolMetricOperation, PoolMetricOutcome, PoolMetricReason, PoolMetrics},
77 replay::{
78 ReplayMetricOperation, ReplayMetricOutcome, ReplayMetricReason, ReplayMetrics,
79 },
80 scaling::{
81 ScalingMetricOperation, ScalingMetricOutcome, ScalingMetricReason, ScalingMetrics,
82 },
83 wasm_store::{
84 WasmStoreMetricOperation, WasmStoreMetricOutcome, WasmStoreMetricReason,
85 WasmStoreMetricSource, WasmStoreMetrics,
86 },
87 },
88 };
89
90 #[test]
91 fn page_sorts_metric_rows_before_paginating() {
92 metrics::reset_for_tests();
93
94 AccessMetrics::increment("zeta", AccessMetricKind::Auth, "caller_is_root");
95 AccessMetrics::increment("alpha", AccessMetricKind::Guard, "app_allows_updates");
96
97 let page = MetricsQuery::page(
98 MetricsKind::Access,
99 PageRequest {
100 limit: 1,
101 offset: 0,
102 },
103 );
104
105 assert_eq!(page.total, 2);
106 assert_eq!(
107 page.entries[0].labels,
108 ["alpha", "guard", "app_allows_updates"]
109 );
110
111 let page = MetricsQuery::page(
112 MetricsKind::Access,
113 PageRequest {
114 limit: 1,
115 offset: 1,
116 },
117 );
118
119 assert_eq!(page.total, 2);
120 assert_eq!(page.entries[0].labels, ["zeta", "auth", "caller_is_root"]);
121 }
122
123 #[test]
124 fn page_sorts_auth_metric_family_before_paginating() {
125 metrics::reset_for_tests();
126
127 AuthMetrics::record(
128 AuthMetricSurface::Session,
129 AuthMetricOperation::Session,
130 AuthMetricOutcome::Completed,
131 AuthMetricReason::Created,
132 );
133 AuthMetrics::record(
134 AuthMetricSurface::Attestation,
135 AuthMetricOperation::Verify,
136 AuthMetricOutcome::Failed,
137 AuthMetricReason::UnknownKeyId,
138 );
139
140 assert_first_metric_labels(
141 MetricsKind::Auth,
142 ["attestation", "verify", "failed", "unknown_key_id"],
143 );
144 }
145
146 #[test]
147 fn page_sorts_new_multi_label_metric_families_before_paginating() {
148 metrics::reset_for_tests();
149
150 CanisterOpsMetrics::record(
151 CanisterOpsMetricOperation::Upgrade,
152 &CanisterRole::new("worker"),
153 CanisterOpsMetricOutcome::Completed,
154 CanisterOpsMetricReason::Ok,
155 );
156 CanisterOpsMetrics::record(
157 CanisterOpsMetricOperation::Create,
158 &CanisterRole::new("app"),
159 CanisterOpsMetricOutcome::Started,
160 CanisterOpsMetricReason::Ok,
161 );
162 WasmStoreMetrics::record(
163 WasmStoreMetricOperation::SourceResolve,
164 WasmStoreMetricSource::Store,
165 WasmStoreMetricOutcome::Completed,
166 WasmStoreMetricReason::Ok,
167 );
168 WasmStoreMetrics::record(
169 WasmStoreMetricOperation::ChunkUpload,
170 WasmStoreMetricSource::Bootstrap,
171 WasmStoreMetricOutcome::Skipped,
172 WasmStoreMetricReason::CacheHit,
173 );
174 CascadeMetrics::record(
175 CascadeMetricOperation::RootFanout,
176 CascadeMetricSnapshot::Topology,
177 CascadeMetricOutcome::Completed,
178 CascadeMetricReason::Ok,
179 );
180 CascadeMetrics::record(
181 CascadeMetricOperation::ChildSend,
182 CascadeMetricSnapshot::State,
183 CascadeMetricOutcome::Failed,
184 CascadeMetricReason::SendFailed,
185 );
186 DirectoryMetrics::record(
187 DirectoryMetricOperation::Resolve,
188 DirectoryMetricOutcome::Started,
189 DirectoryMetricReason::Ok,
190 );
191 DirectoryMetrics::record(
192 DirectoryMetricOperation::Classify,
193 DirectoryMetricOutcome::Completed,
194 DirectoryMetricReason::PendingFresh,
195 );
196 PoolMetrics::record(
197 PoolMetricOperation::ImportQueued,
198 PoolMetricOutcome::Skipped,
199 PoolMetricReason::AlreadyPresent,
200 );
201 PoolMetrics::record(
202 PoolMetricOperation::CreateEmpty,
203 PoolMetricOutcome::Completed,
204 PoolMetricReason::Ok,
205 );
206 ReplayMetrics::record(
207 ReplayMetricOperation::Reserve,
208 ReplayMetricOutcome::Failed,
209 ReplayMetricReason::Capacity,
210 );
211 ReplayMetrics::record(
212 ReplayMetricOperation::Check,
213 ReplayMetricOutcome::Completed,
214 ReplayMetricReason::Fresh,
215 );
216 ScalingMetrics::record(
217 ScalingMetricOperation::CreateWorker,
218 ScalingMetricOutcome::Completed,
219 ScalingMetricReason::Ok,
220 );
221 ScalingMetrics::record(
222 ScalingMetricOperation::BootstrapPool,
223 ScalingMetricOutcome::Skipped,
224 ScalingMetricReason::TargetSatisfied,
225 );
226
227 assert_first_metric_labels(MetricsKind::CanisterOps, ["create", "app", "started", "ok"]);
228 assert_first_metric_labels(
229 MetricsKind::WasmStore,
230 ["chunk_upload", "bootstrap", "skipped", "cache_hit"],
231 );
232 assert_first_metric_labels(
233 MetricsKind::Cascade,
234 ["child_send", "state", "failed", "send_failed"],
235 );
236 assert_first_metric_labels(
237 MetricsKind::Directory,
238 ["classify", "completed", "pending_fresh"],
239 );
240 assert_first_metric_labels(MetricsKind::Pool, ["create_empty", "completed", "ok"]);
241 assert_first_metric_labels(MetricsKind::Replay, ["check", "completed", "fresh"]);
242 assert_first_metric_labels(
243 MetricsKind::Scaling,
244 ["bootstrap_pool", "skipped", "target_satisfied"],
245 );
246 }
247
248 #[cfg(feature = "sharding")]
249 #[test]
250 fn page_sorts_sharding_metric_family_before_paginating() {
251 metrics::reset_for_tests();
252
253 ShardingMetrics::record(
254 ShardingMetricOperation::PlanAssign,
255 ShardingMetricOutcome::Completed,
256 ShardingMetricReason::ExistingCapacity,
257 );
258 ShardingMetrics::record(
259 ShardingMetricOperation::BootstrapPool,
260 ShardingMetricOutcome::Skipped,
261 ShardingMetricReason::TargetSatisfied,
262 );
263
264 assert_first_metric_labels(
265 MetricsKind::Sharding,
266 ["bootstrap_pool", "skipped", "target_satisfied"],
267 );
268 }
269
270 #[test]
271 fn sample_query_returns_value_and_current_counter() {
272 let sample = MetricsQuery::sample_query("ok");
273
274 assert_eq!(sample.value, "ok");
275 assert_eq!(sample.local_instructions, 0);
276 }
277
278 fn assert_first_metric_labels<const N: usize>(kind: MetricsKind, expected: [&str; N]) {
280 let page = MetricsQuery::page(
281 kind,
282 PageRequest {
283 limit: 1,
284 offset: 0,
285 },
286 );
287
288 assert_eq!(page.total, 2);
289 assert_eq!(page.entries[0].labels, expected);
290 }
291}