Skip to main content

antlr4_runtime/
perf.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Konstantin Vyatkin
3//! Lightweight counters for lexer and prediction performance investigations.
4
5#![allow(clippy::missing_const_for_thread_local)]
6
7use std::cell::RefCell;
8use std::collections::BTreeMap;
9
10#[derive(Debug, Default)]
11struct Counters {
12    adaptive_calls: u64,
13    forced_full_context_calls: u64,
14    full_context_retries: u64,
15    full_context_memo_hits: u64,
16    full_context_memo_declined: u64,
17    sll_conflicts: u64,
18    reach_sll_calls: u64,
19    reach_full_context_calls: u64,
20    reach_input_configs: u64,
21    reach_output_configs: u64,
22    reach_max_input_configs: u64,
23    reach_max_output_configs: u64,
24    closure_calls: u64,
25    closure_visited_total: u64,
26    closure_visited_max: u64,
27    config_add_calls: u64,
28    config_inserts: u64,
29    config_merges: u64,
30    config_max_size: u64,
31    context_merge_calls: u64,
32    context_merge_identical: u64,
33    context_merge_cache_hits: u64,
34    context_merge_cache_misses: u64,
35    context_merge_uncached: u64,
36    context_cache_calls: u64,
37    context_cache_hits: u64,
38    context_cache_misses: u64,
39    context_cache_inserts: u64,
40    dfa_edge_lookups: u64,
41    dfa_edge_hits: u64,
42    dfa_edge_misses: u64,
43    dfa_atn_fallbacks: u64,
44    dfa_states_created: u64,
45    dfa_states_deduplicated: u64,
46    dfa_fingerprint_candidates: u64,
47    dfa_fingerprint_collisions: u64,
48    dfa_cache_imports: u64,
49    dfa_cache_import_nanos: u64,
50    dfa_cache_import_states: u64,
51    dfa_cache_publications: u64,
52    dfa_cache_publication_nanos: u64,
53    dfa_cache_publication_states: u64,
54    lexer_direct_ascii_chars: u64,
55    lexer_generic_chars: u64,
56    lexer_scalar_replay_chars: u64,
57    lexer_bulk_committed_chars: u64,
58    lexer_compiled_scalar_ascii_chars: u64,
59    lexer_run_scan_calls: u64,
60    lexer_run_scan_bytes: u64,
61    lexer_run_scan_exits: u64,
62    lexer_run_scan_ends: u64,
63    lexer_run_rejected_states: u64,
64    lexer_run_descriptors_none: u64,
65    lexer_run_descriptors_any: u64,
66    lexer_run_descriptors_until: u64,
67    lexer_range_descriptors: [u64; 4],
68    lexer_range_descriptor_classes: [u64; 4],
69    lexer_range_scalar_calls: u64,
70    lexer_range_scalar_bytes: u64,
71    lexer_range_identifier_bytes: u64,
72    lexer_range_number_bytes: u64,
73    lexer_range_whitespace_bytes: u64,
74    lexer_range_other_bytes: u64,
75    parser_token_set_inline_sets: u64,
76    parser_token_set_dense_sets: u64,
77    parser_token_set_interval_sets: u64,
78    parser_token_set_inline_bytes: u64,
79    parser_token_set_dense_bytes: u64,
80    parser_token_set_inline_hits: u64,
81    parser_token_set_inline_misses: u64,
82    parser_token_set_dense_hits: u64,
83    parser_token_set_dense_misses: u64,
84    parser_token_set_interval_hits: u64,
85    parser_token_set_interval_misses: u64,
86    parser_token_set_interval_probes_eliminated: u64,
87    parser_token_set_interval_binary_probes_retained: u64,
88    decisions: BTreeMap<usize, DecisionCounters>,
89}
90
91#[derive(Debug, Default)]
92struct DecisionCounters {
93    adaptive_calls: u64,
94    forced_full_context_calls: u64,
95    full_context_retries: u64,
96    full_context_memo_hits: u64,
97    full_context_memo_declined: u64,
98    sll_conflicts: u64,
99}
100
101thread_local! {
102    static COUNTERS: RefCell<Counters> = RefCell::new(Counters::default());
103}
104
105fn with_counters(update: impl FnOnce(&mut Counters)) {
106    COUNTERS.with(|counters| update(&mut counters.borrow_mut()));
107}
108
109fn add_len(total: &mut u64, max: &mut u64, len: usize) {
110    let len = u64::try_from(len).unwrap_or(u64::MAX);
111    *total = total.saturating_add(len);
112    *max = (*max).max(len);
113}
114
115const fn record_hit(hit: bool, hits: &mut u64, misses: &mut u64) {
116    let counter = if hit { hits } else { misses };
117    *counter = counter.saturating_add(1);
118}
119
120pub(crate) fn record_adaptive_call(decision: usize, forced_full_context: bool) {
121    with_counters(|counters| {
122        counters.adaptive_calls = counters.adaptive_calls.saturating_add(1);
123        let decision_counters = counters.decisions.entry(decision).or_default();
124        decision_counters.adaptive_calls = decision_counters.adaptive_calls.saturating_add(1);
125        if forced_full_context {
126            counters.forced_full_context_calls =
127                counters.forced_full_context_calls.saturating_add(1);
128            decision_counters.forced_full_context_calls = decision_counters
129                .forced_full_context_calls
130                .saturating_add(1);
131        }
132    });
133}
134
135pub(crate) fn record_full_context_retry(decision: usize) {
136    with_counters(|counters| {
137        counters.full_context_retries = counters.full_context_retries.saturating_add(1);
138        let decision_counters = counters.decisions.entry(decision).or_default();
139        decision_counters.full_context_retries =
140            decision_counters.full_context_retries.saturating_add(1);
141    });
142}
143
144pub(crate) fn record_full_context_memo_hit(decision: usize) {
145    with_counters(|counters| {
146        counters.full_context_memo_hits = counters.full_context_memo_hits.saturating_add(1);
147        let decision_counters = counters.decisions.entry(decision).or_default();
148        decision_counters.full_context_memo_hits =
149            decision_counters.full_context_memo_hits.saturating_add(1);
150    });
151}
152
153pub(crate) fn record_full_context_memo_declined(decision: usize) {
154    with_counters(|counters| {
155        counters.full_context_memo_declined = counters.full_context_memo_declined.saturating_add(1);
156        let decision_counters = counters.decisions.entry(decision).or_default();
157        decision_counters.full_context_memo_declined = decision_counters
158            .full_context_memo_declined
159            .saturating_add(1);
160    });
161}
162
163pub(crate) fn record_sll_conflict(decision: usize) {
164    with_counters(|counters| {
165        counters.sll_conflicts = counters.sll_conflicts.saturating_add(1);
166        let decision_counters = counters.decisions.entry(decision).or_default();
167        decision_counters.sll_conflicts = decision_counters.sll_conflicts.saturating_add(1);
168    });
169}
170
171pub(crate) fn record_reach_set(full_context: bool, input_configs: usize, output_configs: usize) {
172    with_counters(|counters| {
173        if full_context {
174            counters.reach_full_context_calls = counters.reach_full_context_calls.saturating_add(1);
175        } else {
176            counters.reach_sll_calls = counters.reach_sll_calls.saturating_add(1);
177        }
178        add_len(
179            &mut counters.reach_input_configs,
180            &mut counters.reach_max_input_configs,
181            input_configs,
182        );
183        add_len(
184            &mut counters.reach_output_configs,
185            &mut counters.reach_max_output_configs,
186            output_configs,
187        );
188    });
189}
190
191pub(crate) fn record_closure(visited_configs: usize) {
192    with_counters(|counters| {
193        counters.closure_calls = counters.closure_calls.saturating_add(1);
194        add_len(
195            &mut counters.closure_visited_total,
196            &mut counters.closure_visited_max,
197            visited_configs,
198        );
199    });
200}
201
202pub(crate) fn record_config_add_call() {
203    with_counters(|counters| {
204        counters.config_add_calls = counters.config_add_calls.saturating_add(1);
205    });
206}
207
208pub(crate) fn record_config_insert(size_after: usize) {
209    with_counters(|counters| {
210        counters.config_inserts = counters.config_inserts.saturating_add(1);
211        counters.config_max_size = counters
212            .config_max_size
213            .max(u64::try_from(size_after).unwrap_or(u64::MAX));
214    });
215}
216
217pub(crate) fn record_config_merge() {
218    with_counters(|counters| {
219        counters.config_merges = counters.config_merges.saturating_add(1);
220    });
221}
222
223pub(crate) fn record_context_merge_call() {
224    with_counters(|counters| {
225        counters.context_merge_calls = counters.context_merge_calls.saturating_add(1);
226    });
227}
228
229pub(crate) fn record_context_merge_identical() {
230    with_counters(|counters| {
231        counters.context_merge_identical = counters.context_merge_identical.saturating_add(1);
232    });
233}
234
235pub(crate) fn record_context_merge_cache_hit() {
236    with_counters(|counters| {
237        counters.context_merge_cache_hits = counters.context_merge_cache_hits.saturating_add(1);
238    });
239}
240
241pub(crate) fn record_context_merge_cache_miss() {
242    with_counters(|counters| {
243        counters.context_merge_cache_misses = counters.context_merge_cache_misses.saturating_add(1);
244    });
245}
246
247pub(crate) fn record_context_merge_uncached() {
248    with_counters(|counters| {
249        counters.context_merge_uncached = counters.context_merge_uncached.saturating_add(1);
250    });
251}
252
253pub(crate) fn record_context_cache_call() {
254    with_counters(|counters| {
255        counters.context_cache_calls = counters.context_cache_calls.saturating_add(1);
256    });
257}
258
259pub(crate) fn record_context_cache_hit() {
260    with_counters(|counters| {
261        counters.context_cache_hits = counters.context_cache_hits.saturating_add(1);
262    });
263}
264
265pub(crate) fn record_context_cache_miss() {
266    with_counters(|counters| {
267        counters.context_cache_misses = counters.context_cache_misses.saturating_add(1);
268    });
269}
270
271pub(crate) fn record_context_cache_insert() {
272    with_counters(|counters| {
273        counters.context_cache_inserts = counters.context_cache_inserts.saturating_add(1);
274    });
275}
276
277pub(crate) fn record_dfa_edge_lookup(hit: bool) {
278    with_counters(|counters| {
279        counters.dfa_edge_lookups = counters.dfa_edge_lookups.saturating_add(1);
280        if hit {
281            counters.dfa_edge_hits = counters.dfa_edge_hits.saturating_add(1);
282        } else {
283            counters.dfa_edge_misses = counters.dfa_edge_misses.saturating_add(1);
284            counters.dfa_atn_fallbacks = counters.dfa_atn_fallbacks.saturating_add(1);
285        }
286    });
287}
288
289pub(crate) fn record_dfa_state_created() {
290    with_counters(|counters| {
291        counters.dfa_states_created = counters.dfa_states_created.saturating_add(1);
292    });
293}
294
295pub(crate) fn record_dfa_state_deduplicated() {
296    with_counters(|counters| {
297        counters.dfa_states_deduplicated = counters.dfa_states_deduplicated.saturating_add(1);
298    });
299}
300
301pub(crate) fn record_dfa_fingerprint_candidate() {
302    with_counters(|counters| {
303        counters.dfa_fingerprint_candidates = counters.dfa_fingerprint_candidates.saturating_add(1);
304    });
305}
306
307pub(crate) fn record_dfa_fingerprint_collision() {
308    with_counters(|counters| {
309        counters.dfa_fingerprint_collisions = counters.dfa_fingerprint_collisions.saturating_add(1);
310    });
311}
312
313pub(crate) fn record_dfa_cache_import(nanos: u128, states: usize) {
314    with_counters(|counters| {
315        counters.dfa_cache_imports = counters.dfa_cache_imports.saturating_add(1);
316        counters.dfa_cache_import_nanos = counters
317            .dfa_cache_import_nanos
318            .saturating_add(u64::try_from(nanos).unwrap_or(u64::MAX));
319        counters.dfa_cache_import_states = counters
320            .dfa_cache_import_states
321            .saturating_add(u64::try_from(states).unwrap_or(u64::MAX));
322    });
323}
324
325pub(crate) fn record_dfa_cache_publication(nanos: u128, states: usize) {
326    with_counters(|counters| {
327        counters.dfa_cache_publications = counters.dfa_cache_publications.saturating_add(1);
328        counters.dfa_cache_publication_nanos = counters
329            .dfa_cache_publication_nanos
330            .saturating_add(u64::try_from(nanos).unwrap_or(u64::MAX));
331        counters.dfa_cache_publication_states = counters
332            .dfa_cache_publication_states
333            .saturating_add(u64::try_from(states).unwrap_or(u64::MAX));
334    });
335}
336
337pub(crate) fn record_lexer_direct_ascii(count: usize) {
338    with_counters(|counters| {
339        counters.lexer_direct_ascii_chars = counters
340            .lexer_direct_ascii_chars
341            .saturating_add(u64::try_from(count).unwrap_or(u64::MAX));
342    });
343}
344
345pub(crate) fn record_lexer_generic_char() {
346    with_counters(|counters| {
347        counters.lexer_generic_chars = counters.lexer_generic_chars.saturating_add(1);
348    });
349}
350
351pub(crate) fn record_lexer_scalar_replay(count: usize) {
352    with_counters(|counters| {
353        counters.lexer_scalar_replay_chars = counters
354            .lexer_scalar_replay_chars
355            .saturating_add(u64::try_from(count).unwrap_or(u64::MAX));
356    });
357}
358
359pub(crate) fn record_lexer_bulk_commit(count: usize) {
360    with_counters(|counters| {
361        counters.lexer_bulk_committed_chars = counters
362            .lexer_bulk_committed_chars
363            .saturating_add(u64::try_from(count).unwrap_or(u64::MAX));
364    });
365}
366
367pub(crate) fn record_lexer_compiled_scalar_ascii(count: usize) {
368    with_counters(|counters| {
369        counters.lexer_compiled_scalar_ascii_chars = counters
370            .lexer_compiled_scalar_ascii_chars
371            .saturating_add(u64::try_from(count).unwrap_or(u64::MAX));
372    });
373}
374
375pub(crate) fn record_lexer_run_scan(bytes: usize, found_exit: bool) {
376    with_counters(|counters| {
377        counters.lexer_run_scan_calls = counters.lexer_run_scan_calls.saturating_add(1);
378        counters.lexer_run_scan_bytes = counters
379            .lexer_run_scan_bytes
380            .saturating_add(u64::try_from(bytes).unwrap_or(u64::MAX));
381        if found_exit {
382            counters.lexer_run_scan_exits = counters.lexer_run_scan_exits.saturating_add(1);
383        } else {
384            counters.lexer_run_scan_ends = counters.lexer_run_scan_ends.saturating_add(1);
385        }
386    });
387}
388
389pub(crate) fn record_lexer_run_rejected_state() {
390    with_counters(|counters| {
391        counters.lexer_run_rejected_states = counters.lexer_run_rejected_states.saturating_add(1);
392    });
393}
394
395pub(crate) fn record_lexer_run_descriptor(kind: crate::atn::lexer_dfa::AsciiRunDescriptorKind) {
396    use crate::atn::lexer_dfa::AsciiRunDescriptorKind;
397
398    with_counters(|counters| match kind {
399        AsciiRunDescriptorKind::None => {
400            counters.lexer_run_descriptors_none =
401                counters.lexer_run_descriptors_none.saturating_add(1);
402        }
403        AsciiRunDescriptorKind::Any => {
404            counters.lexer_run_descriptors_any =
405                counters.lexer_run_descriptors_any.saturating_add(1);
406        }
407        AsciiRunDescriptorKind::Until => {
408            counters.lexer_run_descriptors_until =
409                counters.lexer_run_descriptors_until.saturating_add(1);
410        }
411        AsciiRunDescriptorKind::Ranges { count, class } => {
412            if let Some(counter) = count
413                .checked_sub(1)
414                .and_then(|index| counters.lexer_range_descriptors.get_mut(usize::from(index)))
415            {
416                *counter = counter.saturating_add(1);
417            }
418            let class_index = match class {
419                crate::atn::ascii_range::AsciiRangeClass::Identifier => 0,
420                crate::atn::ascii_range::AsciiRangeClass::Number => 1,
421                crate::atn::ascii_range::AsciiRangeClass::Whitespace => 2,
422                crate::atn::ascii_range::AsciiRangeClass::Other => 3,
423            };
424            counters.lexer_range_descriptor_classes[class_index] =
425                counters.lexer_range_descriptor_classes[class_index].saturating_add(1);
426        }
427    });
428}
429
430pub(crate) fn record_lexer_range_scan(
431    class: crate::atn::ascii_range::AsciiRangeClass,
432    bytes: usize,
433) {
434    use crate::atn::ascii_range::AsciiRangeClass;
435
436    let bytes = u64::try_from(bytes).unwrap_or(u64::MAX);
437    with_counters(|counters| {
438        counters.lexer_range_scalar_calls = counters.lexer_range_scalar_calls.saturating_add(1);
439        counters.lexer_range_scalar_bytes = counters.lexer_range_scalar_bytes.saturating_add(bytes);
440        let class_bytes = match class {
441            AsciiRangeClass::Identifier => &mut counters.lexer_range_identifier_bytes,
442            AsciiRangeClass::Number => &mut counters.lexer_range_number_bytes,
443            AsciiRangeClass::Whitespace => &mut counters.lexer_range_whitespace_bytes,
444            AsciiRangeClass::Other => &mut counters.lexer_range_other_bytes,
445        };
446        *class_bytes = class_bytes.saturating_add(bytes);
447    });
448}
449
450pub(crate) fn record_parser_token_set_selection(
451    kind: crate::atn::parser_atn::ParserTokenSetKind,
452    bytes: usize,
453) {
454    use crate::atn::parser_atn::ParserTokenSetKind;
455
456    let bytes = u64::try_from(bytes).unwrap_or(u64::MAX);
457    with_counters(|counters| match kind {
458        ParserTokenSetKind::Inline128 => {
459            counters.parser_token_set_inline_sets =
460                counters.parser_token_set_inline_sets.saturating_add(1);
461            counters.parser_token_set_inline_bytes =
462                counters.parser_token_set_inline_bytes.saturating_add(bytes);
463        }
464        ParserTokenSetKind::Dense => {
465            counters.parser_token_set_dense_sets =
466                counters.parser_token_set_dense_sets.saturating_add(1);
467            counters.parser_token_set_dense_bytes =
468                counters.parser_token_set_dense_bytes.saturating_add(bytes);
469        }
470        ParserTokenSetKind::Intervals => {
471            counters.parser_token_set_interval_sets =
472                counters.parser_token_set_interval_sets.saturating_add(1);
473        }
474    });
475}
476
477pub(crate) fn record_parser_token_set_probe(
478    kind: crate::atn::parser_atn::ParserTokenSetKind,
479    hit: bool,
480) {
481    use crate::atn::parser_atn::ParserTokenSetKind;
482
483    with_counters(|counters| {
484        match kind {
485            ParserTokenSetKind::Inline128 => record_hit(
486                hit,
487                &mut counters.parser_token_set_inline_hits,
488                &mut counters.parser_token_set_inline_misses,
489            ),
490            ParserTokenSetKind::Dense => record_hit(
491                hit,
492                &mut counters.parser_token_set_dense_hits,
493                &mut counters.parser_token_set_dense_misses,
494            ),
495            ParserTokenSetKind::Intervals => record_hit(
496                hit,
497                &mut counters.parser_token_set_interval_hits,
498                &mut counters.parser_token_set_interval_misses,
499            ),
500        }
501        let probe_counter = if kind == ParserTokenSetKind::Intervals {
502            &mut counters.parser_token_set_interval_binary_probes_retained
503        } else {
504            &mut counters.parser_token_set_interval_probes_eliminated
505        };
506        *probe_counter = probe_counter.saturating_add(1);
507    });
508}
509
510pub fn reset() {
511    COUNTERS.with(|counters| {
512        let mut counters = counters.borrow_mut();
513        // Compiled DFAs are usually initialized during warmup and then reused.
514        // Keep their static descriptor inventory while clearing timed counters.
515        let descriptors = (
516            counters.lexer_run_descriptors_none,
517            counters.lexer_run_descriptors_any,
518            counters.lexer_run_descriptors_until,
519            counters.lexer_range_descriptors,
520            counters.lexer_range_descriptor_classes,
521            counters.parser_token_set_inline_sets,
522            counters.parser_token_set_dense_sets,
523            counters.parser_token_set_interval_sets,
524            counters.parser_token_set_inline_bytes,
525            counters.parser_token_set_dense_bytes,
526        );
527        *counters = Counters::default();
528        counters.lexer_run_descriptors_none = descriptors.0;
529        counters.lexer_run_descriptors_any = descriptors.1;
530        counters.lexer_run_descriptors_until = descriptors.2;
531        counters.lexer_range_descriptors = descriptors.3;
532        counters.lexer_range_descriptor_classes = descriptors.4;
533        counters.parser_token_set_inline_sets = descriptors.5;
534        counters.parser_token_set_dense_sets = descriptors.6;
535        counters.parser_token_set_interval_sets = descriptors.7;
536        counters.parser_token_set_inline_bytes = descriptors.8;
537        counters.parser_token_set_dense_bytes = descriptors.9;
538    });
539}
540
541pub fn dump() {
542    COUNTERS.with(|counters| {
543        let counters = counters.borrow();
544        dump_totals(&counters);
545        dump_decisions(&counters);
546    });
547}
548
549fn dump_totals(counters: &Counters) {
550    for (name, value) in totals(counters) {
551        print_counter(name, value);
552    }
553}
554
555fn dump_decisions(counters: &Counters) {
556    for (decision, counters) in &counters.decisions {
557        print_decision_counter(*decision, "adaptive_calls", counters.adaptive_calls);
558        print_decision_counter(
559            *decision,
560            "forced_full_context_calls",
561            counters.forced_full_context_calls,
562        );
563        print_decision_counter(
564            *decision,
565            "full_context_retries",
566            counters.full_context_retries,
567        );
568        print_decision_counter(
569            *decision,
570            "full_context_memo_hits",
571            counters.full_context_memo_hits,
572        );
573        print_decision_counter(
574            *decision,
575            "full_context_memo_declined",
576            counters.full_context_memo_declined,
577        );
578        print_decision_counter(*decision, "sll_conflicts", counters.sll_conflicts);
579    }
580}
581
582const fn totals(counters: &Counters) -> [(&'static str, u64); 82] {
583    [
584        ("prediction.adaptive_calls", counters.adaptive_calls),
585        (
586            "prediction.forced_full_context_calls",
587            counters.forced_full_context_calls,
588        ),
589        (
590            "prediction.full_context_retries",
591            counters.full_context_retries,
592        ),
593        (
594            "prediction.full_context_memo_hits",
595            counters.full_context_memo_hits,
596        ),
597        (
598            "prediction.full_context_memo_declined",
599            counters.full_context_memo_declined,
600        ),
601        ("prediction.sll_conflicts", counters.sll_conflicts),
602        ("reach.sll_calls", counters.reach_sll_calls),
603        (
604            "reach.full_context_calls",
605            counters.reach_full_context_calls,
606        ),
607        ("reach.input_configs", counters.reach_input_configs),
608        ("reach.output_configs", counters.reach_output_configs),
609        ("reach.max_input_configs", counters.reach_max_input_configs),
610        (
611            "reach.max_output_configs",
612            counters.reach_max_output_configs,
613        ),
614        ("closure.calls", counters.closure_calls),
615        ("closure.visited_total", counters.closure_visited_total),
616        ("closure.visited_max", counters.closure_visited_max),
617        ("config.add_calls", counters.config_add_calls),
618        ("config.inserts", counters.config_inserts),
619        ("config.merges", counters.config_merges),
620        ("config.max_size", counters.config_max_size),
621        ("context_merge.calls", counters.context_merge_calls),
622        ("context_merge.identical", counters.context_merge_identical),
623        (
624            "context_merge.cache_hits",
625            counters.context_merge_cache_hits,
626        ),
627        (
628            "context_merge.cache_misses",
629            counters.context_merge_cache_misses,
630        ),
631        ("context_merge.uncached", counters.context_merge_uncached),
632        ("context_cache.calls", counters.context_cache_calls),
633        ("context_cache.hits", counters.context_cache_hits),
634        ("context_cache.misses", counters.context_cache_misses),
635        ("context_cache.inserts", counters.context_cache_inserts),
636        ("dfa.edge_lookups", counters.dfa_edge_lookups),
637        ("dfa.warm_hits", counters.dfa_edge_hits),
638        ("dfa.warm_misses", counters.dfa_edge_misses),
639        ("dfa.atn_fallbacks", counters.dfa_atn_fallbacks),
640        ("dfa.states_created", counters.dfa_states_created),
641        ("dfa.states_deduplicated", counters.dfa_states_deduplicated),
642        (
643            "dfa.fingerprint_candidates",
644            counters.dfa_fingerprint_candidates,
645        ),
646        (
647            "dfa.fingerprint_collisions",
648            counters.dfa_fingerprint_collisions,
649        ),
650        ("dfa_cache.imports", counters.dfa_cache_imports),
651        ("dfa_cache.import_nanos", counters.dfa_cache_import_nanos),
652        ("dfa_cache.import_states", counters.dfa_cache_import_states),
653        ("dfa_cache.publications", counters.dfa_cache_publications),
654        (
655            "dfa_cache.publication_nanos",
656            counters.dfa_cache_publication_nanos,
657        ),
658        (
659            "dfa_cache.publication_states",
660            counters.dfa_cache_publication_states,
661        ),
662        (
663            "lexer.direct_ascii_chars",
664            counters.lexer_direct_ascii_chars,
665        ),
666        ("lexer.generic_chars", counters.lexer_generic_chars),
667        (
668            "lexer.scalar_replay_chars",
669            counters.lexer_scalar_replay_chars,
670        ),
671        (
672            "lexer.bulk_committed_chars",
673            counters.lexer_bulk_committed_chars,
674        ),
675        (
676            "lexer.compiled_scalar_ascii_chars",
677            counters.lexer_compiled_scalar_ascii_chars,
678        ),
679        ("lexer.run_scan_calls", counters.lexer_run_scan_calls),
680        ("lexer.run_scan_bytes", counters.lexer_run_scan_bytes),
681        ("lexer.run_scan_exits", counters.lexer_run_scan_exits),
682        ("lexer.run_scan_ends", counters.lexer_run_scan_ends),
683        (
684            "lexer.run_rejected_states",
685            counters.lexer_run_rejected_states,
686        ),
687        (
688            "lexer.run_descriptors.none",
689            counters.lexer_run_descriptors_none,
690        ),
691        (
692            "lexer.run_descriptors.any",
693            counters.lexer_run_descriptors_any,
694        ),
695        (
696            "lexer.run_descriptors.until",
697            counters.lexer_run_descriptors_until,
698        ),
699        (
700            "lexer.range_descriptors.1",
701            counters.lexer_range_descriptors[0],
702        ),
703        (
704            "lexer.range_descriptors.2",
705            counters.lexer_range_descriptors[1],
706        ),
707        (
708            "lexer.range_descriptors.3",
709            counters.lexer_range_descriptors[2],
710        ),
711        (
712            "lexer.range_descriptors.4",
713            counters.lexer_range_descriptors[3],
714        ),
715        (
716            "lexer.range_descriptor_classes.identifier",
717            counters.lexer_range_descriptor_classes[0],
718        ),
719        (
720            "lexer.range_descriptor_classes.number",
721            counters.lexer_range_descriptor_classes[1],
722        ),
723        (
724            "lexer.range_descriptor_classes.whitespace",
725            counters.lexer_range_descriptor_classes[2],
726        ),
727        (
728            "lexer.range_descriptor_classes.other",
729            counters.lexer_range_descriptor_classes[3],
730        ),
731        (
732            "lexer.range_scalar_calls",
733            counters.lexer_range_scalar_calls,
734        ),
735        (
736            "lexer.range_scalar_bytes",
737            counters.lexer_range_scalar_bytes,
738        ),
739        (
740            "lexer.range_identifier_bytes",
741            counters.lexer_range_identifier_bytes,
742        ),
743        (
744            "lexer.range_number_bytes",
745            counters.lexer_range_number_bytes,
746        ),
747        (
748            "lexer.range_whitespace_bytes",
749            counters.lexer_range_whitespace_bytes,
750        ),
751        ("lexer.range_other_bytes", counters.lexer_range_other_bytes),
752        (
753            "parser.token_set.selection.inline128",
754            counters.parser_token_set_inline_sets,
755        ),
756        (
757            "parser.token_set.selection.dense",
758            counters.parser_token_set_dense_sets,
759        ),
760        (
761            "parser.token_set.selection.intervals",
762            counters.parser_token_set_interval_sets,
763        ),
764        (
765            "parser.token_set.inline_bytes",
766            counters.parser_token_set_inline_bytes,
767        ),
768        (
769            "parser.token_set.dense_bytes",
770            counters.parser_token_set_dense_bytes,
771        ),
772        (
773            "parser.token_set.inline_hits",
774            counters.parser_token_set_inline_hits,
775        ),
776        (
777            "parser.token_set.inline_misses",
778            counters.parser_token_set_inline_misses,
779        ),
780        (
781            "parser.token_set.dense_hits",
782            counters.parser_token_set_dense_hits,
783        ),
784        (
785            "parser.token_set.dense_misses",
786            counters.parser_token_set_dense_misses,
787        ),
788        (
789            "parser.token_set.interval_hits",
790            counters.parser_token_set_interval_hits,
791        ),
792        (
793            "parser.token_set.interval_misses",
794            counters.parser_token_set_interval_misses,
795        ),
796        (
797            "parser.token_set.interval_probes_eliminated",
798            counters.parser_token_set_interval_probes_eliminated,
799        ),
800        (
801            "parser.token_set.interval_binary_probes_retained",
802            counters.parser_token_set_interval_binary_probes_retained,
803        ),
804    ]
805}
806
807#[cfg(test)]
808pub(crate) fn lexer_snapshot() -> [u64; 4] {
809    COUNTERS.with(|counters| {
810        let counters = counters.borrow();
811        [
812            counters.lexer_direct_ascii_chars,
813            counters.lexer_generic_chars,
814            counters.lexer_scalar_replay_chars,
815            counters.lexer_bulk_committed_chars,
816        ]
817    })
818}
819
820#[cfg(test)]
821pub(crate) fn lexer_run_snapshot() -> [u64; 6] {
822    COUNTERS.with(|counters| {
823        let counters = counters.borrow();
824        [
825            counters.lexer_compiled_scalar_ascii_chars,
826            counters.lexer_run_scan_calls,
827            counters.lexer_run_scan_bytes,
828            counters.lexer_run_scan_exits,
829            counters.lexer_run_scan_ends,
830            counters.lexer_run_rejected_states,
831        ]
832    })
833}
834
835#[cfg(test)]
836pub(crate) fn lexer_range_descriptor_snapshot() -> [u64; 11] {
837    COUNTERS.with(|counters| {
838        let counters = counters.borrow();
839        [
840            counters.lexer_run_descriptors_none,
841            counters.lexer_run_descriptors_any,
842            counters.lexer_run_descriptors_until,
843            counters.lexer_range_descriptors[0],
844            counters.lexer_range_descriptors[1],
845            counters.lexer_range_descriptors[2],
846            counters.lexer_range_descriptors[3],
847            counters.lexer_range_descriptor_classes[0],
848            counters.lexer_range_descriptor_classes[1],
849            counters.lexer_range_descriptor_classes[2],
850            counters.lexer_range_descriptor_classes[3],
851        ]
852    })
853}
854
855#[cfg(test)]
856pub(crate) fn lexer_range_scan_snapshot() -> [u64; 6] {
857    COUNTERS.with(|counters| {
858        let counters = counters.borrow();
859        [
860            counters.lexer_range_scalar_calls,
861            counters.lexer_range_scalar_bytes,
862            counters.lexer_range_identifier_bytes,
863            counters.lexer_range_number_bytes,
864            counters.lexer_range_whitespace_bytes,
865            counters.lexer_range_other_bytes,
866        ]
867    })
868}
869
870#[cfg(test)]
871pub(crate) fn parser_token_set_snapshot() -> [u64; 13] {
872    COUNTERS.with(|counters| {
873        let counters = counters.borrow();
874        [
875            counters.parser_token_set_inline_sets,
876            counters.parser_token_set_dense_sets,
877            counters.parser_token_set_interval_sets,
878            counters.parser_token_set_inline_bytes,
879            counters.parser_token_set_dense_bytes,
880            counters.parser_token_set_inline_hits,
881            counters.parser_token_set_inline_misses,
882            counters.parser_token_set_dense_hits,
883            counters.parser_token_set_dense_misses,
884            counters.parser_token_set_interval_hits,
885            counters.parser_token_set_interval_misses,
886            counters.parser_token_set_interval_probes_eliminated,
887            counters.parser_token_set_interval_binary_probes_retained,
888        ]
889    })
890}
891
892fn print_counter(name: &str, value: u64) {
893    #[allow(clippy::print_stderr)]
894    {
895        eprintln!("perf {name}={value}");
896    }
897}
898
899fn print_decision_counter(decision: usize, name: &str, value: u64) {
900    #[allow(clippy::print_stderr)]
901    {
902        eprintln!("perf decision.{decision}.{name}={value}");
903    }
904}