Add syscall durations (-T) and process-name annotation for follow-forks#137
Conversation
The sys_enter program now records a bpf_ktime_get_ns() timestamp in the enter map; submit_compact_payload() computes the elapsed time at exit and carries it in a new duration_ns wire header field (wire version bumped to 5). The enter map entry is now removed at submit time rather than in get_args() so the timestamp survives until the event is built; entries for aborted paths are overwritten by the tid's next syscall enter. The client's new -T/--syscall-times flag appends the time spent in each syscall to the trace line, strace-style: ` <0.000123>`. Events with no matching enter (e.g. execve, which uses its own enter tracepoint) simply omit the suffix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds end-to-end support for syscall timing (-T/--syscall-times) and process-name (comm) annotations (used for -f/--follow-forks) by extending the wire header and updating both eBPF emission and userspace formatting.
Changes:
- Extend the wire protocol (
WIRE_VERSION→ 5) to carryduration_nsandcomminWireEventHeader. - Record syscall entry timestamps in eBPF and compute elapsed time at exit; plumb through client formatting under
-T. - Annotate follow-forks output with a fixed-width process name field derived from
bpf_get_current_comm, with new unit/integration tests.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents -T syscall timing and -f comm annotations. |
| pinchy/tests/integration.rs | Updates follow-forks expectations to include [comm] and adds an integration test for -T. |
| pinchy/src/tests/mod.rs | Adds unit tests for duration suffix and comm annotation formatting. |
| pinchy/src/formatting.rs | Adds formatter support for duration suffix and comm prefixing. |
| pinchy/src/client.rs | Adds -T CLI flag and conditionally enables duration/comm formatting when relaying events. |
| pinchy-ebpf/src/util.rs | Computes duration at submit time, removes enter-map entries, and fills comm in-place in ringbuf memory. |
| pinchy-ebpf/src/main.rs | Records enter_ns on sys_enter for later duration computation. |
| pinchy-common/src/lib.rs | Bumps wire version and extends WireEventHeader with duration_ns + comm. |
| let tid = ctx.pid(); | ||
| let mut duration_ns = 0u64; | ||
|
|
||
| if let Some(enter_data) = unsafe { ENTER_MAP.get(&tid) } { | ||
| if enter_data.syscall_nr == syscall_nr { | ||
| duration_ns = unsafe { aya_ebpf::helpers::bpf_ktime_get_ns() } | ||
| .saturating_sub(enter_data.enter_ns); | ||
|
|
||
| let _ = unsafe { ENTER_MAP.remove(&tid) }; | ||
| } | ||
| } |
There was a problem hiding this comment.
Addressed: the ENTER_MAP entry removal moved back to get_args() (which runs before any reserve attempt) as part of reworking duration/comm capture into a per-CPU exit context — that rework also fixes the x86_64 linker failure, together with splitting the oversized trivial handler in two. comm bytes are now sanitized (non-printable bytes become '?') before being written to the terminal.
| if let Some(comm) = comm { | ||
| let len = comm.iter().position(|&b| b == 0).unwrap_or(comm.len()); | ||
|
|
||
| match self.style { | ||
| FormattingStyle::OneLine => { | ||
| let len = len.min(COMM_DISPLAY_WIDTH); | ||
| let mut field = [b' '; COMM_DISPLAY_WIDTH]; | ||
| field[..len].copy_from_slice(&comm[..len]); | ||
|
|
||
| output.write_all(b" [").await?; | ||
| output.write_all(&field).await?; | ||
| output.write_all(b"]").await?; | ||
| } | ||
| FormattingStyle::MultiLine => { | ||
| output.write_all(b" ").await?; | ||
| output | ||
| .write_all(&comm[..len.min(MULTILINE_COMM_MAX)]) | ||
| .await?; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Addressed: the ENTER_MAP entry removal moved back to get_args() (which runs before any reserve attempt) as part of reworking duration/comm capture into a per-CPU exit context — that rework also fixes the x86_64 linker failure, together with splitting the oversized trivial handler in two. comm bytes are now sanitized (non-printable bytes become '?') before being written to the terminal.
The wire header gains a fixed-size comm field (16 bytes, the kernel's
TASK_COMM_LEN). With -f, the client annotates every line with the
process name next to the PID, so interleaved output from several
processes stays readable.
Capturing comm (and the syscall duration) in every monomorphization
of submit_compact_payload() pushed the larger eBPF programs past the
BPF branch offset range at link time ("Branch target out of insn
range"), x86_64 being the first to overflow. Both values are now
captured once per exit, in the dispatching exit program before the
tail call, into a per-CPU scratch map; the submit path only does a
cheap per-CPU read. Tracepoint programs are not preempted, so the
slot is stable across the tail call.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One-line traces now show the process name as a fixed-width bracketed field after the PID (`1317891 [ls ] brk(...)`). The field is 8 characters wide to keep the columns compact; longer names are truncated. The multi-line style instead puts the name unpadded on the header line (`1322191 sh`), bounded at 65 bytes for when richer process information (full command line, terminal-width awareness) uses the room later. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Final PR from the review pass (after #132-#136): per-syscall durations and process-name annotations for follow-forks output.
Syscall durations (
-T/--syscall-times) — the sys_enter program records abpf_ktime_get_ns()timestamp in the enter map;submit_compact_payload()computes the elapsed time at exit and carries it in a newduration_nswire header field (WIRE_VERSIONbumped to 5). The enter-map entry is now removed at submit time rather than inget_args()so the timestamp survives until the event is built; entries from aborted paths are overwritten by the tid's next syscall enter. The client flag appends the time strace-style:) = 0 (success) <0.000123>. Events with no matching enter (e.g. execve, which has its own enter tracepoint) just omit the suffix.Process-name annotation for
-f— the wire header gains a fixed-sizecommfield (16 bytes, the kernel'sTASK_COMM_LEN), filled in place in the reserved ringbuf memory with the rawbpf_get_current_commhelper. Going through the safe aya wrapper would cost a stack buffer + copy in every monomorphization of the submit path, which pushed the eBPF programs past the BPF branch-offset range ("Branch target out of insn range" at link time); the in-place write keeps them small and is cheaper anyway.With
-f, one-line output annotates every line with a fixed-width bracketed name field for readable interleaving:The field is 8 characters (
COMM_LEN / 2); longer names truncate to keep columns aligned. The multi-line style instead puts the name unpadded on the header line (1322191 sh), bounded at 65 bytes for when richer process information (full command line, terminal-width awareness) uses the room later. Since execve exits are reported by the new program image, the name follows exec correctly:shforkinglsshows<sh>for the fork andlsfrom the exec onward.🤖 Generated with Claude Code