…them as one stream
Dynamo's request-trace sink is a rotating gzip JSONL appender. With
DYN_REQUEST_TRACE_FILE_PATH=<log_dir>/dynamo-request-trace (what
observability.enabled sets) it writes dynamo-request-trace.000000.jsonl.gz,
.000001.jsonl.gz, ... and never a bare `dynamo-request-trace` file. The
ingest looked only for the bare name, so on every observability run the
axis was silently empty: hecate 565811 (2026-09-09) left 12 shards of
~128 MB / ~11k requests each on disk and the sweep log said
[L2 req-trace] WARN no request trace matched 'dynamo-request-trace' under ...
[done] bundle ready ...: request_trace=False
- ingest.run_request_trace: default discovery is the bare file plus
`dynamo-request-trace.*.jsonl*` (gz or not); every match is handed to
the processor. --request-trace-input still overrides.
- request_trace.process accepts a path or a list of shard paths, opens
.gz transparently (errors="replace" so a torn last line in the shard
being written at kill time is skipped, not fatal), and merges shards
before the existing received_ms sort. The CLI takes IN_PATH... OUT_PATH.
- docs/component-dashboard.md names the real input files.
- tests/test_request_trace.py: shard merge order, plain file, torn line,
CLI, discovery (rotated / bare / uncompressed / unrelated-prefix files),
explicit glob precedence, skip path, disabled axis.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yuewei Na <nv-yna@users.noreply.github.com>
What
The request-trace axis of the ingest never found its input on real runs. Dynamo's request-trace sink is a rotating gzip JSONL appender: with
DYN_REQUEST_TRACE_FILE_PATH=<log_dir>/dynamo-request-trace(whatobservability.enabledsets viaANALYTICS_REQUEST_TRACE_ENV) it writesdynamo-request-trace.000000.jsonl.gz,.000001.jsonl.gz, ... and never a baredynamo-request-tracefile.run_request_tracelooked only for the bare name.ingest.run_request_trace: default discovery is now the bare file plusdynamo-request-trace.*.jsonl*(gz or not,REQUEST_TRACE_DEFAULT_PATTERNS); every match is handed to the processor.--request-trace-inputstill overrides.request_trace.processaccepts a path or a list of shard paths, opens.gztransparently (errors="replace", so a torn last line in the shard being written when the frontend was killed is skipped, not fatal) and merges the shards before the existingreceived_mssort. The CLI takesIN_PATH... OUT_PATH.docs/component-dashboard.mdnames the real input files.Evidence
hecate 565811 (2026-09-09, AgentX baseline recipe,
observability.enabled: true, 8 VR200 nodes). The log dir holds 12 shards and no bare file:(
zcat dynamo-request-trace.000000.jsonl.gz | wc -l→ 10,983 records; first record is adynamo.request.trace.v1/request_endevent withsession_id,x_request_id,kv_transfer_estimated_latency_ms, ...)and the sweep log:
The same
request_trace=Falseappears on 565810 and 565854 (default visibility, no trace written — correct there). Only the observability run lost data.Validation
pytest tests/test_request_trace.py→ 9 passed: shard merge order across shards, plain file, torn last line, CLI with several inputs, discovery (rotated / bare / uncompressed shard / unrelateddynamo-request-trace.lockignored), explicit glob precedence over discovery, skip path logs the WARN,--request-trace none.src/ingest/ingest.pyandsrc/ingest/request_trace.pywere alreadyruff format-dirty onmain(checked withgit show HEAD:<file> | ruff format --check --stdin-filename), so this diff does not reformat them; the new test file is clean.Why draft
Scaling caveat worth a reviewer's opinion:
process()keeps every request'sinput_sequence_hashesin memory until the end to computeprefix_reuse_ratio(by design, see the module docstring). Now that the shards are actually read, an hour of AgentX at 8 nodes is ~140k requests × ~1.2k hashes — several GB on the postprocess node. This PR does not change that; if it is a problem in practice the hashes could be reduced per session incrementally instead of held globally.🤖 Generated with Claude Code