Skip to main content

antlr4_runtime/
perf.rs

1//! Lightweight counters for prediction performance investigations.
2
3#![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}