diff options
| -rw-r--r-- | internal/types/fastdecode.go | 93 | ||||
| -rw-r--r-- | internal/types/fastdecode_test.go | 103 |
2 files changed, 158 insertions, 38 deletions
diff --git a/internal/types/fastdecode.go b/internal/types/fastdecode.go index 5b0f588..5cb9b64 100644 --- a/internal/types/fastdecode.go +++ b/internal/types/fastdecode.go @@ -3,41 +3,58 @@ package types import "encoding/binary" const ( - openEventSize = 300 - execEventSize = 304 - nullEventSize = 24 - fdEventSize = 28 - retEventSize = 36 - nameEventSize = 536 - pathEventSize = 280 - fcntlEventSize = 40 - dup3EventSize = 32 - openByHandleAtEventSize = 28 - socketEventSize = 36 - socketpairEventSize = 56 - socketpairEventSizeV1 = 52 - acceptEventSize = 40 - acceptEventSizeV1 = 36 - pipeEventSize = 48 - pipeEventSizeV1 = 44 - eventfdEventSize = 40 - eventfdEventSizeV1 = 36 - epollCtlEventSize = 40 - twoFdEventSize = 40 - pollEventSize = 40 - pollEventSizeV1 = 36 - memEventSize = 56 - sleepEventSize = 32 - keyctlEventSize = 40 - ptraceEventSize = 48 - perfOpenEventSize = 56 + // Sizes below are the kernel ringbuf payload sizes, i.e. sizeof(struct ...) + // from internal/c/types.h compiled for the BPF/LP64 target. The kernel + // reserves sizeof(struct) via bpf_ringbuf_reserve, which for structs whose + // last field is 32-bit but that also contain a __u64 includes trailing + // padding to the struct's 8-byte alignment. The fast path must therefore + // gate on sizeof, not on the sum of field widths, or it silently falls back + // to the slow binary.Read path on the hot events (open/read/write/ret/...). + // + // The *V1 constants are the historical, unpadded field-sum sizes still + // produced by Go's binary.Write (used by tests and by Bytes()); the fast + // decoders accept both so test payloads and kernel payloads both hit the + // fast path. The trailing pad bytes carry no data and are ignored. + openEventSize = 304 + openEventSizeV1 = 300 + execEventSize = 304 + nullEventSize = 24 + fdEventSize = 32 + fdEventSizeV1 = 28 + retEventSize = 40 + retEventSizeV1 = 36 + nameEventSize = 536 + pathEventSize = 280 + fcntlEventSize = 40 + dup3EventSize = 32 + openByHandleAtEventSize = 32 + openByHandleAtEventSizeV1 = 28 + socketEventSize = 40 + socketEventSizeV1 = 36 + socketpairEventSize = 56 + socketpairEventSizeV1 = 52 + acceptEventSize = 40 + acceptEventSizeV1 = 36 + pipeEventSize = 48 + pipeEventSizeV1 = 44 + eventfdEventSize = 40 + eventfdEventSizeV1 = 36 + epollCtlEventSize = 40 + twoFdEventSize = 40 + pollEventSize = 40 + pollEventSizeV1 = 36 + memEventSize = 56 + sleepEventSize = 32 + keyctlEventSize = 40 + ptraceEventSize = 48 + perfOpenEventSize = 56 ) func NewOpenEventFast(raw []byte) *OpenEvent { - if len(raw) < openEventSize { + if len(raw) < openEventSizeV1 { return nil } - if len(raw) != openEventSize { + if len(raw) != openEventSize && len(raw) != openEventSizeV1 { return NewOpenEvent(raw) } o := poolOfOpenEvents.Get().(*OpenEvent) @@ -89,10 +106,10 @@ func NewNullEventFast(raw []byte) *NullEvent { } func NewFdEventFast(raw []byte) *FdEvent { - if len(raw) < fdEventSize { + if len(raw) < fdEventSizeV1 { return nil } - if len(raw) != fdEventSize { + if len(raw) != fdEventSize && len(raw) != fdEventSizeV1 { return NewFdEvent(raw) } f := poolOfFdEvents.Get().(*FdEvent) @@ -106,10 +123,10 @@ func NewFdEventFast(raw []byte) *FdEvent { } func NewRetEventFast(raw []byte) *RetEvent { - if len(raw) < retEventSize { + if len(raw) < retEventSizeV1 { return nil } - if len(raw) != retEventSize { + if len(raw) != retEventSize && len(raw) != retEventSizeV1 { return NewRetEvent(raw) } r := poolOfRetEvents.Get().(*RetEvent) @@ -196,10 +213,10 @@ func NewDup3EventFast(raw []byte) *Dup3Event { } func NewOpenByHandleAtEventFast(raw []byte) *OpenByHandleAtEvent { - if len(raw) < openByHandleAtEventSize { + if len(raw) < openByHandleAtEventSizeV1 { return nil } - if len(raw) != openByHandleAtEventSize { + if len(raw) != openByHandleAtEventSize && len(raw) != openByHandleAtEventSizeV1 { return NewOpenByHandleAtEvent(raw) } o := poolOfOpenByHandleAtEvents.Get().(*OpenByHandleAtEvent) @@ -213,10 +230,10 @@ func NewOpenByHandleAtEventFast(raw []byte) *OpenByHandleAtEvent { } func NewSocketEventFast(raw []byte) *SocketEvent { - if len(raw) < socketEventSize { + if len(raw) < socketEventSizeV1 { return nil } - if len(raw) != socketEventSize { + if len(raw) != socketEventSize && len(raw) != socketEventSizeV1 { return NewSocketEvent(raw) } s := poolOfSocketEvents.Get().(*SocketEvent) diff --git a/internal/types/fastdecode_test.go b/internal/types/fastdecode_test.go index d09f6a9..31ad386 100644 --- a/internal/types/fastdecode_test.go +++ b/internal/types/fastdecode_test.go @@ -544,6 +544,109 @@ func TestNewSleepEventFastKernelLayout(t *testing.T) { } } +// fillCommonHeader writes the shared 24-byte event prefix +// (event_type, trace_id, time, pid, tid) into raw. +func fillCommonHeader(raw []byte, et EventType, tid TraceId) { + binary.LittleEndian.PutUint32(raw[0:4], uint32(et)) + binary.LittleEndian.PutUint32(raw[4:8], uint32(tid)) + binary.LittleEndian.PutUint64(raw[8:16], 111) + binary.LittleEndian.PutUint32(raw[16:20], 22) + binary.LittleEndian.PutUint32(raw[20:24], 33) +} + +// The five tests below feed the *padded* kernel payload (sizeof(struct ...) +// reserved by bpf_ringbuf_reserve, which Go's binary.Write does NOT emit) and +// assert the fast path decodes it correctly. They guard against the regression +// where the fast-size gate used the field-sum size and silently dropped to the +// slow binary.Read path on the hottest events (open/read/write/ret). + +func TestNewOpenEventFastKernelLayout(t *testing.T) { + raw := make([]byte, openEventSize) // 304: sizeof(struct open_event) + fillCommonHeader(raw, ENTER_OPEN_EVENT, SYS_ENTER_OPENAT) + flags := int32(-7) + binary.LittleEndian.PutUint32(raw[24:28], uint32(flags)) + copy(raw[28:284], "hi") + copy(raw[284:300], "bash") + + fast := NewOpenEventFast(raw) + if fast == nil { + t.Fatalf("expected decoded open event for padded kernel payload") + } + defer fast.Recycle() + if fast.Time != 111 || fast.Pid != 22 || fast.Tid != 33 || fast.Flags != -7 || + string(fast.Comm[:4]) != "bash" { + t.Fatalf("unexpected open decode: %#v", fast) + } +} + +func TestNewFdEventFastKernelLayout(t *testing.T) { + raw := make([]byte, fdEventSize) // 32: sizeof(struct fd_event) + fillCommonHeader(raw, ENTER_FD_EVENT, SYS_ENTER_READ) + binary.LittleEndian.PutUint32(raw[24:28], uint32(int32(9))) + + fast := NewFdEventFast(raw) + if fast == nil { + t.Fatalf("expected decoded fd event for padded kernel payload") + } + defer fast.Recycle() + if fast.Time != 111 || fast.Pid != 22 || fast.Tid != 33 || fast.Fd != 9 { + t.Fatalf("unexpected fd decode: %#v", fast) + } +} + +func TestNewRetEventFastKernelLayout(t *testing.T) { + raw := make([]byte, retEventSize) // 40: sizeof(struct ret_event) + fillCommonHeader(raw, EXIT_RET_EVENT, SYS_EXIT_READ) + // ret_event has Ret before Pid/Tid; rewrite pid/tid at their real offsets. + ret := int64(-5) + binary.LittleEndian.PutUint64(raw[16:24], uint64(ret)) + binary.LittleEndian.PutUint32(raw[24:28], 22) + binary.LittleEndian.PutUint32(raw[28:32], 33) + binary.LittleEndian.PutUint32(raw[32:36], READ_CLASSIFIED) + + fast := NewRetEventFast(raw) + if fast == nil { + t.Fatalf("expected decoded ret event for padded kernel payload") + } + defer fast.Recycle() + if fast.Time != 111 || fast.Ret != -5 || fast.Pid != 22 || fast.Tid != 33 || + fast.RetType != READ_CLASSIFIED { + t.Fatalf("unexpected ret decode: %#v", fast) + } +} + +func TestNewSocketEventFastKernelLayout(t *testing.T) { + raw := make([]byte, socketEventSize) // 40: sizeof(struct socket_event) + fillCommonHeader(raw, ENTER_SOCKET_EVENT, SYS_ENTER_SOCKET) + binary.LittleEndian.PutUint32(raw[24:28], uint32(int32(2))) + binary.LittleEndian.PutUint32(raw[28:32], uint32(int32(1))) + binary.LittleEndian.PutUint32(raw[32:36], uint32(int32(6))) + + fast := NewSocketEventFast(raw) + if fast == nil { + t.Fatalf("expected decoded socket event for padded kernel payload") + } + defer fast.Recycle() + if fast.Time != 111 || fast.Family != 2 || fast.Type != 1 || fast.Protocol != 6 { + t.Fatalf("unexpected socket decode: %#v", fast) + } +} + +func TestNewOpenByHandleAtEventFastKernelLayout(t *testing.T) { + raw := make([]byte, openByHandleAtEventSize) // 32: sizeof(struct open_by_handle_at_event) + fillCommonHeader(raw, ENTER_OPEN_BY_HANDLE_AT_EVENT, SYS_ENTER_OPEN_BY_HANDLE_AT) + binary.LittleEndian.PutUint32(raw[24:28], uint32(int32(3))) + + fast := NewOpenByHandleAtEventFast(raw) + if fast == nil { + t.Fatalf("expected decoded open_by_handle_at event for padded kernel payload") + } + defer fast.Recycle() + if fast.Time != 111 || fast.Pid != 22 || fast.Tid != 33 || fast.Flags != 3 { + t.Fatalf("unexpected open_by_handle_at decode: %#v", fast) + } +} + func TestFastDecodersReturnNilOnShortPayload(t *testing.T) { cases := []struct { name string |
