1#![allow(clippy::missing_const_for_thread_local)]
4
5use std::cell::RefCell;
6use std::collections::BTreeMap;
7
8#[derive(Debug, Default)]
9struct Counters {
10 adaptive_calls: u64,
11 forced_full_context_calls: u64,
12 full_context_retries: u64,
13 sll_conflicts: u64,
14 reach_sll_calls: u64,
15 reach_full_context_calls: u64,
16 reach_input_configs: u64,
17 reach_output_configs: u64,
18 reach_max_input_configs: u64,
19 reach_max_output_configs: u64,
20 closure_calls: u64,
21 closure_visited_total: u64,
22 closure_visited_max: u64,
23 config_add_calls: u64,
24 config_inserts: u64,
25 config_merges: u64,
26 config_max_size: u64,
27 context_merge_calls: u64,
28 context_merge_identical: u64,
29 context_merge_cache_hits: u64,
30 context_merge_cache_misses: u64,
31 context_merge_uncached: u64,
32 context_cache_calls: u64,
33 context_cache_empty: u64,
34 context_cache_hits: u64,
35 context_cache_misses: u64,
36 context_cache_inserts: u64,
37 context_cache_visited_total: u64,
38 context_cache_visited_max: u64,
39 decisions: BTreeMap<usize, DecisionCounters>,
40}
41
42#[derive(Debug, Default)]
43struct DecisionCounters {
44 adaptive_calls: u64,
45 forced_full_context_calls: u64,
46 full_context_retries: u64,
47 sll_conflicts: u64,
48}
49
50thread_local! {
51 static COUNTERS: RefCell<Counters> = RefCell::new(Counters::default());
52}
53
54fn with_counters(update: impl FnOnce(&mut Counters)) {
55 COUNTERS.with(|counters| update(&mut counters.borrow_mut()));
56}
57
58fn add_len(total: &mut u64, max: &mut u64, len: usize) {
59 let len = u64::try_from(len).unwrap_or(u64::MAX);
60 *total = total.saturating_add(len);
61 *max = (*max).max(len);
62}
63
64pub(crate) fn record_adaptive_call(decision: usize, forced_full_context: bool) {
65 with_counters(|counters| {
66 counters.adaptive_calls = counters.adaptive_calls.saturating_add(1);
67 let decision_counters = counters.decisions.entry(decision).or_default();
68 decision_counters.adaptive_calls = decision_counters.adaptive_calls.saturating_add(1);
69 if forced_full_context {
70 counters.forced_full_context_calls =
71 counters.forced_full_context_calls.saturating_add(1);
72 decision_counters.forced_full_context_calls = decision_counters
73 .forced_full_context_calls
74 .saturating_add(1);
75 }
76 });
77}
78
79pub(crate) fn record_full_context_retry(decision: usize) {
80 with_counters(|counters| {
81 counters.full_context_retries = counters.full_context_retries.saturating_add(1);
82 let decision_counters = counters.decisions.entry(decision).or_default();
83 decision_counters.full_context_retries =
84 decision_counters.full_context_retries.saturating_add(1);
85 });
86}
87
88pub(crate) fn record_sll_conflict(decision: usize) {
89 with_counters(|counters| {
90 counters.sll_conflicts = counters.sll_conflicts.saturating_add(1);
91 let decision_counters = counters.decisions.entry(decision).or_default();
92 decision_counters.sll_conflicts = decision_counters.sll_conflicts.saturating_add(1);
93 });
94}
95
96pub(crate) fn record_reach_set(full_context: bool, input_configs: usize, output_configs: usize) {
97 with_counters(|counters| {
98 if full_context {
99 counters.reach_full_context_calls = counters.reach_full_context_calls.saturating_add(1);
100 } else {
101 counters.reach_sll_calls = counters.reach_sll_calls.saturating_add(1);
102 }
103 add_len(
104 &mut counters.reach_input_configs,
105 &mut counters.reach_max_input_configs,
106 input_configs,
107 );
108 add_len(
109 &mut counters.reach_output_configs,
110 &mut counters.reach_max_output_configs,
111 output_configs,
112 );
113 });
114}
115
116pub(crate) fn record_closure(visited_configs: usize) {
117 with_counters(|counters| {
118 counters.closure_calls = counters.closure_calls.saturating_add(1);
119 add_len(
120 &mut counters.closure_visited_total,
121 &mut counters.closure_visited_max,
122 visited_configs,
123 );
124 });
125}
126
127pub(crate) fn record_config_add_call() {
128 with_counters(|counters| {
129 counters.config_add_calls = counters.config_add_calls.saturating_add(1);
130 });
131}
132
133pub(crate) fn record_config_insert(size_after: usize) {
134 with_counters(|counters| {
135 counters.config_inserts = counters.config_inserts.saturating_add(1);
136 counters.config_max_size = counters
137 .config_max_size
138 .max(u64::try_from(size_after).unwrap_or(u64::MAX));
139 });
140}
141
142pub(crate) fn record_config_merge() {
143 with_counters(|counters| {
144 counters.config_merges = counters.config_merges.saturating_add(1);
145 });
146}
147
148pub(crate) fn record_context_merge_call() {
149 with_counters(|counters| {
150 counters.context_merge_calls = counters.context_merge_calls.saturating_add(1);
151 });
152}
153
154pub(crate) fn record_context_merge_identical() {
155 with_counters(|counters| {
156 counters.context_merge_identical = counters.context_merge_identical.saturating_add(1);
157 });
158}
159
160pub(crate) fn record_context_merge_cache_hit() {
161 with_counters(|counters| {
162 counters.context_merge_cache_hits = counters.context_merge_cache_hits.saturating_add(1);
163 });
164}
165
166pub(crate) fn record_context_merge_cache_miss() {
167 with_counters(|counters| {
168 counters.context_merge_cache_misses = counters.context_merge_cache_misses.saturating_add(1);
169 });
170}
171
172pub(crate) fn record_context_merge_uncached() {
173 with_counters(|counters| {
174 counters.context_merge_uncached = counters.context_merge_uncached.saturating_add(1);
175 });
176}
177
178pub(crate) fn record_context_cache_call() {
179 with_counters(|counters| {
180 counters.context_cache_calls = counters.context_cache_calls.saturating_add(1);
181 });
182}
183
184pub(crate) fn record_context_cache_empty() {
185 with_counters(|counters| {
186 counters.context_cache_empty = counters.context_cache_empty.saturating_add(1);
187 });
188}
189
190pub(crate) fn record_context_cache_hit() {
191 with_counters(|counters| {
192 counters.context_cache_hits = counters.context_cache_hits.saturating_add(1);
193 });
194}
195
196pub(crate) fn record_context_cache_miss() {
197 with_counters(|counters| {
198 counters.context_cache_misses = counters.context_cache_misses.saturating_add(1);
199 });
200}
201
202pub(crate) fn record_context_cache_insert() {
203 with_counters(|counters| {
204 counters.context_cache_inserts = counters.context_cache_inserts.saturating_add(1);
205 });
206}
207
208pub(crate) fn record_context_cache_visited(visited_contexts: usize) {
209 with_counters(|counters| {
210 add_len(
211 &mut counters.context_cache_visited_total,
212 &mut counters.context_cache_visited_max,
213 visited_contexts,
214 );
215 });
216}
217
218pub fn reset() {
219 COUNTERS.with(|counters| *counters.borrow_mut() = Counters::default());
220}
221
222pub fn dump() {
223 COUNTERS.with(|counters| {
224 let counters = counters.borrow();
225 dump_totals(&counters);
226 dump_decisions(&counters);
227 });
228}
229
230fn dump_totals(counters: &Counters) {
231 for (name, value) in totals(counters) {
232 print_counter(name, value);
233 }
234}
235
236fn dump_decisions(counters: &Counters) {
237 for (decision, counters) in &counters.decisions {
238 print_decision_counter(*decision, "adaptive_calls", counters.adaptive_calls);
239 print_decision_counter(
240 *decision,
241 "forced_full_context_calls",
242 counters.forced_full_context_calls,
243 );
244 print_decision_counter(
245 *decision,
246 "full_context_retries",
247 counters.full_context_retries,
248 );
249 print_decision_counter(*decision, "sll_conflicts", counters.sll_conflicts);
250 }
251}
252
253const fn totals(counters: &Counters) -> [(&'static str, u64); 29] {
254 [
255 ("prediction.adaptive_calls", counters.adaptive_calls),
256 (
257 "prediction.forced_full_context_calls",
258 counters.forced_full_context_calls,
259 ),
260 (
261 "prediction.full_context_retries",
262 counters.full_context_retries,
263 ),
264 ("prediction.sll_conflicts", counters.sll_conflicts),
265 ("reach.sll_calls", counters.reach_sll_calls),
266 (
267 "reach.full_context_calls",
268 counters.reach_full_context_calls,
269 ),
270 ("reach.input_configs", counters.reach_input_configs),
271 ("reach.output_configs", counters.reach_output_configs),
272 ("reach.max_input_configs", counters.reach_max_input_configs),
273 (
274 "reach.max_output_configs",
275 counters.reach_max_output_configs,
276 ),
277 ("closure.calls", counters.closure_calls),
278 ("closure.visited_total", counters.closure_visited_total),
279 ("closure.visited_max", counters.closure_visited_max),
280 ("config.add_calls", counters.config_add_calls),
281 ("config.inserts", counters.config_inserts),
282 ("config.merges", counters.config_merges),
283 ("config.max_size", counters.config_max_size),
284 ("context_merge.calls", counters.context_merge_calls),
285 ("context_merge.identical", counters.context_merge_identical),
286 (
287 "context_merge.cache_hits",
288 counters.context_merge_cache_hits,
289 ),
290 (
291 "context_merge.cache_misses",
292 counters.context_merge_cache_misses,
293 ),
294 ("context_merge.uncached", counters.context_merge_uncached),
295 ("context_cache.calls", counters.context_cache_calls),
296 ("context_cache.empty", counters.context_cache_empty),
297 ("context_cache.hits", counters.context_cache_hits),
298 ("context_cache.misses", counters.context_cache_misses),
299 ("context_cache.inserts", counters.context_cache_inserts),
300 (
301 "context_cache.visited_total",
302 counters.context_cache_visited_total,
303 ),
304 (
305 "context_cache.visited_max",
306 counters.context_cache_visited_max,
307 ),
308 ]
309}
310
311fn print_counter(name: &str, value: u64) {
312 #[allow(clippy::print_stderr)]
313 {
314 eprintln!("perf {name}={value}");
315 }
316}
317
318fn print_decision_counter(decision: usize, name: &str, value: u64) {
319 #[allow(clippy::print_stderr)]
320 {
321 eprintln!("perf decision.{decision}.{name}={value}");
322 }
323}