Skip to main content

antlr4_runtime/
perf.rs

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