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 use crate::{
52 ids::{AccessMetricKind, CanisterRole},
53 ops::runtime::metrics::{
54 self,
55 access::AccessMetrics,
56 canister_ops::{
57 CanisterOpsMetricOperation, CanisterOpsMetricOutcome, CanisterOpsMetricReason,
58 CanisterOpsMetrics,
59 },
60 wasm_store::{
61 WasmStoreMetricOperation, WasmStoreMetricOutcome, WasmStoreMetricReason,
62 WasmStoreMetricSource, WasmStoreMetrics,
63 },
64 },
65 };
66
67 #[test]
68 fn page_sorts_metric_rows_before_paginating() {
69 metrics::reset_for_tests();
70
71 AccessMetrics::increment("zeta", AccessMetricKind::Auth, "caller_is_root");
72 AccessMetrics::increment("alpha", AccessMetricKind::Guard, "app_allows_updates");
73
74 let page = MetricsQuery::page(
75 MetricsKind::Access,
76 PageRequest {
77 limit: 1,
78 offset: 0,
79 },
80 );
81
82 assert_eq!(page.total, 2);
83 assert_eq!(
84 page.entries[0].labels,
85 ["alpha", "guard", "app_allows_updates"]
86 );
87
88 let page = MetricsQuery::page(
89 MetricsKind::Access,
90 PageRequest {
91 limit: 1,
92 offset: 1,
93 },
94 );
95
96 assert_eq!(page.total, 2);
97 assert_eq!(page.entries[0].labels, ["zeta", "auth", "caller_is_root"]);
98 }
99
100 #[test]
101 fn page_sorts_new_multi_label_metric_families_before_paginating() {
102 metrics::reset_for_tests();
103
104 CanisterOpsMetrics::record(
105 CanisterOpsMetricOperation::Upgrade,
106 &CanisterRole::new("worker"),
107 CanisterOpsMetricOutcome::Completed,
108 CanisterOpsMetricReason::Ok,
109 );
110 CanisterOpsMetrics::record(
111 CanisterOpsMetricOperation::Create,
112 &CanisterRole::new("app"),
113 CanisterOpsMetricOutcome::Started,
114 CanisterOpsMetricReason::Ok,
115 );
116 WasmStoreMetrics::record(
117 WasmStoreMetricOperation::SourceResolve,
118 WasmStoreMetricSource::Store,
119 WasmStoreMetricOutcome::Completed,
120 WasmStoreMetricReason::Ok,
121 );
122 WasmStoreMetrics::record(
123 WasmStoreMetricOperation::ChunkUpload,
124 WasmStoreMetricSource::Bootstrap,
125 WasmStoreMetricOutcome::Skipped,
126 WasmStoreMetricReason::CacheHit,
127 );
128
129 let page = MetricsQuery::page(
130 MetricsKind::CanisterOps,
131 PageRequest {
132 limit: 1,
133 offset: 0,
134 },
135 );
136
137 assert_eq!(page.total, 2);
138 assert_eq!(page.entries[0].labels, ["create", "app", "started", "ok"]);
139
140 let page = MetricsQuery::page(
141 MetricsKind::WasmStore,
142 PageRequest {
143 limit: 1,
144 offset: 0,
145 },
146 );
147
148 assert_eq!(page.total, 2);
149 assert_eq!(
150 page.entries[0].labels,
151 ["chunk_upload", "bootstrap", "skipped", "cache_hit"]
152 );
153 }
154
155 #[test]
156 fn sample_query_returns_value_and_current_counter() {
157 let sample = MetricsQuery::sample_query("ok");
158
159 assert_eq!(sample.value, "ok");
160 assert_eq!(sample.local_instructions, 0);
161 }
162}