Conversation
ldionne
added this pull request to stack #255
September 25, 2026 19:16
Step 3 accepted profile blobs, checked the format version byte and stored
them, deliberately parsing nothing further. This adds the reader for D12's
version 2 binary format and the four endpoints over it:
GET /runs/{uuid}/profiles -- {test, uuid} for a run
GET /profiles/{uuid} -- metadata + top-level counters
GET /profiles/{uuid}/functions -- functions, hottest first
GET /profiles/{uuid}/functions/{fn_name} -- disassembly + counters
All `read` scope. Counters are served raw, never as percentages; the client
computes shares.
The reader is pure and database-free, so the endpoints are the only thing
that knows both the format and HTTP. The format's whole point is that its
index is uncompressed and its per-instruction data is not, so `read_profile`
parses the index eagerly and only a request for one function's disassembly
pays for bz2. The metadata and function-list endpoints therefore decompress
nothing, and a test pins that by clobbering every bz2 stream in an otherwise
valid blob and checking those two responses are unchanged.
Every way a stored blob can fail to be read is one `ProfileError`, which R4
turns into `internal_error` (500): the data is bad, not the request. That
guarantee holds by construction -- every byte goes through a bounds-checked
cursor -- with an enumerated net at the two entry points. Two exhaustive
sweeps over a golden blob (every prefix, and every single-byte flip) assert
the result is always either a successful read or a `ProfileError`, never a
stray `struct.error` or `UnicodeDecodeError`. A blob cannot be trusted about
how much work it describes, so counts are refused against the bytes that
could hold them, bz2 is expanded incrementally under a budget, and a
function's instruction count is capped.
The blob stays out of every default result set (D5): only the two queries
that serve a profile's contents name `data`, and the listing is pinned by
renaming the column out from under the server and checking it still answers.
The disassembly endpoint gives the connection back before expanding anything,
so the most expensive read in the API does not hold a pooled connection and
an open transaction across it.
Tests use golden blobs written by v4's own writer rather than a hand-rolled
encoder, so what is tested is the format as it exists in the wild.
Two deliberate deviations, both recorded in the design docs:
R1 claimed percent-encoding rescued a function name as a path segment. It
does not. v4's importer runs `objdump -C`, so stored names are demangled, and
`std::operator/(std::filesystem::path const&, ...)` contains a `/` that `%2F`
cannot save -- a server decodes before routing. Rather than move the name to
a query parameter, the segment now spans the remainder of the path, which
works here and did not work for test names for a reason worth stating: the
function name is the *last* segment, so nothing follows it to collide with.
The documented URL shape is unchanged. Two residual names remain unreachable
(one ending in `/`, one containing a `.` or `..` segment); unlike a machine
name these cannot be refused at creation, since they arrive inside a blob
stored verbatim, and no symbol a compiler emits hits either.
The read-side bounds are new policy. A blob accepted under D5's 50 MB cap can
still expand without limit, and submission is specified not to parse it, so
the bound can only live in the reader and can only be reported as corruption.
D12 now states it as floors rather than exact numbers. The expansion floor is
set to dominate the store cap at the ratios these sections really reach, so
that a well-formed profile the server accepted stays readable; D12 says
plainly that this cannot be guaranteed, because bz2's ratio has no bound.
D12 also gains a specification of the binary format itself, which existed
nowhere in the design docs -- only in v4's Python -- and was the one piece of
this system that could not be re-implemented from the documents. It records
two places where v4's own comments contradict what v4's code writes, so the
next reader does not "correct" the accurate description back.
The CFG view stays deferred; nothing here is for it.
ldionne
force-pushed
the
users/ldionne/v5-profiles
branch
from
September 25, 2026 19:53
a25df41 to
cbca0cd
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR implements the profile endpoints:
Stack created with GitHub Stacks CLI • Give Feedback 💬