Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2025-05-19 - Manual Binary Parsing vs binary.Read
**Learning:** `binary.Read` uses reflection and can be significantly slower (orders of magnitude) than manual parsing using `binary.LittleEndian.UintXX`, especially in hot loops like eBPF event processing. Additionally, `binary.Read` forces full struct allocation and copying, which is wasteful for large structs where only a part of the data is needed (e.g. variable length payloads).
**Action:** For high-frequency binary data deserialization, prefer manual parsing with `encoding/binary` and explicit bounds checking.
177 changes: 89 additions & 88 deletions ebpftracer/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,52 +355,6 @@ func (t EventReason) String() string {
return "unknown: " + strconv.Itoa(int(t))
}

type procEvent struct {
Type EventType
Pid uint32
Reason uint32
}

type tcpEvent struct {
Fd uint64
Timestamp uint64
Duration uint64
Type EventType
Pid uint32
BytesSent uint64
BytesReceived uint64
SPort uint16
DPort uint16
Aport uint16
SAddr [16]byte
DAddr [16]byte
AAddr [16]byte
}

type fileEvent struct {
Type EventType
Pid uint32
Fd uint64
Mnt uint64
Log uint64
}

type l7Event struct {
Fd uint64
ConnectionTimestamp uint64
Pid uint32
Status int32
Duration uint64
Protocol uint8
Method uint8
Padding uint16
StatementId uint32
PayloadSize uint64
ResponseSize uint64
Payload [4096]byte // Must match MAX_PAYLOAD_SIZE in eBPF
Response [4096]byte // Must match MAX_PAYLOAD_SIZE in eBPF
}

// HTTP response fragment event (must match eBPF struct)
type httpResponseFragment struct {
Fd uint64
Expand Down Expand Up @@ -468,78 +422,125 @@ func runEventsReader(name string, r *perf.Reader, ch chan<- Event, typ perfMapTy

switch typ {
case perfMapTypeL7Events:
v := &l7Event{}
data := rec.RawSample

if err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, v); err != nil {
klog.Warningln("failed to read l7 event:", err)
if len(data) < 56 {
klog.Warningln("l7 event too short")
continue
}
fd := binary.LittleEndian.Uint64(data[0:8])
timestamp := binary.LittleEndian.Uint64(data[8:16])
pid := binary.LittleEndian.Uint32(data[16:20])
status := int32(binary.LittleEndian.Uint32(data[20:24]))
duration := binary.LittleEndian.Uint64(data[24:32])
protocol := data[32]
method := data[33]
// padding 2 bytes
statementId := binary.LittleEndian.Uint32(data[36:40])
payloadSizeVal := binary.LittleEndian.Uint64(data[40:48])
responseSizeVal := binary.LittleEndian.Uint64(data[48:56])

const maxPayloadSize = 4096
payloadSize := int(payloadSizeVal)
if payloadSize > maxPayloadSize {
payloadSize = maxPayloadSize
}
responseSize := int(responseSizeVal)
if responseSize > maxPayloadSize {
responseSize = maxPayloadSize
}

// Extract payload data directly from the struct arrays
payloadSize := min(int(v.PayloadSize), len(v.Payload))
responseSize := min(int(v.ResponseSize), len(v.Response))
payloadStart := 56
responseStart := 56 + maxPayloadSize
if len(data) < responseStart+responseSize {
klog.Warningln("l7 event too short for payload/response")
continue
}

// Copy the actual data (preventing garbage from unused buffer space)
payloadData := make([]byte, payloadSize)
copy(payloadData, v.Payload[:payloadSize])
copy(payloadData, data[payloadStart:payloadStart+payloadSize])

responseData := make([]byte, responseSize)
copy(responseData, v.Response[:responseSize])
copy(responseData, data[responseStart:responseStart+responseSize])

req := &l7.RequestData{
Protocol: l7.Protocol(v.Protocol),
Status: l7.Status(v.Status),
Duration: time.Duration(v.Duration),
Method: l7.Method(v.Method),
StatementId: v.StatementId,
PayloadSize: v.PayloadSize,
ResponseSize: v.ResponseSize,
Protocol: l7.Protocol(protocol),
Status: l7.Status(status),
Duration: time.Duration(duration),
Method: l7.Method(method),
StatementId: statementId,
PayloadSize: payloadSizeVal,
ResponseSize: responseSizeVal,
Payload: payloadData,
Response: responseData,
}

event = Event{
Type: EventTypeL7Request,
Pid: v.Pid,
Fd: v.Fd,
Timestamp: v.ConnectionTimestamp,
Pid: pid,
Fd: fd,
Timestamp: timestamp,
L7Request: req,
}
case perfMapTypeFileEvents:
v := &fileEvent{}
if err := binary.Read(bytes.NewBuffer(rec.RawSample), binary.LittleEndian, v); err != nil {
klog.Warningln("failed to read file event:", err)
data := rec.RawSample
if len(data) < 32 {
klog.Warningln("file event too short")
continue
}
event = Event{Type: v.Type, Pid: v.Pid, Fd: v.Fd, Mnt: v.Mnt, Log: v.Log > 0}
typ := EventType(binary.LittleEndian.Uint32(data[0:4]))
pid := binary.LittleEndian.Uint32(data[4:8])
fd := binary.LittleEndian.Uint64(data[8:16])
mnt := binary.LittleEndian.Uint64(data[16:24])
logFlag := binary.LittleEndian.Uint64(data[24:32])

event = Event{Type: typ, Pid: pid, Fd: fd, Mnt: mnt, Log: logFlag > 0}
case perfMapTypeProcEvents:
v := &procEvent{}
if err := binary.Read(bytes.NewBuffer(rec.RawSample), binary.LittleEndian, v); err != nil {
klog.Warningln("failed to read proc event:", err)
data := rec.RawSample
if len(data) < 12 {
klog.Warningln("proc event too short")
continue
}
event = Event{Type: v.Type, Reason: EventReason(v.Reason), Pid: v.Pid}
typ := EventType(binary.LittleEndian.Uint32(data[0:4]))
pid := binary.LittleEndian.Uint32(data[4:8])
reason := binary.LittleEndian.Uint32(data[8:12])

event = Event{Type: typ, Reason: EventReason(reason), Pid: pid}
case perfMapTypeTCPEvents:
v := &tcpEvent{}
if err := binary.Read(bytes.NewBuffer(rec.RawSample), binary.LittleEndian, v); err != nil {
klog.Warningln("failed to read tcp event:", err)
data := rec.RawSample
if len(data) < 102 {
klog.Warningln("tcp event too short")
continue
}
fd := binary.LittleEndian.Uint64(data[0:8])
timestamp := binary.LittleEndian.Uint64(data[8:16])
duration := binary.LittleEndian.Uint64(data[16:24])
typ := EventType(binary.LittleEndian.Uint32(data[24:28]))
pid := binary.LittleEndian.Uint32(data[28:32])
bytesSent := binary.LittleEndian.Uint64(data[32:40])
bytesReceived := binary.LittleEndian.Uint64(data[40:48])
sPort := binary.LittleEndian.Uint16(data[48:50])
dPort := binary.LittleEndian.Uint16(data[50:52])
aPort := binary.LittleEndian.Uint16(data[52:54])

var sAddr, dAddr, aAddr [16]byte
copy(sAddr[:], data[54:70])
copy(dAddr[:], data[70:86])
copy(aAddr[:], data[86:102])

event = Event{
Type: v.Type,
Pid: v.Pid,
SrcAddr: ipPort(v.SAddr, v.SPort),
DstAddr: ipPort(v.DAddr, v.DPort),
ActualDstAddr: ipPort(v.AAddr, v.Aport),
Fd: v.Fd,
Timestamp: v.Timestamp,
Duration: time.Duration(v.Duration),
Type: typ,
Pid: pid,
SrcAddr: ipPort(sAddr, sPort),
DstAddr: ipPort(dAddr, dPort),
ActualDstAddr: ipPort(aAddr, aPort),
Fd: fd,
Timestamp: timestamp,
Duration: time.Duration(duration),
}
if v.Type == EventTypeConnectionClose {
if typ == EventTypeConnectionClose {
event.TrafficStats = &TrafficStats{
BytesSent: v.BytesSent,
BytesReceived: v.BytesReceived,
BytesSent: bytesSent,
BytesReceived: bytesReceived,
}
}
default:
Expand Down
Loading