Commit c07f350
* docs: sync resume anchors and PMU claims to v0.2.0 (Codex #127/#128 follow-up)
Resolve the Codex review findings left on `main` by PRs #127 and #128:
- PROGRESS.md: remove the stale "Next action remains" block that still steered
/resume to the merged PR #125 on `perf/linux-host-artifact-refresh`; replace
with the v0.2.0 between-releases state (no active milestone; #94/#90 gated).
- AGENTS.md: bring it into sync with CLAUDE.md's v0.2.0 partial-PMU reframe. The
constraints bullet, the "correct claim" block, and the "M29 perf evidence
status" subsection no longer label the artifacts "constrained Docker
validation"; the stale `perf/linux-host-artifact-refresh` follow-up line is
updated (also in CLAUDE.md) to the released state.
- docs/perf_analysis.md: narrow the PMU claim so it no longer implies the Apple
Blizzard (E-core) PMU carries live counts. The `apple_blizzard_pmu/...` rows
read `<not counted>` in results/perf_stat_linux.txt because the single-threaded
benchmark stays on the Avalanche P-cores — expected scheduling, not a counter.
Docs/memory only; no code or artifacts changed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf: add flamegraph generator and make target (#32)
Add `make flamegraph`, the missing-flamegraph follow-up tracked by issue #32.
The perf stat/record text workflow already existed; this renders a perf
call-graph flamegraph.
- scripts/flamegraph.sh: records `perf record --call-graph dwarf -F 4000 -g -e
cpu-clock` on qsl-bench and writes results/flamegraph.svg plus a
results/flamegraph.txt provenance/classification companion (top folded
stacks). Mirrors perf_record.sh: Linux-only, reuses qsl_common.sh provenance
+ qsl_publish_artifact, and honours QSL_PERF_ALLOW_PARTIAL for constrained
hosts. DWARF call graphs unwind correctly despite the Release `bench` preset
omitting frame pointers.
- scripts/flamegraph.py: dependency-free (stdlib-only) stackcollapse + SVG
renderer, so the artifact is reproducible from the repo without vendoring the
Perl FlameGraph toolkit. Deterministic: frames sorted by name, colors a pure
function of the name, no RNG/timestamps in the drawn body.
- tests/shell/test_flamegraph.sh: CTest-registered (python3-only, skips cleanly
if absent) — folding (offset/dso stripping, perf-order reversal, comm-at-base,
count aggregation, sortedness), SVG well-formedness, XML escaping,
determinism, empty-input handling.
- docs (perf_analysis.md, results/README.md), command lists (CLAUDE.md,
AGENTS.md), MILESTONES.md backlog, PROGRESS.md log.
`make check` 242/242. Full hardware cache-PMU evidence stays in #90.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf: add generated flamegraph artifact on bare-metal Fedora Asahi (#32)
results/flamegraph.svg + results/flamegraph.txt generated by `make flamegraph`
from the clean committed tree on the bare-metal Apple M2 (aarch64) Fedora Asahi
host: 397 cpu-clock samples, 171 folded stacks, `Dirty inputs: no`. The hot
paths resolve to real engine symbols (OrderBook::modify/cancel/add_limit, the
dispatch_storage cancel path, decode_new_order, the gateway Session path,
replay::generate_flow). Software cpu-clock sampling hot-symbol profile — not a
latency/throughput claim; full hardware cache-PMU evidence stays in #90.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add FIX-like text protocol adapter (#29)
Add a human-readable `tag=value` text protocol alongside the binary codec,
mapping the same internal message structs (issue #29, reprioritized by the human
from the backlog).
- include/qsl/protocol/fix.hpp + src/protocol/fix.cpp: SOH-framed `tag=value`
adapter with genuine FIX framing — 8 BeginString / 9 BodyLength / 35 MsgType /
... / 10 mod-256 CheckSum — for the client->gateway order path:
NewOrderSingle (35=D) -> NewOrder, OrderCancelRequest (35=F) -> CancelOrder.
Decoding is total, deterministic, and noexcept (fixed field table,
std::from_chars, std::string_view; no heap on the decode path) and reports
every malformed input through a FixError taxonomy mirroring the binary codec's
DecodeError. Documented, deliberate simplifications: Symbol (55) carries the
numeric SymbolId; Price (44) carries integer ticks and is always present, so
NewOrder<->FIX is a lossless bijection like the binary codec (price is never a
float).
- tests/unit/test_fix_protocol.cpp: mirrors the binary required tests and adds a
cross-codec equivalence test (binary and FIX decode the same order to identical
structs across all Side x OrdType x TIF), a byte-pinned fixture, and rejection
of malformed framing / unsupported BeginString / unknown-or-wrong MsgType /
BodyLength mismatch / CheckSum mismatch / missing field / invalid field /
invalid enum / out-of-range / oversized messages.
- docs/fix_protocol.md (+ pointer from docs/binary_protocol.md); MILESTONES.md
and PROGRESS.md updated.
make check 260/260; make asan 260/260 (the adapter parses untrusted text).
Closes #29.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf: harden flamegraph collapsed-stack parsing (Codex review)
Address two Codex review findings in scripts/flamegraph.py::parse_collapsed:
- Prefer a tab separator when present so a tab-separated folded line whose stack
contains spaces (C++ signatures) splits on the trailing count instead of an
interior space and is silently dropped.
- Ignore non-positive sample counts, so hand-crafted --from-collapsed input with
0/negative counts cannot render a misleading SVG (all-non-positive input now
fails with exit 1 via the existing empty-folded guard).
Adds test coverage in tests/shell/test_flamegraph.sh (19/19).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf: regenerate flamegraph artifact after parser hardening
flamegraph.py is a provenance input, so regenerate results/flamegraph.svg +
.txt from the clean tree to keep the Source digest consistent (423 samples,
Dirty inputs: no).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: enforce FIX-required ClOrdID on OrderCancelRequest (Codex review)
decode_cancel_order validated only OrigClOrdID (41) and Symbol (55), so a 35=F
message missing ClOrdID (tag 11) — which FIX requires and encode() emits — was
accepted. Validate tag 11 (present and numeric) on decode without storing it,
keeping decode symmetric with encode. Adds a rejection test and clarifies the
docs/fix_protocol.md note for tag 11.
make check 261/261; make asan 261/261.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: improve flamegraph.py code health (CodeScene gate)
CodeScene's delta gate scored scripts/flamegraph.py at 7.81 (render_svg: Large
Method + Excess Arguments + complexity; fold_perf_script: Bumpy Road / nested
complexity). Restructure without changing output:
- fold_perf_script: move per-line state into a small _Folder helper so the
parsing loop is a flat if/elif/else instead of a nested block.
- render_svg: bundle styling knobs into a FlameOptions dataclass (2 args, was
7) and extract _append_chrome, _frame_svg, _truncate; geometry constants
(_SIDE/_PAD_TOP/_PAD_BOTTOM) hoisted to module scope and a _Canvas dataclass
carries derived geometry.
Emitted SVG/collapsed bytes are unchanged; tests/shell/test_flamegraph.sh 19/19.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf: regenerate flamegraph artifact after code-health refactor
flamegraph.py is a provenance input; regenerate results/flamegraph.svg + .txt
from the clean tree (402 samples, Dirty inputs: no).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: reduce decode_new_order complexity in fix.cpp (CodeScene gate)
CodeScene's delta gate scored src/protocol/fix.cpp at 8.02 (decode_new_order:
Complex Method / Complex Conditional / Overall Code Complexity). Restructure
without changing behavior:
- Extract map_side / map_ord_type / map_tif (the enum-code switches) and
expect_msg_type (the tag-35 check) as small helpers.
- Add a FieldReader accumulator that reads required integer/coded fields and
short-circuits on the first error, so decode_new_order and decode_cancel_order
become a flat fluent chain plus a single error check instead of a long
if-return ladder with three inline switches.
make check 261/261; make asan 261/261; FIX tests 19 cases / 140 assertions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: flatten flamegraph.py remaining complexity (CodeScene)
Clear the last two CodeScene flags on scripts/flamegraph.py:
- _clean_symbol: replace the balanced-paren dso scan (a deep nested loop) with a
flat regex _DSO_RE. perf prints a space before the "(dso)" and dso strings
never contain parens, so a non-nested " (...)$" match is exact and won't strip
a C++ signature's own parentheses.
- _layout: drop the unused `total` parameter (5 args -> 4).
Output unchanged; tests/shell/test_flamegraph.sh 19/19.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf: regenerate flamegraph artifact after complexity flattening
Provenance input changed; regenerate from clean tree (416 samples, Dirty
inputs: no).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: remove decoder duplication and split parse_envelope (CodeScene)
Clear the remaining CodeScene flags on src/protocol/fix.cpp (Code Duplication,
Complex Method, Complex Conditional):
- Extract a decode_typed<T> skeleton (validate envelope -> confirm MsgType ->
fill body via FieldReader -> error check) so decode_new_order and
decode_cancel_order collapse to just their field maps, removing the duplicated
prologue/epilogue.
- Split parse_envelope into tokenize / check_envelope_shape /
verify_length_and_checksum, each a small single-purpose function, and fold the
8/9/10 ordering check into a named bool.
Behavior unchanged: make check 261/261, make asan 261/261, FIX tests 19 cases /
140 assertions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: table-driven enum maps + simpler msg-type check (CodeScene)
Clear the last two CodeScene advisory flags on src/protocol/fix.cpp:
- Code Duplication: the three near-identical map_side/map_ord_type/map_tif
switches are replaced by one generic FieldReader::coded(tag, out, table) that
looks a code up in a small constexpr {code, enum} table; the decoder supplies
the per-enum tables. No per-enum mapping duplication remains.
- Complex Conditional: expect_msg_type's 3-term `||` is split so each branch has
a single operator.
make check 261/261; make asan 261/261; FIX tests 19 cases / 140 assertions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: record resume-anchor sync in PROGRESS current-state (Codex #129)
The decision-log entry and bottom "Next action remains" block already
covered this follow-up, but the top `## Current state` bullets — the first
thing `/resume` reads — still presented the v0.2.0 release (PR #127) as the
"Last action". A resuming agent could therefore miss that this resume-anchor
/ PMU sync already happened and duplicate it.
Record the sync as the current "Last action" and "Last completed docs sync",
demoting the v0.2.0 release detail to a "Prior action" bullet. Docs-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf: harden flamegraph.sh classification + sample gating (Codex #130)
Address five Codex review findings on the flamegraph driver:
1. Classify `zero-sized data` (perf script's no-sample report) as a perf
limitation, matching scripts/perf_record.sh, so the documented
QSL_PERF_ALLOW_PARTIAL=1 constrained-host path works instead of tripping
the unexpected-failure exit.
2. Remove any prior results/flamegraph.svg when a partial run captures no
folded stacks, so a constrained rerun cannot leave a previous host's SVG
beside a .txt that says there is no sample report.
3. Accept perf's `(~N samples)` estimate marker (optional `~`), and base the
minimum-sample gate on the authoritative folded sample total rather than
perf record's self-described estimate. Report both counts.
4. Capture flamegraph.py --collapse-only's exit status instead of `|| true`;
a renderer/parser failure now exits 4 (unmaskable) rather than being
published as a constrained-environment artifact.
5. Derive the sampling-kind label/caveat from the selected event (software
cpu-clock/task-clock vs hardware-PMU) so the artifact type, SVG comment,
and text companion stay consistent for QSL_FLAMEGRAPH_EVENT=cycles etc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf: regenerate flamegraph artifact after classification hardening
Bare-metal Apple M2 (aarch64) Fedora Asahi, cpu-clock @ 4000Hz: 329 folded
samples / 159 stacks, classified `flamegraph (software cpu-clock sampling
hot-symbol profile)`, `Dirty inputs: no`. Source digest now covers the
hardened scripts/flamegraph.sh; the .txt reports both the folded total and
perf record's estimate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: enforce FIX envelope MsgType position + reject duplicate tags (Codex #131)
Two Codex review findings on the FIX adapter's parser strictness:
- check_envelope_shape now requires MsgType (35) as the first body field,
immediately after BodyLength, so a non-standard header like 8/9/34/35/.../10
is rejected as Malformed instead of decoding via a first-match scan.
- tokenize now rejects any repeated tag. This adapter maps each business tag
exactly once (no repeating groups), so a duplicate such as `55=2` then
`55=999` is an ambiguous/malformed frame rather than a silently-ignored
later value.
Adds a deterministic rejection test for each. (The earlier ClOrdID-required
finding was already resolved by 3e4c8e3 and is covered by an existing test.)
make check 263/263, make asan 263/263.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: drop delivered #29 from open-backlog anchors (Codex #131)
The decision log marks #29 (FIX adapter) closed by this PR, but the
current-state / resume anchors in PROGRESS.md and HANDOFF.md still listed it
as open backlog, so /resume could send the next session to re-implement work
this PR just added. Remove #29 from those backlog lists and note it as
delivered in this PR. (#32 stays listed here; the v0.2.1 release PR removes it
as part of its release sweep.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: scope partial-PMU claim to perf-stat; perf-record is a software profile (Codex #129)
The constraints bullet labeled all perf artifacts as partial hardware PMU
evidence, but only results/perf_stat_linux.txt carries real PMU counters
(cycles/instructions/branches/branch-misses). results/perf_report_linux.txt
is a software cpu-clock sampling profile, not PMU evidence. Scope the claim
to the perf-stat artifact and call out perf-record separately, identically in
AGENTS.md and CLAUDE.md so the two memories stay in sync.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: embed the flamegraph as a visible image in the README
The flamegraph artifact, generator, provenance companion, and docs already
existed but no page actually displayed the SVG — it was only referenced by
filename. Embed the rendered results/flamegraph.svg as a visible image under
the Benchmarks section, with a caption that classifies it honestly as a
software cpu-clock sampling hot-symbol profile (not PMU evidence), names the
hot frames, and links the provenance .txt and docs/perf_analysis.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 31d16f6 commit c07f350
10 files changed
Lines changed: 938 additions & 13 deletions
File tree
- docs
- include/qsl/protocol
- src/protocol
- tests
- unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
85 | | - | |
| 85 | + | |
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
92 | | - | |
93 | | - | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
94 | 95 | | |
95 | 96 | | |
96 | 97 | | |
| |||
107 | 108 | | |
108 | 109 | | |
109 | 110 | | |
110 | | - | |
| 111 | + | |
| 112 | + | |
111 | 113 | | |
112 | 114 | | |
113 | 115 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
469 | 469 | | |
470 | 470 | | |
471 | 471 | | |
472 | | - | |
473 | | - | |
474 | | - | |
475 | | - | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
476 | 477 | | |
477 | 478 | | |
478 | 479 | | |
| |||
481 | 482 | | |
482 | 483 | | |
483 | 484 | | |
484 | | - | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
485 | 490 | | |
486 | 491 | | |
487 | 492 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
55 | | - | |
| 55 | + | |
| 56 | + | |
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
59 | 60 | | |
60 | 61 | | |
61 | 62 | | |
62 | 63 | | |
63 | | - | |
| 64 | + | |
64 | 65 | | |
65 | 66 | | |
66 | 67 | | |
| |||
386 | 387 | | |
387 | 388 | | |
388 | 389 | | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
389 | 408 | | |
390 | 409 | | |
391 | 410 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
0 commit comments