Skip to content

Complete 9.6.1 Codec Performance Benchmarks, Tests, and Docs#476

Merged
leynos merged 8 commits intomainfrom
benchmark-codec-performance-qgw4sp
Feb 25, 2026
Merged

Complete 9.6.1 Codec Performance Benchmarks, Tests, and Docs#476
leynos merged 8 commits intomainfrom
benchmark-codec-performance-qgw4sp

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Feb 23, 2026

Summary

  • Status: COMPLETE (benchmarks, tests, and docs implemented and validated).
  • Adds a complete 9.6.1 codec performance benchmark suite using Criterion, including an ExecPlan-aligned benchmark harness, unit tests, and behavioural tests, plus supporting fixtures and docs.
  • Introduces bench binaries, test scaffolding (unit and behavioural), and Makefile integration for running codec benchmarks.
  • Adds documentation and planning updates to guide implementation, validation, and future changes, and updates related ADRs/roadmap accordingly.
  • No public API changes to library runtime; changes are focused on benchmarks, tests, and docs.
  • Bug fixes: improves robustness of interleaved push queue tests by ensuring test helpers propagate results correctly and do not produce unused/ignored error conditions.

Changes

  • Added benchmark harness and supporting code:
    • benches/codec_performance.rs
    • benches/codec_performance_alloc.rs
    • Makefile: bench-codec target
    • Cargo.toml: bench registrations and criterion dependency
  • Added benchmark support and tests:
    • tests/common/codec_benchmark_support.rs
    • tests/codec_performance_benchmark_helpers.rs
    • tests/features/codec_performance_benchmarks.feature
    • tests/fixtures/codec_performance_benchmarks.rs
    • tests/scenarios/codec_performance_benchmarks_scenarios.rs
    • tests/scenarios/mod.rs (updated)
    • tests/steps/codec_performance_benchmarks_steps.rs
    • tests/steps/mod.rs (updated)
    • tests/fixtures/mod.rs (updated to expose new bench module)
  • Documentation and planning:
    • docs/execplans/9-6-1-codec-performance-benchmarks.md
    • docs/adr-004-pluggable-protocol-codecs.md (updated with benchmark coverage section)
    • docs/roadmap.md (9.6.1 marked done following complete implementation/docs/validation)
  • Public API impact: none. All changes are within benchmarks, tests, and docs.
  • Bug fixes (test reliability): adjusted interleaved push queue tests to handle results from test helpers and avoid unused/ignored error conditions.

Rationale / Why

  • Establishes a complete, repeatable benchmark surface (encode/decode throughput/latency, fragmentation overhead, and allocation baselines) for 9.6.1 as described in the ExecPlan. Provides deterministic scaffolding for future CI gates and regression detection, while aligning with related ADRs and roadmap updates.
  • Adds a dedicated benchmark harness and test coverage to ensure plan execution is observable, verifiable, and maintainable.

Testing plan

  • Build and run benchmarks:
    • make bench-codec (wired to cargo bench for codec_performance and codec_performance_alloc)
  • Run unit tests for benchmark helpers:
    • cargo test -p wireframe --features test-support (unit tests in codec_performance_benchmark_helpers.rs and common benchmark support)
  • Run behavioural tests via rstest-bdd workbench:
    • make test-bdd
  • Validation criteria:
    • Benchmark binaries compile and run, producing encode/decode, fragmentation, and allocation sections.
    • Unit tests validate workload matrix and basic measurement outputs without depending on wall-clock timing thresholds.
    • Behavioural tests exercise the benchmark harness workflow, not timing thresholds.

Follow-ups (post-merge)

  • Implement and validate the full benchmark harness, Makefile targets, and benches as described in the ExecPlan (Stages A–F).
  • Integrate benchmarks into CI quality gates and roadmap/documentation updates upon completion.
  • Expand with additional codecs or scenarios if required by future roadmap items.

Notes for reviewers

  • This PR intentionally focuses on documentation plus a complete benchmark harness, test matrix, and supporting tests. Feedback on scope, clarity, and alignment with existing docs is welcome.
  • If gaps are identified in referenced materials (e.g., ADRs or roadmaps), please propose targeted links to ensure traceability.

◳ Generated by DevBoxer


ℹ️ Tag @devboxerhub to ask questions and address PR feedback

📎 Task: https://www.devboxer.com/task/97b59cec-f42a-424a-95d3-d7ebea2f8bd6

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Feb 23, 2026

Reviewer's Guide

Introduces a Criterion-based benchmark harness, helper utilities, and BDD/unit tests for 9.6.1 codec performance (throughput, latency, fragmentation overhead, allocations), wires them into Cargo/Makefile and documentation/roadmap, and fixes some existing test result-handling issues.

Sequence diagram for codec benchmark execution via make bench-codec

sequenceDiagram
    actor Developer
    participant Makefile
    participant Cargo
    participant BenchCodecPerformance as codec_performance
    participant BenchCodecAlloc as codec_performance_alloc
    participant CriterionLib as Criterion
    participant WireframeCrate as wireframe_codec

    Developer->>Makefile: make bench-codec
    Makefile->>Cargo: cargo bench --bench codec_performance --bench codec_performance_alloc --features test-support

    Cargo->>BenchCodecPerformance: run bench binary
    BenchCodecPerformance->>CriterionLib: Criterion::default().configure_from_args()
    BenchCodecPerformance->>CriterionLib: benchmark_encode
    CriterionLib->>WireframeCrate: measure_encode (LengthDelimitedFrameCodec, HotlineFrameCodec)
    CriterionLib-->>BenchCodecPerformance: encode measurements
    BenchCodecPerformance->>CriterionLib: benchmark_decode
    CriterionLib->>WireframeCrate: measure_decode (LengthDelimitedFrameCodec, HotlineFrameCodec)
    CriterionLib-->>BenchCodecPerformance: decode measurements
    BenchCodecPerformance->>CriterionLib: benchmark_fragmentation_overhead
    CriterionLib->>WireframeCrate: measure_fragmented_wrap / measure_unfragmented_wrap
    CriterionLib-->>BenchCodecPerformance: fragmentation metrics
    BenchCodecPerformance->>CriterionLib: final_summary

    Cargo->>BenchCodecAlloc: run bench binary
    BenchCodecAlloc->>CriterionLib: Criterion::default().configure_from_args()
    BenchCodecAlloc->>BenchCodecAlloc: count_allocations(measure_encode)
    BenchCodecAlloc->>WireframeCrate: measure_encode with counting allocator
    BenchCodecAlloc->>BenchCodecAlloc: count_allocations(measure_decode)
    BenchCodecAlloc->>WireframeCrate: measure_decode with counting allocator
    BenchCodecAlloc->>CriterionLib: benchmark_allocations
    CriterionLib-->>BenchCodecAlloc: allocation benchmarks
    BenchCodecAlloc->>CriterionLib: final_summary

    CriterionLib-->>Cargo: benchmark results
    Cargo-->>Developer: console output with throughput, latency, fragmentation, allocations
Loading

Class diagram for codec benchmark helper types and allocation bench

classDiagram
    class CodecUnderTest {
        <<enum>>
        +LengthDelimited
        +Hotline
        +label() &str
    }

    class PayloadClass {
        <<enum>>
        +Small
        +Large
        +label() &str
        +len() usize
    }

    class BenchmarkWorkload {
        +codec CodecUnderTest
        +payload_class PayloadClass
        +label() String
    }

    class MeasurementAllocBench {
        <<struct>>
        +payload_bytes u64
        +elapsed Duration
    }

    class AllocationBaseline {
        <<struct>>
        +wrap_allocations usize
        +decode_allocations usize
    }

    class FragmentationBaseline {
        <<struct>>
        +unfragmented MeasurementSupport
        +fragmented MeasurementSupport
        +nanos_ratio() f64
    }

    class MeasurementSupport {
        <<struct>>
        +payload_bytes u64
        +elapsed Duration
        +nanos_per_op() u64
    }

    class CountingAllocator {
        <<struct>>
        +alloc(layout Layout) u8*
        +dealloc(ptr u8*, layout Layout) void
        +alloc_zeroed(layout Layout) u8*
        +realloc(ptr u8*, layout Layout, new_size usize) u8*
    }

    class LengthDelimitedOps {
        <<struct>>
        +codec_name() &str
        +create_codec() LengthDelimitedFrameCodec
        +extract_payload(frame Frame) u8[]
    }

    class HotlineOps {
        <<struct>>
        +codec_name() &str
        +create_codec() HotlineFrameCodec
        +extract_payload(frame Frame) u8[]
    }

    class CodecBenchmarkSupportModule {
        <<module>>
        +SMALL_PAYLOAD_BYTES usize
        +LARGE_PAYLOAD_BYTES usize
        +FRAGMENT_PAYLOAD_CAP_BYTES usize
        +VALIDATION_ITERATIONS u64
        +benchmark_workloads() BenchmarkWorkload[]
        +payload_for_class(class PayloadClass) Bytes
        +measure_encode(workload BenchmarkWorkload, iterations u64) MeasurementSupport~Result~
        +measure_decode(workload BenchmarkWorkload, iterations u64) MeasurementSupport~Result~
        +measure_unfragmented_wrap(class PayloadClass, iterations u64) MeasurementSupport
        +measure_fragmented_wrap(class PayloadClass, iterations u64, cap_bytes usize) MeasurementSupport~Result~
        +measure_fragmentation_overhead(class PayloadClass, iterations u64, cap_bytes usize) FragmentationBaseline~Result~
        +allocation_label(workload BenchmarkWorkload, baseline AllocationBaseline) String
    }

    class CodecPerformanceBenchBinary {
        <<binary>>
        +benchmark_encode(c Criterion)
        +benchmark_decode(c Criterion)
        +benchmark_fragmentation_overhead(c Criterion)
        +main()
    }

    class CodecPerformanceAllocBenchBinary {
        <<binary>>
        +benchmark_allocations(c Criterion)
        +benchmark_workloads() BenchmarkWorkload[]
        +measure_encode(workload BenchmarkWorkload, iterations u64) MeasurementAllocBench~Result~
        +measure_decode(workload BenchmarkWorkload, iterations u64) MeasurementAllocBench~Result~
        +prepare_decode(payload Bytes) (Bytes, Decoder)~Result~
        +run_decode_iterations(decoder Decoder, encoded Bytes, iterations u64) Result
        +count_allocations(operation FnOnce) usize
        +allocation_label(workload BenchmarkWorkload, wrap_allocations usize, decode_allocations usize) String
        +main()
    }

    CodecBenchmarkSupportModule --> CodecUnderTest
    CodecBenchmarkSupportModule --> PayloadClass
    CodecBenchmarkSupportModule --> BenchmarkWorkload
    CodecBenchmarkSupportModule --> MeasurementSupport
    CodecBenchmarkSupportModule --> FragmentationBaseline
    CodecBenchmarkSupportModule --> AllocationBaseline

    CodecPerformanceBenchBinary --> CodecBenchmarkSupportModule

    CodecPerformanceAllocBenchBinary --> CodecUnderTest
    CodecPerformanceAllocBenchBinary --> PayloadClass
    CodecPerformanceAllocBenchBinary --> BenchmarkWorkload
    CodecPerformanceAllocBenchBinary --> MeasurementAllocBench
    CodecPerformanceAllocBenchBinary --> LengthDelimitedOps
    CodecPerformanceAllocBenchBinary --> HotlineOps
    CodecPerformanceAllocBenchBinary --> CountingAllocator

    LengthDelimitedOps ..|> CodecDecodeOps
    HotlineOps ..|> CodecDecodeOps

    class CodecDecodeOps {
        <<trait>>
        +Codec FrameCodec
        +Decoder Decoder
        +codec_name() &str
        +create_codec() Codec
        +extract_payload(frame Item) u8[]
    }
Loading

File-Level Changes

Change Details Files
Add shared codec benchmark support module defining workloads, payload generation, and measurement helpers for encode/decode, fragmentation, and allocations.
  • Define SMALL/LARGE payload sizes, fragmentation cap, and validation iteration constants.
  • Introduce CodecUnderTest, PayloadClass, BenchmarkWorkload enums/structs and matrix generator.
  • Implement deterministic payload generation and Measurement/FragmentationOverhead/AllocationBaseline types.
  • Provide measure_encode/measure_decode helpers for LengthDelimitedFrameCodec and HotlineFrameCodec.
  • Add fragmentation measurement helpers using Fragmenter/encode_fragment_payload and label builders for allocation baselines.
tests/common/codec_benchmark_support.rs
Implement Criterion benches for throughput/latency, fragmentation overhead, and allocation baselines, each with explicit main entrypoints.
  • Add codec_performance bench measuring encode/decode throughput across codec/payload matrix and fragmented vs unfragmented wrapping.
  • Add codec_performance_alloc bench with a global counting allocator to embed allocation baselines in benchmark IDs.
  • Reuse shared helpers from tests/common for workloads and measurements, and configure Criterion groups and throughput metadata.
  • Introduce allocation_label usage/black_box calls to keep labels and metrics from being optimized out.
benches/codec_performance.rs
benches/codec_performance_alloc.rs
Wire benchmarks into Cargo and Makefile and add Criterion dependency.
  • Add criterion dev-dependency to Cargo.toml.
  • Register codec_performance and codec_performance_alloc as harnessless benches.
  • Add a bench-codec Makefile target running both benches with RUSTFLAGS=-D warnings and test-support feature.
  • Extend PHONY list to include bench-codec.
Cargo.toml
Makefile
Add unit and BDD behavioural tests around the benchmark harness and workload matrix.
  • Add rstest unit tests for payload sizing, workload coverage, encode/decode/fragmentation measurements, and allocation label formatting.
  • Introduce CodecPerformanceBenchmarksWorld fixture that runs matrix/fragmentation/allocation flows using shared helpers.
  • Define rstest-bdd steps and scenarios plus a Gherkin feature file that validate matrix coverage, fragmentation metrics, and allocation labels.
  • Wire new fixtures/steps/scenarios modules into existing tests mod trees and re-export TestResult for step code.
tests/codec_performance_benchmark_helpers.rs
tests/fixtures/codec_performance_benchmarks.rs
tests/steps/codec_performance_benchmarks_steps.rs
tests/scenarios/codec_performance_benchmarks_scenarios.rs
tests/features/codec_performance_benchmarks.feature
tests/fixtures/mod.rs
tests/steps/mod.rs
tests/scenarios/mod.rs
Update docs/ADR and roadmap with the new benchmark coverage and add an execution plan document for 9.6.1.
  • Extend ADR-004 with a section describing the new Criterion benches, what they measure, and the internal nature of the harness.
  • Mark roadmap item 9.6.1 and its sub-bullets as completed for throughput, fragmentation, and allocation baselines.
  • Add an ExecPlan document detailing constraints, stages A–F, risks, decisions, and outcomes for the 9.6.1 benchmarks.
docs/adr-004-pluggable-protocol-codecs.md
docs/roadmap.md
docs/execplans/9-6-1-codec-performance-benchmarks.md
Fix existing tests to correctly propagate push_expect! results instead of discarding them.
  • Replace let _ = push_expect!(...) with push_expect!(...)? in interleaved push queue tests and fixtures to satisfy Result handling and error propagation.
  • Ensure affected async tests return TestResult and use ? for failure reporting.
tests/interleaved_push_queues.rs
tests/fixtures/interleaved_push_queues.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 23, 2026

Walkthrough

Add Criterion-based codec benchmarks and an allocation-counting harness; introduce shared benchmark/test utilities, BDD fixtures and scenarios, a Makefile bench target, documentation and roadmap updates; change test push helpers to propagate errors instead of ignoring them.

Changes

Cohort / File(s) Summary
Build & targets
Cargo.toml, Makefile
Add Criterion dev-dependency and two bench binaries (codec_performance, codec_performance_alloc); add bench-codec Makefile target and include it in .PHONY.
Benchmarks
benches/codec_performance.rs, benches/codec_performance_alloc.rs
Add Criterion benches for encode/decode throughput, latency, fragmentation overhead, and allocation baselines; introduce a global CountingAllocator for allocation measurement.
Shared benchmark support
tests/common/codec_benchmark_support.rs, tests/codec_performance_benchmark_helpers.rs
Introduce deterministic payloads, workload definitions, Measurement/FragmentationOverhead/AllocationBaseline types and helper measure_* functions used by benches and tests.
BDD fixtures & scenarios
tests/fixtures/codec_performance_benchmarks.rs, tests/fixtures/mod.rs, tests/scenarios/codec_performance_benchmarks_scenarios.rs, tests/scenarios/mod.rs, tests/steps/codec_performance_benchmarks_steps.rs, tests/steps/mod.rs, tests/features/codec_performance_benchmarks.feature
Add fixture world, step definitions, scenarios and feature file to exercise benchmark matrix, fragmentation sampling and allocation label generation.
Test fixtures & expectations
tests/fixtures/interleaved_push_queues.rs
Change push helper usage to propagate errors (push_expect!(...)?) rather than ignoring results.
Test callsites
tests/interleaved_push_queues.rs
Replace let _ = push_expect!(...) with push_expect!(...)? across tests to surface failures via early returns.
Test helpers (unit)
tests/codec_performance_benchmark_helpers.rs, tests/common/...
Add unit tests validating workload coverage, encode/decode measurements, fragmentation overhead and allocation label formatting.
Documentation & plans
docs/adr-004-pluggable-protocol-codecs.md, docs/execplans/9-6-1-codec-performance-benchmarks.md, docs/roadmap.md, docs/execplans/10-3-2-exercise-interleaved-queues.md
Document benchmark design, execution plan and mark roadmap item complete; reformat one shell block for linting.
Small docs/usage examples
src/app_data_store.rs
Update RustDoc examples to use full path wireframe::app_data_store::AppDataStore; no API changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

Benchmarks rise where silence once reigned,
Frames counted, fragments neatly chained,
Allocations tick their tiny score,
Timings whisper what optimisers adore,
Metrics light the path to do more.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title 'Complete 9.6.1 Codec Performance Benchmarks, Tests, and Docs' directly and comprehensively summarises the main change: implementation of a codec performance benchmark suite with associated tests and documentation.
Description check ✅ Passed The description provides detailed context about the changeset, covering the benchmark harness implementation, test scaffolding, documentation updates, bug fixes, testing plan, and rationale—all directly related to the actual changes in the pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch benchmark-codec-performance-qgw4sp

Comment @coderabbitai help to get the list of available commands and usage tips.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos changed the title Add 9.6.1 Codec Performance Benchmarks ExecPlan Add 9.6.1 Codec Performance Benchmarks (ExecPlan, Bench, Tests) Feb 23, 2026
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Feb 24, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

tests/common/codec_benchmark_support.rs

Comment on file

pub fn measure_decode(workload: BenchmarkWorkload, iterations: u64) -> Result<Measurement, String> {
    if iterations == 0 {
        return Ok(Measurement {
            operations: 0,
            payload_bytes: 0,
            elapsed: Duration::ZERO,
        });
    }

    let payload = payload_for_class(workload.payload_class);
    let payload_len = payload.len() as u64;

    match workload.codec {
        CodecUnderTest::LengthDelimited => {
            let codec = LengthDelimitedFrameCodec::new(LARGE_PAYLOAD_BYTES + 4096);
            let mut seed_encoder = codec.encoder();
            let mut encoded = BytesMut::new();
            seed_encoder
                .encode(codec.wrap_payload(payload), &mut encoded)
                .map_err(|err| format!("length-delimited seed encode failed: {err}"))?;
            let encoded = encoded.freeze();

            let mut decoder = codec.decoder();
            let started = Instant::now();
            for _ in 0..iterations {
                let mut wire = BytesMut::from(encoded.as_ref());
                let frame = decoder
                    .decode(&mut wire)
                    .map_err(|err| format!("length-delimited decode failed: {err}"))?
                    .ok_or("length-delimited decode produced no frame")?;
                if LengthDelimitedFrameCodec::frame_payload(&frame).is_empty() {
                    return Err("length-delimited decode produced empty payload".to_string());
                }
            }
            Ok(Measurement {
                operations: iterations,
                payload_bytes: iterations * payload_len,
                elapsed: started.elapsed(),
            })
        }
        CodecUnderTest::Hotline => {
            let codec = HotlineFrameCodec::new(LARGE_PAYLOAD_BYTES + 4096);
            let mut seed_encoder = codec.encoder();
            let mut encoded = BytesMut::new();
            seed_encoder
                .encode(codec.wrap_payload(payload), &mut encoded)
                .map_err(|err| format!("hotline seed encode failed: {err}"))?;
            let encoded = encoded.freeze();

            let mut decoder = codec.decoder();
            let started = Instant::now();
            for _ in 0..iterations {
                let mut wire = BytesMut::from(encoded.as_ref());
                let frame = decoder
                    .decode(&mut wire)
                    .map_err(|err| format!("hotline decode failed: {err}"))?
                    .ok_or("hotline decode produced no frame")?;
                if HotlineFrameCodec::frame_payload(&frame).is_empty() {
                    return Err("hotline decode produced empty payload".to_string());
                }
            }
            Ok(Measurement {
                operations: iterations,
                payload_bytes: iterations * payload_len,
                elapsed: started.elapsed(),
            })
        }
    }
}

❌ New issue: Bumpy Road Ahead
measure_decode has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Feb 24, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

benches/codec_performance_alloc.rs

Comment on file

fn measure_decode(workload: BenchmarkWorkload, iterations: u64) -> Result<Measurement, String> {
    let payload = payload_for_class(workload.payload_class);
    let payload_len = payload.len() as u64;

    match workload.codec {
        CodecUnderTest::LengthDelimited => {
            let codec = LengthDelimitedFrameCodec::new(LARGE_PAYLOAD_BYTES + 4096);
            let mut seed_encoder = codec.encoder();
            let mut encoded = BytesMut::new();
            seed_encoder
                .encode(codec.wrap_payload(payload), &mut encoded)
                .map_err(|err| format!("length-delimited seed encode failed: {err}"))?;
            let encoded = encoded.freeze();

            let mut decoder = codec.decoder();
            let started = Instant::now();
            for _ in 0..iterations {
                let mut wire = BytesMut::from(encoded.as_ref());
                let frame = decoder
                    .decode(&mut wire)
                    .map_err(|err| format!("length-delimited decode failed: {err}"))?
                    .ok_or("length-delimited decode produced no frame")?;
                if LengthDelimitedFrameCodec::frame_payload(&frame).is_empty() {
                    return Err("length-delimited decode produced empty payload".to_string());
                }
            }
            Ok(Measurement {
                payload_bytes: iterations.saturating_mul(payload_len),
                elapsed: started.elapsed(),
            })
        }
        CodecUnderTest::Hotline => {
            let codec = HotlineFrameCodec::new(LARGE_PAYLOAD_BYTES + 4096);
            let mut seed_encoder = codec.encoder();
            let mut encoded = BytesMut::new();
            seed_encoder
                .encode(codec.wrap_payload(payload), &mut encoded)
                .map_err(|err| format!("hotline seed encode failed: {err}"))?;
            let encoded = encoded.freeze();

            let mut decoder = codec.decoder();
            let started = Instant::now();
            for _ in 0..iterations {
                let mut wire = BytesMut::from(encoded.as_ref());
                let frame = decoder
                    .decode(&mut wire)
                    .map_err(|err| format!("hotline decode failed: {err}"))?
                    .ok_or("hotline decode produced no frame")?;
                if HotlineFrameCodec::frame_payload(&frame).is_empty() {
                    return Err("hotline decode produced empty payload".to_string());
                }
            }
            Ok(Measurement {
                payload_bytes: iterations.saturating_mul(payload_len),
                elapsed: started.elapsed(),
            })
        }
    }
}

❌ New issue: Bumpy Road Ahead
measure_decode has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

leynos and others added 2 commits February 24, 2026 07:09
Add a comprehensive living ExecPlan for roadmap item 9.6.1 that defines benchmark contracts, progress stages, constraints, risks, decision logs, tolerances, and planned work for codec performance benchmarking. This new document provides structure for implementing and validating throughput, latency, fragmentation overhead, and allocation baselines across codecs, along with testing guidance and documentation updates.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
…and allocation benchmarks

- Introduced Criterion benchmark suites for codec encoding/decoding throughput and latency.
- Benchmarked default LengthDelimitedFrameCodec and custom HotlineFrameCodec with small and large payloads.
- Measured fragmentation overhead comparing fragmented vs unfragmented wrapping.
- Added allocation baseline benchmarks with a counting global allocator reporting wrap and decode allocations.
- Integrated benchmarks into Makefile with a new bench-codec target.
- Added extensive unit tests and behavior-driven tests for benchmark validation.
- Updated ADR, roadmap, and execution plan docs to mark item 9.6.1 as complete.
- Fixed related lint and test issues, including properly handling push_expect! results in interleaved push queue tests.

No public runtime API changes introduced; benchmarks remain internal tooling.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@devboxerhub devboxerhub Bot force-pushed the benchmark-codec-performance-qgw4sp branch from 44081af to 726f129 Compare February 24, 2026 07:12
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos changed the title Add 9.6.1 Codec Performance Benchmarks (ExecPlan, Bench, Tests) Add 9.6.1 Codec Performance Benchmarks, Tests, and Docs Feb 24, 2026
…ogic

Refactored the codec benchmark code by extracting the common preparation and iteration loops for length-delimited and hotline decode benchmarks into separate helper functions. This reduces duplication and improves readability in benchmark measurement functions across benches and tests/common modules.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Feb 24, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

benches/codec_performance_alloc.rs

Comment on file

fn prepare_length_delimited_decode(
    payload: Bytes,
) -> Result<(Bytes, LengthDelimitedFrameDecoder), String> {
    let codec = LengthDelimitedFrameCodec::new(LARGE_PAYLOAD_BYTES + 4096);
    let mut seed_encoder = codec.encoder();
    let mut encoded = BytesMut::new();
    seed_encoder
        .encode(codec.wrap_payload(payload), &mut encoded)
        .map_err(|err| format!("length-delimited seed encode failed: {err}"))?;
    Ok((encoded.freeze(), codec.decoder()))
}

❌ New issue: Code Duplication
The module contains 4 functions with similar structure: prepare_hotline_decode,prepare_length_delimited_decode,run_hotline_decode_iterations,run_length_delimited_decode_iterations

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Feb 24, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

tests/common/codec_benchmark_support.rs

Comment on file

fn measure_decode_length_delimited(
    payload: Bytes,
    payload_len: u64,
    iterations: u64,
) -> Result<Measurement, String> {
    let codec = LengthDelimitedFrameCodec::new(LARGE_PAYLOAD_BYTES + 4096);
    let mut seed_encoder = codec.encoder();
    let mut encoded = BytesMut::new();
    seed_encoder
        .encode(codec.wrap_payload(payload), &mut encoded)
        .map_err(|err| format!("length-delimited seed encode failed: {err}"))?;
    let encoded = encoded.freeze();
    let mut decoder = codec.decoder();
    let started = Instant::now();
    for _ in 0..iterations {
        let mut wire = BytesMut::from(encoded.as_ref());
        let frame = decoder
            .decode(&mut wire)
            .map_err(|err| format!("length-delimited decode failed: {err}"))?
            .ok_or("length-delimited decode produced no frame")?;
        if LengthDelimitedFrameCodec::frame_payload(&frame).is_empty() {
            return Err("length-delimited decode produced empty payload".to_string());
        }
    }
    Ok(Measurement {
        operations: iterations,
        payload_bytes: iterations * payload_len,
        elapsed: started.elapsed(),
    })
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: measure_decode_hotline,measure_decode_length_delimited

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

…aits and macros

- Introduced a CodecDecodeOps trait to generalize codec decoding operations.
- Replaced codec-specific decode preparation and iteration functions with generic ones using CodecDecodeOps.
- Added a macro to generate decode measurement functions to reduce code duplication.
- Updated benchmarks to use the new generic abstractions for LengthDelimited and Hotline codecs.

This improves code maintainability by reducing duplication and enhancing extensibility for codec benchmarks.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos marked this pull request as ready for review February 24, 2026 18:01
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 5 issues, and left some high level feedback:

  • The allocation benchmarks in benches/codec_performance_alloc.rs reintroduce their own CodecUnderTest/PayloadClass/workload definitions and encode/decode helpers instead of reusing tests/common/codec_benchmark_support.rs; consider factoring these into a single shared module to avoid drift and reduce duplication.
  • Constants like SMALL_PAYLOAD_BYTES, LARGE_PAYLOAD_BYTES, and VALIDATION_ITERATIONS are now defined both in codec_benchmark_support and codec_performance_alloc; centralising them in the shared benchmark support module will make it easier to keep benchmark parameters consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The allocation benchmarks in `benches/codec_performance_alloc.rs` reintroduce their own `CodecUnderTest`/`PayloadClass`/workload definitions and encode/decode helpers instead of reusing `tests/common/codec_benchmark_support.rs`; consider factoring these into a single shared module to avoid drift and reduce duplication.
- Constants like `SMALL_PAYLOAD_BYTES`, `LARGE_PAYLOAD_BYTES`, and `VALIDATION_ITERATIONS` are now defined both in `codec_benchmark_support` and `codec_performance_alloc`; centralising them in the shared benchmark support module will make it easier to keep benchmark parameters consistent.

## Individual Comments

### Comment 1
<location path="benches/codec_performance_alloc.rs" line_range="85-94" />
<code_context>
+static ALLOCATION_COUNT: AtomicUsize = AtomicUsize::new(0);
</code_context>
<issue_to_address>
**issue (testing):** Allocation counting may be polluted by unrelated allocations and relaxed ordering, making baselines noisy or misleading.

Using a global `ALLOCATION_COUNT` with `Ordering::Relaxed` in Criterion benchmarks risks counting unrelated background allocations (e.g., stats collection, Vec growth, logging) between the `before` and `after` reads. That noise, plus the lack of ordering guarantees, means increments may be observed outside the intended measurement window.

To get cleaner codec-only counts, you could (a) narrow what’s executed inside `count_allocations`, (b) use stronger atomics (`SeqCst` or at least Acquire/Release), or (c) switch to a scoped allocator/counter rather than a single global shared across the whole process.
</issue_to_address>

### Comment 2
<location path="benches/codec_performance_alloc.rs" line_range="254-257" />
<code_context>
+    }
+}
+
+fn prepare_decode<Ops: CodecDecodeOps>(payload: Bytes) -> Result<(Bytes, Ops::Decoder), String>
+where
+    <Ops::Codec as FrameCodec>::Frame: Clone,
+{
+    let codec = Ops::create_codec();
</code_context>
<issue_to_address>
**suggestion:** The extra `Frame: Clone` bound in `prepare_decode` appears unnecessary.

In `prepare_decode`, the `where <Ops::Codec as FrameCodec>::Frame: Clone` bound isn’t used—the code only encodes into `BytesMut` and never clones the frame. If nothing else in the call chain depends on this, consider removing the bound to relax the generic constraints and support non-`Clone` frame types.

```suggestion
fn prepare_decode<Ops: CodecDecodeOps>(payload: Bytes) -> Result<(Bytes, Ops::Decoder), String> {
```
</issue_to_address>

### Comment 3
<location path="benches/codec_performance.rs" line_range="94" />
<code_context>
+            Ok(result) => result,
+            Err(err) => panic!("fragmentation baseline setup failed: {err}"),
+        };
+        let ratio = baseline.nanos_ratio().unwrap_or(0.0);
+        black_box(baseline.unfragmented.nanos_per_op());
+        black_box(baseline.fragmented.nanos_per_op());
+
+        group.bench_function(
+            BenchmarkId::new("unfragmented", payload_class.label()),
+            |b| {
+                b.iter_custom(|iters| {
+                    let measurement = measure_unfragmented_wrap(payload_class, iters);
+                    black_box(measurement.payload_bytes);
+                    measurement.elapsed
+                });
+            },
+        );
+
+        group.bench_function(
+            BenchmarkId::new(
+                "fragmented",
+                format!("{}_ratio_{ratio:.3}", payload_class.label()),
+            ),
+            |b| {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Silently treating a missing fragmentation ratio as 0.0 may hide unexpected baseline issues.

In `benchmark_fragmentation_overhead`, `baseline.nanos_ratio().unwrap_or(0.0)` folds all failure/degenerate cases into `0.0`, which then goes into the benchmark ID. If `nanos_ratio()` returns `None` (e.g., empty measurements, division guard), the benchmark still runs but the label falsely shows `0.0` overhead.

Instead, consider either reflecting `None` in the label (e.g., `ratio_na`) or treating it as an error and panicking with context, matching the other setup code. This will make unexpected fragmentation conditions visible rather than silently masked.

```suggestion
        let ratio = baseline.nanos_ratio().unwrap_or_else(|| {
            panic!(
                "fragmentation baseline has no valid nanos_ratio for payload_class={}",
                payload_class.label()
            )
        });
```
</issue_to_address>

### Comment 4
<location path="benches/codec_performance_alloc.rs" line_range="149" />
<code_context>
+    Bytes::from(payload)
+}
+
+trait CodecDecodeOps {
+    type Codec: FrameCodec<Decoder = Self::Decoder>;
+    type Decoder: Decoder;
</code_context>
<issue_to_address>
**issue (complexity):** Consider unifying the codec-specific encode/decode logic behind a single trait, centralizing shared configuration, and simplifying error handling to reduce duplication and make the benchmarks easier to follow.

You can reduce complexity and duplication without changing behavior by unifying the encode/decode abstractions and centralizing config.

### 1. Unify encode/decode paths via a single `CodecOps` trait

Right now `CodecDecodeOps` is only used for decode, while encode does a manual `match` over `CodecUnderTest`. You can extend the ops trait so both encode and decode use the same abstraction, and then use it in both `measure_encode` and `measure_decode`.

```rust
trait CodecOps {
    type Codec: FrameCodec<Decoder = Self::Decoder>;
    type Decoder: Decoder;

    fn codec_name() -> &'static str;
    fn create_codec() -> Self::Codec;
    fn extract_payload(frame: &<Self::Decoder as Decoder>::Item) -> &[u8];
}

struct LengthDelimitedOps;
struct HotlineOps;

impl CodecOps for LengthDelimitedOps {
    type Codec = LengthDelimitedFrameCodec;
    type Decoder = LengthDelimitedFrameDecoder;

    fn codec_name() -> &'static str { "length-delimited" }

    fn create_codec() -> Self::Codec {
        LengthDelimitedFrameCodec::new(MAX_FRAME_BYTES)
    }

    fn extract_payload(frame: &<Self::Decoder as Decoder>::Item) -> &[u8] {
        LengthDelimitedFrameCodec::frame_payload(frame)
    }
}

// same pattern for HotlineOps
```

Then add a small helper to map `CodecUnderTest` to the ops type:

```rust
fn with_codec_ops<T>(
    codec: CodecUnderTest,
    f: impl FnOnce::<LengthDelimitedOps>() -> T,
    g: impl FnOnce::<HotlineOps>() -> T,
) -> T {
    match codec {
        CodecUnderTest::LengthDelimited => f(),
        CodecUnderTest::Hotline => g(),
    }
}
```

Or more directly, use a plain `match` but only to select the type:

```rust
fn measure_encode(workload: BenchmarkWorkload, iterations: u64) -> Result<Measurement, String> {
    match workload.codec {
        CodecUnderTest::LengthDelimited => measure_encode_with::<LengthDelimitedOps>(workload, iterations),
        CodecUnderTest::Hotline => measure_encode_with::<HotlineOps>(workload, iterations),
    }
}

fn measure_encode_with<Ops: CodecOps>(
    workload: BenchmarkWorkload,
    iterations: u64,
) -> Result<Measurement, String>
where
    <Ops::Codec as FrameCodec>::Frame: Clone,
{
    let payload = payload_for_class(workload.payload_class);
    let payload_len = payload.len() as u64;

    let codec = Ops::create_codec();
    let mut encoder = codec.encoder();
    let mut wire = BytesMut::new();
    let started = Instant::now();
    for _ in 0..iterations {
        wire.clear();
        encoder
            .encode(codec.wrap_payload(payload.clone()), &mut wire)
            .map_err(|err| format!("{} encode failed: {err}", Ops::codec_name()))?;
    }
    Ok(Measurement {
        payload_bytes: iterations.saturating_mul(payload_len),
        elapsed: started.elapsed(),
    })
}
```

You can likewise make `measure_decode` delegate to a generic `measure_decode_with::<Ops>` using the same `CodecOps` trait, so the encode and decode paths share the same mental model.

### 2. Centralize the max frame size

The expression `LARGE_PAYLOAD_BYTES + 4096` appears in multiple places. Pull it into a single constant and use that everywhere:

```rust
const SMALL_PAYLOAD_BYTES: usize = 32;
const LARGE_PAYLOAD_BYTES: usize = 64 * 1024;
const MAX_FRAME_BYTES: usize = LARGE_PAYLOAD_BYTES + 4096;
```

Then replace each:

```rust
LengthDelimitedFrameCodec::new(LARGE_PAYLOAD_BYTES + 4096);
HotlineFrameCodec::new(LARGE_PAYLOAD_BYTES + 4096);
```

with:

```rust
LengthDelimitedFrameCodec::new(MAX_FRAME_BYTES);
HotlineFrameCodec::new(MAX_FRAME_BYTES);
```

This keeps the benchmark configuration in one place and avoids drift between encode/decode or between codecs.

### 3. Simplify error handling in benchmark-only measurement functions

Since this is benchmark code and failures already panic, you can inline the panics into the measurement helpers, which removes `Result` plumbing and closure wrapping in `count_allocations`.

For example:

```rust
fn measure_encode(workload: BenchmarkWorkload, iterations: u64) -> Measurement {
    // same body, but use `expect` / `unwrap_or_else`:
    encoder
        .encode(codec.wrap_payload(payload.clone()), &mut wire)
        .unwrap_or_else(|err| panic!("{} encode failed: {err}", Ops::codec_name()));
    // ...
}
```

Then `count_allocations` can take a simpler closure:

```rust
fn count_allocations<F>(operation: F) -> usize
where
    F: FnOnce(),
{
    let before = ALLOCATION_COUNT.load(Ordering::Relaxed);
    operation();
    let after = ALLOCATION_COUNT.load(Ordering::Relaxed);
    after.saturating_sub(before)
}
```

And the call sites become:

```rust
let wrap_allocations = count_allocations(|| {
    for _ in 0..VALIDATION_ITERATIONS {
        let _ = measure_encode(workload, 1);
    }
});

let decode_allocations = count_allocations(|| {
    for _ in 0..VALIDATION_ITERATIONS {
        let _ = measure_decode(workload, 1);
    }
});
```

And inside `bench_function`:

```rust
b.iter_custom(|iters| {
    let encode = measure_encode(workload, iters);
    let decode = measure_decode(workload, iters);
    black_box(encode.payload_bytes.saturating_add(decode.payload_bytes));
    encode.elapsed + decode.elapsed
});
```

This keeps all functionality (still panics on failure) but trims branching and reduces the visual noise around the benchmark core.
</issue_to_address>

### Comment 5
<location path="docs/execplans/9-6-1-codec-performance-benchmarks.md" line_range="3" />
<code_context>
+# 9.6.1 Codec performance benchmarks for throughput, latency, and allocations
+
+This ExecPlan is a living document. The sections `Constraints`, `Tolerances`,
+`Risks`, `Progress`, `Surprises & Discoveries`, `Decision Log`, and
+`Outcomes & Retrospective` must be kept up to date as work proceeds.
</code_context>
<issue_to_address>
**suggestion (review_instructions):** ExecPlan is introduced as an acronym without defining it on first use, which violates the requirement to define uncommon acronyms.

Consider expanding this to something like "This execution plan (ExecPlan) is a living document" so the acronym is defined on first use.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread benches/codec_performance_alloc.rs
Comment thread benches/codec_performance_alloc.rs Outdated
Comment thread benches/codec_performance.rs Outdated
Comment thread benches/codec_performance_alloc.rs Outdated
Comment thread docs/execplans/9-6-1-codec-performance-benchmarks.md Outdated
@coderabbitai coderabbitai Bot added the codex label Feb 24, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4dc5b22f80

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/common/codec_benchmark_support.rs Outdated
Comment thread benches/codec_performance_alloc.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@benches/codec_performance_alloc.rs`:
- Around line 26-75: The enums and structs CodecUnderTest, PayloadClass,
BenchmarkWorkload and the helper functions benchmark_workloads() and
payload_for_class() are duplicated; extract these domain types and helpers into
a shared module (e.g., a new mod or crate feature) and replace the local
definitions in benches/codec_performance_alloc.rs and
tests/common/codec_benchmark_support.rs with imports from that module, keeping
Measurement local if it must differ; update usages of CodecUnderTest::label,
PayloadClass::label/len, BenchmarkWorkload::label and any calls to
benchmark_workloads() and payload_for_class() to reference the shared items and
remove the duplicated definitions from this file.
- Around line 291-301: Update the count_allocations function's documentation to
state that it measures a global allocation delta via ALLOCATION_COUNT (loaded
with Ordering::Relaxed) and therefore includes allocations from Criterion,
closures, iterators, and other test harness activity, not only the codec
operation passed as operation: FnOnce() -> Result<(), String>; mark the counts
as noisy/approximate and intended as a relative baseline rather than precise
codec-only allocation numbers so future maintainers won't misinterpret the
results.
- Around line 188-226: measure_encode duplicates the encode loop for
LengthDelimited and Hotline; introduce a CodecEncodeOps trait (or extend
CodecDecodeOps) exposing encoder(), wrap_payload(), and a constructor-compatible
new(size) so you can factor the loop into a generic run_encode_iterations
function; implement CodecEncodeOps for LengthDelimitedFrameCodec and
HotlineFrameCodec, update measure_encode to call
run_encode_iterations(codec_new, payload, iterations) and ensure
run_encode_iterations maps codec-specific errors into the existing
"length-delimited encode failed" / "hotline encode failed" messages or accepts
an error-label parameter to preserve diagnostic text.

In `@benches/codec_performance.rs`:
- Around line 37-43: Remove the stray benchmarking call: delete the
black_box(allocation_label(...)) invocation (and remove the now-unused
AllocationBaseline import) from the encode benchmark; if you actually need the
workload, avoid panicking indexing by replacing benchmark_workloads()[0] with a
safe accessor such as benchmark_workloads().first() and handle the Option,
otherwise just remove the entire allocation_label call and any related unused
symbols (allocation_label, AllocationBaseline).

In `@docs/adr-004-pluggable-protocol-codecs.md`:
- Around line 328-332: Rewrite the lead-in sentence so it does not use the
preposition-plus-colon construction "across:" before the list; for example
change the first bullet to read something like "`benches/codec_performance.rs`
measures encode and decode throughput/latency for `LengthDelimitedFrameCodec`
and `HotlineFrameCodec`, and for small (32-byte) and large (64 KiB) payload
classes." Keep the second bullet about fragmentation overhead unchanged except
adjust wording to match en-GB spelling/grammar if needed; update text that
references `benches/codec_performance.rs` and
`LengthDelimitedFrameCodec`/`HotlineFrameCodec` to ensure the new lead-in flows
naturally into the list.

In `@docs/execplans/9-6-1-codec-performance-benchmarks.md`:
- Around line 117-171: Change the three top-level headings to sentence case and
expand ampersands: replace "Surprises & Discoveries" with "Surprises and
discoveries", replace "Decision Log" with "Decision log", and replace "Outcomes
& Retrospective" with "Outcomes and retrospective" so headings follow the
repository's "sentence case" and no-ampersand style; update those exact heading
strings in the markdown file.

In `@tests/codec_performance_benchmark_helpers.rs`:
- Line 3: Add a short comment above the crate-level attribute explaining why the
tests must not run under loom (for example: they use real-thread timing,
non-deterministic behavior, external resources, or unsafe code incompatible with
loom). Specifically, update the top of the file to place a one-line explanatory
comment immediately above the existing attribute `#![cfg(not(loom))]` that
references the reason (e.g., "tests rely on real threads/timing and are not
deterministic under loom") so future readers understand why `#![cfg(not(loom))]`
is required.
- Around line 84-92: The test uses a verbose match + panic to handle the Result
from measure_encode; replace that pattern with a single .expect(...) call to
make failures idiomatic and clearer — e.g., in
encode_measurement_reports_requested_iterations (and the other test functions
that call measure_encode with VALIDATION_ITERATIONS), call
measure_encode(workload, VALIDATION_ITERATIONS).expect("encode measurement
failed") and then assert on measurement; apply the same replacement for the
other two occurrences that currently use match/panic.

In `@tests/common/codec_benchmark_support.rs`:
- Around line 223-225: The Measurement construction uses unchecked
multiplication for payload_bytes (payload_bytes: iterations * payload_len) which
can overflow; update these instances to use saturating_mul on iterations (e.g.,
iterations.saturating_mul(payload_len)) for defensive consistency with the alloc
benchmark; change all similar occurrences (including the other block around
lines 240-242) so Measurement { operations: iterations, payload_bytes:
iterations.saturating_mul(payload_len), ... } uses saturating_mul and keep the
rest of the fields unchanged.
- Around line 198-247: The two almost-identical match arms in measure_encode
(handling CodecUnderTest::LengthDelimited vs CodecUnderTest::Hotline) should be
deduplicated by extracting the common logic into a small macro or a trait helper
(mirror the existing codec_measure_decode_fn! pattern): create a macro (e.g.,
codec_measure_encode_fn!) or a trait method that takes the codec constructor and
error label, instantiates LengthDelimitedFrameCodec or HotlineFrameCodec, calls
.encoder(), loop-encodes codec.wrap_payload(payload.clone()) into a BytesMut,
and returns the Measurement; then replace both arms in measure_encode with calls
to that macro/helper using LengthDelimitedFrameCodec::new(...) and
HotlineFrameCodec::new(...) and appropriate error messages to eliminate
duplication while keeping measure_encode simple.

In `@tests/fixtures/codec_performance_benchmarks.rs`:
- Around line 117-135: The two discarded calls to nanos_per_op() inside
assert_fragmentation_overhead_available are unclear; either assert on their
returned values (e.g., ensure they are finite/greater than zero) or, if the
intent is only to ensure the methods don't panic, add a concise inline comment
above the calls stating "calling nanos_per_op() to verify it does not panic" so
future readers understand why the results are ignored; update the code in
assert_fragmentation_overhead_available around
overhead.unfragmented.nanos_per_op() and overhead.fragmented.nanos_per_op()
accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2933264 and 4dc5b22.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • Cargo.toml
  • Makefile
  • benches/codec_performance.rs
  • benches/codec_performance_alloc.rs
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/execplans/9-6-1-codec-performance-benchmarks.md
  • docs/roadmap.md
  • tests/codec_performance_benchmark_helpers.rs
  • tests/common/codec_benchmark_support.rs
  • tests/features/codec_performance_benchmarks.feature
  • tests/fixtures/codec_performance_benchmarks.rs
  • tests/fixtures/interleaved_push_queues.rs
  • tests/fixtures/mod.rs
  • tests/interleaved_push_queues.rs
  • tests/scenarios/codec_performance_benchmarks_scenarios.rs
  • tests/scenarios/mod.rs
  • tests/steps/codec_performance_benchmarks_steps.rs
  • tests/steps/mod.rs

Comment thread benches/codec_performance_alloc.rs Outdated
Comment thread benches/codec_performance_alloc.rs Outdated
Comment thread benches/codec_performance_alloc.rs Outdated
Comment thread benches/codec_performance.rs Outdated
Comment thread docs/adr-004-pluggable-protocol-codecs.md Outdated
Comment thread tests/codec_performance_benchmark_helpers.rs
Comment thread tests/codec_performance_benchmark_helpers.rs
Comment thread tests/common/codec_benchmark_support.rs
Comment thread tests/common/codec_benchmark_support.rs Outdated
Comment thread tests/fixtures/codec_performance_benchmarks.rs
…e benchmarks

- Move common codec benchmark support types and functions to a shared module
- Refactor encode/decode measurement functions using macros to reduce duplication
- Introduce PreparedDecodeInput enum to unify decode input preparations
- Improve allocation counting with scoped enable/disable and memory fences
- Add helper function to keep shared symbols reachable for bench linking
- Clean up benchmark code for better error handling and clarity
- Minor docs updates and code style improvements related to benchmarks

This refactor improves code maintainability, clarity, and benchmarking correctness without changing benchmark behavior.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/execplans/10-3-2-exercise-interleaved-queues.md`:
- Around line 392-411: Replace the indented command sequence (the repeated "set
-o pipefail" + "make fmt/check-fmt/lint/test-bdd/test/markdownlint/nixie ..."
lines) with a fenced code block using the shell language identifier (start with
```shell and end with ```), so the Stage D command sequence is a proper fenced
shell block that linters can validate; ensure you include all the original lines
inside the fenced block exactly as they appear.

In `@docs/execplans/9-6-1-codec-performance-benchmarks.md`:
- Around line 366-374: Replace the indented command list with a fenced code
block that includes a language identifier (use "shell") so linters can validate
it; locate the block containing the sequence of make commands (e.g., the lines
starting with "set -o pipefail; make fmt ..." through "set -o pipefail; make
bench-codec ...") and wrap those lines in a triple-backtick fenced block with
"shell" on the opening fence and a closing triple-backtick, preserving each
command exactly as-is.

In `@tests/fixtures/codec_performance_benchmarks.rs`:
- Around line 30-36: The public fixture function
codec_performance_benchmarks_world is missing a Rustdoc and the doc comment must
be placed above the attribute; add a short /// Rustdoc describing the fixture
(what it returns and that it sets iterations to VALIDATION_ITERATIONS) and move
that doc comment so it precedes the #[fixture] attribute on the
codec_performance_benchmarks_world function; leave the function body intact
(returning CodecPerformanceBenchmarksWorld with iterations:
VALIDATION_ITERATIONS and ..CodecPerformanceBenchmarksWorld::default()).
- Around line 22-28: Add a Rustdoc comment for the public struct
CodecPerformanceBenchmarksWorld that briefly describes its purpose (holds
benchmark configuration and state for codec performance tests) and documents key
fields like iterations, matrix_samples, fragmentation_overhead, and
allocation_labels so cargo doc will produce complete API docs; place the
triple-slash comments (///) immediately above the
CodecPerformanceBenchmarksWorld declaration and include short descriptions for
the struct and each field.
- Around line 38-153: Add Rustdoc comments (///) to every public method in impl
CodecPerformanceBenchmarksWorld: configure_iterations, run_codec_matrix_samples,
run_fragmentation_sample, build_allocation_labels,
assert_matrix_samples_cover_workloads, assert_fragmentation_overhead_available,
and assert_allocation_labels_include_counters; for each method include a
one-line summary of behavior, `# Arguments` (or `# Parameters`) describing
inputs like `iterations`, any preconditions (e.g., iterations must be > 0 or
must be configured first), and `# Returns` describing the TestResult success
case and possible Err conditions so docs generated by cargo doc clearly show
usage and failure modes.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4dc5b22 and cbc8de9.

📒 Files selected for processing (9)
  • benches/codec_performance.rs
  • benches/codec_performance_alloc.rs
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/execplans/10-3-2-exercise-interleaved-queues.md
  • docs/execplans/9-6-1-codec-performance-benchmarks.md
  • src/app_data_store.rs
  • tests/codec_performance_benchmark_helpers.rs
  • tests/common/codec_benchmark_support.rs
  • tests/fixtures/codec_performance_benchmarks.rs

Comment thread docs/execplans/10-3-2-exercise-interleaved-queues.md
Comment thread docs/execplans/9-6-1-codec-performance-benchmarks.md Outdated
Comment thread tests/fixtures/codec_performance_benchmarks.rs
Comment thread tests/fixtures/codec_performance_benchmarks.rs
Comment thread tests/fixtures/codec_performance_benchmarks.rs
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Feb 24, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

benches/codec_performance_alloc.rs

Comment on file

fn prepare_length_delimited_decode_input(
    payload: Bytes,
) -> Result<(Bytes, LengthDelimitedFrameDecoder), String> {
    let codec = LengthDelimitedFrameCodec::new(LARGE_PAYLOAD_BYTES + 4096);
    let mut seed_encoder = codec.encoder();
    let mut encoded = BytesMut::new();
    seed_encoder
        .encode(codec.wrap_payload(payload), &mut encoded)
        .map_err(|err| format!("length-delimited seed encode failed: {err}"))?;
    Ok((encoded.freeze(), codec.decoder()))
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: prepare_hotline_decode_input,prepare_length_delimited_decode_input

@coderabbitai

This comment was marked as resolved.

… tests

- Introduce `CodecPerformanceBenchmarksWorld` struct to hold benchmark state.
- Provide configuration, sampling, and assertion methods for encoding/decoding benchmarks.
- Replace repetitive codec input preparation functions with a macro to reduce duplication.
- Add Markdown lint disables around shell script blocks in execplan docs to fix lint warnings.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Feb 24, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@benches/codec_performance_alloc.rs`:
- Around line 253-267: The encode allocation measurement is asymmetric:
count_allocations currently calls measure_encode in VALIDATION_ITERATIONS
separate invocations (measure_encode, VALIDATION_ITERATIONS) while decode runs
many iterations inside a single closure via run_prepared_decode_iterations after
prepare_decode_input; to fix, make the encode path mirror decode by either (A)
preparing an encode input and adding a run_prepared_encode_iterations(&mut
prepared_encode_input, VALIDATION_ITERATIONS) invoked once inside
count_allocations, or (B) move the for _ in 0..VALIDATION_ITERATIONS loop into
the closure passed to count_allocations so count_allocations measures the whole
batch (use measure_encode inside that single closure), and keep references to
measure_encode, prepare_decode_input, run_prepared_decode_iterations,
count_allocations, and VALIDATION_ITERATIONS when implementing the change.
- Around line 226-246: Remove the dead helper function
keep_shared_support_symbols_reachable and its invocation and instead add a
scoped lint allow with a FIXME reason on the module declaration (e.g., replace
the top-level mod declaration with something like #[allow(dead_code)] // FIXME:
reason) so the unused symbols are explicitly documented; delete the function and
its call, and remove the unused imports FRAGMENT_PAYLOAD_CAP_BYTES,
FragmentationOverhead, Measurement, PayloadClass, measure_unfragmented_wrap,
measure_fragmented_wrap, and measure_fragmentation_overhead from the use block
so no dead-code suppression is hidden inside a dummy function.

In `@docs/execplans/9-6-1-codec-performance-benchmarks.md`:
- Around line 164-168: Update the sentence in the Decision block that currently
reads "macros in order to satisfy strict `missing_docs` linting..." by replacing
"in order to" with "to" so it reads "macros to satisfy strict `missing_docs`
linting..."; locate this text in the Decision paragraph (the line starting
"Decision: implement criterion entrypoints...") and apply the simple phrasing
change.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbc8de9 and f6c8738.

📒 Files selected for processing (4)
  • benches/codec_performance_alloc.rs
  • docs/execplans/10-3-2-exercise-interleaved-queues.md
  • docs/execplans/9-6-1-codec-performance-benchmarks.md
  • tests/fixtures/codec_performance_benchmarks.rs

Comment thread benches/codec_performance_alloc.rs Outdated
Comment thread benches/codec_performance_alloc.rs
Comment thread docs/execplans/9-6-1-codec-performance-benchmarks.md Outdated
…lify allocation benchmark

Removed the keep_shared_support_symbols_reachable function and its usage, which kept various unused measurement helpers alive unnecessarily. Cleaned up imports by removing unused items related to fragmentation measurement. Simplified the allocation benchmark by removing redundant loops and calls, improving code clarity and reducing dead code.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@benches/codec_performance_alloc.rs`:
- Around line 22-28: Update the FIXME reason on the #[expect(dead_code, reason =
"...")] attribute that precedes the mod codec_benchmark_support declaration to
include a tracking issue URL (e.g., "FIXME: https://github.com/.../issues/NN -
split shared benchmark support to avoid importing unused helpers in this
bench"); if no issue exists, create one to track splitting the shared benchmark
support module and then reference its URL in the FIXME reason string.

In `@docs/execplans/9-6-1-codec-performance-benchmarks.md`:
- Line 209: Replace the phrase "touch points" with the single word "touchpoints"
in the sentence "Primary implementation files and expected touch points:" within
the docs/execplans/9-6-1-codec-performance-benchmarks.md file so it reads
"Primary implementation files and expected touchpoints:"; search for the exact
string "expected touch points" to locate and update the occurrence.
- Around line 366-376: Remove the leading four-space indentation from each shell
command inside the fenced code block so the commands (e.g., the lines starting
with "set -o pipefail; make fmt", "set -o pipefail; make check-fmt", ..., "set
-o pipefail; make bench-codec") begin at column 1; update the fenced block in
the document so each command line has no extra leading whitespace.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6c8738 and a5ad3c2.

📒 Files selected for processing (2)
  • benches/codec_performance_alloc.rs
  • docs/execplans/9-6-1-codec-performance-benchmarks.md

Comment thread benches/codec_performance_alloc.rs
Comment thread docs/execplans/9-6-1-codec-performance-benchmarks.md Outdated
Comment thread docs/execplans/9-6-1-codec-performance-benchmarks.md
- Removed extra indentation in shell commands block
- Corrected 'touch points' to 'touchpoints'
- Updated FIXME comment with issue URL in benchmarks code

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos changed the title Add 9.6.1 Codec Performance Benchmarks, Tests, and Docs Complete 9.6.1 Codec Performance Benchmarks, Tests, and Docs Feb 24, 2026
@leynos leynos merged commit 7345fbf into main Feb 25, 2026
6 checks passed
@leynos leynos deleted the benchmark-codec-performance-qgw4sp branch February 25, 2026 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant