From 7031211501884555139351bb676fc0592c9df14c Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Tue, 9 Jun 2026 22:18:42 +0300 Subject: feat(parquet): surface epoll_ctl op/target-fd/events metadata epoll_ctl's BPF handler already decodes the operation (args[1]), target descriptor (args[2]), and requested event mask (args[3]->events) into an EpollCtlEvent, but the single resolved-epfd `fd` column was the only epoll detail reaching the output schema. Consumers could not see which descriptor was registered nor the operation performed. Surface the metadata as three additive, backward-compatible columns, mirroring the existing dedicated optional-column convention used by requested_sleep_ns and address_space_bytes: - epoll_op (String): ADD/MOD/DEL, or the raw decimal for unknown ops; empty for non-epoll_ctl rows. - epoll_target_fd (Int32): registered descriptor (args[2]); 0 otherwise. - epoll_events (UInt32): requested event mask; 0 otherwise. Data flows EpollCtlEvent -> event.Pair (new EpollCtl/HasEpoll fields, populated in handleEpollCtlExit) -> streamrow.Row -> parquet.Record. The op-to-string mapping lives on event.EpollCtl.OpName. Docs (docs/parquet-querying.md) and the Magefile parquetValidate column list updated in lockstep (also adding the previously-undocumented address_space_bytes/requested_sleep_ns columns). The polling parquet integration test now asserts epoll_ctl rows carry a decoded op and a valid target fd, and that other syscalls leave epoll_op empty. Co-Authored-By: Claude Opus 4.8 --- internal/streamrow/row.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'internal/streamrow') diff --git a/internal/streamrow/row.go b/internal/streamrow/row.go index a6ccdf7..c846346 100644 --- a/internal/streamrow/row.go +++ b/internal/streamrow/row.go @@ -30,6 +30,13 @@ type Row struct { RetVal int64 IsError bool FD int32 + // EpollOp is the epoll_ctl operation as a readable token (ADD/MOD/DEL), + // empty for non-epoll_ctl rows. EpollTargetFD and EpollEvents hold the + // registered descriptor (args[2]) and requested event mask (args[3]->events) + // for epoll_ctl rows; both are zero when EpollOp is empty. + EpollOp string + EpollTargetFD int32 + EpollEvents uint32 } func (r Row) SyscallValue() string { @@ -122,6 +129,15 @@ func New(seq uint64, pair *event.Pair) Row { row.FD = fd } + // Surface epoll_ctl control metadata when present. The Pair's FD/File still + // reflect the epoll instance (epfd); these fields expose the target fd and + // operation so consumers can see which descriptor was registered. + if pair.HasEpoll { + row.EpollOp = pair.Epoll.OpName() + row.EpollTargetFD = pair.Epoll.TargetFD + row.EpollEvents = pair.Epoll.Events + } + if retEv, ok := pair.ExitEv.(*types.RetEvent); ok { row.RetVal = retEv.Ret row.IsError = retEv.Ret < 0 -- cgit v1.2.3