1use clap::clap_derive::ValueEnum;
2use rustc_hash::FxHasher;
3use serde::Serialize;
4use std::{
5 fmt::{Display, Formatter},
6 hash::BuildHasherDefault,
7 sync::Arc,
8};
9use web_time::Duration;
10
11pub(crate) type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasherDefault<FxHasher>>;
12pub(crate) type IndexSet<T> = indexmap::IndexSet<T, BuildHasherDefault<FxHasher>>;
13
14#[derive(ValueEnum, Default, Serialize, Debug, Clone, Copy)]
15pub enum ReportLevel {
16 #[default]
18 TimeOnly,
19 WithPlan,
21 StageInfo,
23}
24
25#[derive(Serialize, Clone, Debug)]
26pub struct SingleScan(pub String, pub (String, i64));
27#[derive(Serialize, Clone, Debug)]
28pub struct Scan(pub String, pub Vec<(String, i64)>);
29
30#[derive(Serialize, Clone, Debug)]
31pub enum Stage {
32 Intersect {
33 scans: Vec<SingleScan>,
34 },
35 FusedIntersect {
36 cover: Scan, to_intersect: Vec<Scan>, },
39}
40
41#[derive(Serialize, Clone, Debug)]
42pub struct StageStats {
43 pub num_candidates: usize,
44 pub num_succeeded: usize,
45}
46
47#[derive(Serialize, Clone, Debug, Default)]
48pub struct Plan {
49 pub stages: Vec<(
50 Stage,
51 Option<StageStats>,
52 Vec<usize>,
54 )>,
55}
56
57#[derive(Debug, Serialize, Clone, Default)]
58pub struct RuleReport {
59 pub plan: Option<Plan>,
60 pub search_and_apply_time: Duration,
61 pub num_matches: usize,
63}
64
65#[derive(Debug, Serialize, Clone, Default)]
66pub struct RuleSetReport {
67 pub changed: bool,
68 pub rule_reports: HashMap<Arc<str>, Vec<RuleReport>>,
69 pub search_and_apply_time: Duration,
70 pub merge_time: Duration,
71}
72
73impl RuleSetReport {
74 pub fn num_matches(&self, rule: &str) -> usize {
75 self.rule_reports
76 .get(rule)
77 .map(|r| r.iter().map(|r| r.num_matches).sum())
78 .unwrap_or(0)
79 }
80
81 pub fn rule_search_and_apply_time(&self, rule: &str) -> Duration {
82 self.rule_reports
83 .get(rule)
84 .map(|r| r.iter().map(|r| r.search_and_apply_time).sum())
85 .unwrap_or(Duration::ZERO)
86 }
87}
88
89#[derive(Debug, Serialize, Clone, Default)]
90pub struct IterationReport {
91 pub rule_set_report: RuleSetReport,
92 pub rebuild_time: Duration,
93}
94
95impl IterationReport {
96 pub fn changed(&self) -> bool {
97 self.rule_set_report.changed
98 }
99
100 pub fn search_and_apply_time(&self) -> Duration {
101 self.rule_set_report.search_and_apply_time
102 }
103
104 pub fn rule_reports(&self) -> &HashMap<Arc<str>, Vec<RuleReport>> {
105 &self.rule_set_report.rule_reports
106 }
107
108 pub fn rules(&self) -> impl Iterator<Item = &Arc<str>> {
109 self.rule_set_report.rule_reports.keys()
110 }
111}
112
113#[derive(Debug, Serialize, Clone, Default)]
119pub struct RunReport {
120 pub iterations: Vec<Arc<IterationReport>>,
123 pub updated: bool,
125 pub search_and_apply_time_per_rule: HashMap<Arc<str>, Duration>,
126 pub num_matches_per_rule: HashMap<Arc<str>, usize>,
127 pub search_and_apply_time_per_ruleset: HashMap<Arc<str>, Duration>,
128 pub merge_time_per_ruleset: HashMap<Arc<str>, Duration>,
129 pub rebuild_time_per_ruleset: HashMap<Arc<str>, Duration>,
130}
131
132impl Display for RunReport {
133 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
134 let mut rule_times_vec: Vec<_> = self.search_and_apply_time_per_rule.iter().collect();
135 rule_times_vec.sort_by_key(|(_, time)| **time);
136
137 for (rule, time) in rule_times_vec {
138 let name = Self::truncate_rule_name(rule.to_string());
139 let time = time.as_secs_f64();
140 let num_matches = self.num_matches_per_rule.get(rule).copied().unwrap_or(0);
141 writeln!(
142 f,
143 "Rule {name}: search and apply {time:.3}s, num matches {num_matches}",
144 )?;
145 }
146
147 let rulesets = self
148 .search_and_apply_time_per_ruleset
149 .keys()
150 .chain(self.merge_time_per_ruleset.keys())
151 .chain(self.rebuild_time_per_ruleset.keys())
152 .collect::<IndexSet<_>>();
153
154 for ruleset in rulesets {
155 let search_and_apply_time = self
157 .search_and_apply_time_per_ruleset
158 .get(ruleset)
159 .cloned()
160 .unwrap_or(Duration::ZERO)
161 .as_secs_f64();
162 let merge_time = self
163 .merge_time_per_ruleset
164 .get(ruleset)
165 .cloned()
166 .unwrap_or(Duration::ZERO)
167 .as_secs_f64();
168 let rebuild_time = self
169 .rebuild_time_per_ruleset
170 .get(ruleset)
171 .cloned()
172 .unwrap_or(Duration::ZERO)
173 .as_secs_f64();
174 writeln!(
175 f,
176 "Ruleset {ruleset}: search {search_and_apply_time:.3}s, merge {merge_time:.3}s, rebuild {rebuild_time:.3}s",
177 )?;
178 }
179
180 Ok(())
181 }
182}
183
184impl RunReport {
185 fn truncate_rule_name(mut s: String) -> String {
188 s = s.replace('\n', " ");
190 if s.len() > 80 {
191 s.truncate(80);
192 s.push_str("...");
193 }
194 s
195 }
196
197 fn union_times(
198 times: &mut HashMap<Arc<str>, Duration>,
199 other_times: HashMap<Arc<str>, Duration>,
200 ) {
201 for (k, v) in other_times {
202 *times.entry(k).or_default() += v;
203 }
204 }
205
206 fn union_counts(counts: &mut HashMap<Arc<str>, usize>, other_counts: HashMap<Arc<str>, usize>) {
207 for (k, v) in other_counts {
208 *counts.entry(k).or_default() += v;
209 }
210 }
211
212 pub fn singleton(ruleset: &str, iteration: IterationReport) -> Self {
213 let mut report = RunReport::default();
214
215 for rule in iteration.rules() {
216 *report
217 .search_and_apply_time_per_rule
218 .entry(rule.clone())
219 .or_default() += iteration.rule_set_report.rule_search_and_apply_time(rule);
220 *report.num_matches_per_rule.entry(rule.clone()).or_default() +=
221 iteration.rule_set_report.num_matches(rule);
222 }
223
224 let ruleset: Arc<str> = ruleset.into();
225 let per_ruleset = |x| [(ruleset.clone(), x)].into_iter().collect();
226
227 report.search_and_apply_time_per_ruleset = per_ruleset(iteration.search_and_apply_time());
228 report.merge_time_per_ruleset = per_ruleset(iteration.rule_set_report.merge_time);
229 report.rebuild_time_per_ruleset = per_ruleset(iteration.rebuild_time);
230 report.updated = iteration.changed();
231 report.iterations.push(Arc::new(iteration));
232
233 report
234 }
235
236 pub fn add_iteration(&mut self, ruleset: &str, iteration: IterationReport) {
237 self.union(RunReport::singleton(ruleset, iteration));
238 }
239
240 pub fn union(&mut self, other: Self) {
242 self.iterations.extend(other.iterations);
243 self.updated |= other.updated;
244 RunReport::union_times(
245 &mut self.search_and_apply_time_per_rule,
246 other.search_and_apply_time_per_rule,
247 );
248 RunReport::union_counts(&mut self.num_matches_per_rule, other.num_matches_per_rule);
249 RunReport::union_times(
250 &mut self.search_and_apply_time_per_ruleset,
251 other.search_and_apply_time_per_ruleset,
252 );
253 RunReport::union_times(
254 &mut self.merge_time_per_ruleset,
255 other.merge_time_per_ruleset,
256 );
257 RunReport::union_times(
258 &mut self.rebuild_time_per_ruleset,
259 other.rebuild_time_per_ruleset,
260 );
261 }
262}