1
//! Counts rule applications in memory and writes the totals once, when the CLI exits.
2
use std::fmt::Write as _;
3
use std::fs::{self, File};
4
use std::path::PathBuf;
5

            
6
use anyhow::Context as _;
7
use conjure_cp_cli::utils::rule_trace::RuleApplicationCounter;
8

            
9
/// Owns the CLI output path and shared rule application counter.
10
#[derive(Clone)]
11
pub struct RuleTraceAggregatesHandle {
12
    path: PathBuf,
13
    counter: RuleApplicationCounter,
14
}
15

            
16
impl RuleTraceAggregatesHandle {
17
    /// Create the output file before rewriting, so an unusable path fails early.
18
18
    pub fn new(path: PathBuf) -> anyhow::Result<Self> {
19
18
        File::create(&path)
20
            .with_context(|| format!("Unable to create aggregate trace file {}", path.display()))?;
21
18
        Ok(Self {
22
18
            path,
23
18
            counter: RuleApplicationCounter::default(),
24
18
        })
25
18
    }
26

            
27
    /// A layer sharing the counts written by this handle.
28
18
    pub fn layer(&self) -> RuleApplicationCounter {
29
18
        self.counter.clone()
30
18
    }
31

            
32
    /// Write the counts collected so far.
33
36
    pub fn flush(&self) {
34
36
        self.write().expect("failed to write rule trace aggregates")
35
36
    }
36
}
37

            
38
impl RuleTraceAggregatesHandle {
39
36
    fn write(&self) -> anyhow::Result<()> {
40
36
        let counts = self.counter.snapshot();
41
36
        let mut rows: Vec<_> = counts.iter().collect();
42
1308
        rows.sort_by(|(rule_name_a, count_a), (rule_name_b, count_b)| {
43
1308
            count_b
44
1308
                .cmp(count_a)
45
112
                .then_with(|| rule_name_a.cmp(rule_name_b))
46
1308
        });
47

            
48
36
        let mut contents = format!(
49
            "total_rule_applications: {}\n",
50
36
            counts.values().sum::<u64>()
51
        );
52
36
        for (rule_name, count) in rows {
53
392
            writeln!(contents, "{count:6} {rule_name}")?;
54
        }
55

            
56
36
        fs::write(&self.path, contents).with_context(|| {
57
            format!(
58
                "Unable to write aggregate trace file {}",
59
                self.path.display()
60
            )
61
        })
62
36
    }
63
}