Add the trace ring buffer blueprint, and do the arithmetic the stats file will not - #69
Merged
Merged
Conversation
…file will not The fourth blueprint, and the one the other three rest on. Everything this project says about what a trace contains is a claim about this file first and the tracer second, so it is worth writing down what it actually promises. It promises less than people think. A writer into this buffer never blocks, never waits and never fails for lack of room. When the buffer is full it throws the oldest page of events away and carries on, and no caller is told. The capture in section 5 is that on purpose: 44275 events written, 273 still there, 44002 gone, which is 99.4% of everything the tracer recorded, in seven thousandths of a second on an idle machine running two small commands. The part worth the blueprint is why the trace file goes quiet about it. The kernel does know the number. A consuming reader computes it as overrun minus last_overrun and trace_pipe prints CPU:0 [LOST 44002 EVENTS]. An iterating reader, which is what the trace file uses, asks a different question: it reads a missed events flag that comes from a bit in a page's commit field, and the kernel's own comment says the main write buffer does not set that bit. So on an ordinary buffer the flag is always zero. That is invariant 11, nothing in the kernel enforces it, and it is written down here because it is the difference between a trace that is quiet because nothing was lost and one that is quiet because nobody asked. Section 5 is generated by a new tracefs-stats reader in bpcgen. The file it reads answers no question anybody has on its own: what was written is not in it, the share thrown away is not in it, and neither is how much history the buffer was holding. All three are addition and division on numbers that are, so the generator does them, because a reader who has to do the sum by hand is a reader who takes the trace at face value. kxray.tracefs gains parse_timestamps and window for the two clock lines, which account_stats still counts as skipped, so the baseline is untouched and the output now says which two lines those are instead of leaving a gap. Seven names in the header have no symbol in the A-full build and section 7 says so rather than leaving them out. Six are static functions the compiler inlined. The seventh is ring_buffer_alloc, which is not a function at all: it is a macro that declares a static lock class key so every ring buffer gets its own lockdep class, which is the lock ordering blueprint reaching into this one. Somebody who greps for it and finds nothing draws exactly the wrong conclusion, so it gets a paragraph. 75 citations, all confirmed against the pinned tree, all anchored to text that matches exactly one line in the file it names. Three anchors were rejected while writing them for matching more than one place, which is the check earning its keep.
This was referenced Sep 6, 2026
Open
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.
What this is
The fourth blueprint,
trace-ring-buffer, and the one the other three quietly rest on. Every claim this project makes about what a trace contains is a claim about this file first and about the tracer second, so it is worth writing down what it actually promises.It promises less than most people assume, and the whole blueprint is built around one fact.
A writer never fails, so a full buffer is a silent deletion
A writer into this buffer never blocks, never waits and never fails for lack of room. When the next page is the oldest page, it throws that whole page of events away, adds their count to a counter, moves the tail onto the space and carries on. The caller is handed a perfectly good pointer and is never told what it cost.
The capture in section 5 is that, on purpose. An eight kilobyte buffer, an unfiltered tracer, and about seven thousandths of a second of an idle machine running two small commands:
44275 events were written. 273 are still there. 44002 are gone. That is 161 events discarded for every one kept, and 99.4% of everything the tracer recorded.
The part that is worth a blueprint
Why the trace file does not say so. The kernel does know the number, and it will tell you over one of the two ways of reading a trace and not the other.
trace_pipedoes a consuming read, which computes the count asoverrunminuslast_overrunand carries it out to the caller, and it printsCPU:0 [LOST 44002 EVENTS].The
tracefile uses an iterator, and an iterator asks a different question. It callsring_buffer_iter_dropped, which reads a missed events flag, and that flag has exactly one source: a bit in a page's commit field. The kernel's own comment next to the one place that sets it says the main write buffer does not set these bits. So on an ordinary buffer that flag is always zero, the count is never consulted, and the file prints nothing about the 44002 events that are gone.That is invariant 11 in section 4. Nothing in the kernel enforces it, nothing tests it, and it is not stated anywhere except in a comment about a different case. It is the difference between a trace that is quiet because nothing was lost and a trace that is quiet because nobody asked.
The generator does the arithmetic, because a reader who does it by hand does not
Section 5 comes from a new
tracefs-statsreader intools/bpcgen.py. The file it reads answers no question anybody actually has on its own. What was written is not in it. The share that was thrown away is not in it. Neither is how much of the machine's history the buffer was holding. All three are addition and division on numbers that are in the file, so the generator does them and prints them next to the table.kxray/tracefs.pygainsparse_timestampsandwindowfor the two clock lines.account_statsis deliberately unchanged, socorpora/BASELINE.tomland the line accounting are untouched, and the generated text now says which two lines were skipped and why rather than leaving a bare number that looks like the parser gave up.Two numbers that mislead, written down
The buffer was asked for as 8 kilobytes, reads back as 11, and cost 16. A request in bytes is divided by the usable size of a sub buffer, which is a page minus a twelve byte header, so 4084 rather than 4096. 8192 over 4084 rounds up to three. Three times 4084 is 12252, and the file prints kilobytes rounded down, so it says 11. The reader page is allocated on top, so four pages went. The kernel's own documentation says the number is a request rather than a setting.
And
overrunanddropped eventsare not two views of the same thing. One counts events lost with overwrite on, the other counts events lost with overwrite off, exactly one of them can be non zero on a given buffer, and a reader who checks the zero one concludes that nothing was lost.Seven names section 7 cannot find
Six are
staticfunctions the compiler inlined, which is the case the lock ordering blueprint already documents.The seventh is
ring_buffer_alloc, and it is a better story. It is not a function at all. It is a macro that declares astatic struct lock_class_keyand passes its address to the real function, so that every ring buffer in the kernel gets a lockdep class of its own. That is the lock ordering blueprint reaching into this one. Somebody who greps a build for that name and finds nothing draws exactly the wrong conclusion, so it gets a paragraph rather than a table row.Evidence
75 citations in
blueprints/trace-ring-buffer.refs.toml, all confirmed against the pinned 7.2.2 tree, every one anchored to text that matches exactly one line in the file it names. Three anchors were thrown out while writing them because they matched more than one place, which is the uniqueness check earning its keep. Several anchors are long comments on purpose: this is a lockless algorithm whose correctness argument is written in prose and nowhere else.statusispartial, and the reason is the usual one. The tooling has done everything it can and nobody has read it through yet.One limit stated plainly in section 8 rather than glossed: the pinned machine has one processor, so the reader and writer races in section 3 were reasoned about from the source and not observed. The evidence here covers the counters and the loss behaviour.
Also in here
blueprints/assets/trace-ring-buffer-overrun.diagram.py, with alt text, an SVG and an excalidraw file.coverage.tomlgains the blueprint and the twoinclude/linux/ring_buffer*.hheaders on thetracingentry.Gates
Every one of them, locally: ruff check and format, pytest, the node tests,
--checkruns of diagrams, nbbuild, sitebuild, kxbox and vendor, and plain runs of baseline, claimledger, coverage, bpc, kconfig, refcheck, lintnb, kxmanim and lintprose.bpc: 5 blueprint(s) clean,refcheck: paths clean, 207 citation(s),baseline: 41 artefact(s), 7968 line(s), 0 unparsed.