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