flamegraph: intern module paths, and say what width means on a GPU page (#123) - #133
Merged
Conversation
data-module carried the full module path on every frame. Measured on a PyTorch CPU capture: 566,966 bytes of a 3,165,745-byte page -- 18% -- for THIRTEEN distinct values totalling about a kilobyte. Nothing read it: not the page's own script, not a test, not any Go caller. The renderer already applies the opposite rule two lines above the emit site, and says so: "No inline colour: data-domain below selects fill, hatch and outline from paletteCSS, so a domain's colour is declared once for the whole page rather than once per frame." Modules are the field that missed it. The paths are now a JSON table at the top of the chart and each frame carries data-m, an index into it: AFTER-cpu-python-walker 3,165,745 -> 2,668,568 -15% GPU-mnist-pytorch 1,100,683 -> 970,101 -11% The table is emitted only when a profile has modules, so a GPU profile -- which the builder writes with empty mappings -- pays nothing for a table it would never reference. Sorted, so the same profile renders byte-identical and a future change to tree-walk order does not show up as a page diff. NOT HTML-escaped, which is the subtle part and where the first version was wrong. HTML entities are not decoded inside a script element, so escaping there would hand JSON.parse a document full of " -- a failure that appears only in a browser, long after every Go test has passed. json.Marshal already writes < > & as < > &, which is exactly the escaping this context needs: it makes "</script>" unrepresentable, so a module path cannot close the element early. Asserted with a path that tries to.
Under [gpu:launch] a CPU frame is as wide as the DEVICE time launched from
that call path, not the CPU time spent in the frame. Someone fluent in flame
graphs reads it the other way -- that is what the shape means everywhere else
-- and the page carried no signal to correct them. It was documented in prose
on an index page most viewers never open.
The tooltip now says so, per band:
gpu-kernel measured time this kernel ran on the device
boundary device time launched from this path, sampled one
launch in N
boundary-unattributed measured device time whose launch was not
stack-sampled, so it has no caller
everything else GPU time launched from this call path -- not CPU
time spent here
Only on a GPU profile, and only because the axis says so. On a CPU profile the
width is the obvious thing and a caption on every frame would be noise; the
line earns its place by appearing exactly where a reader would otherwise be
wrong.
ALSO FIXES A REGRESSION I INTRODUCED one commit ago. Interning the module
paths removed data-module, and detail() reads it as d.module where
d = it.el.dataset -- an alias a grep for "dataset.module" does not find. So
"nothing reads this attribute" was wrong: the page's own tooltip did, and
interning silently emptied its module line. The script now reads the interned
table, and a test asserts the page ships a reader for it rather than only the
table itself.
TestTheShippedAssetsCarryNoProseComments exists because every comment in the
script is bytes sent to every reader, and I had put a nine-line paragraph in
there. The explanation moves to the Go declaration; the page keeps the code.
Removing it took the `function widthMeaning(it,d){` line with it, leaving a
bare function body -- and the whole Go suite still passed, because nothing here
parses JavaScript. A broken script would have shipped and failed only in a
browser.
So there is now a structural check on the asset: brace and paren balance, and
the presence of every function the tooltip path calls. It is not a parser, but
it catches the class of damage a text edit to a template literal actually
does -- which is exactly what happened.
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.
Two items from the flame-graph UI research, both self-contained.
1. Modules declared once per page
data-modulecarried the full path on every frame: 566,966 bytes of a 3,165,745-byte page (18%)for thirteen distinct values totalling about a kilobyte.
The renderer already applies the opposite rule two lines above the emit site, and says so:
Modules are the field that missed it. They are now a JSON table at the top of the chart, with each
frame carrying
data-m, an index into it.Emitted only when a profile has modules, so a GPU profile — which the builder writes with empty
mappings — pays nothing. Sorted, so the same profile renders byte-identical.
Not HTML-escaped, which is the subtle part: HTML entities are not decoded inside a
<script>element, so escaping would hand
JSON.parsea document full of".json.Marshalalreadywrites
<,>,&as<etc., which makes</script>unrepresentable — asserted with amodule path that tries to break out.
2. A GPU page says what a frame's width measures (#123)
Under
[gpu:launch]a CPU frame is as wide as the device time launched from that call path,not the CPU time spent in the frame. A reader fluent in flame graphs assumes the opposite, because
that is what the shape means everywhere else, and the page carried no signal to correct them. It
was documented only in prose on an index page most viewers never open.
The tooltip now says so, per band — kernel, boundary, unattributed, and everything else — and
only on a GPU profile, decided from the axis label. On a CPU profile the width is the obvious
thing and a caption on every frame would be noise.
Please review this one properly
I introduced three bugs on this branch and caught each only after writing the code:
data-module" — thepage's own
detail()reads it asd.modulethrough adatasetalias my grep missed. There isnow a test asserting the page ships a reader for the table, not just the table.
TestTheShippedAssetsCarryNoProseCommentsexists to prevent — every comment there is bytes sentto every reader. Moved to the Go declaration.
function widthMeaning(it,d){line with it, leaving abare function body — and the entire Go suite still passed, because nothing here parses
JavaScript. A broken script would have shipped and failed only in a browser. There is now a
structural check on the asset: brace/paren balance, and the presence of every function the
tooltip path calls.
The third is the one worth a second opinion: the guard I added catches the damage a text edit to a
template literal does, but it is not a parser, and this branch is evidence that the JS in this
repo has no real safety net.