Read the flat function tracer, and the flags column it prints - #56
Merged
Merged
Conversation
function_graph answers what called what and how long it took. The flat function tracer answers what ran and under what rules, which is a different question and the one every concurrency lesson needs. Two real Tier 0 captures, a parser, and the shared line header both tracers print. Flags.context maps the column onto five of the six contexts in kxray/vocabulary.py and refuses to guess at the sixth.
This was referenced Sep 5, 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.
kxray.tracehad one parser in it.function_graphgives you the call tree with a duration against every frame, and it is the right tool for showing the shape of a system call, which is what the three lessons so far use it for.It is the wrong tool for about half of what M1 needs. It does not print the one column that says what state the machine was in when a call happened, and that column is what decides whether the code on the line was allowed to do what it did. So this adds the flat function tracer, which prints one line per call with no nesting and no duration, and prints that column on every line.
Two real captures, not fixtures
Both came off the pinned 7.2.2 under v86, on this machine, through
node kxbox/web/headless.js. Both areevidence = trueand both record the exactset_ftrace_filterthat produced them, so either can be taken again and compared rather than argued about.corpora/traces/tier0/flat-write.txtis the same one byte write thatwrite-1byte.txtalready shows as a tree, taken the other way. It is here to be read next to that file. Seven lines, and the flags column is.....on all seven, which means process context the whole way down and every rule that applies to sleeping code applies to the entire write. That is the boring case and it is worth seeing before the other one.The last line of it is a second
vfs_writeand it is not the byte going in twice. It is theecho 0 > tracing_onthat stopped the recording, which is a write like any other and therefore matches the filter. The tracer records the act of switching itself off.corpora/traces/tier0/flat-interrupt.txtis one second of an idle machine with the filter set to the interrupt and softirq path. Eight lines, and this is what the new parser makes of them:Two things in there are hard to show any other way.
The task column says
sleepon every line andsleepdid none of this. A timer interrupt runs on whichever task was on the CPU when it arrived and borrows that task's stack and its name, so the comm column answers who got interrupted rather than who ran. The flags column is the only thing on the line that says which is which, and a reader who does not know that will spend a while wondering whysleepis calling into RCU.And the middle four lines are a gap rather than a delay.
raise_softirqat7.712292is the interrupt handler writing down that work is needed.handle_softirqsat7.712408is that work running, afterwards, with interrupts back on. The handler did not do the job, it left a note. That gap is what deferred work means and here it is with timestamps on it.What the flags column is read as
Flags.contextmaps the column ontokxray/vocabulary.py, and it answers five of the six contexts. It never answersatomic, and that is a limit rather than an omission. A held spinlock raises the preemption count and so does a barepreempt_disable(), the column carries only the count, and nothing in it can tell the two apart. So both come back asnopreempt, which is true of both, and the docstring says why. A parser that guessed there would be right most of the time and wrong in exactly the cases somebody was debugging.Four character columns are read too. Kernels from before migrate-disable existed print four, and a trace somebody took in 2021 is still a trace.
The shared header
Three ftrace formats print the same six columns before they print anything of their own, so
kxray/trace/common.pyreads them once and hands the rest of the line back. The flat tracer takes it now and the event reader will take it next.The one thing in there worth knowing is that the pid anchors the regular expression rather than the comm, because a comm can hold dashes and slashes and spaces.
kworker/0:1-9is one task calledkworker/0:1with pid 9, and there is a test for it and formy prog-7.Two existing tests got narrower
test_every_committed_recording_is_one_task_doing_one_thingand the bridge options check both globbed every.txtincorpora/traces/tier0/and both assumed function_graph. They now filter on thetracerkey in each capture's metadata. The interrupt capture has no single task in it by design, which is the whole point of it, and it was never a both-ways recipe.corpora/BASELINE.tomlgrows two rows. The new route has to sit above the existing one, because both tracers write.txtinto the same directory andflat-on the front of the name is what tells them apart.Not done here
The
kxray.tracerow in #2 also asks fortrace_pipeevents read through each event's ownformatfile, and that is the next PR rather than this one, so the row stays unticked.common.pyexists in the shape it does because of that half.