1use std::fmt::Debug;
16
17use dyn_clone::DynClone;
18
19pub trait Metrics: Send + Sync + DynClone + Debug {
21 fn create_counter(&self, name: String, unit_label: Option<String>) -> Box<dyn Counter>;
25 fn create_gauge(&self, name: String, unit_label: Option<String>) -> Box<dyn Gauge>;
29 fn create_histogram(&self, name: String, unit_label: Option<String>) -> Box<dyn Histogram>;
33
34 fn create_text(&self, name: String);
41
42 fn counter_family(&self, name: String, labels: Vec<String>) -> Box<dyn CounterFamily>;
44
45 fn gauge_family(&self, name: String, labels: Vec<String>) -> Box<dyn GaugeFamily>;
47
48 fn histogram_family(&self, name: String, labels: Vec<String>) -> Box<dyn HistogramFamily>;
50
51 fn text_family(&self, name: String, labels: Vec<String>) -> Box<dyn TextFamily>;
53
54 fn subgroup(&self, subgroup_name: String) -> Box<dyn Metrics>;
56}
57
58pub trait MetricsFamily<M>: Send + Sync + DynClone + Debug {
109 fn create(&self, labels: Vec<String>) -> M;
115}
116
117pub trait CounterFamily: MetricsFamily<Box<dyn Counter>> {}
119impl<T: MetricsFamily<Box<dyn Counter>>> CounterFamily for T {}
120
121pub trait GaugeFamily: MetricsFamily<Box<dyn Gauge>> {}
123impl<T: MetricsFamily<Box<dyn Gauge>>> GaugeFamily for T {}
124
125pub trait HistogramFamily: MetricsFamily<Box<dyn Histogram>> {}
127impl<T: MetricsFamily<Box<dyn Histogram>>> HistogramFamily for T {}
128
129pub trait TextFamily: MetricsFamily<()> {}
131impl<T: MetricsFamily<()>> TextFamily for T {}
132
133#[derive(Clone, Copy, Debug, Default)]
135pub struct NoMetrics;
136
137impl NoMetrics {
138 #[must_use]
140 pub fn boxed() -> Box<dyn Metrics> {
141 Box::<Self>::default()
142 }
143}
144
145impl Metrics for NoMetrics {
146 fn create_counter(&self, _: String, _: Option<String>) -> Box<dyn Counter> {
147 Box::new(NoMetrics)
148 }
149
150 fn create_gauge(&self, _: String, _: Option<String>) -> Box<dyn Gauge> {
151 Box::new(NoMetrics)
152 }
153
154 fn create_histogram(&self, _: String, _: Option<String>) -> Box<dyn Histogram> {
155 Box::new(NoMetrics)
156 }
157
158 fn create_text(&self, _: String) {}
159
160 fn counter_family(&self, _: String, _: Vec<String>) -> Box<dyn CounterFamily> {
161 Box::new(NoMetrics)
162 }
163
164 fn gauge_family(&self, _: String, _: Vec<String>) -> Box<dyn GaugeFamily> {
165 Box::new(NoMetrics)
166 }
167
168 fn histogram_family(&self, _: String, _: Vec<String>) -> Box<dyn HistogramFamily> {
169 Box::new(NoMetrics)
170 }
171
172 fn text_family(&self, _: String, _: Vec<String>) -> Box<dyn TextFamily> {
173 Box::new(NoMetrics)
174 }
175
176 fn subgroup(&self, _: String) -> Box<dyn Metrics> {
177 Box::new(NoMetrics)
178 }
179}
180
181impl Counter for NoMetrics {
182 fn add(&self, _: usize) {}
183}
184impl Gauge for NoMetrics {
185 fn set(&self, _: usize) {}
186 fn update(&self, _: i64) {}
187}
188impl Histogram for NoMetrics {
189 fn add_point(&self, _: f64) {}
190}
191impl MetricsFamily<Box<dyn Counter>> for NoMetrics {
192 fn create(&self, _: Vec<String>) -> Box<dyn Counter> {
193 Box::new(NoMetrics)
194 }
195}
196impl MetricsFamily<Box<dyn Gauge>> for NoMetrics {
197 fn create(&self, _: Vec<String>) -> Box<dyn Gauge> {
198 Box::new(NoMetrics)
199 }
200}
201impl MetricsFamily<Box<dyn Histogram>> for NoMetrics {
202 fn create(&self, _: Vec<String>) -> Box<dyn Histogram> {
203 Box::new(NoMetrics)
204 }
205}
206impl MetricsFamily<()> for NoMetrics {
207 fn create(&self, _: Vec<String>) {}
208}
209
210pub trait Counter: Send + Sync + Debug + DynClone {
212 fn add(&self, amount: usize);
214}
215
216pub trait Gauge: Send + Sync + Debug + DynClone {
218 fn set(&self, amount: usize);
220
221 fn update(&self, delta: i64);
223}
224
225pub trait Histogram: Send + Sync + Debug + DynClone {
227 fn add_point(&self, point: f64);
229}
230
231dyn_clone::clone_trait_object!(Metrics);
232dyn_clone::clone_trait_object!(Gauge);
233dyn_clone::clone_trait_object!(Counter);
234dyn_clone::clone_trait_object!(Histogram);
235
236#[cfg(test)]
237mod test {
238 use std::{
239 collections::HashMap,
240 sync::{Arc, Mutex},
241 };
242
243 use super::*;
244
245 #[derive(Debug, Clone)]
246 struct TestMetrics {
247 prefix: String,
248 values: Arc<Mutex<Inner>>,
249 }
250
251 impl TestMetrics {
252 fn sub(&self, name: String) -> Self {
253 let prefix = if self.prefix.is_empty() {
254 name
255 } else {
256 format!("{}-{name}", self.prefix)
257 };
258 Self {
259 prefix,
260 values: Arc::clone(&self.values),
261 }
262 }
263
264 fn family(&self, labels: Vec<String>) -> Self {
265 let mut curr = self.clone();
266 for label in labels {
267 curr = curr.sub(label);
268 }
269 curr
270 }
271 }
272
273 impl Metrics for TestMetrics {
274 fn create_counter(
275 &self,
276 name: String,
277 _unit_label: Option<String>,
278 ) -> Box<dyn super::Counter> {
279 Box::new(self.sub(name))
280 }
281
282 fn create_gauge(&self, name: String, _unit_label: Option<String>) -> Box<dyn super::Gauge> {
283 Box::new(self.sub(name))
284 }
285
286 fn create_histogram(
287 &self,
288 name: String,
289 _unit_label: Option<String>,
290 ) -> Box<dyn super::Histogram> {
291 Box::new(self.sub(name))
292 }
293
294 fn create_text(&self, name: String) {
295 self.create_gauge(name, None).set(1);
296 }
297
298 fn counter_family(&self, name: String, _: Vec<String>) -> Box<dyn CounterFamily> {
299 Box::new(self.sub(name))
300 }
301
302 fn gauge_family(&self, name: String, _: Vec<String>) -> Box<dyn GaugeFamily> {
303 Box::new(self.sub(name))
304 }
305
306 fn histogram_family(&self, name: String, _: Vec<String>) -> Box<dyn HistogramFamily> {
307 Box::new(self.sub(name))
308 }
309
310 fn text_family(&self, name: String, _: Vec<String>) -> Box<dyn TextFamily> {
311 Box::new(self.sub(name))
312 }
313
314 fn subgroup(&self, subgroup_name: String) -> Box<dyn Metrics> {
315 Box::new(self.sub(subgroup_name))
316 }
317 }
318
319 impl Counter for TestMetrics {
320 fn add(&self, amount: usize) {
321 *self
322 .values
323 .lock()
324 .unwrap()
325 .counters
326 .entry(self.prefix.clone())
327 .or_default() += amount;
328 }
329 }
330
331 impl Gauge for TestMetrics {
332 fn set(&self, amount: usize) {
333 *self
334 .values
335 .lock()
336 .unwrap()
337 .gauges
338 .entry(self.prefix.clone())
339 .or_default() = amount;
340 }
341 fn update(&self, delta: i64) {
342 let mut values = self.values.lock().unwrap();
343 let value = values.gauges.entry(self.prefix.clone()).or_default();
344 let signed_value = i64::try_from(*value).unwrap_or(i64::MAX);
345 *value = usize::try_from(signed_value + delta).unwrap_or(0);
346 }
347 }
348
349 impl Histogram for TestMetrics {
350 fn add_point(&self, point: f64) {
351 self.values
352 .lock()
353 .unwrap()
354 .histograms
355 .entry(self.prefix.clone())
356 .or_default()
357 .push(point);
358 }
359 }
360
361 impl MetricsFamily<Box<dyn Counter>> for TestMetrics {
362 fn create(&self, labels: Vec<String>) -> Box<dyn Counter> {
363 Box::new(self.family(labels))
364 }
365 }
366
367 impl MetricsFamily<Box<dyn Gauge>> for TestMetrics {
368 fn create(&self, labels: Vec<String>) -> Box<dyn Gauge> {
369 Box::new(self.family(labels))
370 }
371 }
372
373 impl MetricsFamily<Box<dyn Histogram>> for TestMetrics {
374 fn create(&self, labels: Vec<String>) -> Box<dyn Histogram> {
375 Box::new(self.family(labels))
376 }
377 }
378
379 impl MetricsFamily<()> for TestMetrics {
380 fn create(&self, labels: Vec<String>) {
381 self.family(labels).set(1);
382 }
383 }
384
385 #[derive(Default, Debug)]
386 struct Inner {
387 counters: HashMap<String, usize>,
388 gauges: HashMap<String, usize>,
389 histograms: HashMap<String, Vec<f64>>,
390 }
391
392 #[test]
393 fn test() {
394 let values = Arc::default();
395 {
397 let metrics: Box<dyn Metrics> = Box::new(TestMetrics {
398 prefix: String::new(),
399 values: Arc::clone(&values),
400 });
401
402 let gauge = metrics.create_gauge("foo".to_string(), None);
403 let counter = metrics.create_counter("bar".to_string(), None);
404 let histogram = metrics.create_histogram("baz".to_string(), None);
405
406 gauge.set(5);
407 gauge.update(-2);
408
409 for i in 0..5 {
410 counter.add(i);
411 }
412
413 for i in 0..10 {
414 histogram.add_point(f64::from(i));
415 }
416
417 let sub = metrics.subgroup("child".to_string());
418
419 let sub_gauge = sub.create_gauge("foo".to_string(), None);
420 let sub_counter = sub.create_counter("bar".to_string(), None);
421 let sub_histogram = sub.create_histogram("baz".to_string(), None);
422
423 sub_gauge.set(10);
424
425 for i in 0..5 {
426 sub_counter.add(i * 2);
427 }
428
429 for i in 0..10 {
430 sub_histogram.add_point(f64::from(i) * 2.0);
431 }
432 }
433
434 let values = Arc::try_unwrap(values).unwrap().into_inner().unwrap();
437 assert_eq!(values.gauges["foo"], 3);
438 assert_eq!(values.counters["bar"], 10); assert_eq!(
440 values.histograms["baz"],
441 vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
442 );
443
444 assert_eq!(values.gauges["child-foo"], 10);
445 assert_eq!(values.counters["child-bar"], 20); assert_eq!(
447 values.histograms["child-baz"],
448 vec![0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0]
449 );
450 }
451}