You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf(coverage): emit the whole LCOV report in one awk invocation #1090
After #1085 and #1088 the report phase is report_lcov, and it is the only
place left that still forks per file and loops per line in Bash.
Per tracked file it runs three awk forks (lines, functions, and the branch
extractor once it moves) plus two Bash loops: the per-function span scan that
decides FNDA, and the per-arm scan that decides BRDA.
Measured on Bash 3.2 arm64, 128 tracked src/*.sh files:
128 files
report_lcov total
2785 ms
of which compute_branch_hits
1479 ms
of which extract_functions
535 ms
of which awk_lcov_lines
440 ms
bare awk fork x128 (the floor)
~350 ms
The remaining cost is almost entirely fork overhead and Bash glue: the awk
work itself is a rounding error, as #1088 showed when the same shape took the
stats phase from 2585 ms to 153 ms.
Proposal
One awk invocation for the whole report, driven by the same manifest #1088
introduced. Every record a file needs — SF, FN/FNDA/FNF/FNH, BRDA/BRF/BRH, DA/LF/LH, end_of_record — comes out of a single
pass that already has the source lines and the propagated hit counts in hand.
This needs the function scanner and the branch extractor in function form so
both the per-file API and the batch pass call the same code, not a copy.
Verification
Byte-identical LCOV against a fixed corpus, with hits, plus the existing unit
tests. The branch extractor has already been ported and diffed against the
Bash implementation over all 463 shell files in the repo with no mismatch.
Prototype done, measured on the same 128 tracked src/*.sh files with hits:
128 files
reference, per file (today)
3133 ms
one awk invocation
262 ms
Output is byte-identical: 15,357 LCOV records, every SF/FN/FNDA/ FNF/FNH/BRDA/BRF/BRH/DA/LF/LH/end_of_record line in the same
order.
One trap worth recording for the port: the function scanner keeps its
single-quote character in BEGIN { SQ = sprintf("%c", 39) }, because a literal ' cannot appear inside the single-quoted shell string that holds the awk
source. Composing the scanner into another program without carrying that BEGIN over leaves SQ empty, so quotes stop being tracked and the scanner
silently regresses to the pre-#1087 behaviour — it does not error. Whatever
shape the shared chunk takes, SQ has to travel with bu_scan.
Scope for the change:
the scanner helpers (bu_scan + SQ) and the branch helpers move into the
shared awk chunk, so the per-file API and the batch pass run the same code
Activity
Chemaclass commentedon Aug 13, 2026
Prototype done, measured on the same 128 tracked
src/*.shfiles with hits:Output is byte-identical: 15,357 LCOV records, every
SF/FN/FNDA/FNF/FNH/BRDA/BRF/BRH/DA/LF/LH/end_of_recordline in the sameorder.
One trap worth recording for the port: the function scanner keeps its
single-quote character in
BEGIN { SQ = sprintf("%c", 39) }, because a literal'cannot appear inside the single-quoted shell string that holds the awksource. Composing the scanner into another program without carrying that
BEGINover leavesSQempty, so quotes stop being tracked and the scannersilently regresses to the pre-#1087 behaviour — it does not error. Whatever
shape the shared chunk takes,
SQhas to travel withbu_scan.Scope for the change:
bu_scan+SQ) and the branch helpers move into theshared awk chunk, so the per-file API and the batch pass run the same code
report_lcovwrites the perf(coverage): compute every file's line stats in one awk pass, not one Bash loop per file #1088 manifest and makes one callextract_branchesover every shell file in the repo, the way perf(coverage): move line classification and LCOV emission into awk #1059 pinnedthe classifier (already run by hand: 463 files, 0 mismatches)
Chemaclass commentedon Aug 13, 2026
Done in #1091 (merged).