Skip to content

Commit 3b8c0f6

Browse files
committed
perf(ebpf): improve NUL byte trimming
Introduce utils.TrimTrailingNUL function to handle NUL byte trimming in a more efficient manner. Running tool: /home/gg/.goenv/versions/1.24.0/bin/go test -benchmem -run=^$ -tags ebpf -bench ^(BenchmarkBytesTrimRight_WithNUL|BenchmarkBytesTrimRight_NoNUL| BenchmarkTrimTrailingNUL_WithNUL|BenchmarkTrimTrailingNUL_NoNUL)$ github.com/aquasecurity/tracee/pkg/utils -benchtime=200000000x -count=5 timeout=5m goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/utils cpu: AMD Ryzen 9 7950X 16-Core Processor | Benchmark | Avg Time (ns/op) | Improvement vs TrimRight | |-------------------------|------------------|--------------------------| | TrimTrailingNUL_WithNUL | 1.980 ns | 17.1% faster | | BytesTrimRight_WithNUL | 2.388 ns | — | | TrimTrailingNUL_NoNUL | 1.311 ns | 26.9% faster | | BytesTrimRight_NoNUL | 1.794 ns | — |
1 parent d7861e6 commit 3b8c0f6

File tree

7 files changed

+148
-14
lines changed

7 files changed

+148
-14
lines changed

pkg/ebpf/capture.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package ebpf
22

33
import (
4-
"bytes"
54
"context"
65
"fmt"
76
"io"
@@ -136,7 +135,7 @@ func (t *Tracee) handleFileCaptures(ctx context.Context) {
136135
t.handleError(err)
137136
continue
138137
}
139-
bpfName := string(bytes.TrimRight(bpfObjectMeta.Name[:], "\x00"))
138+
bpfName := utils.TrimTrailingNUL(bpfObjectMeta.Name[:])
140139
filename = fmt.Sprintf("bpf.name-%s", bpfName)
141140
if bpfObjectMeta.Pid != 0 {
142141
filename = fmt.Sprintf("%s.pid-%d", filename, bpfObjectMeta.Pid)

pkg/ebpf/events_pipeline.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package ebpf
22

33
import (
4-
"bytes"
54
"context"
65
"encoding/binary"
76
"fmt"
@@ -211,6 +210,9 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch
211210
PodUID: containerInfo.Pod.UID,
212211
}
213212

213+
commStr := string(utils.TrimTrailingNUL(eCtx.Comm[:])) // clean potential trailing null
214+
utsNameStr := string(utils.TrimTrailingNUL(eCtx.UtsName[:])) // clean potential trailing null
215+
214216
flags := parseContextFlags(containerData.ID, eCtx.Flags)
215217
syscall := ""
216218
if eCtx.Syscall != noSyscall {
@@ -221,8 +223,6 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch
221223
id := events.ID(eCtx.Syscall)
222224
syscallDef := events.Core.GetDefinitionByID(id)
223225
if syscallDef.NotValid() {
224-
commStr := string(eCtx.Comm[:bytes.IndexByte(eCtx.Comm[:], 0)])
225-
utsNameStr := string(eCtx.UtsName[:bytes.IndexByte(eCtx.UtsName[:], 0)])
226226
logger.Debugw(
227227
fmt.Sprintf("Event %s with an invalid syscall id %d", evtName, id),
228228
"Comm", commStr,
@@ -254,8 +254,8 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch
254254
evt.UserID = int(eCtx.Uid)
255255
evt.MountNS = int(eCtx.MntID)
256256
evt.PIDNS = int(eCtx.PidID)
257-
evt.ProcessName = string(bytes.TrimRight(eCtx.Comm[:], "\x00")) // set and clean potential trailing null
258-
evt.HostName = string(bytes.TrimRight(eCtx.UtsName[:], "\x00")) // set and clean potential trailing null
257+
evt.ProcessName = commStr
258+
evt.HostName = utsNameStr
259259
evt.CgroupID = uint(eCtx.CgroupID)
260260
evt.ContainerID = containerData.ID
261261
evt.Container = containerData

pkg/ebpf/events_pipeline_bench_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package ebpf
22

33
import (
4-
"bytes"
54
"sync"
65
"testing"
76
"time"
@@ -87,8 +86,8 @@ func BenchmarkGetEventFromPool(b *testing.B) {
8786
evt.UserID = int(ctx.Uid)
8887
evt.MountNS = int(ctx.MntID)
8988
evt.PIDNS = int(ctx.PidID)
90-
evt.ProcessName = string(bytes.TrimRight(ctx.Comm[:], "\x00"))
91-
evt.HostName = string(bytes.TrimRight(ctx.UtsName[:], "\x00"))
89+
evt.ProcessName = string(utils.TrimTrailingNUL(ctx.Comm[:]))
90+
evt.HostName = string(utils.TrimTrailingNUL(ctx.UtsName[:]))
9291
evt.CgroupID = uint(ctx.CgroupID)
9392
evt.ContainerID = containerData.ID
9493
evt.Container = containerData
@@ -247,8 +246,8 @@ func BenchmarkNewEventObject(b *testing.B) {
247246
UserID: int(ctx.Uid),
248247
MountNS: int(ctx.MntID),
249248
PIDNS: int(ctx.PidID),
250-
ProcessName: string(bytes.TrimRight(ctx.Comm[:], "\x00")),
251-
HostName: string(bytes.TrimRight(ctx.UtsName[:], "\x00")),
249+
ProcessName: string(utils.TrimTrailingNUL(ctx.Comm[:])),
250+
HostName: string(utils.TrimTrailingNUL(ctx.UtsName[:])),
252251
CgroupID: uint(ctx.CgroupID),
253252
ContainerID: containerData.ID,
254253
Container: containerData,

pkg/ebpf/processor_funcs.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package ebpf
22

33
import (
4-
"bytes"
54
"errors"
65
"fmt"
76
"os"
@@ -334,7 +333,7 @@ func (t *Tracee) processPrintMemDump(event *trace.Event) error {
334333
if err := unix.Uname(&utsName); err != nil {
335334
return errfmt.WrapError(err)
336335
}
337-
arch = string(bytes.TrimRight(utsName.Machine[:], "\x00"))
336+
arch = string(utils.TrimTrailingNUL(utsName.Machine[:]))
338337
err = events.SetArgValue(event, "arch", arch)
339338
if err != nil {
340339
return err

pkg/utils/utils.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,23 @@ func ReverseString(s string) string {
6363
}
6464
return string(bytes)
6565
}
66+
67+
// TrimTrailingNUL returns a subslice of the input with all trailing NUL bytes (0x00) removed.
68+
// It performs a reverse scan and returns b[:end], avoiding any allocations.
69+
//
70+
// This function is optimized for fixed-size, ASCII-compatible C-style buffers where padding
71+
// with trailing NULs may occur.
72+
//
73+
// Note:
74+
// - The returned slice shares memory with the original input.
75+
// - If you need an independent string or slice, copy it manually.
76+
// - This function is not safe for UTF-8 or multibyte character data; it assumes ASCII content only.
77+
func TrimTrailingNUL(b []byte) []byte {
78+
end := len(b)
79+
80+
for end > 0 && b[end-1] == 0 {
81+
end--
82+
}
83+
84+
return b[:end]
85+
}

pkg/utils/utils_bench_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package utils
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
var result []byte
9+
10+
func BenchmarkBytesTrimRight_WithNUL(b *testing.B) {
11+
var arr [64]byte
12+
for i := 0; i < 61; i++ {
13+
arr[i] = 'A'
14+
}
15+
// last 3 bytes are \x00 by default
16+
17+
for b.Loop() {
18+
result = bytes.TrimRight(arr[:], "\x00")
19+
}
20+
_ = result
21+
}
22+
23+
func BenchmarkBytesTrimRight_NoNUL(b *testing.B) {
24+
var arr [64]byte
25+
for i := 0; i < 64; i++ {
26+
arr[i] = 'A'
27+
}
28+
29+
for b.Loop() {
30+
result = bytes.TrimRight(arr[:], "\x00")
31+
}
32+
_ = result
33+
}
34+
35+
func BenchmarkTrimTrailingNUL_WithNUL(b *testing.B) {
36+
var arr [64]byte
37+
for i := 0; i < 61; i++ {
38+
arr[i] = 'A'
39+
}
40+
// last 3 bytes are \x00 by default
41+
42+
for b.Loop() {
43+
result = TrimTrailingNUL(arr[:])
44+
}
45+
_ = result
46+
}
47+
48+
func BenchmarkTrimTrailingNUL_NoNUL(b *testing.B) {
49+
var arr [64]byte
50+
for i := 0; i < 64; i++ {
51+
arr[i] = 'A'
52+
}
53+
54+
for b.Loop() {
55+
result = TrimTrailingNUL(arr[:])
56+
}
57+
_ = result
58+
}

pkg/utils/utils_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package utils
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestTrimTrailingNUL(t *testing.T) {
10+
t.Parallel()
11+
12+
tests := []struct {
13+
name string
14+
input []byte
15+
expected []byte
16+
}{
17+
{
18+
name: "no NUL",
19+
input: []byte("hello"),
20+
expected: []byte("hello"),
21+
},
22+
{
23+
name: "single trailing NUL",
24+
input: []byte("hello\x00"),
25+
expected: []byte("hello"),
26+
},
27+
{
28+
name: "multiple trailing NULs",
29+
input: []byte("hello\x00\x00\x00"),
30+
expected: []byte("hello"),
31+
},
32+
{
33+
name: "intermediate NUL (preserved)",
34+
input: []byte("he\x00llo\x00"),
35+
expected: []byte("he\x00llo"),
36+
},
37+
{
38+
name: "all NULs",
39+
input: []byte("\x00\x00\x00"),
40+
expected: []byte(""),
41+
},
42+
{
43+
name: "empty slice",
44+
input: []byte(""),
45+
expected: []byte(""),
46+
},
47+
{
48+
name: "no trailing NUL but contains middle NUL",
49+
input: []byte("\x00he\x00llo"),
50+
expected: []byte("\x00he\x00llo"),
51+
},
52+
}
53+
54+
for _, tt := range tests {
55+
t.Run(tt.name, func(t *testing.T) {
56+
require.Equal(t, tt.expected, TrimTrailingNUL(tt.input))
57+
})
58+
}
59+
}

0 commit comments

Comments
 (0)