Skip to content

feat(bench): OTLP/JSON corpus loader — RFC 0003 §6.5 MVP path (PR-K2) - #58

Merged
jensholdgaard merged 1 commit into
mainfrom
feat/ourios-bench-otlp-loader
May 28, 2026
Merged

feat(bench): OTLP/JSON corpus loader — RFC 0003 §6.5 MVP path (PR-K2)#58
jensholdgaard merged 1 commit into
mainfrom
feat/ourios-bench-otlp-loader

Conversation

@jensholdgaard

@jensholdgaard jensholdgaard commented May 28, 2026

Copy link
Copy Markdown
Owner

Summary

Extends ourios-bench's corpus loader to consume OTLP/JSON Lines files (*.jsonl / *.json — the OTel File Exporter format, one LogsData per line) alongside the existing plain-text *.txt path.

This is the route RFC 0003 §6.5 itself names as the MVP bench source for OTLP data:

"the MVP bench reads OTLP from the on-disk corpus, bypassing this component entirely" (until ourios-wal lands; a live receiver without WAL-before-ack would violate CLAUDE.md §3.4).

So no architectural new ground — this fills a slot RFC 0003 explicitly carved out.

Mapping (RFC 0003 §6.6 in-memory shape, 1:1)

The walker dispatches on extension; both formats may coexist in the same corpus directory. Each wire LogRecord maps to one OtlpLogRecord:

OTLP wire OtlpLogRecord field Note
severityNumber (i32) severity_number (u8) Clamped to OTLP's 0..=24 (FATAL4) — "narrowed from proto's unbounded i32 at the receiver boundary"
severityText / scope.name / scope.version / eventName matching Option<String> Empty-string → None collapse
attributes, droppedAttributesCount attributes, dropped_attributes_count Preserved verbatim
resource.attributes resource_attributes Copied per record (matches existing OtlpLogRecord doc-comment contract)
traceId / spanId (Vec<u8>) Option<[u8;16]> / Option<[u8;8]> Length-validated, None on the wrong length (RFC0003.11 transport-error surface, not a panic)
body.stringValue Some(Body::String(_)) Unwrapped for the §6.2 step-0 fast path
body.{kvlistValue,arrayValue,intValue,…} Some(Body::Structured(AnyValue)) Verbatim per RFC 0003 §6.4 (canonicalisation deferred to storage)
timeUnixNano time_unix_nano Wire timestamps honoured — file-static = run-reproducible. The §3.3 deterministic baseline still drives the text path

Parsing uses serde_json::from_str::<LogsData> against opentelemetry-proto's types. The with-serde feature (added on the workspace's existing dep) gives the OTLP/JSON spec mapping for free — camelCase keys, string-encoded u64s, base64 bytes. Pattern follows rotel's OTLP HTTP receiver. Keeps the spec single-sourced in opentelemetry-proto rather than a hand-rolled struct that could drift.

RFC 0006 §3.1 amendments

Three places, each using the **Amendment** block pattern RFC 0003 §6.4 uses (preserves the historical record): the "deferred" notes become "landed in PR-K2 (2026-05-28)." The protobuf (*.binpb) LogsData decode remains out of scope.

Test plan

  • cargo fmt --all --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-features284 passed / 19 ignored (was 278/19; +6 OTLP tests)
  • mdbook build

The +6 tests cover, colocated in corpus.rs:

  • Round-trip every envelope field on the committed fixture (3 LogsData lines, 4 records, mixed string + structured + kvlist bodies).
  • Structured body lands as Body::Structured(AnyValue).
  • Blank lines (and a trailing newline at EOF) are skipped without erroring.
  • Malformed JSON surfaces a BenchError::Corpus whose detail carries the 1-based line number (operators paste that into sed -n '<n>p').
  • severityNumber above 24 clamps to 24.
  • Mixed .txt + .jsonl in one corpus dir both contribute records.

Follow-ups

  • Workflow-side: wire bench.yml to a representative OTLP corpus (telemetrygen → collector / fileexporter → JSONL). Out of scope here; PR adds the loader, not the corpus-generation side.
  • Protobuf (*.binpb) LogsData decode — a follow-up if the fileexporter output ever lands in protobuf form.
  • The production OTLP gRPC + HTTP receiver (RFC 0003 §6.1) remains gated on ourios-wal per §6.5; that's a separate multi-RFC arc.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Corpus loader now accepts OTLP/JSON inputs (.jsonl/.json) in addition to plain-text, merging both formats into the ingest pipeline.
  • Bug Fixes

    • Improved parsing: skips blank lines, clamps out-of-range severity, preserves record ordering, and handles malformed lines with 1-based line errors.
  • Tests

    • Added OTLP/JSON sample suite validating mapping, body typing, and mixed-directory behavior.
  • Documentation

    • RFC updated to describe multi-format loading and mapping behavior.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b21fb89-630b-467e-92e8-f4a84c676147

📥 Commits

Reviewing files that changed from the base of the PR and between c3bea46 and 5bf8356.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • crates/ourios-bench/Cargo.toml
  • crates/ourios-bench/src/corpus.rs
  • crates/ourios-bench/tests/data/otlp/sample.jsonl
  • crates/ourios-core/Cargo.toml
  • docs/rfcs/0006-bench-harness.md
✅ Files skipped from review due to trivial changes (1)
  • crates/ourios-bench/tests/data/otlp/sample.jsonl
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/ourios-bench/Cargo.toml
  • docs/rfcs/0006-bench-harness.md
  • crates/ourios-core/Cargo.toml
  • crates/ourios-bench/src/corpus.rs

📝 Walkthrough

Walkthrough

Adds multi-format ingestion to ourios-bench: the walker dispatches .txt to a streaming text loader and .jsonl/.json to an OTLP/JSONL parser using serde-backed LogsData, mapping wire records into OtlpLogRecord with severity clamping, attribute hoisting, and updated tests and RFC docs.

Changes

OTLP/JSON corpus loader for bench harness

Layer / File(s) Summary
OTLP proto dependency setup
crates/ourios-bench/Cargo.toml, crates/ourios-core/Cargo.toml
Both crates enable opentelemetry-proto (v0.32) with with-serde plus gen-tonic-messages and logs to allow Serde deserialization of OTLP LogsData and AnyValue for JSONL ingestion.
Multi-format corpus loader
crates/ourios-bench/src/corpus.rs
Directory walker now dispatches by extension. ingest_txt streams plain-text with CRLF stripping, blank-line skipping, default envelope, and deterministic timestamp advance. ingest_otlp_jsonl parses per-line LogsData and iterates resource/scope/log records to produce OtlpLogRecord. Module docs updated.
Wire-to-record mapping helpers
crates/ourios-bench/src/corpus.rs
map_log_record, any_value_to_body, and empty_to_none added: clamp severity to 0..=24, collapse empty strings to None, hoist resource attributes, validate trace/span id lengths, and map AnyValue into Body::String or Body::Structured.
OTLP/JSON loader test suite and sample data
crates/ourios-bench/tests/data/otlp/sample.jsonl, crates/ourios-bench/src/corpus.rs (tests)
Replaced sample JSONL with three OTLP lines and added tests for envelope mapping, body typing, blank-line permissiveness, malformed JSON error reporting (1-based line numbers), severity clamping, and mixed .txt + .jsonl behavior including file/byte counts.
RFC 0006 documentation updates
docs/rfcs/0006-bench-harness.md
RFC amended to record the OTLP-LogsData migration as landed, describe .jsonl/.json per-line LogsData parsing and mapping into OtlpLogRecord (severity, body, trace/attrs), and note protobuf .binpb decoding remains out of scope.

Sequence Diagram

sequenceDiagram
  participant Walker as CorpusLoad.walk
  participant FileExt as ExtensionDispatch
  participant TxtLoader as ingest_txt
  participant JsonLoader as ingest_otlp_jsonl
  participant Mapper as map_log_record
  participant Records as OtlpLogRecord output

  Walker->>FileExt: path + metadata
  alt .txt file
    FileExt->>TxtLoader: stream BufReader
    TxtLoader->>TxtLoader: strip CRLF, skip blanks, advance ts
    TxtLoader->>Records: emit enveloped OtlpLogRecord
  else .jsonl/.json file
    FileExt->>JsonLoader: read non-blank lines
    JsonLoader->>JsonLoader: serde_json::from_slice::<LogsData>()
    JsonLoader->>JsonLoader: iterate resource_logs -> scope_logs -> log_records
    JsonLoader->>Mapper: per-record wire fields
    Mapper->>Mapper: clamp severity, any_value → Body, hoist attrs, validate ids
    Mapper->>Records: emit mapped OtlpLogRecord
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • jensholdgaard/ourios#51: Makes overlapping changes to crates/ourios-bench/src/corpus.rs corpus loader and OtlpLogRecord construction within the C1 harness pipeline.
  • jensholdgaard/ourios#48: RFC changes that relate to documenting the bench loader formats and behavior.
  • jensholdgaard/ourios#49: Earlier crate scaffold and manifest work for ourios-bench that the dependency changes build on.

Poem

🐇 I hop through files, both text and JSON bright,
Lines turned to records in morning light.
Severity snugged in its tidy bed,
Attributes hoisted, timestamps sped—
A rabbit reads corpora with delight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description comprehensively covers the Summary, Related RFCs, and implementation details; however, the Checklist section is missing entirely—no checkboxes for cargo fmt, clippy, tests, docs, or RFC validation. Add the standard Checklist section with checkboxes for cargo fmt, clippy, tests/docs updates, and RFC links, even if marked as completed to document verification steps.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly references the feature (OTLP/JSON corpus loader) and clearly maps to the primary changes: multi-format ingest pipeline, RFC 0003 §6.5 MVP path, and the PR identifier.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ourios-bench-otlp-loader

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/ourios-bench/src/corpus.rs (1)

153-160: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the empty-corpus error message for multi-format input.

The message still says "*.txt lines", but this loader now consumes .txt, .jsonl, and .json. That will mislead operators when OTLP-only corpora fail validation.

Suggested patch
-                "no non-empty `*.txt` lines under {} (read {} file(s))",
+                "no non-empty records under {} from supported corpus files (`*.txt`, `*.jsonl`, `*.json`) (read {} file(s))",
                 dir.display(),
                 total_files,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ourios-bench/src/corpus.rs` around lines 153 - 160, Update the error
text produced in the lines.is_empty() branch where BenchError::Corpus is
constructed so it no longer references only "`*.txt` lines"; change the detail
string to mention the actual accepted formats (e.g. ".txt, .jsonl, .json" or
similar) and the fact there were no non-empty entries under the directory (keep
dir.display() and total_files in the message and return value). Locate the block
that builds BenchError::Corpus in corpus.rs (the lines.is_empty() check) and
replace the format string accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/rfcs/0006-bench-harness.md`:
- Around line 234-251: Update the RFC's normative definitions for the raw corpus
so they match the amendment: change the description of bytes(raw_corpus) and the
CLI option --corpus to explicitly state that corpus inputs include both
plaintext `*.txt` files and OTLP JSON/JSONL `*.json` / `*.jsonl` (OTLP/JSON
Lines format parsed as LogsData), and clarify that `*.binpb` protobuf-encoded
LogsData remains out of scope; ensure the wording and any examples/validation
rules that previously limited `bytes(raw_corpus)` and `--corpus` to `*.txt` are
broadened to accept the same file extension set as the amendment.

---

Outside diff comments:
In `@crates/ourios-bench/src/corpus.rs`:
- Around line 153-160: Update the error text produced in the lines.is_empty()
branch where BenchError::Corpus is constructed so it no longer references only
"`*.txt` lines"; change the detail string to mention the actual accepted formats
(e.g. ".txt, .jsonl, .json" or similar) and the fact there were no non-empty
entries under the directory (keep dir.display() and total_files in the message
and return value). Locate the block that builds BenchError::Corpus in corpus.rs
(the lines.is_empty() check) and replace the format string accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d285bb6-323f-4551-96f5-b9d148c02ccb

📥 Commits

Reviewing files that changed from the base of the PR and between 738e6ff and c3bea46.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • crates/ourios-bench/Cargo.toml
  • crates/ourios-bench/src/corpus.rs
  • crates/ourios-bench/tests/data/otlp/sample.jsonl
  • crates/ourios-core/Cargo.toml
  • docs/rfcs/0006-bench-harness.md

Comment thread docs/rfcs/0006-bench-harness.md
…-K2)

Extends `ourios-bench`'s corpus loader to consume OTLP/JSON
Lines files (`*.jsonl` / `*.json` — the OTel File Exporter
format, one `LogsData` per line) alongside the existing
plain-text `*.txt` path. This is the route RFC 0003 §6.5
itself names as the MVP bench source for OTLP data:

> "the MVP bench reads OTLP from the on-disk corpus, bypassing
> this component entirely" (until `ourios-wal` lands; a live
> receiver without WAL-before-ack would violate CLAUDE.md §3.4).

The walker dispatches on extension; both formats may coexist
in the same corpus directory. Wire `LogRecord`s map 1:1 onto
the RFC 0003 §6.6 `OtlpLogRecord` shape:

- severity_number clamped to OTLP's `0..=24` (FATAL4)
- severity_text / scope_name / scope_version / event_name
  empty-string → `None` collapse
- attributes, dropped_attributes_count, resource_attributes
  preserved (resource attrs copied per record, matching the
  existing `OtlpLogRecord` doc-comment contract)
- trace_id / span_id length-validated to `[u8;16]` / `[u8;8]`
  (`None` on the wrong length — RFC0003.11 transport-error
  surface)
- body: `stringValue` → `Body::String`, anything else →
  `Body::Structured(AnyValue)` verbatim per RFC 0003 §6.4
- wire timestamps honoured (file-static =
  run-reproducible); the §3.3 deterministic baseline still
  drives the text path.

Parsing uses `serde_json::from_str::<LogsData>` against
`opentelemetry-proto`'s types — the `with-serde` feature
(added on the workspace's existing dep) gives the OTLP/JSON
spec mapping for free (camelCase keys, string-encoded `u64`s
for `timeUnixNano`, base64 bytes). Pattern follows rotel's
OTLP HTTP receiver. Keeps the spec single-sourced in
`opentelemetry-proto` rather than a hand-rolled struct that
could drift.

RFC 0006 §3.1 amended in three places (the "deferred" notes
become "landed in PR-K2"); the protobuf (`*.binpb`) `LogsData`
decode remains out of scope.

Tests (+6, total 284 passing / 19 ignored): committed OTLP
fixture round-trips through every envelope field; structured
body lands as `Body::Structured`; blank lines skipped;
malformed JSON surfaces a `BenchError::Corpus` carrying the
1-based line number; severity above 24 clamps; mixed .txt +
.jsonl in one corpus dir both contribute records.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jensholdgaard
jensholdgaard force-pushed the feat/ourios-bench-otlp-loader branch from c3bea46 to 5bf8356 Compare May 28, 2026 21:45
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Actionable comments posted: 0

@jensholdgaard
jensholdgaard requested a review from Copilot May 28, 2026 21:49
@jensholdgaard
jensholdgaard merged commit 0118322 into main May 28, 2026
9 of 10 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.

Comment thread crates/ourios-bench/src/corpus.rs
Comment thread crates/ourios-bench/src/corpus.rs
Comment thread crates/ourios-bench/src/corpus.rs
Comment thread crates/ourios-core/Cargo.toml
jensholdgaard added a commit that referenced this pull request May 29, 2026
Follow-up to PR #58. Copilot and CodeRabbit between them flagged that
the OTLP/JSON loader landed with a surface that wasn't internally
consistent — and one of the issues was a real correctness bug. Bundles
the fixes here as a single squash-merge so the seven review threads
across PR #58 + PR #59 close together.

**Fixes (G — the real bug):** §3.4.1's A1 math invariant — both
`bytes(raw_corpus)` and `bytes(zstd_corpus)` must process the same
input. PR #58 broadened `bytes(raw_corpus)` to OTLP/JSON via the
loader change but left `zstd_level_19_bytes` in `a1.rs` filtering on
`*.txt` only. An OTLP-only corpus would therefore have
`bytes(raw_corpus) > 0` and `bytes(zstd_corpus) = 0`, producing
`zstd_ratio = 0` and an undefined A1 delta. The zstd reference codec
now consumes the same extension set (`.txt | .jsonl | .json`) — kept
in lockstep with `corpus::walk`'s dispatch via a comment on both sides.
RFC §3.4.1 amended to spell out the invariant.

**Tightens (consistency):**

- `corpus.rs` `CorpusLoad` field docs + the `no non-empty corpus
  lines` error message now name the actual supported extensions.
- The extension dispatch drops the per-entry `to_ascii_lowercase`
  allocation and uses `eq_ignore_ascii_case` on `&str` (allocation-
  free, hot loader path). `match`-with-guards per the project's
  feedback memory.
- `Body::from_any_value` (already the single-sourced fork in
  `ourios-core::otlp`) replaces the local duplicate `any_value_to_body`
  in the loader. Drift risk → 0.
- `main.rs` clap help text on `--corpus` broadened so
  `ourios-bench --help` matches RFC §3.7.
- `ourios-core/Cargo.toml` comment aligned to the actual
  `serde_json::from_str` call (was `from_slice`).
- RFC §3.4.1 reworded so `corpus.directory` no longer claims to
  disambiguate mixed-format corpora — mixed dirs produce an aggregate
  number; cleanly comparable runs need one encoding per dir. Per-format
  byte breakdown noted as a future enhancement.

**Test (+1):** `a1::tests::zstd_consumes_every_corpus_extension` —
mixed-extension corpus produces > 0 bytes, OTLP-only corpus does too
(guards the failure mode the pre-fix `txt`-only filter introduced).

Closes the Copilot threads on PR #58 (corpus.rs:38 / :459 / :275 +
Cargo.toml:19) and PR #59 (RFC :278 / :289 / :640).

5 files changed (§5.2 phase limit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jensholdgaard added a commit that referenced this pull request May 29, 2026
* fix(bench): tighten OTLP-loader consistency surface (PR-K2.1)

Follow-up to PR #58. Copilot and CodeRabbit between them flagged that
the OTLP/JSON loader landed with a surface that wasn't internally
consistent — and one of the issues was a real correctness bug. Bundles
the fixes here as a single squash-merge so the seven review threads
across PR #58 + PR #59 close together.

**Fixes (G — the real bug):** §3.4.1's A1 math invariant — both
`bytes(raw_corpus)` and `bytes(zstd_corpus)` must process the same
input. PR #58 broadened `bytes(raw_corpus)` to OTLP/JSON via the
loader change but left `zstd_level_19_bytes` in `a1.rs` filtering on
`*.txt` only. An OTLP-only corpus would therefore have
`bytes(raw_corpus) > 0` and `bytes(zstd_corpus) = 0`, producing
`zstd_ratio = 0` and an undefined A1 delta. The zstd reference codec
now consumes the same extension set (`.txt | .jsonl | .json`) — kept
in lockstep with `corpus::walk`'s dispatch via a comment on both sides.
RFC §3.4.1 amended to spell out the invariant.

**Tightens (consistency):**

- `corpus.rs` `CorpusLoad` field docs + the `no non-empty corpus
  lines` error message now name the actual supported extensions.
- The extension dispatch drops the per-entry `to_ascii_lowercase`
  allocation and uses `eq_ignore_ascii_case` on `&str` (allocation-
  free, hot loader path). `match`-with-guards per the project's
  feedback memory.
- `Body::from_any_value` (already the single-sourced fork in
  `ourios-core::otlp`) replaces the local duplicate `any_value_to_body`
  in the loader. Drift risk → 0.
- `main.rs` clap help text on `--corpus` broadened so
  `ourios-bench --help` matches RFC §3.7.
- `ourios-core/Cargo.toml` comment aligned to the actual
  `serde_json::from_str` call (was `from_slice`).
- RFC §3.4.1 reworded so `corpus.directory` no longer claims to
  disambiguate mixed-format corpora — mixed dirs produce an aggregate
  number; cleanly comparable runs need one encoding per dir. Per-format
  byte breakdown noted as a future enhancement.

**Test (+1):** `a1::tests::zstd_consumes_every_corpus_extension` —
mixed-extension corpus produces > 0 bytes, OTLP-only corpus does too
(guards the failure mode the pre-fix `txt`-only filter introduced).

Closes the Copilot threads on PR #58 (corpus.rs:38 / :459 / :275 +
Cargo.toml:19) and PR #59 (RFC :278 / :289 / :640).

5 files changed (§5.2 phase limit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fixup! fix(bench): tighten OTLP-loader consistency surface (PR-K2.1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants