a tracepoint
A kprobe lets you trace any kernel function, but it is fragile: it pins to an internal function whose name and shape can change with every release. A tracepoint is the opposite bargain. It is a tracing hook that the kernel developers deliberately placed in the source code at a meaningful event — 'a process is about to be scheduled', 'a packet was dropped', 'a block I/O completed' — and committed to keeping stable, with a documented set of fields, so tools can rely on it for the long term.
Concretely, a tracepoint is a named, static instrumentation point compiled into the kernel at a chosen line. When no one is watching, it sits dormant and costs almost nothing — it is implemented so that an inactive tracepoint is essentially a single predicted branch that falls through, so the overhead on the normal path is negligible. When a tool enables it, the tracepoint activates and, on each occurrence, hands a defined record of fields (for example, for a scheduling event: the previous task, the next task, and their priorities) to whatever is listening — ftrace, perf, or an eBPF program. Because the developers named it and promised to keep its fields stable across versions, a script written against a tracepoint keeps working after kernel upgrades in a way a kprobe does not.
It matters because it is the foundation for reliable, portable kernel observability: the most-watched kernel events (scheduling, syscalls, block I/O, networking) are exposed as tracepoints precisely so production tools can depend on them. The trade-off versus a kprobe is coverage versus stability: a tracepoint only exists where a developer chose to add one, so for an arbitrary internal function with no tracepoint you fall back to a kprobe and accept its fragility. The honest caveat: 'stable' is a promise of intent, not an absolute guarantee — tracepoints can still be changed or removed when the kernel evolves, just far less casually than an internal function name.
# attach to the STABLE scheduler-switch tracepoint and count switches per task $ bpftrace -e 'tracepoint:sched:sched_switch { @[args->prev_comm] = count(); }' # the fields args->prev_comm, args->next_comm are part of the tracepoint's documented, stable contract
A tracepoint exposes named, stable fields at a meaningful kernel event, so the script survives kernel upgrades a kprobe-based one might not.
Think of the pairing as a contract: a tracepoint is a stable, documented interface the kernel maintains for observers, whereas a kprobe reaches into private internals — prefer a tracepoint when one exists, fall back to a kprobe only when none does.