Skip to content

perf(coverage): emit the whole LCOV report in one awk invocation #1090

Closed

Description

@Chemaclass

Problem

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.

Activity

  1. self-assigned this
    on Aug 13, 2026
  2. Chemaclass commented on Aug 13, 2026

    @Chemaclass
    MemberAuthor

    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:

  3. Chemaclass commented on Aug 13, 2026

    @Chemaclass
    MemberAuthor

    Done in #1091 (merged).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions