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