cimetrics_rs/
lib.rs

1use std::collections::BTreeMap;
2use std::io::Write;
3use std::sync::Mutex;
4use std::sync::OnceLock;
5
6static METRICS: Metrics = Metrics::new();
7
8struct Metrics(OnceLock<MetricsInner>);
9
10/// Use a `BTreeMap` over a `HashMap` so metrics are output in a consistent order and give a useful
11/// diff when commited.
12struct MetricsInner(Mutex<(u64, BTreeMap<&'static str, u64>)>);
13
14pub struct MetricsHandle<'a>(&'a MetricsInner);
15
16pub fn handle() -> MetricsHandle<'static> {
17    let metrics = METRICS
18        .0
19        .get_or_init(|| MetricsInner(Mutex::new((0, BTreeMap::new()))));
20    let mut guard = metrics.0.lock().unwrap();
21    let (count, _map) = &mut *guard;
22    *count += 1;
23
24    MetricsHandle(metrics)
25}
26
27impl Metrics {
28    pub const fn new() -> Self {
29        Self(OnceLock::new())
30    }
31}
32
33impl MetricsHandle<'_> {
34    pub fn add(&self, name: &'static str, value: u64) {
35        let mut guard = self.0 .0.lock().unwrap();
36        let (_count, map) = &mut *guard;
37        map.insert(name, value);
38    }
39}
40
41impl Drop for MetricsHandle<'_> {
42    fn drop(&mut self) {
43        use std::fmt::Write;
44
45        let mut guard = self.0 .0.lock().unwrap();
46        let (count, map) = &mut *guard;
47        *count -= 1;
48        if *count == 0 {
49            let mut file = std::fs::OpenOptions::new()
50                .create(true)
51                .truncate(true)
52                .write(true)
53                .open("metrics.csv")
54                .unwrap();
55            let csv = map.iter_mut().fold(String::new(), |mut acc, (k, v)| {
56                writeln!(acc, "{k},{v}").unwrap();
57                acc
58            });
59            file.write_all(csv.as_bytes()).unwrap();
60        }
61    }
62}