canic_core/workflow/metrics/
query.rs1use 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 canister_ops::{
61 CanisterOpsMetricOperation, CanisterOpsMetricOutcome, CanisterOpsMetricReason,
62 CanisterOpsMetrics,
63 },
64 cascade::{
65 CascadeMetricOperation, CascadeMetricOutcome, CascadeMetricReason,
66 CascadeMetricSnapshot, CascadeMetrics,
67 },
68 directory::{
69 DirectoryMetricOperation, DirectoryMetricOutcome, DirectoryMetricReason,
70 DirectoryMetrics,
71 },
72 pool::{PoolMetricOperation, PoolMetricOutcome, PoolMetricReason, PoolMetrics},
73 scaling::{
74 ScalingMetricOperation, ScalingMetricOutcome, ScalingMetricReason, ScalingMetrics,
75 },
76 wasm_store::{
77 WasmStoreMetricOperation, WasmStoreMetricOutcome, WasmStoreMetricReason,
78 WasmStoreMetricSource, WasmStoreMetrics,
79 },
80 },
81 };
82
83 #[test]
84 fn page_sorts_metric_rows_before_paginating() {
85 metrics::reset_for_tests();
86
87 AccessMetrics::increment("zeta", AccessMetricKind::Auth, "caller_is_root");
88 AccessMetrics::increment("alpha", AccessMetricKind::Guard, "app_allows_updates");
89
90 let page = MetricsQuery::page(
91 MetricsKind::Access,
92 PageRequest {
93 limit: 1,
94 offset: 0,
95 },
96 );
97
98 assert_eq!(page.total, 2);
99 assert_eq!(
100 page.entries[0].labels,
101 ["alpha", "guard", "app_allows_updates"]
102 );
103
104 let page = MetricsQuery::page(
105 MetricsKind::Access,
106 PageRequest {
107 limit: 1,
108 offset: 1,
109 },
110 );
111
112 assert_eq!(page.total, 2);
113 assert_eq!(page.entries[0].labels, ["zeta", "auth", "caller_is_root"]);
114 }
115
116 #[test]
117 fn page_sorts_new_multi_label_metric_families_before_paginating() {
118 metrics::reset_for_tests();
119
120 CanisterOpsMetrics::record(
121 CanisterOpsMetricOperation::Upgrade,
122 &CanisterRole::new("worker"),
123 CanisterOpsMetricOutcome::Completed,
124 CanisterOpsMetricReason::Ok,
125 );
126 CanisterOpsMetrics::record(
127 CanisterOpsMetricOperation::Create,
128 &CanisterRole::new("app"),
129 CanisterOpsMetricOutcome::Started,
130 CanisterOpsMetricReason::Ok,
131 );
132 WasmStoreMetrics::record(
133 WasmStoreMetricOperation::SourceResolve,
134 WasmStoreMetricSource::Store,
135 WasmStoreMetricOutcome::Completed,
136 WasmStoreMetricReason::Ok,
137 );
138 WasmStoreMetrics::record(
139 WasmStoreMetricOperation::ChunkUpload,
140 WasmStoreMetricSource::Bootstrap,
141 WasmStoreMetricOutcome::Skipped,
142 WasmStoreMetricReason::CacheHit,
143 );
144 CascadeMetrics::record(
145 CascadeMetricOperation::RootFanout,
146 CascadeMetricSnapshot::Topology,
147 CascadeMetricOutcome::Completed,
148 CascadeMetricReason::Ok,
149 );
150 CascadeMetrics::record(
151 CascadeMetricOperation::ChildSend,
152 CascadeMetricSnapshot::State,
153 CascadeMetricOutcome::Failed,
154 CascadeMetricReason::SendFailed,
155 );
156 DirectoryMetrics::record(
157 DirectoryMetricOperation::Resolve,
158 DirectoryMetricOutcome::Started,
159 DirectoryMetricReason::Ok,
160 );
161 DirectoryMetrics::record(
162 DirectoryMetricOperation::Classify,
163 DirectoryMetricOutcome::Completed,
164 DirectoryMetricReason::PendingFresh,
165 );
166 PoolMetrics::record(
167 PoolMetricOperation::ImportQueued,
168 PoolMetricOutcome::Skipped,
169 PoolMetricReason::AlreadyPresent,
170 );
171 PoolMetrics::record(
172 PoolMetricOperation::CreateEmpty,
173 PoolMetricOutcome::Completed,
174 PoolMetricReason::Ok,
175 );
176 ScalingMetrics::record(
177 ScalingMetricOperation::CreateWorker,
178 ScalingMetricOutcome::Completed,
179 ScalingMetricReason::Ok,
180 );
181 ScalingMetrics::record(
182 ScalingMetricOperation::BootstrapPool,
183 ScalingMetricOutcome::Skipped,
184 ScalingMetricReason::TargetSatisfied,
185 );
186
187 assert_first_metric_labels(MetricsKind::CanisterOps, ["create", "app", "started", "ok"]);
188 assert_first_metric_labels(
189 MetricsKind::WasmStore,
190 ["chunk_upload", "bootstrap", "skipped", "cache_hit"],
191 );
192 assert_first_metric_labels(
193 MetricsKind::Cascade,
194 ["child_send", "state", "failed", "send_failed"],
195 );
196 assert_first_metric_labels(
197 MetricsKind::Directory,
198 ["classify", "completed", "pending_fresh"],
199 );
200 assert_first_metric_labels(MetricsKind::Pool, ["create_empty", "completed", "ok"]);
201 assert_first_metric_labels(
202 MetricsKind::Scaling,
203 ["bootstrap_pool", "skipped", "target_satisfied"],
204 );
205 }
206
207 #[cfg(feature = "sharding")]
208 #[test]
209 fn page_sorts_sharding_metric_family_before_paginating() {
210 metrics::reset_for_tests();
211
212 ShardingMetrics::record(
213 ShardingMetricOperation::PlanAssign,
214 ShardingMetricOutcome::Completed,
215 ShardingMetricReason::ExistingCapacity,
216 );
217 ShardingMetrics::record(
218 ShardingMetricOperation::BootstrapPool,
219 ShardingMetricOutcome::Skipped,
220 ShardingMetricReason::TargetSatisfied,
221 );
222
223 assert_first_metric_labels(
224 MetricsKind::Sharding,
225 ["bootstrap_pool", "skipped", "target_satisfied"],
226 );
227 }
228
229 #[test]
230 fn sample_query_returns_value_and_current_counter() {
231 let sample = MetricsQuery::sample_query("ok");
232
233 assert_eq!(sample.value, "ok");
234 assert_eq!(sample.local_instructions, 0);
235 }
236
237 fn assert_first_metric_labels<const N: usize>(kind: MetricsKind, expected: [&str; N]) {
239 let page = MetricsQuery::page(
240 kind,
241 PageRequest {
242 limit: 1,
243 offset: 0,
244 },
245 );
246
247 assert_eq!(page.total, 2);
248 assert_eq!(page.entries[0].labels, expected);
249 }
250}