The Linux kernel's function_graph tracer records every entry into and exit from kernel functions, letting you see the exact call tree the kernel executed on a CPU, with per-function durations. Perfetto can drive this tracer through the linux.ftrace data source and visualises the resulting calls as nested slices on the timeline, just like userspace slices.
This is a powerful way to answer “what was the kernel actually doing here?” without adding any instrumentation of your own. It is, however, a high-bandwidth feature: tracing too many functions will overwhelm the trace buffer, so it is designed to be used together with filters.
If instead you want to add your own tracepoints to the kernel, see Instrumenting the kernel with ftrace.
CONFIG_FUNCTION_GRAPH_TRACER. You can confirm support with:cat /sys/kernel/tracing/available_tracersThe output must include
function_graph.symbolize_ksyms: true in the ftrace config. Without it, every function shows up as a raw hexadecimal address. See Symbolization: kernel symbols for why kernel symbols must be resolved at record time and cannot be added afterwards with traceconv bundle.debuggable (userdebug/eng) builds, and was introduced in Android U.traced_probes must run as root (or kptr_restrict lowered), both to read /proc/kallsyms and to control the kernel tracer.The relevant options live in FtraceConfig:
enable_function_graph: turns on the function_graph tracer.function_filters: a set of globs; only matching functions are traced.function_graph_roots: a set of globs; matching functions and all of their callees are traced.function_graph_max_depth: limit how many call levels below a root are traced.symbolize_ksyms: required to get function names instead of addresses.WARNING: Always constrain the traced set with function_filters and/or function_graph_roots. Tracing all kernel functions generates an enormous event stream that will fill the buffer in milliseconds and is rarely useful.
Example: trace the scheduler functions and everything they call, for 10 seconds.
buffers { size_kb: 65536 fill_policy: DISCARD } data_sources { config { name: "linux.ftrace" ftrace_config { symbolize_ksyms: true enable_function_graph: true # Trace these functions and all of their callees. function_graph_roots: "__schedule" # Optionally also keep a flat set of functions of interest. function_filters: "handle_mm_fault" function_graph_max_depth: 10 } } } duration_ms: 10000
Record it with tracebox:
./tracebox -c funcgraph.cfg --txt -o funcgraph.pftrace
See the system tracing guide for how to set up tracebox and the necessary permissions on Linux.
Function graph calls appear as nested slices. Each kernel function entry/exit pair becomes a slice whose duration is the time spent inside that function (including callees), so the call tree reads exactly like a userspace flame chart.
swapper idle task) are grouped onto a per-CPU swapper<N> -funcgraph track.You can select a slice to see the function name and duration, and use the flamegraph/aggregation features as with any other slice track.
Function graph calls are ordinary slices, so they live in the slice table and can be queried like any other slice. For example, to find the kernel functions that accounted for the most aggregate time:
SELECT name, COUNT(*) AS calls, SUM(dur) AS total_dur FROM slice JOIN track ON slice.track_id = track.id WHERE track.name = 'Funcgraph' GROUP BY name ORDER BY total_dur DESC LIMIT 20;
0xffffffff8108abcd): you did not set symbolize_ksyms: true, or traced_probes could not read /proc/kallsyms (not root / kptr_restrict too high). This must be fixed at record time; see Symbolization: kernel symbols.function_graph is in available_tracers, and that your config sets at least one of function_filters / function_graph_roots.linux.ftrace data source that uses a different kernel tracer, as the kernel tracer cannot be switched mid-trace.