objectstore_metrics/
mock.rs1use std::sync::{Arc, Mutex};
7
8use metrics::{Counter, Gauge, Histogram, Key, KeyName, Metadata, Recorder, SharedString, Unit};
9
10pub fn with_capturing_test_client(f: impl FnOnce()) -> Vec<String> {
24 let recorder = MockRecorder::default();
25 metrics::with_local_recorder(&recorder, f);
26 recorder.consume()
27}
28
29#[derive(Clone, Default)]
31struct MockRecorder {
32 inner: Arc<Mutex<Vec<String>>>,
33}
34
35impl MockRecorder {
36 fn consume(self) -> Vec<String> {
38 self.inner
39 .lock()
40 .unwrap_or_else(|e| e.into_inner())
41 .drain(..)
42 .collect()
43 }
44}
45
46impl Recorder for MockRecorder {
47 fn describe_counter(&self, _key: KeyName, _unit: Option<Unit>, _description: SharedString) {}
48 fn describe_gauge(&self, _key: KeyName, _unit: Option<Unit>, _description: SharedString) {}
49 fn describe_histogram(&self, _key: KeyName, _unit: Option<Unit>, _description: SharedString) {}
50
51 fn register_counter(&self, key: &Key, _metadata: &Metadata<'_>) -> Counter {
52 Counter::from_arc(Arc::new(MockFn::new(key.clone(), self.inner.clone())))
53 }
54
55 fn register_gauge(&self, key: &Key, _metadata: &Metadata<'_>) -> Gauge {
56 Gauge::from_arc(Arc::new(MockFn::new(key.clone(), self.inner.clone())))
57 }
58
59 fn register_histogram(&self, key: &Key, _metadata: &Metadata<'_>) -> Histogram {
60 Histogram::from_arc(Arc::new(MockFn::new(key.clone(), self.inner.clone())))
61 }
62}
63
64struct MockFn {
66 key: Key,
67 inner: Arc<Mutex<Vec<String>>>,
68}
69
70impl MockFn {
71 fn new(key: Key, inner: Arc<Mutex<Vec<String>>>) -> Self {
72 Self { key, inner }
73 }
74
75 fn push(&self, value: &str, ty: &str) {
76 let labels = self
77 .key
78 .labels()
79 .map(|l| format!("{}:{}", l.key(), l.value()))
80 .collect::<Vec<_>>()
81 .join(",");
82
83 let entry = if labels.is_empty() {
84 format!("{}:{}|{}", self.key.name(), value, ty)
85 } else {
86 format!("{}:{}|{}|#{}", self.key.name(), value, ty, labels)
87 };
88
89 if let Ok(mut vec) = self.inner.lock() {
90 vec.push(entry);
91 }
92 }
93}
94
95impl metrics::CounterFn for MockFn {
96 fn increment(&self, value: u64) {
97 self.push(&format!("+{value}"), "c");
98 }
99
100 fn absolute(&self, value: u64) {
101 self.push(&format!("={value}"), "c");
102 }
103}
104
105impl metrics::GaugeFn for MockFn {
106 fn increment(&self, value: f64) {
107 self.push(&format!("+{value}"), "g");
108 }
109
110 fn decrement(&self, value: f64) {
111 self.push(&format!("-{value}"), "g");
112 }
113
114 fn set(&self, value: f64) {
115 self.push(&format!("{value}"), "g");
116 }
117}
118
119impl metrics::HistogramFn for MockFn {
120 fn record(&self, value: f64) {
121 self.push(&format!("{value}"), "d");
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn captures_counter() {
131 let captured = with_capturing_test_client(|| {
132 crate::counter!("test.counter": 1);
133 });
134 assert_eq!(captured.len(), 1);
135 assert_eq!(captured[0], "test.counter:+1|c");
136 }
137
138 #[test]
139 fn captures_counter_with_tags() {
140 let captured = with_capturing_test_client(|| {
141 crate::counter!("test.counter": 1, "env" => "prod", "region" => "us");
142 });
143 assert_eq!(captured.len(), 1);
144 assert_eq!(captured[0], "test.counter:+1|c|#env:prod,region:us");
145 }
146
147 #[test]
148 fn captures_gauge() {
149 let captured = with_capturing_test_client(|| {
150 crate::gauge!("test.gauge": 42usize);
151 });
152 assert_eq!(captured.len(), 1);
153 assert_eq!(captured[0], "test.gauge:42|g");
154 }
155
156 #[test]
157 fn captures_gauge_bytes() {
158 let captured = with_capturing_test_client(|| {
159 crate::gauge!("test.gauge"@b: 1024u64);
160 });
161 assert_eq!(captured.len(), 1);
162 assert_eq!(captured[0], "test.gauge:1024|g");
163 }
164
165 #[test]
166 fn captures_distribution() {
167 let captured = with_capturing_test_client(|| {
168 crate::distribution!("test.dist": 2.78f64);
169 });
170 assert_eq!(captured.len(), 1);
171 assert_eq!(captured[0], "test.dist:2.78|d");
172 }
173
174 #[test]
175 fn captures_distribution_seconds() {
176 let captured = with_capturing_test_client(|| {
177 let dur = std::time::Duration::from_millis(1500);
178 crate::distribution!("test.latency"@s: dur);
179 });
180 assert_eq!(captured.len(), 1);
181 assert_eq!(captured[0], "test.latency:1.5|d");
182 }
183
184 #[test]
185 fn captures_distribution_bytes() {
186 let captured = with_capturing_test_client(|| {
187 crate::distribution!("test.size"@b: 4096u64);
188 });
189 assert_eq!(captured.len(), 1);
190 assert_eq!(captured[0], "test.size:4096|d");
191 }
192
193 #[test]
194 fn captures_distribution_with_tags() {
195 let captured = with_capturing_test_client(|| {
196 let dur = std::time::Duration::from_secs(2);
197 crate::distribution!(
198 "test.latency"@s: dur,
199 "route" => "/v1/test",
200 "method" => "GET"
201 );
202 });
203 assert_eq!(captured.len(), 1);
204 assert_eq!(captured[0], "test.latency:2|d|#route:/v1/test,method:GET");
205 }
206
207 #[test]
208 fn integer_tag_values() {
209 let captured = with_capturing_test_client(|| {
210 crate::counter!("test.status": 1, "status" => 200u16);
211 });
212 assert_eq!(captured.len(), 1);
213 assert_eq!(captured[0], "test.status:+1|c|#status:200");
214 }
215}