|
| 1 | +// SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell |
| 3 | + |
| 4 | +//! intsoc-core benchmarks — domain model construction, state machine transitions, |
| 5 | +//! and draft name parsing. |
| 6 | +//! |
| 7 | +//! Covers the hot paths used during batch document processing: |
| 8 | +//! - `InternetDraft::parse_draft_name` — called on every document ingest |
| 9 | +//! - `IetfState::valid_transitions` — called on every state machine step |
| 10 | +//! - `StateMachine::transition` — full state advance including history append |
| 11 | +//! - `Organization::datatracker_base` + `uses_datatracker` — called per request |
| 12 | +
|
| 13 | +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; |
| 14 | +use intsoc_core::{ |
| 15 | + document::InternetDraft, |
| 16 | + organization::Organization, |
| 17 | + state::{IetfState, StateMachine, StreamState}, |
| 18 | + stream::Stream, |
| 19 | + validation::{CheckCategory, CheckResult, CheckSummary, Fixability, Severity}, |
| 20 | +}; |
| 21 | + |
| 22 | +// ============================================================================ |
| 23 | +// Draft name parsing benchmarks |
| 24 | +// ============================================================================ |
| 25 | + |
| 26 | +/// Benchmark parsing a well-formed draft name (common fast path). |
| 27 | +fn bench_parse_draft_name_valid(c: &mut Criterion) { |
| 28 | + let draft_names = [ |
| 29 | + "draft-ietf-quic-transport-34", |
| 30 | + "draft-ietf-tls-rfc8446bis-09", |
| 31 | + "draft-iab-protocol-maintenance-14", |
| 32 | + "draft-irtf-cfrg-hpke-12", |
| 33 | + "draft-ietf-httpbis-semantics-19", |
| 34 | + ]; |
| 35 | + |
| 36 | + c.bench_function("parse_draft_name_valid", |b| { |
| 37 | + b.iter(|| { |
| 38 | + for name in &draft_names { |
| 39 | + black_box(InternetDraft::parse_draft_name(black_box(name))); |
| 40 | + } |
| 41 | + }) |
| 42 | + }); |
| 43 | +} |
| 44 | + |
| 45 | +/// Benchmark parsing an invalid draft name (error path). |
| 46 | +fn bench_parse_draft_name_invalid(c: &mut Criterion) { |
| 47 | + let invalid_names = [ |
| 48 | + "not-a-draft", |
| 49 | + "draft-no-version", |
| 50 | + "draft-", |
| 51 | + "", |
| 52 | + "RFC7230", |
| 53 | + ]; |
| 54 | + |
| 55 | + c.bench_function("parse_draft_name_invalid", |b| { |
| 56 | + b.iter(|| { |
| 57 | + for name in &invalid_names { |
| 58 | + black_box(InternetDraft::parse_draft_name(black_box(name))); |
| 59 | + } |
| 60 | + }) |
| 61 | + }); |
| 62 | +} |
| 63 | + |
| 64 | +// ============================================================================ |
| 65 | +// State machine benchmarks |
| 66 | +// ============================================================================ |
| 67 | + |
| 68 | +/// Benchmark querying valid transitions from each IETF state. |
| 69 | +/// |
| 70 | +/// This is called on every UI render and state display, so it needs |
| 71 | +/// to be cheap even for complex state graphs. |
| 72 | +fn bench_ietf_valid_transitions(c: &mut Criterion) { |
| 73 | + let states = [ |
| 74 | + IetfState::Draft, |
| 75 | + IetfState::IdnitsCheck, |
| 76 | + IetfState::WgDocument, |
| 77 | + IetfState::IesgEvaluation, |
| 78 | + IetfState::Approved, |
| 79 | + ]; |
| 80 | + |
| 81 | + c.bench_function("ietf_valid_transitions", |b| { |
| 82 | + b.iter(|| { |
| 83 | + for state in &states { |
| 84 | + black_box(state.valid_transitions()); |
| 85 | + } |
| 86 | + }) |
| 87 | + }); |
| 88 | +} |
| 89 | + |
| 90 | +/// Benchmark creating a new StateMachine starting from the initial IETF state. |
| 91 | +fn bench_state_machine_new(c: &mut Criterion) { |
| 92 | + c.bench_function("state_machine_new_ietf", |b| { |
| 93 | + b.iter(|| black_box(StateMachine::<IetfState>::new())) |
| 94 | + }); |
| 95 | +} |
| 96 | + |
| 97 | +/// Benchmark a single state transition (Draft -> IdnitsCheck). |
| 98 | +fn bench_state_machine_single_transition(c: &mut Criterion) { |
| 99 | + c.bench_function("state_machine_transition_single", |b| { |
| 100 | + b.iter(|| { |
| 101 | + let mut sm = StateMachine::<IetfState>::new(); |
| 102 | + black_box(sm.transition(black_box(IetfState::IdnitsCheck))) |
| 103 | + }) |
| 104 | + }); |
| 105 | +} |
| 106 | + |
| 107 | +/// Benchmark walking the full happy-path IETF lifecycle: |
| 108 | +/// Draft -> IdnitsCheck -> WgDocument -> ... -> Published |
| 109 | +fn bench_state_machine_full_lifecycle(c: &mut Criterion) { |
| 110 | + // Represents the happy-path transitions for a WG document. |
| 111 | + let lifecycle = [ |
| 112 | + IetfState::IdnitsCheck, |
| 113 | + IetfState::IndividualSubmitted, |
| 114 | + IetfState::WgAdopted, |
| 115 | + IetfState::WgDocument, |
| 116 | + IetfState::WgLastCall, |
| 117 | + IetfState::WaitingForWriteup, |
| 118 | + IetfState::AdEvaluation, |
| 119 | + IetfState::IesgEvaluation, |
| 120 | + IetfState::IesgLastCall, |
| 121 | + IetfState::Approved, |
| 122 | + IetfState::RfcEditorQueue, |
| 123 | + IetfState::Auth48, |
| 124 | + IetfState::Published, |
| 125 | + ]; |
| 126 | + |
| 127 | + c.bench_function("state_machine_full_lifecycle", |b| { |
| 128 | + b.iter(|| { |
| 129 | + let mut sm = StateMachine::<IetfState>::new(); |
| 130 | + for next_state in &lifecycle { |
| 131 | + let _ = sm.transition(black_box(next_state.clone())); |
| 132 | + } |
| 133 | + black_box(sm.is_complete()) |
| 134 | + }) |
| 135 | + }); |
| 136 | +} |
| 137 | + |
| 138 | +/// Benchmark checking `is_terminal` for all IETF states. |
| 139 | +fn bench_is_terminal_all_states(c: &mut Criterion) { |
| 140 | + let states = [ |
| 141 | + IetfState::Draft, |
| 142 | + IetfState::Published, |
| 143 | + IetfState::Withdrawn, |
| 144 | + IetfState::Expired, |
| 145 | + IetfState::WgDocument, |
| 146 | + ]; |
| 147 | + |
| 148 | + c.bench_function("is_terminal_all_states", |b| { |
| 149 | + b.iter(|| { |
| 150 | + for s in &states { |
| 151 | + black_box(s.is_terminal()); |
| 152 | + } |
| 153 | + }) |
| 154 | + }); |
| 155 | +} |
| 156 | + |
| 157 | +// ============================================================================ |
| 158 | +// Organization benchmarks |
| 159 | +// ============================================================================ |
| 160 | + |
| 161 | +/// Benchmark organization metadata lookups (called per HTTP request). |
| 162 | +fn bench_organization_metadata(c: &mut Criterion) { |
| 163 | + let orgs = [ |
| 164 | + Organization::Ietf, |
| 165 | + Organization::Irtf, |
| 166 | + Organization::Iab, |
| 167 | + Organization::Independent, |
| 168 | + Organization::Iana, |
| 169 | + Organization::RfcEditor, |
| 170 | + ]; |
| 171 | + |
| 172 | + c.bench_function("organization_datatracker_base", |b| { |
| 173 | + b.iter(|| { |
| 174 | + for org in &orgs { |
| 175 | + black_box(org.datatracker_base()); |
| 176 | + black_box(org.uses_datatracker()); |
| 177 | + } |
| 178 | + }) |
| 179 | + }); |
| 180 | +} |
| 181 | + |
| 182 | +// ============================================================================ |
| 183 | +// ValidationReport benchmarks |
| 184 | +// ============================================================================ |
| 185 | + |
| 186 | +/// Benchmark building a ValidationReport from N check results. |
| 187 | +fn bench_validation_report_build(c: &mut Criterion) { |
| 188 | + let make_result = |i: u32, severity: Severity| CheckResult { |
| 189 | + check_id: format!("check-{i:04}"), |
| 190 | + severity, |
| 191 | + message: format!("Check {i} finding"), |
| 192 | + location: None, |
| 193 | + category: CheckCategory::Boilerplate, |
| 194 | + fixable: Fixability::AutoSafe, |
| 195 | + suggestion: None, |
| 196 | + }; |
| 197 | + |
| 198 | + let mut group = c.benchmark_group("validation_report_n"); |
| 199 | + for n in [8usize, 32, 128, 512] { |
| 200 | + let results: Vec<CheckResult> = (0..n as u32) |
| 201 | + .map(|i| make_result(i, if i % 3 == 0 { Severity::Error } else { Severity::Warning })) |
| 202 | + .collect(); |
| 203 | + group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, _| { |
| 204 | + b.iter(|| black_box(CheckSummary::from_results(black_box(results.clone())))) |
| 205 | + }); |
| 206 | + } |
| 207 | + group.finish(); |
| 208 | +} |
| 209 | + |
| 210 | +criterion_group!( |
| 211 | + benches, |
| 212 | + bench_parse_draft_name_valid, |
| 213 | + bench_parse_draft_name_invalid, |
| 214 | + bench_ietf_valid_transitions, |
| 215 | + bench_state_machine_new, |
| 216 | + bench_state_machine_single_transition, |
| 217 | + bench_state_machine_full_lifecycle, |
| 218 | + bench_is_terminal_all_states, |
| 219 | + bench_organization_metadata, |
| 220 | + bench_validation_report_build, |
| 221 | +); |
| 222 | +criterion_main!(benches); |
0 commit comments