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