From 34f4950b061b82de07be909e1706fe261eefb4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 13 Jan 2025 18:46:28 -0300 Subject: [PATCH 01/25] chore(bufferdecode): add DecodeArguments benchmark Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^BenchmarkDecodeArguments$ github.com/aquasecurity/tracee/pkg/bufferdecoder -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/bufferdecoder cpu: AMD Ryzen 9 7950X 16-Core Processor BenchmarkDecodeArguments-32 100000000 206.3 ns/op 512 B/op 1 alloc/op PASS ok github.com/aquasecurity/tracee/pkg/bufferdecoder 20.646s --- pkg/bufferdecoder/decoder_test.go | 78 +++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/pkg/bufferdecoder/decoder_test.go b/pkg/bufferdecoder/decoder_test.go index 6d7900b6ff3c..d6bbf43ac63f 100644 --- a/pkg/bufferdecoder/decoder_test.go +++ b/pkg/bufferdecoder/decoder_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" + "github.com/aquasecurity/tracee/pkg/events" "github.com/aquasecurity/tracee/types/trace" ) @@ -1005,3 +1006,80 @@ func BenchmarkBinaryMprotectWriteMeta(*testing.B) { binary.Read(binBuf, binary.LittleEndian, &s) } } + +func BenchmarkDecodeArguments(b *testing.B) { + /* + args := []trace.Argument{ + { + Name: "arg1", + Type: "u64", + Value: 1, + }, + { + Name: "arg2", + Type: "u64", + Value: 2, + }, + { + Name: "arg3", + Type: "u64", + Value: 3, + }, + ... + } + ****************** + buffer is the []byte representation of args instance + ****************** + */ + + buffer := []byte{ + 0, 1, 0, 0, 0, 0, 0, 0, 0, // arg1 + 1, 2, 0, 0, 0, 0, 0, 0, 0, // arg2 + 2, 3, 0, 0, 0, 0, 0, 0, 0, // arg3 + 3, 4, 0, 0, 0, 0, 0, 0, 0, // arg4 + 4, 5, 0, 0, 0, 0, 0, 0, 0, // arg5 + 5, 6, 0, 0, 0, 0, 0, 0, 0, // arg6 + 6, 7, 0, 0, 0, 0, 0, 0, 0, // arg7 + 7, 8, 0, 0, 0, 0, 0, 0, 0, // arg8 + } + evtFields := []trace.ArgMeta{ + {Name: "arg1", Type: "u64", Zero: 0}, + {Name: "arg2", Type: "u64", Zero: 0}, + {Name: "arg3", Type: "u64", Zero: 0}, + {Name: "arg4", Type: "u64", Zero: 0}, + {Name: "arg5", Type: "u64", Zero: 0}, + {Name: "arg6", Type: "u64", Zero: 0}, + {Name: "arg7", Type: "u64", Zero: 0}, + {Name: "arg8", Type: "u64", Zero: 0}, + } + + // decode half of the arguments leaving the rest to be populated as zero values + argnum := len(evtFields) / 2 + + evtVersion := events.NewVersion(1, 0, 0) + evtName := "test" + eventId := events.ID(0) + evtDef := events.NewDefinition( + eventId, + eventId+1000, + evtName, + evtVersion, + "", + "", + false, + false, + []string{}, + events.Dependencies{}, + evtFields, // fields + nil, + ) + + events.Core.AddBatch(map[events.ID]events.Definition{eventId: evtDef}) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + decoder := New(buffer) + args := make([]trace.Argument, len(evtFields)) + _ = decoder.DecodeArguments(args, argnum, evtFields, evtName, eventId) + } +} From 76902b5db10b3e345a6cc058957b24730d567831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 13 Jan 2025 20:10:26 -0300 Subject: [PATCH 02/25] chore(bufferdecoder): set zero from def fields It's a cosmetic change to make the code more readable. --- pkg/bufferdecoder/decoder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/bufferdecoder/decoder.go b/pkg/bufferdecoder/decoder.go index 4acb3f5407a5..8b7df285c02f 100644 --- a/pkg/bufferdecoder/decoder.go +++ b/pkg/bufferdecoder/decoder.go @@ -111,7 +111,7 @@ func (decoder *EbpfDecoder) DecodeArguments(args []trace.Argument, argnum int, e for i := 0; i < len(evtFields); i++ { if args[i].Value == nil { args[i].ArgMeta = evtFields[i] - args[i].Value = args[i].Zero + args[i].Value = evtFields[i].Zero } } return nil From 000e1220b4d510bc3e33436b0e4a21a09a9ae4d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 13 Jan 2025 15:54:15 -0300 Subject: [PATCH 03/25] perf: reduce events.Core lock contention When retrieving the event definition, there is no longer a need to check beforehand Core.IsDefined(). Validation can now be performed directly using the NotValid() method on the Definition type returned by GetEventDefinitionID() and GetEventDefinitionName(). Besides the lock contention reduction, this also gets rid of the window where the event definition could be changed between the check and the actual use of the definition. This also fixes a wrong logger usage in the pipeline. --- pkg/ebpf/controlplane/signal.go | 7 ++++--- pkg/ebpf/event_parameters.go | 9 ++++----- pkg/ebpf/events_pipeline.go | 14 ++++++++------ pkg/ebpf/tracee.go | 5 ++--- pkg/events/definition.go | 4 ++++ pkg/events/definition_group.go | 27 +++++++++++++++++++++++++-- pkg/events/definition_group_test.go | 26 ++++++++++++++++++++++++++ pkg/events/parse_args.go | 11 +++++------ pkg/policy/v1beta1/policy_file.go | 6 ++---- pkg/server/grpc/tracee.go | 5 ++--- 10 files changed, 82 insertions(+), 32 deletions(-) diff --git a/pkg/ebpf/controlplane/signal.go b/pkg/ebpf/controlplane/signal.go index 8836d05d13c4..da5aa9cea294 100644 --- a/pkg/ebpf/controlplane/signal.go +++ b/pkg/ebpf/controlplane/signal.go @@ -26,10 +26,11 @@ func (sig *signal) Unmarshal(buffer []byte) error { return errfmt.Errorf("failed to decode signal argnum: %v", err) } - if !events.Core.IsDefined(sig.id) { - return errfmt.Errorf("failed to get event %d configuration", sig.id) - } eventDefinition := events.Core.GetDefinitionByID(sig.id) + if eventDefinition.NotValid() { + return errfmt.Errorf("%d is not a valid event id", sig.id) + } + evtFields := eventDefinition.GetFields() evtName := eventDefinition.GetName() sig.args = make([]trace.Argument, len(evtFields)) diff --git a/pkg/ebpf/event_parameters.go b/pkg/ebpf/event_parameters.go index 4a205572a7c7..e97fa08fdf33 100644 --- a/pkg/ebpf/event_parameters.go +++ b/pkg/ebpf/event_parameters.go @@ -85,15 +85,14 @@ func getSyscallsFromParams(eventParams []map[string]filters.Filter[*filters.Stri } syscallID := events.ID(syscallIDInt) - if !events.Core.IsDefined(syscallID) { - return syscalls, errfmt.Errorf("syscall id %d is not defined", syscallID) + syscallDef := events.Core.GetDefinitionByID(events.ID(syscallID)) + if syscallDef.NotValid() { + return syscalls, errfmt.Errorf("syscall id %d is not valid", syscallID) } - syscallName := events.Core.GetDefinitionByID(syscallID).GetName() - syscalls = append(syscalls, syscallInfo{ id: syscallID, - name: syscallName, + name: syscallDef.GetName(), }) } } diff --git a/pkg/ebpf/events_pipeline.go b/pkg/ebpf/events_pipeline.go index fa42d474484b..2baaa36b638b 100644 --- a/pkg/ebpf/events_pipeline.go +++ b/pkg/ebpf/events_pipeline.go @@ -176,11 +176,12 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch continue } eventId := events.ID(eCtx.EventID) - if !events.Core.IsDefined(eventId) { + eventDefinition := events.Core.GetDefinitionByID(eventId) + if eventDefinition.NotValid() { t.handleError(errfmt.Errorf("failed to get configuration of event %d", eventId)) continue } - eventDefinition := events.Core.GetDefinitionByID(eventId) + evtFields := eventDefinition.GetFields() evtName := eventDefinition.GetName() args := make([]trace.Argument, len(evtFields)) @@ -217,11 +218,12 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch // For 32-bit (compat) processes, the syscall ID gets translated in eBPF to the event ID of its // 64-bit counterpart, or if it's a 32-bit exclusive syscall, to the event ID corresponding to it. id := events.ID(eCtx.Syscall) - if events.Core.IsDefined(id) { - syscall = events.Core.GetDefinitionByID(id).GetName() - } else { + syscallDef := events.Core.GetDefinitionByID(id) + if syscallDef.NotValid() { // This should never fail, as the translation used in eBPF relies on the same event definitions - logger.Errorw("No syscall event with id %d", id) + logger.Errorw("No syscall event defined", "id", id) + } else { + syscall = syscallDef.GetName() } } diff --git a/pkg/ebpf/tracee.go b/pkg/ebpf/tracee.go index e6039335ca54..f2f91ed62744 100644 --- a/pkg/ebpf/tracee.go +++ b/pkg/ebpf/tracee.go @@ -1580,12 +1580,11 @@ func (t *Tracee) getSelfLoadedPrograms(kprobesOnly bool) map[string]int { uniqueHooksMap := map[probeMapKey]struct{}{} for _, tr := range t.policyManager.EventsSelected() { - if !events.Core.IsDefined(tr) { + definition := events.Core.GetDefinitionByID(tr) + if definition.NotValid() { continue } - definition := events.Core.GetDefinitionByID(tr) - for _, depProbes := range definition.GetDependencies().GetProbes() { currProbe := t.defaultProbes.GetProbeByHandle(depProbes.GetHandle()) name := "" diff --git a/pkg/events/definition.go b/pkg/events/definition.go index e0c3c6780aee..1a5cd8288bc2 100644 --- a/pkg/events/definition.go +++ b/pkg/events/definition.go @@ -114,3 +114,7 @@ func (d Definition) IsNetwork() bool { func (d Definition) GetProperties() map[string]interface{} { return d.properties } + +func (d Definition) NotValid() bool { + return d.id == Undefined || d.id == Unsupported +} diff --git a/pkg/events/definition_group.go b/pkg/events/definition_group.go index 365fcbd817f6..6cf62aeaa61b 100644 --- a/pkg/events/definition_group.go +++ b/pkg/events/definition_group.go @@ -61,10 +61,12 @@ func (d *DefinitionGroup) GetDefinitions() []Definition { func (d *DefinitionGroup) GetDefinitionIDByName(givenName string) (ID, bool) { d.mutex.RLock() defer d.mutex.RUnlock() + id, found := d.getDefinitionIDByName(givenName) if !found { logger.Debugw("definition name not found", "name", givenName) } + return id, found } @@ -80,7 +82,6 @@ func (d *DefinitionGroup) getDefinitionIDByName(givenName string) (ID, bool) { } // GetDefinitionByID returns a definition by its ID. -// NOTE: should be used together with IsDefined when definition might not exist. func (d *DefinitionGroup) GetDefinitionByID(givenDef ID) Definition { d.mutex.RLock() defer d.mutex.RUnlock() @@ -94,8 +95,30 @@ func (d *DefinitionGroup) GetDefinitionByID(givenDef ID) Definition { return def } +// GetDefinitionByName returns a definition by its name. +func (d *DefinitionGroup) GetDefinitionByName(givenName string) Definition { + d.mutex.RLock() + defer d.mutex.RUnlock() + + def, _ := d.getDefinitionByName(givenName) + return def +} + +// getDefinitionByName returns a definition by its name (no locking). +func (d *DefinitionGroup) getDefinitionByName(givenName string) (Definition, bool) { + for _, def := range d.definitions { + if def.GetName() == givenName { + return def, true + } + } + + return Definition{id: Undefined}, false +} + // IsDefined returns true if the definition exists in the definition group. -// NOTE: needed as GetDefinitionByID() is used as GetDefinitionByID().Method() multiple times. +// This method only verifies the existence of a definition. +// To retrieve the Definition, use GetDefinitionByID and check its validity with +// the NotValid method. func (d *DefinitionGroup) IsDefined(givenDef ID) bool { d.mutex.RLock() defer d.mutex.RUnlock() diff --git a/pkg/events/definition_group_test.go b/pkg/events/definition_group_test.go index 4ba495ec379f..678442045c87 100644 --- a/pkg/events/definition_group_test.go +++ b/pkg/events/definition_group_test.go @@ -84,6 +84,32 @@ func TestDefinitionGroup_GetDefinitionIDByName(t *testing.T) { require.Equal(t, id, id2) } +// TestGetDefinitionByName tests that GetDefinitionByName returns a definition by its name. +func TestDefinitionGroup_GetDefinitionByName(t *testing.T) { + t.Parallel() + + defGroup := NewDefinitionGroup() + + id1 := ID(1) + id2 := ID(2) + + def1 := NewDefinition(id1, id1+1000, "def1", version, "", "", false, false, []string{}, Dependencies{}, nil, nil) + def2 := NewDefinition(id2, id2+1000, "def2", version, "", "", false, false, []string{}, Dependencies{}, nil, nil) + + defGroup.AddBatch(map[ID]Definition{id1: def1, id2: def2}) + + // found definition + + def := defGroup.GetDefinitionByName("def1") + require.Equal(t, def.GetID(), id1) + require.Equal(t, def.GetName(), "def1") + + // definition not found (undefined) + + def = defGroup.GetDefinitionByName("def3") + require.Equal(t, def.GetID(), Undefined) +} + // TestDefinitionGroup_GetDefinitionByID tests that GetDefinitionByID returns a definition by its ID. func TestDefinitionGroup_GetDefinitionByID(t *testing.T) { t.Parallel() diff --git a/pkg/events/parse_args.go b/pkg/events/parse_args.go index 5145ac9335a5..a899afe6e25a 100644 --- a/pkg/events/parse_args.go +++ b/pkg/events/parse_args.go @@ -242,12 +242,11 @@ func ParseArgs(event *trace.Event) error { case SuspiciousSyscallSource, StackPivot: if syscallArg := GetArg(event, "syscall"); syscallArg != nil { if id, isInt32 := syscallArg.Value.(int32); isInt32 { - if Core.IsDefined(ID(id)) { - eventDefinition := Core.GetDefinitionByID(ID(id)) - if eventDefinition.IsSyscall() { - syscallArg.Value = eventDefinition.GetName() - syscallArg.Type = "string" - } + eventDefinition := Core.GetDefinitionByID(ID(id)) + // no need to check for NotValid() since it is syscall only if it's a valid event + if eventDefinition.IsSyscall() { + syscallArg.Value = eventDefinition.GetName() + syscallArg.Type = "string" } } } diff --git a/pkg/policy/v1beta1/policy_file.go b/pkg/policy/v1beta1/policy_file.go index 8c5103493627..05f2e067d45c 100644 --- a/pkg/policy/v1beta1/policy_file.go +++ b/pkg/policy/v1beta1/policy_file.go @@ -269,13 +269,11 @@ func validateEventData(policyName, eventName, dataName string) error { dataName = s[0] - eventDefID, ok := events.Core.GetDefinitionIDByName(eventName) - if !ok { + eventDefinition := events.Core.GetDefinitionByName(eventName) + if eventDefinition.NotValid() { return errfmt.Errorf("policy %s, event %s is not valid", policyName, eventName) } - eventDefinition := events.Core.GetDefinitionByID(eventDefID) - for _, set := range eventDefinition.GetSets() { if set == "signatures" { // no sig event validation (arguments are dynamic) return nil diff --git a/pkg/server/grpc/tracee.go b/pkg/server/grpc/tracee.go index 84e974887d61..d629d92a2c2b 100644 --- a/pkg/server/grpc/tracee.go +++ b/pkg/server/grpc/tracee.go @@ -677,12 +677,11 @@ func getDefinitions(in *pb.GetEventDefinitionsRequest) ([]events.Definition, err definitions := make([]events.Definition, 0, len(in.EventNames)) for _, name := range in.EventNames { - id, ok := events.Core.GetDefinitionIDByName(name) - if !ok { + definition := events.Core.GetDefinitionByName(name) + if definition.NotValid() { return nil, fmt.Errorf("event %s not found", name) } - definition := events.Core.GetDefinitionByID(id) definitions = append(definitions, definition) } From d87ed624175fa5a437feace7a1435755228fd869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 13 Jan 2025 22:09:18 -0300 Subject: [PATCH 04/25] chore(ebpf): add Benchmark_procTreeForkProcessor Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeForkProcessor$ github.com/aquasecurity/tracee/pkg/ebpf -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeForkProcessor-32 100000000 547.4 ns/op 496 B/op 5 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf 54.757s --- pkg/ebpf/processor_proctree_bench_test.go | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 pkg/ebpf/processor_proctree_bench_test.go diff --git a/pkg/ebpf/processor_proctree_bench_test.go b/pkg/ebpf/processor_proctree_bench_test.go new file mode 100644 index 000000000000..2c842f3057bc --- /dev/null +++ b/pkg/ebpf/processor_proctree_bench_test.go @@ -0,0 +1,46 @@ +package ebpf + +import ( + "context" + "testing" + + "github.com/aquasecurity/tracee/pkg/proctree" + "github.com/aquasecurity/tracee/types/trace" +) + +func Benchmark_procTreeForkProcessor(b *testing.B) { + t := &Tracee{} + t.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + event := &trace.Event{ + Args: []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "parent_process_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_ns_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_ns_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_start_time"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_ns_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_ns_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_start_time"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "child_tid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_ns_tid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_pid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_ns_pid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "start_time"}, Value: uint64(2)}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = t.procTreeForkProcessor(event) + } +} From d9de86edd2ff7d12165bea97d423150dc026af06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 13 Jan 2025 22:18:34 -0300 Subject: [PATCH 05/25] perf(ebpf): improve procTreeForkProcessor | Metric | Old Value | New Value | Improvement (%) | |-------------------------|------------|-------------|-----------------| | Time per operation (ns) | 547.4 | 267.5 | 51.14% | | Bytes allocated (B/op) | 496 | 0 | 100.00% | | Allocations per op | 5 | 0 | 100.00% | | Total runtime (s) | 54.757 | 26.763 | 51.13% | --- Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeForkProcessor$ github.com/aquasecurity/tracee/pkg/ebpf -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeForkProcessor-32 100000000 267.5 ns/op 0 B/op 0 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf 26.763s --- pkg/ebpf/processor_proctree.go | 69 ++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/pkg/ebpf/processor_proctree.go b/pkg/ebpf/processor_proctree.go index 559fb4f063a5..a242ccbd62fd 100644 --- a/pkg/ebpf/processor_proctree.go +++ b/pkg/ebpf/processor_proctree.go @@ -17,8 +17,6 @@ import ( // procTreeForkProcessor handles process fork events. func (t *Tracee) procTreeForkProcessor(event *trace.Event) error { - var errs []error - if t.processTree == nil { return fmt.Errorf("process tree is disabled") } @@ -27,51 +25,74 @@ func (t *Tracee) procTreeForkProcessor(event *trace.Event) error { // Parent Process (Go up in hierarchy until parent is a process and not a lwp) parentTid, err := parse.ArgVal[int32](event.Args, "parent_process_tid") - errs = append(errs, err) + if err != nil { + return err + } parentNsTid, err := parse.ArgVal[int32](event.Args, "parent_process_ns_tid") - errs = append(errs, err) + if err != nil { + return err + } parentPid, err := parse.ArgVal[int32](event.Args, "parent_process_pid") - errs = append(errs, err) + if err != nil { + return err + } parentNsPid, err := parse.ArgVal[int32](event.Args, "parent_process_ns_pid") - errs = append(errs, err) + if err != nil { + return err + } parentStartTime, err := parse.ArgVal[uint64](event.Args, "parent_process_start_time") - errs = append(errs, err) + if err != nil { + return err + } // Thread Group Leader (might be the same as the "child", if "child" is a process) leaderTid, err := parse.ArgVal[int32](event.Args, "leader_tid") - errs = append(errs, err) + if err != nil { + return err + } leaderNsTid, err := parse.ArgVal[int32](event.Args, "leader_ns_tid") - errs = append(errs, err) + if err != nil { + return err + } leaderPid, err := parse.ArgVal[int32](event.Args, "leader_pid") - errs = append(errs, err) + if err != nil { + return err + } leaderNsPid, err := parse.ArgVal[int32](event.Args, "leader_ns_pid") - errs = append(errs, err) + if err != nil { + return err + } leaderStartTime, err := parse.ArgVal[uint64](event.Args, "leader_start_time") - errs = append(errs, err) + if err != nil { + return err + } // Child (might be a process or a thread) childTid, err := parse.ArgVal[int32](event.Args, "child_tid") - errs = append(errs, err) + if err != nil { + return err + } childNsTid, err := parse.ArgVal[int32](event.Args, "child_ns_tid") - errs = append(errs, err) + if err != nil { + return err + } childPid, err := parse.ArgVal[int32](event.Args, "child_pid") - errs = append(errs, err) + if err != nil { + return err + } childNsPid, err := parse.ArgVal[int32](event.Args, "child_ns_pid") - errs = append(errs, err) + if err != nil { + return err + } childStartTime, err := parse.ArgVal[uint64](event.Args, "start_time") // child_start_time - errs = append(errs, err) - - // Deal with errors - for _, err := range errs { - if err != nil { - return err - } + if err != nil { + return err } // Calculate hashes - childHash := utils.HashTaskID(uint32(childTid), uint64(childStartTime)) parentHash := utils.HashTaskID(uint32(parentTid), uint64(parentStartTime)) leaderHash := utils.HashTaskID(uint32(leaderTid), uint64(leaderStartTime)) + childHash := utils.HashTaskID(uint32(childTid), uint64(childStartTime)) return t.processTree.FeedFromFork( proctree.ForkFeed{ From 34fe1de41c256fab59b80f46d54b92eb5dcfcd6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 13 Jan 2025 22:33:59 -0300 Subject: [PATCH 06/25] chore(controlplane): add procTreeForkProcessor bench Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeForkProcessor$ github.com/aquasecurity/tracee/pkg/ebpf/controlplane -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf/controlplane cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeForkProcessor-32 100000000 618.2 ns/op 496 B/op 5 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf/controlplane 61.827s --- pkg/ebpf/controlplane/processes_bench_test.go | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 pkg/ebpf/controlplane/processes_bench_test.go diff --git a/pkg/ebpf/controlplane/processes_bench_test.go b/pkg/ebpf/controlplane/processes_bench_test.go new file mode 100644 index 000000000000..d0b852c9a070 --- /dev/null +++ b/pkg/ebpf/controlplane/processes_bench_test.go @@ -0,0 +1,45 @@ +package controlplane + +import ( + "context" + "testing" + + "github.com/aquasecurity/tracee/pkg/proctree" + "github.com/aquasecurity/tracee/types/trace" +) + +func Benchmark_procTreeForkProcessor(b *testing.B) { + ctrl := &Controller{} + ctrl.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + args := []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "timestamp"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_ns_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_ns_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_start_time"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_ns_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_ns_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_start_time"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "child_tid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_ns_tid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_pid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_ns_pid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "start_time"}, Value: uint64(2)}, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = ctrl.procTreeForkProcessor(args) + } +} From 91d7df00f4f3f0b3da69d1b5d16b893ae0e229fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 13 Jan 2025 22:40:20 -0300 Subject: [PATCH 07/25] perf(controlplane): improve procTreeForkProcessor | Metric | Old Value | New Value | Improvement (%) | |-------------------------|------------|-------------|-----------------| | Time per operation (ns) | 618.2 | 274.0 | 55.67% | | Bytes allocated (B/op) | 496 | 0 | 100.00% | | Allocations per op | 5 | 0 | 100.00% | | Total runtime (s) | 61.827 | 27.415 | 55.67% | --- Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeForkProcessor$ github.com/aquasecurity/tracee/pkg/ebpf/controlplane -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf/controlplane cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeForkProcessor-32 100000000 274.0 ns/op 0 B/op 0 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf/controlplane 27.415s --- pkg/ebpf/controlplane/processes.go | 71 ++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/pkg/ebpf/controlplane/processes.go b/pkg/ebpf/controlplane/processes.go index 200a2bb00fb7..2e8d6f11b5d1 100644 --- a/pkg/ebpf/controlplane/processes.go +++ b/pkg/ebpf/controlplane/processes.go @@ -13,8 +13,6 @@ import ( // func (ctrl *Controller) procTreeForkProcessor(args []trace.Argument) error { - var errs []error - if ctrl.processTree == nil { return nil // process tree is disabled } @@ -23,49 +21,74 @@ func (ctrl *Controller) procTreeForkProcessor(args []trace.Argument) error { // Process & Event identification arguments timestamp, err := parse.ArgVal[uint64](args, "timestamp") - errs = append(errs, err) + if err != nil { + return err + } // Parent Process (Go up in hierarchy until parent is a process and not a lwp) parentTid, err := parse.ArgVal[int32](args, "parent_process_tid") - errs = append(errs, err) + if err != nil { + return err + } parentNsTid, err := parse.ArgVal[int32](args, "parent_process_ns_tid") - errs = append(errs, err) + if err != nil { + return err + } parentPid, err := parse.ArgVal[int32](args, "parent_process_pid") - errs = append(errs, err) + if err != nil { + return err + } parentNsPid, err := parse.ArgVal[int32](args, "parent_process_ns_pid") - errs = append(errs, err) + if err != nil { + return err + } parentStartTime, err := parse.ArgVal[uint64](args, "parent_process_start_time") - errs = append(errs, err) + if err != nil { + return err + } // Thread Group Leader (might be the same as the "child", if "child" is a process) leaderTid, err := parse.ArgVal[int32](args, "leader_tid") - errs = append(errs, err) + if err != nil { + return err + } leaderNsTid, err := parse.ArgVal[int32](args, "leader_ns_tid") - errs = append(errs, err) + if err != nil { + return err + } leaderPid, err := parse.ArgVal[int32](args, "leader_pid") - errs = append(errs, err) + if err != nil { + return err + } leaderNsPid, err := parse.ArgVal[int32](args, "leader_ns_pid") - errs = append(errs, err) + if err != nil { + return err + } leaderStartTime, err := parse.ArgVal[uint64](args, "leader_start_time") - errs = append(errs, err) + if err != nil { + return err + } // Child (might be a process or a thread) childTid, err := parse.ArgVal[int32](args, "child_tid") - errs = append(errs, err) + if err != nil { + return err + } childNsTid, err := parse.ArgVal[int32](args, "child_ns_tid") - errs = append(errs, err) + if err != nil { + return err + } childPid, err := parse.ArgVal[int32](args, "child_pid") - errs = append(errs, err) + if err != nil { + return err + } childNsPid, err := parse.ArgVal[int32](args, "child_ns_pid") - errs = append(errs, err) + if err != nil { + return err + } childStartTime, err := parse.ArgVal[uint64](args, "start_time") // child_start_time - errs = append(errs, err) - - // Handle errors - for _, err := range errs { - if err != nil { - return err - } + if err != nil { + return err } timestamp = time.BootToEpochNS(timestamp) From 597171c91b1a8a3344ab1214164a8e543b544df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Tue, 14 Jan 2025 08:33:01 -0300 Subject: [PATCH 08/25] chore(ebpf): add Benchmark_procTreeExecProcessor Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeExecProcessor$ github.com/aquasecurity/tracee/pkg/ebpf -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeExecProcessor-32 100000000 514.7 ns/op 500 B/op 6 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf 51.483s --- pkg/ebpf/processor_proctree_bench_test.go | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/pkg/ebpf/processor_proctree_bench_test.go b/pkg/ebpf/processor_proctree_bench_test.go index 2c842f3057bc..9f548c3895ed 100644 --- a/pkg/ebpf/processor_proctree_bench_test.go +++ b/pkg/ebpf/processor_proctree_bench_test.go @@ -44,3 +44,39 @@ func Benchmark_procTreeForkProcessor(b *testing.B) { _ = t.procTreeForkProcessor(event) } } + +func Benchmark_procTreeExecProcessor(b *testing.B) { + t := &Tracee{} + t.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + event := &trace.Event{ + Args: []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "cmdpath"}, Value: "/bin/bash"}, + {ArgMeta: trace.ArgMeta{Name: "pathname"}, Value: "/bin/bash"}, + {ArgMeta: trace.ArgMeta{Name: "dev"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "inode"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "ctime"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "inode_mode"}, Value: uint16(1)}, + {ArgMeta: trace.ArgMeta{Name: "interpreter_pathname"}, Value: "/lib64/ld-linux-x86-64.so.2"}, + {ArgMeta: trace.ArgMeta{Name: "interpreter_dev"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "interpreter_inode"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "interpreter_ctime"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "interp"}, Value: "/lib64/ld-linux-x86-64.so.2"}, + {ArgMeta: trace.ArgMeta{Name: "stdin_type"}, Value: uint16(1)}, + {ArgMeta: trace.ArgMeta{Name: "stdin_path"}, Value: "/dev/null"}, + {ArgMeta: trace.ArgMeta{Name: "invoked_from_kernel"}, Value: int32(1)}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = t.procTreeExecProcessor(event) + } +} From 8d28c342f14405dd1c10622e8a2a46f02190a78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Tue, 14 Jan 2025 08:42:53 -0300 Subject: [PATCH 09/25] perf(ebpf): improve procTreeExecProcessor | Metric | Old Value | New Value | Improvement (%) | |-------------------------|------------|-------------|-----------------| | Time per operation (ns) | 514.7 | 215.6 | 58.12% | | Bytes allocated (B/op) | 500 | 4 | 99.20% | | Allocations per op | 6 | 1 | 83.33% | | Total runtime (s) | 51.483 | 21.571 | 58.12% | --- Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeExecProcessor$ github.com/aquasecurity/tracee/pkg/ebpf -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeExecProcessor-32 100000000 215.6 ns/op 4 B/op 1 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf 21.571s --- pkg/ebpf/processor_proctree.go | 53 ++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/pkg/ebpf/processor_proctree.go b/pkg/ebpf/processor_proctree.go index a242ccbd62fd..7127550adf92 100644 --- a/pkg/ebpf/processor_proctree.go +++ b/pkg/ebpf/processor_proctree.go @@ -121,8 +121,6 @@ func (t *Tracee) procTreeForkProcessor(event *trace.Event) error { // procTreeExecProcessor handles process exec events. func (t *Tracee) procTreeExecProcessor(event *trace.Event) error { - var errs []error - if t.processTree == nil { return fmt.Errorf("process tree is disabled") } @@ -130,22 +128,31 @@ func (t *Tracee) procTreeExecProcessor(event *trace.Event) error { return nil // chek FeedFromExec for TODO of execve() handled by threads } - timestamp := uint64(event.Timestamp) - taskHash := utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) - // Executable cmdPath, err := parse.ArgVal[string](event.Args, "cmdpath") - errs = append(errs, err) + if err != nil { + return err + } pathName, err := parse.ArgVal[string](event.Args, "pathname") - errs = append(errs, err) + if err != nil { + return err + } dev, err := parse.ArgVal[uint32](event.Args, "dev") - errs = append(errs, err) + if err != nil { + return err + } inode, err := parse.ArgVal[uint64](event.Args, "inode") - errs = append(errs, err) + if err != nil { + return err + } ctime, err := parse.ArgVal[uint64](event.Args, "ctime") - errs = append(errs, err) + if err != nil { + return err + } inodeMode, err := parse.ArgVal[uint16](event.Args, "inode_mode") - errs = append(errs, err) + if err != nil { + return err + } // Binary Interpreter (or Loader): might come empty from the kernel interPathName, _ := parse.ArgVal[string](event.Args, "interpreter_pathname") @@ -155,23 +162,27 @@ func (t *Tracee) procTreeExecProcessor(event *trace.Event) error { // Real Interpreter interp, err := parse.ArgVal[string](event.Args, "interp") - errs = append(errs, err) + if err != nil { + return err + } // Others stdinType, err := parse.ArgVal[uint16](event.Args, "stdin_type") - errs = append(errs, err) + if err != nil { + return err + } stdinPath, err := parse.ArgVal[string](event.Args, "stdin_path") - errs = append(errs, err) + if err != nil { + return err + } invokedFromKernel, err := parse.ArgVal[int32](event.Args, "invoked_from_kernel") - errs = append(errs, err) - - // Handle errors - for _, err := range errs { - if err != nil { - return err - } + if err != nil { + return err } + timestamp := uint64(event.Timestamp) + taskHash := utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) + return t.processTree.FeedFromExec( proctree.ExecFeed{ TimeStamp: timestamp, From 3830a3265d86a7592d74c12047a023eb88e38797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Tue, 14 Jan 2025 08:58:23 -0300 Subject: [PATCH 10/25] chore(controlplane): add Benchmark_procTreeExecProcessor Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeExecProcessor$ github.com/aquasecurity/tracee/pkg/ebpf/controlplane -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf/controlplane cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeExecProcessor-32 100000000 649.7 ns/op 500 B/op 6 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf/controlplane 64.981s --- pkg/ebpf/controlplane/processes_bench_test.go | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pkg/ebpf/controlplane/processes_bench_test.go b/pkg/ebpf/controlplane/processes_bench_test.go index d0b852c9a070..5c4748371aab 100644 --- a/pkg/ebpf/controlplane/processes_bench_test.go +++ b/pkg/ebpf/controlplane/processes_bench_test.go @@ -43,3 +43,41 @@ func Benchmark_procTreeForkProcessor(b *testing.B) { _ = ctrl.procTreeForkProcessor(args) } } + +func Benchmark_procTreeExecProcessor(b *testing.B) { + ctrl := &Controller{} + ctrl.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + args := []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "timestamp"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "task_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "cmdpath"}, Value: "/bin/bash"}, + {ArgMeta: trace.ArgMeta{Name: "pathname"}, Value: "/bin/bash"}, + {ArgMeta: trace.ArgMeta{Name: "dev"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "inode"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "ctime"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "inode_mode"}, Value: uint16(1)}, + {ArgMeta: trace.ArgMeta{Name: "interpreter_pathname"}, Value: "/lib64/ld-linux-x86-64.so.2"}, + {ArgMeta: trace.ArgMeta{Name: "interpreter_dev"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "interpreter_inode"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "interpreter_ctime"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "interp"}, Value: "/lib64/ld-linux-x86-64.so.2"}, + {ArgMeta: trace.ArgMeta{Name: "stdin_type"}, Value: uint16(1)}, + {ArgMeta: trace.ArgMeta{Name: "stdin_path"}, Value: "/dev/null"}, + {ArgMeta: trace.ArgMeta{Name: "invoked_from_kernel"}, Value: int32(1)}, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = ctrl.procTreeExecProcessor(args) + } +} From dd32475cc69bb989eb5e55c7bf01749aa3ade740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Tue, 14 Jan 2025 09:05:22 -0300 Subject: [PATCH 11/25] perf(controlplane): improve procTreeExecProcessor | Metric | Old Value | New Value | Improvement (%) | |-------------------------|------------|-------------|-----------------| | Time per operation (ns) | 649.7 | 284.2 | 56.26% | | Bytes allocated (B/op) | 500 | 4 | 99.20% | | Allocations per op | 6 | 1 | 83.33% | | Total runtime (s) | 64.981 | 28.435 | 56.26% | --- Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeExecProcessor$ github.com/aquasecurity/tracee/pkg/ebpf/controlplane -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf/controlplane cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeExecProcessor-32 100000000 284.2 ns/op 4 B/op 1 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf/controlplane 28.435s --- pkg/ebpf/controlplane/processes.go | 54 ++++++++++++++++++------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/pkg/ebpf/controlplane/processes.go b/pkg/ebpf/controlplane/processes.go index 2e8d6f11b5d1..5076cb139bae 100644 --- a/pkg/ebpf/controlplane/processes.go +++ b/pkg/ebpf/controlplane/processes.go @@ -126,35 +126,44 @@ func (ctrl *Controller) procTreeForkProcessor(args []trace.Argument) error { } func (ctrl *Controller) procTreeExecProcessor(args []trace.Argument) error { - var errs []error - if ctrl.processTree == nil { return nil // process tree is disabled } // Process & Event identification arguments (won't exist for regular events) timestamp, err := parse.ArgVal[uint64](args, "timestamp") - errs = append(errs, err) + if err != nil { + return err + } taskHash, _ := parse.ArgVal[uint32](args, "task_hash") - errs = append(errs, err) parentHash, _ := parse.ArgVal[uint32](args, "parent_hash") - errs = append(errs, err) leaderHash, _ := parse.ArgVal[uint32](args, "leader_hash") - errs = append(errs, err) // Executable cmdPath, err := parse.ArgVal[string](args, "cmdpath") - errs = append(errs, err) + if err != nil { + return err + } pathName, err := parse.ArgVal[string](args, "pathname") - errs = append(errs, err) + if err != nil { + return err + } dev, err := parse.ArgVal[uint32](args, "dev") - errs = append(errs, err) + if err != nil { + return err + } inode, err := parse.ArgVal[uint64](args, "inode") - errs = append(errs, err) + if err != nil { + return err + } ctime, err := parse.ArgVal[uint64](args, "ctime") - errs = append(errs, err) + if err != nil { + return err + } inodeMode, err := parse.ArgVal[uint16](args, "inode_mode") - errs = append(errs, err) + if err != nil { + return err + } // Binary Interpreter (or Loader): might come empty from the kernel interPathName, _ := parse.ArgVal[string](args, "interpreter_pathname") @@ -164,21 +173,22 @@ func (ctrl *Controller) procTreeExecProcessor(args []trace.Argument) error { // Real Interpreter interp, err := parse.ArgVal[string](args, "interp") - errs = append(errs, err) + if err != nil { + return err + } // Others stdinType, err := parse.ArgVal[uint16](args, "stdin_type") - errs = append(errs, err) + if err != nil { + return err + } stdinPath, err := parse.ArgVal[string](args, "stdin_path") - errs = append(errs, err) + if err != nil { + return err + } invokedFromKernel, err := parse.ArgVal[int32](args, "invoked_from_kernel") - errs = append(errs, err) - - // Handle errors - for _, err := range errs { - if err != nil { - return err - } + if err != nil { + return err } return ctrl.processTree.FeedFromExec( From edf331a41b7227f3804e5d8cf392c555092769dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Tue, 14 Jan 2025 09:26:55 -0300 Subject: [PATCH 12/25] perf: remove unused ExecFeed interpreter fields Disable (comment out) ExecFeed interpreter fields not used by the feeders. This removal was already started by 4a5bb5d0f. --- Tracee | Metric | Old Value | New Value | Improvement (%) | |-------------------------|------------|-------------|-----------------| | Time per operation (ns) | 215.6 | 168.1 | 22.03% | | Bytes allocated (B/op) | 4 | 4 | 0.00% | | Allocations per op | 1 | 1 | 0.00% | | Total runtime (s) | 21.571 | 16.825 | 22.03% | - Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeExecProcessor$ github.com/aquasecurity/tracee/pkg/ebpf -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeExecProcessor-32 100000000 168.1 ns/op 4 B/op 1 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf 16.825s --- Controller | Metric | Old Value | New Value | Improvement (%) | |-------------------------|------------|-------------|-----------------| | Time per operation (ns) | 284.2 | 209.7 | 26.20% | | Bytes allocated (B/op) | 4 | 4 | 0.00% | | Allocations per op | 1 | 1 | 0.00% | | Total runtime (s) | 28.435 | 20.983 | 26.20% | - Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeExecProcessor$ github.com/aquasecurity/tracee/pkg/ebpf/controlplane -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf/controlplane cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeExecProcessor-32 100000000 209.7 ns/op 4 B/op 1 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf/controlplane 20.983s --- pkg/ebpf/controlplane/processes.go | 38 +++++++++---------- pkg/ebpf/controlplane/processes_bench_test.go | 8 ++-- pkg/ebpf/processor_proctree.go | 38 +++++++++---------- pkg/ebpf/processor_proctree_bench_test.go | 8 ++-- pkg/proctree/proctree_feed.go | 28 +++++++------- 5 files changed, 60 insertions(+), 60 deletions(-) diff --git a/pkg/ebpf/controlplane/processes.go b/pkg/ebpf/controlplane/processes.go index 5076cb139bae..a0ae6e45ad21 100644 --- a/pkg/ebpf/controlplane/processes.go +++ b/pkg/ebpf/controlplane/processes.go @@ -165,11 +165,11 @@ func (ctrl *Controller) procTreeExecProcessor(args []trace.Argument) error { return err } - // Binary Interpreter (or Loader): might come empty from the kernel - interPathName, _ := parse.ArgVal[string](args, "interpreter_pathname") - interDev, _ := parse.ArgVal[uint32](args, "interpreter_dev") - interInode, _ := parse.ArgVal[uint64](args, "interpreter_inode") - interCtime, _ := parse.ArgVal[uint64](args, "interpreter_ctime") + // // Binary Interpreter (or Loader): might come empty from the kernel + // interPathName, _ := parse.ArgVal[string](args, "interpreter_pathname") + // interDev, _ := parse.ArgVal[uint32](args, "interpreter_dev") + // interInode, _ := parse.ArgVal[uint64](args, "interpreter_inode") + // interCtime, _ := parse.ArgVal[uint64](args, "interpreter_ctime") // Real Interpreter interp, err := parse.ArgVal[string](args, "interp") @@ -193,20 +193,20 @@ func (ctrl *Controller) procTreeExecProcessor(args []trace.Argument) error { return ctrl.processTree.FeedFromExec( proctree.ExecFeed{ - TimeStamp: time.BootToEpochNS(timestamp), - TaskHash: taskHash, - ParentHash: parentHash, - LeaderHash: leaderHash, - CmdPath: cmdPath, - PathName: pathName, - Dev: dev, - Inode: inode, - Ctime: ctime, - InodeMode: inodeMode, - InterpreterPath: interPathName, - InterpreterDev: interDev, - InterpreterInode: interInode, - InterpreterCtime: interCtime, + TimeStamp: time.BootToEpochNS(timestamp), + TaskHash: taskHash, + ParentHash: parentHash, + LeaderHash: leaderHash, + CmdPath: cmdPath, + PathName: pathName, + Dev: dev, + Inode: inode, + Ctime: ctime, + InodeMode: inodeMode, + // InterpreterPath: interPathName, + // InterpreterDev: interDev, + // InterpreterInode: interInode, + // InterpreterCtime: interCtime, Interp: interp, StdinType: stdinType, StdinPath: stdinPath, diff --git a/pkg/ebpf/controlplane/processes_bench_test.go b/pkg/ebpf/controlplane/processes_bench_test.go index 5c4748371aab..53df41bcb90f 100644 --- a/pkg/ebpf/controlplane/processes_bench_test.go +++ b/pkg/ebpf/controlplane/processes_bench_test.go @@ -66,10 +66,10 @@ func Benchmark_procTreeExecProcessor(b *testing.B) { {ArgMeta: trace.ArgMeta{Name: "inode"}, Value: uint64(1)}, {ArgMeta: trace.ArgMeta{Name: "ctime"}, Value: uint64(1)}, {ArgMeta: trace.ArgMeta{Name: "inode_mode"}, Value: uint16(1)}, - {ArgMeta: trace.ArgMeta{Name: "interpreter_pathname"}, Value: "/lib64/ld-linux-x86-64.so.2"}, - {ArgMeta: trace.ArgMeta{Name: "interpreter_dev"}, Value: uint32(1)}, - {ArgMeta: trace.ArgMeta{Name: "interpreter_inode"}, Value: uint64(1)}, - {ArgMeta: trace.ArgMeta{Name: "interpreter_ctime"}, Value: uint64(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_pathname"}, Value: "/lib64/ld-linux-x86-64.so.2"}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_dev"}, Value: uint32(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_inode"}, Value: uint64(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_ctime"}, Value: uint64(1)}, {ArgMeta: trace.ArgMeta{Name: "interp"}, Value: "/lib64/ld-linux-x86-64.so.2"}, {ArgMeta: trace.ArgMeta{Name: "stdin_type"}, Value: uint16(1)}, {ArgMeta: trace.ArgMeta{Name: "stdin_path"}, Value: "/dev/null"}, diff --git a/pkg/ebpf/processor_proctree.go b/pkg/ebpf/processor_proctree.go index 7127550adf92..9ba2039e182f 100644 --- a/pkg/ebpf/processor_proctree.go +++ b/pkg/ebpf/processor_proctree.go @@ -154,11 +154,11 @@ func (t *Tracee) procTreeExecProcessor(event *trace.Event) error { return err } - // Binary Interpreter (or Loader): might come empty from the kernel - interPathName, _ := parse.ArgVal[string](event.Args, "interpreter_pathname") - interDev, _ := parse.ArgVal[uint32](event.Args, "interpreter_dev") - interInode, _ := parse.ArgVal[uint64](event.Args, "interpreter_inode") - interCtime, _ := parse.ArgVal[uint64](event.Args, "interpreter_ctime") + // // Binary Interpreter (or Loader): might come empty from the kernel + // interPathName, _ := parse.ArgVal[string](event.Args, "interpreter_pathname") + // interDev, _ := parse.ArgVal[uint32](event.Args, "interpreter_dev") + // interInode, _ := parse.ArgVal[uint64](event.Args, "interpreter_inode") + // interCtime, _ := parse.ArgVal[uint64](event.Args, "interpreter_ctime") // Real Interpreter interp, err := parse.ArgVal[string](event.Args, "interp") @@ -185,20 +185,20 @@ func (t *Tracee) procTreeExecProcessor(event *trace.Event) error { return t.processTree.FeedFromExec( proctree.ExecFeed{ - TimeStamp: timestamp, - TaskHash: taskHash, - ParentHash: 0, // regular pipeline does not have parent hash - LeaderHash: 0, // regular pipeline does not have leader hash - CmdPath: cmdPath, - PathName: pathName, - Dev: dev, - Inode: inode, - Ctime: ctime, - InodeMode: inodeMode, - InterpreterPath: interPathName, - InterpreterDev: interDev, - InterpreterInode: interInode, - InterpreterCtime: interCtime, + TimeStamp: timestamp, + TaskHash: taskHash, + ParentHash: 0, // regular pipeline does not have parent hash + LeaderHash: 0, // regular pipeline does not have leader hash + CmdPath: cmdPath, + PathName: pathName, + Dev: dev, + Inode: inode, + Ctime: ctime, + InodeMode: inodeMode, + // InterpreterPath: interPathName, + // InterpreterDev: interDev, + // InterpreterInode: interInode, + // InterpreterCtime: interCtime, Interp: interp, StdinType: stdinType, StdinPath: stdinPath, diff --git a/pkg/ebpf/processor_proctree_bench_test.go b/pkg/ebpf/processor_proctree_bench_test.go index 9f548c3895ed..8a7d83f33ef1 100644 --- a/pkg/ebpf/processor_proctree_bench_test.go +++ b/pkg/ebpf/processor_proctree_bench_test.go @@ -64,10 +64,10 @@ func Benchmark_procTreeExecProcessor(b *testing.B) { {ArgMeta: trace.ArgMeta{Name: "inode"}, Value: uint64(1)}, {ArgMeta: trace.ArgMeta{Name: "ctime"}, Value: uint64(1)}, {ArgMeta: trace.ArgMeta{Name: "inode_mode"}, Value: uint16(1)}, - {ArgMeta: trace.ArgMeta{Name: "interpreter_pathname"}, Value: "/lib64/ld-linux-x86-64.so.2"}, - {ArgMeta: trace.ArgMeta{Name: "interpreter_dev"}, Value: uint32(1)}, - {ArgMeta: trace.ArgMeta{Name: "interpreter_inode"}, Value: uint64(1)}, - {ArgMeta: trace.ArgMeta{Name: "interpreter_ctime"}, Value: uint64(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_pathname"}, Value: "/lib64/ld-linux-x86-64.so.2"}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_dev"}, Value: uint32(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_inode"}, Value: uint64(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_ctime"}, Value: uint64(1)}, {ArgMeta: trace.ArgMeta{Name: "interp"}, Value: "/lib64/ld-linux-x86-64.so.2"}, {ArgMeta: trace.ArgMeta{Name: "stdin_type"}, Value: uint16(1)}, {ArgMeta: trace.ArgMeta{Name: "stdin_path"}, Value: "/dev/null"}, diff --git a/pkg/proctree/proctree_feed.go b/pkg/proctree/proctree_feed.go index 9714abfb4ad0..354f66f0af8f 100644 --- a/pkg/proctree/proctree_feed.go +++ b/pkg/proctree/proctree_feed.go @@ -172,20 +172,20 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { } type ExecFeed struct { - TimeStamp uint64 - TaskHash uint32 - ParentHash uint32 - LeaderHash uint32 - CmdPath string - PathName string - Dev uint32 - Inode uint64 - Ctime uint64 - InodeMode uint16 - InterpreterPath string - InterpreterDev uint32 - InterpreterInode uint64 - InterpreterCtime uint64 + TimeStamp uint64 + TaskHash uint32 + ParentHash uint32 + LeaderHash uint32 + CmdPath string + PathName string + Dev uint32 + Inode uint64 + Ctime uint64 + InodeMode uint16 + // InterpreterPath string + // InterpreterDev uint32 + // InterpreterInode uint64 + // InterpreterCtime uint64 Interp string StdinType uint16 StdinPath string From 861103cfb678827ecc4e5f8891aad551b169b32a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Tue, 14 Jan 2025 10:07:07 -0300 Subject: [PATCH 13/25] chore: add Benchmark_procTreeExitProcessor For both Tracee and Controller. - Tracee Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeExitProcessor$ github.com/aquasecurity/tracee/pkg/ebpf -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeExitProcessor-32 100000000 159.9 ns/op 48 B/op 2 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf 16.001s --- Controller Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeExitProcessor$ github.com/aquasecurity/tracee/pkg/ebpf/controlplane -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf/controlplane cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeExitProcessor-32 100000000 335.5 ns/op 240 B/op 4 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf/controlplane 33.558s --- pkg/ebpf/controlplane/processes_bench_test.go | 26 +++++++++++++++++++ pkg/ebpf/processor_proctree_bench_test.go | 24 +++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/pkg/ebpf/controlplane/processes_bench_test.go b/pkg/ebpf/controlplane/processes_bench_test.go index 53df41bcb90f..7354f611cc67 100644 --- a/pkg/ebpf/controlplane/processes_bench_test.go +++ b/pkg/ebpf/controlplane/processes_bench_test.go @@ -81,3 +81,29 @@ func Benchmark_procTreeExecProcessor(b *testing.B) { _ = ctrl.procTreeExecProcessor(args) } } + +func Benchmark_procTreeExitProcessor(b *testing.B) { + ctrl := &Controller{} + ctrl.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + args := []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "timestamp"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "task_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "exit_code"}, Value: int64(1)}, + {ArgMeta: trace.ArgMeta{Name: "process_group_exit"}, Value: true}, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = ctrl.procTreeExitProcessor(args) + } +} diff --git a/pkg/ebpf/processor_proctree_bench_test.go b/pkg/ebpf/processor_proctree_bench_test.go index 8a7d83f33ef1..7e75654b2a7b 100644 --- a/pkg/ebpf/processor_proctree_bench_test.go +++ b/pkg/ebpf/processor_proctree_bench_test.go @@ -80,3 +80,27 @@ func Benchmark_procTreeExecProcessor(b *testing.B) { _ = t.procTreeExecProcessor(event) } } + +func Benchmark_procTreeExitProcessor(b *testing.B) { + t := &Tracee{} + t.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + event := &trace.Event{ + Args: []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "exit_code"}, Value: int64(1)}, + {ArgMeta: trace.ArgMeta{Name: "process_group_exit"}, Value: true}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = t.procTreeExitProcessor(event) + } +} From d40a345fed51a288ace2a411382a1f4d2065e7cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Tue, 14 Jan 2025 10:15:17 -0300 Subject: [PATCH 14/25] perf: improve procTreeExitProcessor Improve procTreeExitProcessor for both Tracee and Controller. - Tracee | Metric | Old Value | New Value | Improvement (%) | |-------------------------|------------|-------------|-----------------| | Time per operation (ns) | 159.9 | 95.71 | 40.14% | | Bytes allocated (B/op) | 48 | 0 | 100.00% | | Allocations per op | 2 | 0 | 100.00% | | Total runtime (s) | 16.001 | 9.586 | 40.14% | Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeExitProcessor$ github.com/aquasecurity/tracee/pkg/ebpf -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeExitProcessor-32 100000000 95.71 ns/op 0 B/op 0 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf 9.586s --- Controller | Metric | Old Value | New Value | Improvement (%) | |-------------------------|------------|-------------|-----------------| | Time per operation (ns) | 335.5 | 115.4 | 65.60% | | Bytes allocated (B/op) | 240 | 0 | 100.00% | | Allocations per op | 4 | 0 | 100.00% | | Total runtime (s) | 33.558 | 11.553 | 65.60% | Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^Benchmark_procTreeExitProcessor$ github.com/aquasecurity/tracee/pkg/ebpf/controlplane -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/ebpf/controlplane cpu: AMD Ryzen 9 7950X 16-Core Processor Benchmark_procTreeExitProcessor-32 100000000 115.4 ns/op 0 B/op 0 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/ebpf/controlplane 11.553s --- pkg/ebpf/controlplane/processes.go | 31 ++++++++++++++++-------------- pkg/ebpf/processor_proctree.go | 21 ++++++++------------ 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/pkg/ebpf/controlplane/processes.go b/pkg/ebpf/controlplane/processes.go index a0ae6e45ad21..7cde1c3031b0 100644 --- a/pkg/ebpf/controlplane/processes.go +++ b/pkg/ebpf/controlplane/processes.go @@ -216,33 +216,36 @@ func (ctrl *Controller) procTreeExecProcessor(args []trace.Argument) error { } func (ctrl *Controller) procTreeExitProcessor(args []trace.Argument) error { - var errs []error - if ctrl.processTree == nil { return nil // process tree is disabled } // Process & Event identification arguments (won't exist for regular events) timestamp, err := parse.ArgVal[uint64](args, "timestamp") - errs = append(errs, err) + if err != nil { + return err + } taskHash, err := parse.ArgVal[uint32](args, "task_hash") - errs = append(errs, err) + if err != nil { + return err + } parentHash, err := parse.ArgVal[uint32](args, "parent_hash") - errs = append(errs, err) + if err != nil { + return err + } leaderHash, err := parse.ArgVal[uint32](args, "leader_hash") - errs = append(errs, err) + if err != nil { + return err + } // Exit logic arguments exitCode, err := parse.ArgVal[int64](args, "exit_code") - errs = append(errs, err) + if err != nil { + return err + } groupExit, err := parse.ArgVal[bool](args, "process_group_exit") - errs = append(errs, err) - - // Handle errors - for _, err := range errs { - if err != nil { - return err - } + if err != nil { + return err } return ctrl.processTree.FeedFromExit( diff --git a/pkg/ebpf/processor_proctree.go b/pkg/ebpf/processor_proctree.go index 9ba2039e182f..7758f275ff88 100644 --- a/pkg/ebpf/processor_proctree.go +++ b/pkg/ebpf/processor_proctree.go @@ -209,8 +209,6 @@ func (t *Tracee) procTreeExecProcessor(event *trace.Event) error { // procTreeExitProcessor handles process exit events. func (t *Tracee) procTreeExitProcessor(event *trace.Event) error { - var errs []error - if t.processTree == nil { return fmt.Errorf("process tree is disabled") } @@ -218,22 +216,19 @@ func (t *Tracee) procTreeExitProcessor(event *trace.Event) error { return nil // chek FeedFromExec for TODO of execve() handled by threads } - timestamp := uint64(event.Timestamp) - taskHash := utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) - // Exit logic arguments exitCode, err := parse.ArgVal[int64](event.Args, "exit_code") - errs = append(errs, err) + if err != nil { + return err + } groupExit, err := parse.ArgVal[bool](event.Args, "process_group_exit") - errs = append(errs, err) - - // Handle errors - for _, err := range errs { - if err != nil { - return err - } + if err != nil { + return err } + timestamp := uint64(event.Timestamp) + taskHash := utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) + return t.processTree.FeedFromExit( proctree.ExitFeed{ TimeStamp: timestamp, // time of exit is already a timestamp From 572343cd870f55be37904d67f3cf15e327a11f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Tue, 14 Jan 2025 12:26:01 -0300 Subject: [PATCH 15/25] chore(events): add BenchmarkArgVal Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^BenchmarkArgVal$ github.com/aquasecurity/tracee/pkg/events/parse -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/events/parse cpu: AMD Ryzen 9 7950X 16-Core Processor BenchmarkArgVal/int32/valid_args-32 100000000 14.43 ns/op 0 B/op 0 allocs/op BenchmarkArgVal/int32/invalid_val_type-32 100000000 551.7 ns/op 584 B/op 10 allocs/op BenchmarkArgVal/int32/not_found_arg-32 100000000 499.2 ns/op 520 B/op 10 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/events/parse 106.538s --- pkg/events/parse/params_bench_test.go | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 pkg/events/parse/params_bench_test.go diff --git a/pkg/events/parse/params_bench_test.go b/pkg/events/parse/params_bench_test.go new file mode 100644 index 000000000000..6eca4f7bce18 --- /dev/null +++ b/pkg/events/parse/params_bench_test.go @@ -0,0 +1,63 @@ +package parse + +import ( + "testing" + + "github.com/aquasecurity/tracee/types/trace" +) + +var args = []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "valid_arg1", + Type: "int", + }, + Value: int32(1878), + }, + { + ArgMeta: trace.ArgMeta{ + Name: "valid_arg2", + Type: "int", + }, + Value: int32(1878), + }, + { + ArgMeta: trace.ArgMeta{ + Name: "invalid_val_type", // in the middle of the list + Type: "int", + }, + Value: int64(1878), + }, + { + ArgMeta: trace.ArgMeta{ + Name: "valid_arg3", + Type: "int", + }, + Value: int32(1878), + }, +} + +func BenchmarkArgVal(b *testing.B) { + b.Run("int32", func(b *testing.B) { + b.Run("valid_args", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = ArgVal[int32](args, "valid_arg1") + _, _ = ArgVal[int32](args, "valid_arg2") + _, _ = ArgVal[int32](args, "valid_arg3") + } + }) + b.Run("invalid_val_type", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = ArgVal[int32](args, "invalid_val_type") + } + }) + b.Run("not_found_arg", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = ArgVal[int32](args, "not_found_arg") + } + }) + }) +} From b25ac6325db1bcbcb4c6c0d5e78110bbb62ba27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Tue, 14 Jan 2025 12:33:48 -0300 Subject: [PATCH 16/25] perf(events): improve ArgVal | Sub-Benchmark | Old (ns/op) | New (ns/op) | Change (%) | |------------------|-------------|-------------|------------| | valid_args | 14.43 | 13.35 | -7.48% | | invalid_val_type | 551.7 | 589.8 | +6.90% | | not_found_arg | 499.2 | 586.0 | +17.38% | The valid_args is the most relevant case, since it traverses args based on a specific order. The other cases are not deterministic and used to measure upcoming changes for the worst case. --- Running tool: /home/gg/.goenv/versions/1.22.4/bin/go test -benchmem -run=^$ -tags ebpf -bench ^BenchmarkArgVal$ github.com/aquasecurity/tracee/pkg/events/parse -benchtime=100000000x goos: linux goarch: amd64 pkg: github.com/aquasecurity/tracee/pkg/events/parse cpu: AMD Ryzen 9 7950X 16-Core Processor BenchmarkArgVal/int32/valid_args-32 100000000 13.35 ns/op 0 B/op 0 allocs/op BenchmarkArgVal/int32/invalid_val_type-32 100000000 589.8 ns/op 584 B/op 10 allocs/op BenchmarkArgVal/int32/not_found_arg-32 100000000 586.0 ns/op 520 B/op 10 allocs/op PASS ok github.com/aquasecurity/tracee/pkg/events/parse 118.922s --- pkg/events/parse/params.go | 43 +++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/pkg/events/parse/params.go b/pkg/events/parse/params.go index e2550a602496..78767dd8ff54 100644 --- a/pkg/events/parse/params.go +++ b/pkg/events/parse/params.go @@ -8,22 +8,37 @@ import ( ) func ArgVal[T any](args []trace.Argument, argName string) (T, error) { - for _, arg := range args { - if arg.Name == argName { - val, ok := arg.Value.(T) - if !ok { - zeroVal := *new(T) - return zeroVal, errfmt.Errorf( - "argument %s is not of type %T, is of type %T", - argName, - zeroVal, - arg.Value, - ) - } - return val, nil + var ok bool + var val T + var foundAndNotOk bool + + var i int + for i = range len(args) { + if args[i].Name != argName { + continue + } + + val, ok = args[i].Value.(T) + if !ok { + foundAndNotOk = true + break } + + return val, nil + } + + var zeroVal T + if foundAndNotOk { + return zeroVal, + errfmt.Errorf( + "argument %s is not of type %T, is of type %T", + argName, + zeroVal, + args[i].Value, + ) } - return *new(T), errfmt.Errorf("argument %s not found", argName) + + return zeroVal, errfmt.Errorf("argument %s not found", argName) } func ArgZeroValueFromType(t string) interface{} { From ad5532e884a4953fbabf86252adf5397b95e48a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Tue, 14 Jan 2025 16:58:44 -0300 Subject: [PATCH 17/25] perf(proctree): move functions from FeedFromFork --- pkg/proctree/proctree_feed.go | 142 +++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 63 deletions(-) diff --git a/pkg/proctree/proctree_feed.go b/pkg/proctree/proctree_feed.go index 354f66f0af8f..ea1297b42aab 100644 --- a/pkg/proctree/proctree_feed.go +++ b/pkg/proctree/proctree_feed.go @@ -2,6 +2,7 @@ package proctree import ( "path/filepath" + "time" "github.com/aquasecurity/tracee/pkg/errfmt" "github.com/aquasecurity/tracee/pkg/logger" @@ -34,6 +35,81 @@ type ForkFeed struct { ChildStartTime uint64 } +func (pt *ProcessTree) setParentFeed( + parent *Process, + feed *ForkFeed, + feedTimeStamp time.Time, +) { + parent.GetInfo().SetFeedAt( + TaskInfoFeed{ + Name: "", // do not change the parent name + Tid: int(feed.ParentTid), + Pid: int(feed.ParentPid), + NsTid: int(feed.ParentNsTid), + NsPid: int(feed.ParentNsPid), + StartTimeNS: feed.ParentStartTime, + PPid: -1, // do not change the parent ppid + NsPPid: -1, // do not change the parent nsppid + Uid: -1, // do not change the parent uid + Gid: -1, // do not change the parent gid + }, + feedTimeStamp, + ) + + if pt.procfsQuery { + pt.FeedFromProcFSAsync(int(feed.ParentPid)) // try to enrich ppid and name from procfs + } +} + +func (pt *ProcessTree) setLeaderFeed( + leader, parent *Process, + feed *ForkFeed, + feedTimeStamp time.Time, +) { + leader.GetInfo().SetFeedAt( + TaskInfoFeed{ + Name: parent.GetInfo().GetName(), + Tid: int(feed.LeaderTid), + Pid: int(feed.LeaderPid), + NsTid: int(feed.LeaderNsTid), + NsPid: int(feed.LeaderNsPid), + StartTimeNS: feed.LeaderStartTime, + PPid: int(feed.ParentPid), + NsPPid: int(feed.ParentNsPid), + Uid: -1, // do not change the parent ui + Gid: -1, // do not change the parent gid + }, + feedTimeStamp, + ) + + if pt.procfsQuery { + pt.FeedFromProcFSAsync(int(feed.LeaderPid)) // try to enrich name from procfs if needed + } +} + +func (pt *ProcessTree) setThreadFeed( + thread *Thread, + leader *Process, + feed *ForkFeed, + feedTimeStamp time.Time, +) { + thread.GetInfo().SetFeedAt( + TaskInfoFeed{ + Name: leader.GetInfo().GetName(), + Tid: int(feed.ChildTid), + Pid: int(feed.ChildPid), + NsTid: int(feed.ChildNsTid), + NsPid: int(feed.ChildNsPid), + StartTimeNS: feed.ChildStartTime, + PPid: int(feed.ParentPid), + NsPPid: int(feed.ParentNsPid), + Uid: -1, // do not change the thread uid + Gid: -1, // do not change the thread gid + }, + feedTimeStamp, + ) +} + // FeedFromFork feeds the process tree with a fork event. func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { if feed.ChildHash == 0 || feed.ParentHash == 0 { @@ -51,27 +127,6 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // Update the parent process (might already exist) - setParentFeed := func(parent *Process) { - parent.GetInfo().SetFeedAt( - TaskInfoFeed{ - Name: "", // do not change the parent name - Tid: int(feed.ParentTid), - Pid: int(feed.ParentPid), - NsTid: int(feed.ParentNsTid), - NsPid: int(feed.ParentNsPid), - StartTimeNS: feed.ParentStartTime, - PPid: -1, // do not change the parent ppid - NsPPid: -1, // do not change the parent nsppid - Uid: -1, // do not change the parent uid - Gid: -1, // do not change the parent gid - }, - feedTimeStamp, - ) - if pt.procfsQuery { - pt.FeedFromProcFSAsync(int(feed.ParentPid)) // try to enrich ppid and name from procfs - } - } - parent, found := pt.GetProcessByHash(feed.ParentHash) // always a real process if !found { parent = pt.GetOrCreateProcessByHash(feed.ParentHash) @@ -82,34 +137,13 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // ppid, for example). if !found || parent.GetInfo().GetPid() != int(feed.ParentPid) { - setParentFeed(parent) + pt.setParentFeed(parent, &feed, feedTimeStamp) } parent.AddChild(feed.LeaderHash) // add the leader as a child of the parent // Update the leader process (might exist, might be the same as child if child is a process) - setLeaderFeed := func(leader *Process) { - leader.GetInfo().SetFeedAt( - TaskInfoFeed{ - Name: parent.GetInfo().GetName(), - Tid: int(feed.LeaderTid), - Pid: int(feed.LeaderPid), - NsTid: int(feed.LeaderNsTid), - NsPid: int(feed.LeaderNsPid), - StartTimeNS: feed.LeaderStartTime, - PPid: int(feed.ParentPid), - NsPPid: int(feed.ParentNsPid), - Uid: -1, // do not change the parent ui - Gid: -1, // do not change the parent gid - }, - feedTimeStamp, - ) - if pt.procfsQuery { - pt.FeedFromProcFSAsync(int(feed.LeaderPid)) // try to enrich name from procfs if needed - } - } - leader, found := pt.GetProcessByHash(feed.LeaderHash) if !found { leader = pt.GetOrCreateProcessByHash(feed.LeaderHash) @@ -118,7 +152,7 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // Same case here (for events out of order created by execve first) if !found || leader.GetInfo().GetPPid() != int(feed.ParentPid) { - setLeaderFeed(leader) + pt.setLeaderFeed(leader, parent, &feed, feedTimeStamp) } leader.SetParentHash(feed.ParentHash) // add the parent as the parent of the leader @@ -135,24 +169,6 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // In all cases (task is a process, or a thread) there is a thread entry. - setThreadFeed := func(thread *Thread) { - thread.GetInfo().SetFeedAt( - TaskInfoFeed{ - Name: leader.GetInfo().GetName(), - Tid: int(feed.ChildTid), - Pid: int(feed.ChildPid), - NsTid: int(feed.ChildNsTid), - NsPid: int(feed.ChildNsPid), - StartTimeNS: feed.ChildStartTime, - PPid: int(feed.ParentPid), - NsPPid: int(feed.ParentNsPid), - Uid: -1, // do not change the thread uid - Gid: -1, // do not change the thread gid - }, - feedTimeStamp, - ) - } - thread, found := pt.GetThreadByHash(feed.ChildHash) if !found { thread = pt.GetOrCreateThreadByHash(feed.ChildHash) @@ -161,7 +177,7 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // Same case here (for events out of order created by execve first) if !found || thread.GetInfo().GetPPid() != int(feed.ParentPid) { - setThreadFeed(thread) + pt.setThreadFeed(thread, leader, &feed, feedTimeStamp) } thread.SetParentHash(feed.ParentHash) // all threads have the same parent as the thread group leader From e9bb1762324ddedd52bef4aa82e0380afcacdc8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Tue, 14 Jan 2025 19:53:13 -0300 Subject: [PATCH 18/25] perf(proctree): introduce feed pools It helps to reduce the stack dynamic growth and the number of allocations, which is good for performance. Changelog fields now holds pointers to the feeds, instead of the feeds themselves. This way, it aligns with the new feed pointers avoiding de-referencing. --- pkg/ebpf/controlplane/processes.go | 173 ++++++++++++----------------- pkg/ebpf/processor_proctree.go | 161 +++++++++++---------------- pkg/proctree/fileinfo.go | 36 +++--- pkg/proctree/proctree.go | 103 ++++++++++++++++- pkg/proctree/proctree_feed.go | 150 ++++++++++++++----------- pkg/proctree/proctree_procfs.go | 62 +++++++---- pkg/proctree/taskinfo.go | 36 +++--- 7 files changed, 401 insertions(+), 320 deletions(-) diff --git a/pkg/ebpf/controlplane/processes.go b/pkg/ebpf/controlplane/processes.go index 7cde1c3031b0..15fe4a83c07c 100644 --- a/pkg/ebpf/controlplane/processes.go +++ b/pkg/ebpf/controlplane/processes.go @@ -2,7 +2,6 @@ package controlplane import ( "github.com/aquasecurity/tracee/pkg/events/parse" - "github.com/aquasecurity/tracee/pkg/proctree" "github.com/aquasecurity/tracee/pkg/time" "github.com/aquasecurity/tracee/pkg/utils" "github.com/aquasecurity/tracee/types/trace" @@ -17,112 +16,95 @@ func (ctrl *Controller) procTreeForkProcessor(args []trace.Argument) error { return nil // process tree is disabled } + var err error + // NOTE: override all the fields of the forkFeed, to avoid any previous data. + forkFeed := ctrl.processTree.GetForkFeedFromPool() + defer ctrl.processTree.PutForkFeedInPool(forkFeed) + // NOTE: The "parent" related arguments can be ignored for process tree purposes. // Process & Event identification arguments - timestamp, err := parse.ArgVal[uint64](args, "timestamp") + forkFeed.TimeStamp, err = parse.ArgVal[uint64](args, "timestamp") if err != nil { return err } // Parent Process (Go up in hierarchy until parent is a process and not a lwp) - parentTid, err := parse.ArgVal[int32](args, "parent_process_tid") + forkFeed.ParentTid, err = parse.ArgVal[int32](args, "parent_process_tid") if err != nil { return err } - parentNsTid, err := parse.ArgVal[int32](args, "parent_process_ns_tid") + forkFeed.ParentNsTid, err = parse.ArgVal[int32](args, "parent_process_ns_tid") if err != nil { return err } - parentPid, err := parse.ArgVal[int32](args, "parent_process_pid") + forkFeed.ParentPid, err = parse.ArgVal[int32](args, "parent_process_pid") if err != nil { return err } - parentNsPid, err := parse.ArgVal[int32](args, "parent_process_ns_pid") + forkFeed.ParentNsPid, err = parse.ArgVal[int32](args, "parent_process_ns_pid") if err != nil { return err } - parentStartTime, err := parse.ArgVal[uint64](args, "parent_process_start_time") + forkFeed.ParentStartTime, err = parse.ArgVal[uint64](args, "parent_process_start_time") if err != nil { return err } // Thread Group Leader (might be the same as the "child", if "child" is a process) - leaderTid, err := parse.ArgVal[int32](args, "leader_tid") + forkFeed.LeaderTid, err = parse.ArgVal[int32](args, "leader_tid") if err != nil { return err } - leaderNsTid, err := parse.ArgVal[int32](args, "leader_ns_tid") + forkFeed.LeaderNsTid, err = parse.ArgVal[int32](args, "leader_ns_tid") if err != nil { return err } - leaderPid, err := parse.ArgVal[int32](args, "leader_pid") + forkFeed.LeaderPid, err = parse.ArgVal[int32](args, "leader_pid") if err != nil { return err } - leaderNsPid, err := parse.ArgVal[int32](args, "leader_ns_pid") + forkFeed.LeaderNsPid, err = parse.ArgVal[int32](args, "leader_ns_pid") if err != nil { return err } - leaderStartTime, err := parse.ArgVal[uint64](args, "leader_start_time") + forkFeed.LeaderStartTime, err = parse.ArgVal[uint64](args, "leader_start_time") if err != nil { return err } // Child (might be a process or a thread) - childTid, err := parse.ArgVal[int32](args, "child_tid") + forkFeed.ChildTid, err = parse.ArgVal[int32](args, "child_tid") if err != nil { return err } - childNsTid, err := parse.ArgVal[int32](args, "child_ns_tid") + forkFeed.ChildNsTid, err = parse.ArgVal[int32](args, "child_ns_tid") if err != nil { return err } - childPid, err := parse.ArgVal[int32](args, "child_pid") + forkFeed.ChildPid, err = parse.ArgVal[int32](args, "child_pid") if err != nil { return err } - childNsPid, err := parse.ArgVal[int32](args, "child_ns_pid") + forkFeed.ChildNsPid, err = parse.ArgVal[int32](args, "child_ns_pid") if err != nil { return err } - childStartTime, err := parse.ArgVal[uint64](args, "start_time") // child_start_time + forkFeed.ChildStartTime, err = parse.ArgVal[uint64](args, "start_time") // child_start_time if err != nil { return err } - timestamp = time.BootToEpochNS(timestamp) - childStartTime = time.BootToEpochNS(childStartTime) - parentStartTime = time.BootToEpochNS(parentStartTime) - leaderStartTime = time.BootToEpochNS(leaderStartTime) + forkFeed.TimeStamp = time.BootToEpochNS(forkFeed.TimeStamp) + forkFeed.ChildStartTime = time.BootToEpochNS(forkFeed.ChildStartTime) + forkFeed.ParentStartTime = time.BootToEpochNS(forkFeed.ParentStartTime) + forkFeed.LeaderStartTime = time.BootToEpochNS(forkFeed.LeaderStartTime) - childHash := utils.HashTaskID(uint32(childTid), childStartTime) - parentHash := utils.HashTaskID(uint32(parentTid), parentStartTime) - leaderHash := utils.HashTaskID(uint32(leaderTid), leaderStartTime) + forkFeed.ChildHash = utils.HashTaskID(uint32(forkFeed.ChildTid), forkFeed.ChildStartTime) + forkFeed.ParentHash = utils.HashTaskID(uint32(forkFeed.ParentTid), forkFeed.ParentStartTime) + forkFeed.LeaderHash = utils.HashTaskID(uint32(forkFeed.LeaderTid), forkFeed.LeaderStartTime) - return ctrl.processTree.FeedFromFork( - proctree.ForkFeed{ - TimeStamp: timestamp, - ChildHash: childHash, - ParentHash: parentHash, - LeaderHash: leaderHash, - ParentTid: parentTid, - ParentNsTid: parentNsTid, - ParentPid: parentPid, - ParentNsPid: parentNsPid, - ParentStartTime: parentStartTime, - LeaderTid: leaderTid, - LeaderNsTid: leaderNsTid, - LeaderPid: leaderPid, - LeaderNsPid: leaderNsPid, - LeaderStartTime: leaderStartTime, - ChildTid: childTid, - ChildNsTid: childNsTid, - ChildPid: childPid, - ChildNsPid: childNsPid, - ChildStartTime: childStartTime, - }, - ) + return ctrl.processTree.FeedFromFork(forkFeed) } func (ctrl *Controller) procTreeExecProcessor(args []trace.Argument) error { @@ -130,89 +112,74 @@ func (ctrl *Controller) procTreeExecProcessor(args []trace.Argument) error { return nil // process tree is disabled } + var err error + + // NOTE: override all the fields of the execFeed, to avoid any previous data. + execFeed := ctrl.processTree.GetExecFeedFromPool() + defer ctrl.processTree.PutExecFeedInPool(execFeed) + // Process & Event identification arguments (won't exist for regular events) - timestamp, err := parse.ArgVal[uint64](args, "timestamp") + execFeed.TimeStamp, err = parse.ArgVal[uint64](args, "timestamp") if err != nil { return err } - taskHash, _ := parse.ArgVal[uint32](args, "task_hash") - parentHash, _ := parse.ArgVal[uint32](args, "parent_hash") - leaderHash, _ := parse.ArgVal[uint32](args, "leader_hash") + execFeed.TaskHash, _ = parse.ArgVal[uint32](args, "task_hash") + execFeed.ParentHash, _ = parse.ArgVal[uint32](args, "parent_hash") + execFeed.LeaderHash, _ = parse.ArgVal[uint32](args, "leader_hash") // Executable - cmdPath, err := parse.ArgVal[string](args, "cmdpath") + execFeed.CmdPath, err = parse.ArgVal[string](args, "cmdpath") if err != nil { return err } - pathName, err := parse.ArgVal[string](args, "pathname") + execFeed.PathName, err = parse.ArgVal[string](args, "pathname") if err != nil { return err } - dev, err := parse.ArgVal[uint32](args, "dev") + execFeed.Dev, err = parse.ArgVal[uint32](args, "dev") if err != nil { return err } - inode, err := parse.ArgVal[uint64](args, "inode") + execFeed.Inode, err = parse.ArgVal[uint64](args, "inode") if err != nil { return err } - ctime, err := parse.ArgVal[uint64](args, "ctime") + execFeed.Ctime, err = parse.ArgVal[uint64](args, "ctime") if err != nil { return err } - inodeMode, err := parse.ArgVal[uint16](args, "inode_mode") + execFeed.InodeMode, err = parse.ArgVal[uint16](args, "inode_mode") if err != nil { return err } // // Binary Interpreter (or Loader): might come empty from the kernel - // interPathName, _ := parse.ArgVal[string](args, "interpreter_pathname") - // interDev, _ := parse.ArgVal[uint32](args, "interpreter_dev") - // interInode, _ := parse.ArgVal[uint64](args, "interpreter_inode") - // interCtime, _ := parse.ArgVal[uint64](args, "interpreter_ctime") + // InterpreterPath, _ := parse.ArgVal[string](args, "interpreter_pathname") + // InterpreterDev, _ := parse.ArgVal[uint32](args, "interpreter_dev") + // InterpreterInode, _ := parse.ArgVal[uint64](args, "interpreter_inode") + // InterpreterCtime, _ := parse.ArgVal[uint64](args, "interpreter_ctime") // Real Interpreter - interp, err := parse.ArgVal[string](args, "interp") + execFeed.Interp, err = parse.ArgVal[string](args, "interp") if err != nil { return err } // Others - stdinType, err := parse.ArgVal[uint16](args, "stdin_type") + execFeed.StdinType, err = parse.ArgVal[uint16](args, "stdin_type") if err != nil { return err } - stdinPath, err := parse.ArgVal[string](args, "stdin_path") + execFeed.StdinPath, err = parse.ArgVal[string](args, "stdin_path") if err != nil { return err } - invokedFromKernel, err := parse.ArgVal[int32](args, "invoked_from_kernel") + execFeed.InvokedFromKernel, err = parse.ArgVal[int32](args, "invoked_from_kernel") if err != nil { return err } - return ctrl.processTree.FeedFromExec( - proctree.ExecFeed{ - TimeStamp: time.BootToEpochNS(timestamp), - TaskHash: taskHash, - ParentHash: parentHash, - LeaderHash: leaderHash, - CmdPath: cmdPath, - PathName: pathName, - Dev: dev, - Inode: inode, - Ctime: ctime, - InodeMode: inodeMode, - // InterpreterPath: interPathName, - // InterpreterDev: interDev, - // InterpreterInode: interInode, - // InterpreterCtime: interCtime, - Interp: interp, - StdinType: stdinType, - StdinPath: stdinPath, - InvokedFromKernel: invokedFromKernel, - }, - ) + return ctrl.processTree.FeedFromExec(execFeed) } func (ctrl *Controller) procTreeExitProcessor(args []trace.Argument) error { @@ -220,42 +187,42 @@ func (ctrl *Controller) procTreeExitProcessor(args []trace.Argument) error { return nil // process tree is disabled } + var err error + + // NOTE: override all the fields of the exitFeed, to avoid any previous data. + exitFeed := ctrl.processTree.GetExitFeedFromPool() + defer ctrl.processTree.PutExitFeedInPool(exitFeed) + // Process & Event identification arguments (won't exist for regular events) - timestamp, err := parse.ArgVal[uint64](args, "timestamp") + exitFeed.TimeStamp, err = parse.ArgVal[uint64](args, "timestamp") if err != nil { return err } - taskHash, err := parse.ArgVal[uint32](args, "task_hash") + // time of exit is already a timestamp) + exitFeed.TimeStamp = time.BootToEpochNS(exitFeed.TimeStamp) + + exitFeed.TaskHash, err = parse.ArgVal[uint32](args, "task_hash") if err != nil { return err } - parentHash, err := parse.ArgVal[uint32](args, "parent_hash") + exitFeed.ParentHash, err = parse.ArgVal[uint32](args, "parent_hash") if err != nil { return err } - leaderHash, err := parse.ArgVal[uint32](args, "leader_hash") + exitFeed.LeaderHash, err = parse.ArgVal[uint32](args, "leader_hash") if err != nil { return err } // Exit logic arguments - exitCode, err := parse.ArgVal[int64](args, "exit_code") + exitFeed.ExitCode, err = parse.ArgVal[int64](args, "exit_code") if err != nil { return err } - groupExit, err := parse.ArgVal[bool](args, "process_group_exit") + exitFeed.Group, err = parse.ArgVal[bool](args, "process_group_exit") if err != nil { return err } - return ctrl.processTree.FeedFromExit( - proctree.ExitFeed{ - TimeStamp: time.BootToEpochNS(timestamp), // time of exit is already a times)p - TaskHash: taskHash, - ParentHash: parentHash, - LeaderHash: leaderHash, - ExitCode: exitCode, - Group: groupExit, - }, - ) + return ctrl.processTree.FeedFromExit(exitFeed) } diff --git a/pkg/ebpf/processor_proctree.go b/pkg/ebpf/processor_proctree.go index 7758f275ff88..aebf0ee08822 100644 --- a/pkg/ebpf/processor_proctree.go +++ b/pkg/ebpf/processor_proctree.go @@ -5,7 +5,6 @@ import ( "github.com/aquasecurity/tracee/pkg/events/parse" "github.com/aquasecurity/tracee/pkg/logger" - "github.com/aquasecurity/tracee/pkg/proctree" traceetime "github.com/aquasecurity/tracee/pkg/time" "github.com/aquasecurity/tracee/pkg/utils" "github.com/aquasecurity/tracee/types/trace" @@ -21,102 +20,86 @@ func (t *Tracee) procTreeForkProcessor(event *trace.Event) error { return fmt.Errorf("process tree is disabled") } + var err error + // NOTE: override all the fields of the forkFeed, to avoid any previous data. + forkFeed := t.processTree.GetForkFeedFromPool() + defer t.processTree.PutForkFeedInPool(forkFeed) + // NOTE: The "parent" related arguments can be ignored for process tree purposes. // Parent Process (Go up in hierarchy until parent is a process and not a lwp) - parentTid, err := parse.ArgVal[int32](event.Args, "parent_process_tid") + forkFeed.ParentTid, err = parse.ArgVal[int32](event.Args, "parent_process_tid") if err != nil { return err } - parentNsTid, err := parse.ArgVal[int32](event.Args, "parent_process_ns_tid") + forkFeed.ParentNsTid, err = parse.ArgVal[int32](event.Args, "parent_process_ns_tid") if err != nil { return err } - parentPid, err := parse.ArgVal[int32](event.Args, "parent_process_pid") + forkFeed.ParentPid, err = parse.ArgVal[int32](event.Args, "parent_process_pid") if err != nil { return err } - parentNsPid, err := parse.ArgVal[int32](event.Args, "parent_process_ns_pid") + forkFeed.ParentNsPid, err = parse.ArgVal[int32](event.Args, "parent_process_ns_pid") if err != nil { return err } - parentStartTime, err := parse.ArgVal[uint64](event.Args, "parent_process_start_time") + forkFeed.ParentStartTime, err = parse.ArgVal[uint64](event.Args, "parent_process_start_time") if err != nil { return err } // Thread Group Leader (might be the same as the "child", if "child" is a process) - leaderTid, err := parse.ArgVal[int32](event.Args, "leader_tid") + forkFeed.LeaderTid, err = parse.ArgVal[int32](event.Args, "leader_tid") if err != nil { return err } - leaderNsTid, err := parse.ArgVal[int32](event.Args, "leader_ns_tid") + forkFeed.LeaderNsTid, err = parse.ArgVal[int32](event.Args, "leader_ns_tid") if err != nil { return err } - leaderPid, err := parse.ArgVal[int32](event.Args, "leader_pid") + forkFeed.LeaderPid, err = parse.ArgVal[int32](event.Args, "leader_pid") if err != nil { return err } - leaderNsPid, err := parse.ArgVal[int32](event.Args, "leader_ns_pid") + forkFeed.LeaderNsPid, err = parse.ArgVal[int32](event.Args, "leader_ns_pid") if err != nil { return err } - leaderStartTime, err := parse.ArgVal[uint64](event.Args, "leader_start_time") + forkFeed.LeaderStartTime, err = parse.ArgVal[uint64](event.Args, "leader_start_time") if err != nil { return err } // Child (might be a process or a thread) - childTid, err := parse.ArgVal[int32](event.Args, "child_tid") + forkFeed.ChildTid, err = parse.ArgVal[int32](event.Args, "child_tid") if err != nil { return err } - childNsTid, err := parse.ArgVal[int32](event.Args, "child_ns_tid") + forkFeed.ChildNsTid, err = parse.ArgVal[int32](event.Args, "child_ns_tid") if err != nil { return err } - childPid, err := parse.ArgVal[int32](event.Args, "child_pid") + forkFeed.ChildPid, err = parse.ArgVal[int32](event.Args, "child_pid") if err != nil { return err } - childNsPid, err := parse.ArgVal[int32](event.Args, "child_ns_pid") + forkFeed.ChildNsPid, err = parse.ArgVal[int32](event.Args, "child_ns_pid") if err != nil { return err } - childStartTime, err := parse.ArgVal[uint64](event.Args, "start_time") // child_start_time + forkFeed.ChildStartTime, err = parse.ArgVal[uint64](event.Args, "start_time") // child_start_time if err != nil { return err } + forkFeed.TimeStamp = forkFeed.ChildStartTime // event timestamp is the same // Calculate hashes - parentHash := utils.HashTaskID(uint32(parentTid), uint64(parentStartTime)) - leaderHash := utils.HashTaskID(uint32(leaderTid), uint64(leaderStartTime)) - childHash := utils.HashTaskID(uint32(childTid), uint64(childStartTime)) - - return t.processTree.FeedFromFork( - proctree.ForkFeed{ - TimeStamp: childStartTime, // event timestamp is the same - ChildHash: childHash, - ParentHash: parentHash, - LeaderHash: leaderHash, - ParentTid: parentTid, - ParentNsTid: parentNsTid, - ParentPid: parentPid, - ParentNsPid: parentNsPid, - ParentStartTime: parentStartTime, - LeaderTid: leaderTid, - LeaderNsTid: leaderNsTid, - LeaderPid: leaderPid, - LeaderNsPid: leaderNsPid, - LeaderStartTime: leaderStartTime, - ChildTid: childTid, - ChildNsTid: childNsTid, - ChildPid: childPid, - ChildNsPid: childNsPid, - ChildStartTime: childStartTime, - }, - ) + forkFeed.ParentHash = utils.HashTaskID(uint32(forkFeed.ParentTid), uint64(forkFeed.ParentStartTime)) + forkFeed.ChildHash = utils.HashTaskID(uint32(forkFeed.ChildTid), uint64(forkFeed.ChildStartTime)) + forkFeed.LeaderHash = utils.HashTaskID(uint32(forkFeed.LeaderTid), uint64(forkFeed.LeaderStartTime)) + + return t.processTree.FeedFromFork(forkFeed) } // procTreeExecProcessor handles process exec events. @@ -128,83 +111,70 @@ func (t *Tracee) procTreeExecProcessor(event *trace.Event) error { return nil // chek FeedFromExec for TODO of execve() handled by threads } + var err error + + // NOTE: override all the fields of the execFeed, to avoid any previous data. + execFeed := t.processTree.GetExecFeedFromPool() + defer t.processTree.PutExecFeedInPool(execFeed) + // Executable - cmdPath, err := parse.ArgVal[string](event.Args, "cmdpath") + execFeed.CmdPath, err = parse.ArgVal[string](event.Args, "cmdpath") if err != nil { return err } - pathName, err := parse.ArgVal[string](event.Args, "pathname") + execFeed.PathName, err = parse.ArgVal[string](event.Args, "pathname") if err != nil { return err } - dev, err := parse.ArgVal[uint32](event.Args, "dev") + execFeed.Dev, err = parse.ArgVal[uint32](event.Args, "dev") if err != nil { return err } - inode, err := parse.ArgVal[uint64](event.Args, "inode") + execFeed.Inode, err = parse.ArgVal[uint64](event.Args, "inode") if err != nil { return err } - ctime, err := parse.ArgVal[uint64](event.Args, "ctime") + execFeed.Ctime, err = parse.ArgVal[uint64](event.Args, "ctime") if err != nil { return err } - inodeMode, err := parse.ArgVal[uint16](event.Args, "inode_mode") + execFeed.InodeMode, err = parse.ArgVal[uint16](event.Args, "inode_mode") if err != nil { return err } // // Binary Interpreter (or Loader): might come empty from the kernel - // interPathName, _ := parse.ArgVal[string](event.Args, "interpreter_pathname") - // interDev, _ := parse.ArgVal[uint32](event.Args, "interpreter_dev") - // interInode, _ := parse.ArgVal[uint64](event.Args, "interpreter_inode") - // interCtime, _ := parse.ArgVal[uint64](event.Args, "interpreter_ctime") + // execFeed.InterpreterPath, _ := parse.ArgVal[string](event.Args, "interpreter_pathname") + // execFeed.InterpreterDev, _ := parse.ArgVal[uint32](event.Args, "interpreter_dev") + // execFeed.InterpreterInode, _ := parse.ArgVal[uint64](event.Args, "interpreter_inode") + // execFeed.InterpreterCtime, _ := parse.ArgVal[uint64](event.Args, "interpreter_ctime") // Real Interpreter - interp, err := parse.ArgVal[string](event.Args, "interp") + execFeed.Interp, err = parse.ArgVal[string](event.Args, "interp") if err != nil { return err } // Others - stdinType, err := parse.ArgVal[uint16](event.Args, "stdin_type") + execFeed.StdinType, err = parse.ArgVal[uint16](event.Args, "stdin_type") if err != nil { return err } - stdinPath, err := parse.ArgVal[string](event.Args, "stdin_path") + execFeed.StdinPath, err = parse.ArgVal[string](event.Args, "stdin_path") if err != nil { return err } - invokedFromKernel, err := parse.ArgVal[int32](event.Args, "invoked_from_kernel") + execFeed.InvokedFromKernel, err = parse.ArgVal[int32](event.Args, "invoked_from_kernel") if err != nil { return err } - timestamp := uint64(event.Timestamp) - taskHash := utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) - - return t.processTree.FeedFromExec( - proctree.ExecFeed{ - TimeStamp: timestamp, - TaskHash: taskHash, - ParentHash: 0, // regular pipeline does not have parent hash - LeaderHash: 0, // regular pipeline does not have leader hash - CmdPath: cmdPath, - PathName: pathName, - Dev: dev, - Inode: inode, - Ctime: ctime, - InodeMode: inodeMode, - // InterpreterPath: interPathName, - // InterpreterDev: interDev, - // InterpreterInode: interInode, - // InterpreterCtime: interCtime, - Interp: interp, - StdinType: stdinType, - StdinPath: stdinPath, - InvokedFromKernel: invokedFromKernel, - }, - ) + execFeed.TimeStamp = uint64(event.Timestamp) + execFeed.TaskHash = utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) + execFeed.ParentHash = 0 // regular pipeline does not have parent hash + execFeed.LeaderHash = 0 // regular pipeline does not have leader hash + + return t.processTree.FeedFromExec(execFeed) } // procTreeExitProcessor handles process exit events. @@ -216,29 +186,28 @@ func (t *Tracee) procTreeExitProcessor(event *trace.Event) error { return nil // chek FeedFromExec for TODO of execve() handled by threads } + var err error + + // NOTE: override all the fields of the exitFeed, to avoid any previous data. + exitFeed := t.processTree.GetExitFeedFromPool() + defer t.processTree.PutExitFeedInPool(exitFeed) + // Exit logic arguments - exitCode, err := parse.ArgVal[int64](event.Args, "exit_code") + exitFeed.ExitCode, err = parse.ArgVal[int64](event.Args, "exit_code") if err != nil { return err } - groupExit, err := parse.ArgVal[bool](event.Args, "process_group_exit") + exitFeed.Group, err = parse.ArgVal[bool](event.Args, "process_group_exit") if err != nil { return err } - timestamp := uint64(event.Timestamp) - taskHash := utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) + exitFeed.TimeStamp = uint64(event.Timestamp) // time of exit is already a timestamp + exitFeed.TaskHash = utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) + exitFeed.ParentHash = 0 // regular pipeline does not have parent hash + exitFeed.LeaderHash = 0 // regular pipeline does not have leader hash - return t.processTree.FeedFromExit( - proctree.ExitFeed{ - TimeStamp: timestamp, // time of exit is already a timestamp - TaskHash: taskHash, - ParentHash: 0, // regular pipeline does not have parent hash - LeaderHash: 0, // regular pipeline does not have leader hash - ExitCode: exitCode, - Group: groupExit, - }, - ) + return t.processTree.FeedFromExit(exitFeed) } // diff --git a/pkg/proctree/fileinfo.go b/pkg/proctree/fileinfo.go index a41defbedf9a..5a2c6a0bd6bd 100644 --- a/pkg/proctree/fileinfo.go +++ b/pkg/proctree/fileinfo.go @@ -24,20 +24,20 @@ type FileInfoFeed struct { // FileInfo represents a file. type FileInfo struct { - feed *changelog.Changelog[FileInfoFeed] + feed *changelog.Changelog[*FileInfoFeed] mutex *sync.RWMutex } // NewFileInfo creates a new file. func NewFileInfo() *FileInfo { return &FileInfo{ - feed: changelog.NewChangelog[FileInfoFeed](3), + feed: changelog.NewChangelog[*FileInfoFeed](3), mutex: &sync.RWMutex{}, } } // NewFileInfoFeed creates a new file with values from the given feed. -func NewFileInfoFeed(feed FileInfoFeed) *FileInfo { +func NewFileInfoFeed(feed *FileInfoFeed) *FileInfo { new := NewFileInfo() new.setFeed(feed) @@ -51,7 +51,7 @@ func NewFileInfoFeed(feed FileInfoFeed) *FileInfo { // Multiple values at once (using a feed structure) // SetFeed sets the values of the file from a feed at the current time. -func (fi *FileInfo) SetFeed(feed FileInfoFeed) { +func (fi *FileInfo) SetFeed(feed *FileInfoFeed) { fi.mutex.Lock() defer fi.mutex.Unlock() @@ -59,7 +59,7 @@ func (fi *FileInfo) SetFeed(feed FileInfoFeed) { } // SetFeedAt sets the values of the file from a feed at the given time. -func (fi *FileInfo) SetFeedAt(feed FileInfoFeed, targetTime time.Time) { +func (fi *FileInfo) SetFeedAt(feed *FileInfoFeed, targetTime time.Time) { fi.mutex.Lock() defer fi.mutex.Unlock() @@ -68,7 +68,7 @@ func (fi *FileInfo) SetFeedAt(feed FileInfoFeed, targetTime time.Time) { // private setters -func (fi *FileInfo) setFeed(feed FileInfoFeed) { +func (fi *FileInfo) setFeed(feed *FileInfoFeed) { fi.setFeedAt(feed, time.Now()) } @@ -76,7 +76,7 @@ func (fi *FileInfo) setFeed(feed FileInfoFeed) { // managing memory more responsibly. const MaxPathLen = 1024 -func (fi *FileInfo) setFeedAt(feed FileInfoFeed, targetTime time.Time) { +func (fi *FileInfo) setFeedAt(feed *FileInfoFeed, targetTime time.Time) { atFeed := fi.getFeedAt(targetTime) if feed.Path != "" { @@ -115,7 +115,7 @@ func (fi *FileInfo) GetFeed() FileInfoFeed { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.getFeed() + return *fi.getFeed() // return a copy } // GetFeedAt returns the values of the file as a feed at the given time. @@ -123,7 +123,7 @@ func (fi *FileInfo) GetFeedAt(targetTime time.Time) FileInfoFeed { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.getFeedAt(targetTime) + return *fi.getFeedAt(targetTime) // return a copy } // Single values @@ -210,10 +210,20 @@ func (fi *FileInfo) GetInodeModeAt(targetTime time.Time) int { // private getters -func (fi *FileInfo) getFeed() FileInfoFeed { - return fi.feed.GetCurrent() +func (fi *FileInfo) getFeed() *FileInfoFeed { + feed := fi.feed.GetCurrent() + if feed == nil { + feed = &FileInfoFeed{} + } + + return feed } -func (fi *FileInfo) getFeedAt(targetTime time.Time) FileInfoFeed { - return fi.feed.Get(targetTime) +func (fi *FileInfo) getFeedAt(targetTime time.Time) *FileInfoFeed { + feed := fi.feed.Get(targetTime) + if feed == nil { + feed = &FileInfoFeed{} + } + + return feed } diff --git a/pkg/proctree/proctree.go b/pkg/proctree/proctree.go index 7017ffaf427d..ff233415db78 100644 --- a/pkg/proctree/proctree.go +++ b/pkg/proctree/proctree.go @@ -80,6 +80,13 @@ type ProcessTree struct { procfsOnce *sync.Once // busy loop debug message throttling ctx context.Context // context for the process tree procfsQuery bool + + // pools + forkFeedPool *sync.Pool // pool of ForkFeed instances + execFeedPool *sync.Pool // pool of ExecFeed instances + exitFeedPool *sync.Pool // pool of ExitFeed instances + taskInfoFeedPool *sync.Pool // pool of TaskInfoFeed instances + fileInfoFeedPool *sync.Pool // pool of FileInfoFeed instances } // NewProcessTree creates a new process tree. @@ -146,6 +153,31 @@ func NewProcessTree(ctx context.Context, config ProcTreeConfig) (*ProcessTree, e procfsOnce: new(sync.Once), ctx: ctx, procfsQuery: config.ProcfsQuerying, + forkFeedPool: &sync.Pool{ + New: func() interface{} { + return &ForkFeed{} + }, + }, + execFeedPool: &sync.Pool{ + New: func() interface{} { + return &ExecFeed{} + }, + }, + exitFeedPool: &sync.Pool{ + New: func() interface{} { + return &ExitFeed{} + }, + }, + taskInfoFeedPool: &sync.Pool{ + New: func() interface{} { + return &TaskInfoFeed{} + }, + }, + fileInfoFeedPool: &sync.Pool{ + New: func() interface{} { + return &FileInfoFeed{} + }, + }, } if config.ProcfsInitialization { @@ -163,10 +195,6 @@ func NewProcessTree(ctx context.Context, config ProcTreeConfig) (*ProcessTree, e // GetProcessByHash returns a process by its hash. func (pt *ProcessTree) GetProcessByHash(hash uint32) (*Process, bool) { process, ok := pt.processes.Get(hash) - if !ok { - return nil, false - } - return process, ok } @@ -222,3 +250,70 @@ func (pt *ProcessTree) GetOrCreateThreadByHash(hash uint32) *Thread { return thread // return an existing thread } + +// Pools + +// GetForkFeedFromPool returns a ForkFeed from the pool, or creates a new one if the pool is empty. +// ForkFeed certainly contains old data, so it must be updated before use. +func (pt *ProcessTree) GetForkFeedFromPool() *ForkFeed { + // revive:disable:unchecked-type-assertion + return pt.forkFeedPool.Get().(*ForkFeed) + // revive:enable:unchecked-type-assertion +} + +// PutForkFeedInPool returns a ForkFeed to the pool. +func (pt *ProcessTree) PutForkFeedInPool(forkFeed *ForkFeed) { + pt.forkFeedPool.Put(forkFeed) +} + +// GetExecFeedFromPool returns a ExecFeed from the pool, or creates a new one if the pool is empty. +// ExecFeed certainly contains old data, so it must be updated before use. +func (pt *ProcessTree) GetExecFeedFromPool() *ExecFeed { + // revive:disable:unchecked-type-assertion + return pt.execFeedPool.Get().(*ExecFeed) + // revive:enable:unchecked-type-assertion +} + +// PutExecFeedInPool returns a ExecFeed to the pool. +func (pt *ProcessTree) PutExecFeedInPool(execFeed *ExecFeed) { + pt.execFeedPool.Put(execFeed) +} + +// GetExitFeedFromPool returns a ExitFeed from the pool, or creates a new one if the pool is empty. +// ExitFeed certainly contains old data, so it must be updated before use. +func (pt *ProcessTree) GetExitFeedFromPool() *ExitFeed { + // revive:disable:unchecked-type-assertion + return pt.exitFeedPool.Get().(*ExitFeed) + // revive:enable:unchecked-type-assertion +} + +// PutExitFeedInPool returns a ExitFeed to the pool. +func (pt *ProcessTree) PutExitFeedInPool(exitFeed *ExitFeed) { + pt.exitFeedPool.Put(exitFeed) +} + +// GetTaskInfoFeedFromPool returns a TaskInfoFeed from the pool, or creates a new one if the pool is empty. +// TaskInfoFeed certainly contains old data, so it must be updated before use. +func (pt *ProcessTree) GetTaskInfoFeedFromPool() *TaskInfoFeed { + // revive:disable:unchecked-type-assertion + return pt.taskInfoFeedPool.Get().(*TaskInfoFeed) + // revive:enable:unchecked-type-assertion +} + +// PutTaskInfoFeedInPool returns a TaskInfoFeed to the pool. +func (pt *ProcessTree) PutTaskInfoFeedInPool(taskInfoFeed *TaskInfoFeed) { + pt.taskInfoFeedPool.Put(taskInfoFeed) +} + +// GetFileInfoFeedFromPool returns a FileInfoFeed from the pool, or creates a new one if the pool is empty. +// FileInfoFeed certainly contains old data, so it must be updated before use. +func (pt *ProcessTree) GetFileInfoFeedFromPool() *FileInfoFeed { + // revive:disable:unchecked-type-assertion + return pt.fileInfoFeedPool.Get().(*FileInfoFeed) + // revive:enable:unchecked-type-assertion +} + +// PutFileInfoFeedInPool returns a FileInfoFeed to the pool. +func (pt *ProcessTree) PutFileInfoFeedInPool(fileInfoFeed *FileInfoFeed) { + pt.fileInfoFeedPool.Put(fileInfoFeed) +} diff --git a/pkg/proctree/proctree_feed.go b/pkg/proctree/proctree_feed.go index ea1297b42aab..a5c8454818cc 100644 --- a/pkg/proctree/proctree_feed.go +++ b/pkg/proctree/proctree_feed.go @@ -37,81 +37,93 @@ type ForkFeed struct { func (pt *ProcessTree) setParentFeed( parent *Process, - feed *ForkFeed, + forkFeed *ForkFeed, feedTimeStamp time.Time, ) { - parent.GetInfo().SetFeedAt( - TaskInfoFeed{ - Name: "", // do not change the parent name - Tid: int(feed.ParentTid), - Pid: int(feed.ParentPid), - NsTid: int(feed.ParentNsTid), - NsPid: int(feed.ParentNsPid), - StartTimeNS: feed.ParentStartTime, - PPid: -1, // do not change the parent ppid - NsPPid: -1, // do not change the parent nsppid - Uid: -1, // do not change the parent uid - Gid: -1, // do not change the parent gid - }, - feedTimeStamp, - ) + // NOTE: override all the fields of the taskInfoFeed, to avoid any previous data. + taskInfoFeed := pt.GetTaskInfoFeedFromPool() + + taskInfoFeed.Name = "" // do not change the parent name + taskInfoFeed.Tid = int(forkFeed.ParentTid) + taskInfoFeed.Pid = int(forkFeed.ParentPid) + taskInfoFeed.NsTid = int(forkFeed.ParentNsTid) + taskInfoFeed.NsPid = int(forkFeed.ParentNsPid) + taskInfoFeed.StartTimeNS = forkFeed.ParentStartTime + taskInfoFeed.PPid = -1 // do not change the parent ppid + taskInfoFeed.NsPPid = -1 // do not change the parent nsppid + taskInfoFeed.Uid = -1 // do not change the parent uid + taskInfoFeed.Gid = -1 // do not change the parent gid + taskInfoFeed.ExitTimeNS = 0 + + parent.GetInfo().SetFeedAt(taskInfoFeed, feedTimeStamp) + + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutTaskInfoFeedInPool(taskInfoFeed) if pt.procfsQuery { - pt.FeedFromProcFSAsync(int(feed.ParentPid)) // try to enrich ppid and name from procfs + pt.FeedFromProcFSAsync(int(forkFeed.ParentPid)) // try to enrich ppid and name from procfs } } func (pt *ProcessTree) setLeaderFeed( leader, parent *Process, - feed *ForkFeed, + forkFeed *ForkFeed, feedTimeStamp time.Time, ) { - leader.GetInfo().SetFeedAt( - TaskInfoFeed{ - Name: parent.GetInfo().GetName(), - Tid: int(feed.LeaderTid), - Pid: int(feed.LeaderPid), - NsTid: int(feed.LeaderNsTid), - NsPid: int(feed.LeaderNsPid), - StartTimeNS: feed.LeaderStartTime, - PPid: int(feed.ParentPid), - NsPPid: int(feed.ParentNsPid), - Uid: -1, // do not change the parent ui - Gid: -1, // do not change the parent gid - }, - feedTimeStamp, - ) + // NOTE: override all the fields of the taskInfoFeed, to avoid any previous data. + taskInfoFeed := pt.GetTaskInfoFeedFromPool() + + taskInfoFeed.Name = parent.GetInfo().GetName() + taskInfoFeed.Tid = int(forkFeed.LeaderTid) + taskInfoFeed.Pid = int(forkFeed.LeaderPid) + taskInfoFeed.NsTid = int(forkFeed.LeaderNsTid) + taskInfoFeed.NsPid = int(forkFeed.LeaderNsPid) + taskInfoFeed.StartTimeNS = forkFeed.LeaderStartTime + taskInfoFeed.PPid = int(forkFeed.ParentPid) + taskInfoFeed.NsPPid = int(forkFeed.ParentNsPid) + taskInfoFeed.Uid = -1 // do not change the leader uid + taskInfoFeed.Gid = -1 // do not change the leader gid + taskInfoFeed.ExitTimeNS = 0 + + leader.GetInfo().SetFeedAt(taskInfoFeed, feedTimeStamp) + + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutTaskInfoFeedInPool(taskInfoFeed) if pt.procfsQuery { - pt.FeedFromProcFSAsync(int(feed.LeaderPid)) // try to enrich name from procfs if needed + pt.FeedFromProcFSAsync(int(forkFeed.LeaderPid)) // try to enrich name from procfs if needed } } func (pt *ProcessTree) setThreadFeed( thread *Thread, leader *Process, - feed *ForkFeed, + forkFeed *ForkFeed, feedTimeStamp time.Time, ) { - thread.GetInfo().SetFeedAt( - TaskInfoFeed{ - Name: leader.GetInfo().GetName(), - Tid: int(feed.ChildTid), - Pid: int(feed.ChildPid), - NsTid: int(feed.ChildNsTid), - NsPid: int(feed.ChildNsPid), - StartTimeNS: feed.ChildStartTime, - PPid: int(feed.ParentPid), - NsPPid: int(feed.ParentNsPid), - Uid: -1, // do not change the thread uid - Gid: -1, // do not change the thread gid - }, - feedTimeStamp, - ) + // NOTE: override all the fields of the taskInfoFeed, to avoid any previous data. + taskInfoFeed := pt.GetTaskInfoFeedFromPool() + + taskInfoFeed.Name = leader.GetInfo().GetName() + taskInfoFeed.Tid = int(forkFeed.ChildTid) + taskInfoFeed.Pid = int(forkFeed.ChildPid) + taskInfoFeed.NsTid = int(forkFeed.ChildNsTid) + taskInfoFeed.NsPid = int(forkFeed.ChildNsPid) + taskInfoFeed.StartTimeNS = forkFeed.ChildStartTime + taskInfoFeed.PPid = int(forkFeed.ParentPid) + taskInfoFeed.NsPPid = int(forkFeed.ParentNsPid) + taskInfoFeed.Uid = -1 // do not change the thread uid + taskInfoFeed.Gid = -1 // do not change the thread gid + taskInfoFeed.ExitTimeNS = 0 + + thread.GetInfo().SetFeedAt(taskInfoFeed, feedTimeStamp) + + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutTaskInfoFeedInPool(taskInfoFeed) } // FeedFromFork feeds the process tree with a fork event. -func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { +func (pt *ProcessTree) FeedFromFork(feed *ForkFeed) error { if feed.ChildHash == 0 || feed.ParentHash == 0 { return errfmt.Errorf("invalid task hash") } @@ -137,7 +149,7 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // ppid, for example). if !found || parent.GetInfo().GetPid() != int(feed.ParentPid) { - pt.setParentFeed(parent, &feed, feedTimeStamp) + pt.setParentFeed(parent, feed, feedTimeStamp) } parent.AddChild(feed.LeaderHash) // add the leader as a child of the parent @@ -152,7 +164,7 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // Same case here (for events out of order created by execve first) if !found || leader.GetInfo().GetPPid() != int(feed.ParentPid) { - pt.setLeaderFeed(leader, parent, &feed, feedTimeStamp) + pt.setLeaderFeed(leader, parent, feed, feedTimeStamp) } leader.SetParentHash(feed.ParentHash) // add the parent as the parent of the leader @@ -161,8 +173,9 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // leader" of a single threaded process). if feed.ChildHash == feed.LeaderHash { + fileInfoFeed := parent.GetExecutable().GetFeed() leader.GetExecutable().SetFeedAt( - parent.GetExecutable().GetFeed(), + &fileInfoFeed, feedTimeStamp, ) } @@ -177,7 +190,7 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // Same case here (for events out of order created by execve first) if !found || thread.GetInfo().GetPPid() != int(feed.ParentPid) { - pt.setThreadFeed(thread, leader, &feed, feedTimeStamp) + pt.setThreadFeed(thread, leader, feed, feedTimeStamp) } thread.SetParentHash(feed.ParentHash) // all threads have the same parent as the thread group leader @@ -211,7 +224,7 @@ type ExecFeed struct { const COMM_LEN = 16 // FeedFromExec feeds the process tree with an exec event. -func (pt *ProcessTree) FeedFromExec(feed ExecFeed) error { +func (pt *ProcessTree) FeedFromExec(feed *ExecFeed) error { if feed.LeaderHash != 0 && feed.TaskHash != feed.LeaderHash { // Running execve() from a thread is discouraged and behavior can be unexpected: // @@ -246,16 +259,19 @@ func (pt *ProcessTree) FeedFromExec(feed ExecFeed) error { execTimestamp, ) - process.GetExecutable().SetFeedAt( - FileInfoFeed{ - Path: feed.PathName, - Dev: int(feed.Dev), - Ctime: int(feed.Ctime), - Inode: int(feed.Inode), - InodeMode: int(feed.InodeMode), - }, - execTimestamp, - ) + // NOTE: override all the fields of the fileInfoFeed, to avoid any previous data. + fileInfoFeed := pt.GetFileInfoFeedFromPool() + + fileInfoFeed.Path = feed.PathName + fileInfoFeed.Dev = int(feed.Dev) + fileInfoFeed.Ctime = int(feed.Ctime) + fileInfoFeed.Inode = int(feed.Inode) + fileInfoFeed.InodeMode = int(feed.InodeMode) + + process.GetExecutable().SetFeedAt(fileInfoFeed, execTimestamp) + + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutFileInfoFeedInPool(fileInfoFeed) return nil } @@ -270,7 +286,7 @@ type ExitFeed struct { } // FeedFromExit feeds the process tree with an exit event. -func (pt *ProcessTree) FeedFromExit(feed ExitFeed) error { +func (pt *ProcessTree) FeedFromExit(feed *ExitFeed) error { // Always create a tree node because the events might be received out of order. thread := pt.GetOrCreateThreadByHash(feed.TaskHash) diff --git a/pkg/proctree/proctree_procfs.go b/pkg/proctree/proctree_procfs.go index 863447fc010b..5a3d0c5491e7 100644 --- a/pkg/proctree/proctree_procfs.go +++ b/pkg/proctree/proctree_procfs.go @@ -178,22 +178,29 @@ func dealWithProc(pt *ProcessTree, givenPid int) error { procfsTimeStamp := traceetime.BootToEpochNS(startTimeNs) + // NOTE: override all the fields of the taskInfoFeed, to avoid any previous data. + taskInfoFeed := pt.GetTaskInfoFeedFromPool() + + taskInfoFeed.Name = name // command name (add "procfs+" to debug if needed) + taskInfoFeed.Tid = pid // status: pid == tid + taskInfoFeed.Pid = tgid // status: tgid == pid + taskInfoFeed.PPid = ppid // status: ppid == ppid + taskInfoFeed.NsTid = nspid // status: nspid == nspid + taskInfoFeed.NsPid = nstgid // status: nstgid == nspid + taskInfoFeed.NsPPid = nsppid // status: nsppid == nsppid + taskInfoFeed.Uid = -1 // do not change the parent uid + taskInfoFeed.Gid = -1 // do not change the parent gid + taskInfoFeed.StartTimeNS = procfsTimeStamp + taskInfoFeed.ExitTimeNS = 0 + procInfo.SetFeedAt( - TaskInfoFeed{ - Name: name, // command name (add "procfs+" to debug if needed) - Tid: pid, // status: pid == tid - Pid: tgid, // status: tgid == pid - PPid: ppid, // status: ppid == ppid - NsTid: nspid, // status: nspid == nspid - NsPid: nstgid, // status: nstgid == nspid - NsPPid: nsppid, // status: nsppid == nsppid - Uid: -1, // do not change the parent uid - Gid: -1, // do not change the parent gid - StartTimeNS: procfsTimeStamp, - }, + taskInfoFeed, traceetime.NsSinceEpochToTime(procfsTimeStamp), // try to be the first changelog entry ) + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutTaskInfoFeedInPool(taskInfoFeed) + // TODO: Update executable with information from /proc//exe // update given process parent (if exists) @@ -254,22 +261,29 @@ func dealWithThread(pt *ProcessTree, givenPid int, givenTid int) error { procfsTimeStamp := traceetime.BootToEpochNS(startTimeNs) + // NOTE: override all the fields of the taskInfoFeed, to avoid any previous data. + taskInfoFeed := pt.GetTaskInfoFeedFromPool() + + taskInfoFeed.Name = name // command name (add "procfs+" to debug if needed) + taskInfoFeed.Tid = pid // status: pid == tid + taskInfoFeed.Pid = tgid // status: tgid == pid + taskInfoFeed.PPid = ppid // status: ppid == ppid + taskInfoFeed.NsTid = nspid // status: nspid == nspid + taskInfoFeed.NsPid = nstgid // status: nstgid == nspid + taskInfoFeed.NsPPid = nsppid // status: nsppid == nsppid + taskInfoFeed.Uid = -1 // do not change the parent uid + taskInfoFeed.Gid = -1 // do not change the parent gid + taskInfoFeed.StartTimeNS = procfsTimeStamp + taskInfoFeed.ExitTimeNS = 0 + threadInfo.SetFeedAt( - TaskInfoFeed{ - Name: name, // command name (add "procfs+" to debug if needed) - Tid: pid, // status: pid == tid - Pid: tgid, // status: tgid == pid - PPid: ppid, // status: ppid == ppid - NsTid: nspid, // status: nspid == nspid - NsPid: nstgid, // status: nstgid == nspid - NsPPid: nsppid, // status: nsppid == nsppid - Uid: -1, // do not change the parent uid - Gid: -1, // do not change the parent gid - StartTimeNS: procfsTimeStamp, - }, + taskInfoFeed, traceetime.NsSinceEpochToTime(procfsTimeStamp), // try to be the first changelog entry ) + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutTaskInfoFeedInPool(taskInfoFeed) + // thread group leader (leader tid is the same as the thread's pid, so we can find it) leader, err := getProcessByPID(pt, tgid) diff --git a/pkg/proctree/taskinfo.go b/pkg/proctree/taskinfo.go index 954ff1f3fa9c..8a9cd07c4c14 100644 --- a/pkg/proctree/taskinfo.go +++ b/pkg/proctree/taskinfo.go @@ -29,20 +29,20 @@ type TaskInfoFeed struct { // TaskInfo represents a task. type TaskInfo struct { - feed *changelog.Changelog[TaskInfoFeed] + feed *changelog.Changelog[*TaskInfoFeed] mutex *sync.RWMutex } // NewTaskInfo creates a new task. func NewTaskInfo() *TaskInfo { return &TaskInfo{ - feed: changelog.NewChangelog[TaskInfoFeed](3), + feed: changelog.NewChangelog[*TaskInfoFeed](3), mutex: &sync.RWMutex{}, } } // NewTaskInfoFromFeed creates a new task with values from the given feed. -func NewTaskInfoNewFromFeed(feed TaskInfoFeed) *TaskInfo { +func NewTaskInfoNewFromFeed(feed *TaskInfoFeed) *TaskInfo { new := NewTaskInfo() new.setFeed(feed) @@ -56,7 +56,7 @@ func NewTaskInfoNewFromFeed(feed TaskInfoFeed) *TaskInfo { // Multiple values at once (using a feed structure) // SetFeed sets the values of the task from the given feed at the current time. -func (ti *TaskInfo) SetFeed(feed TaskInfoFeed) { +func (ti *TaskInfo) SetFeed(feed *TaskInfoFeed) { ti.mutex.Lock() defer ti.mutex.Unlock() @@ -64,7 +64,7 @@ func (ti *TaskInfo) SetFeed(feed TaskInfoFeed) { } // SetFeedAt sets the values of the task from the given feed at the given time. -func (ti *TaskInfo) SetFeedAt(feed TaskInfoFeed, targetTime time.Time) { +func (ti *TaskInfo) SetFeedAt(feed *TaskInfoFeed, targetTime time.Time) { ti.mutex.Lock() defer ti.mutex.Unlock() @@ -135,11 +135,11 @@ func (ti *TaskInfo) SetExitTime(exitTime uint64) { // private setters -func (ti *TaskInfo) setFeed(feed TaskInfoFeed) { +func (ti *TaskInfo) setFeed(feed *TaskInfoFeed) { ti.setFeedAt(feed, time.Now()) } -func (ti *TaskInfo) setFeedAt(feed TaskInfoFeed, targetTime time.Time) { +func (ti *TaskInfo) setFeedAt(feed *TaskInfoFeed, targetTime time.Time) { ti.feed.Set(feed, targetTime) } @@ -154,7 +154,7 @@ func (ti *TaskInfo) GetFeed() TaskInfoFeed { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.getFeed() + return *ti.getFeed() // return a copy } // GetFeedAt returns the values of the task as a feed at the given time. @@ -162,7 +162,7 @@ func (ti *TaskInfo) GetFeedAt(targetTime time.Time) TaskInfoFeed { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.getFeedAt(targetTime) + return *ti.getFeedAt(targetTime) // return a copy } // Single values @@ -340,10 +340,20 @@ func (ti *TaskInfo) IsAliveAt(targetTime time.Time) bool { // private getters -func (ti *TaskInfo) getFeed() TaskInfoFeed { - return ti.feed.GetCurrent() +func (ti *TaskInfo) getFeed() *TaskInfoFeed { + feed := ti.feed.GetCurrent() + if feed == nil { + feed = &TaskInfoFeed{} + } + + return feed } -func (ti *TaskInfo) getFeedAt(targetTime time.Time) TaskInfoFeed { - return ti.feed.Get(targetTime) +func (ti *TaskInfo) getFeedAt(targetTime time.Time) *TaskInfoFeed { + feed := ti.feed.Get(targetTime) + if feed == nil { + feed = &TaskInfoFeed{} + } + + return feed } From 3e6473d5347a5e1f719cada217c13332ee52e3e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Wed, 15 Jan 2025 11:50:44 -0300 Subject: [PATCH 19/25] chore/perf(proctree): comment out exit fields The unique ExitFeed fields being tackeld by FeedFromExit() are TaskHash and TimeStamp. Then this commit comments out the other fields that are not being used by the proctree in this context. --- pkg/ebpf/controlplane/processes.go | 40 ++++++++++++++++-------------- pkg/ebpf/processor_proctree.go | 28 ++++++++++++--------- pkg/proctree/proctree_feed.go | 5 ++++ 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/pkg/ebpf/controlplane/processes.go b/pkg/ebpf/controlplane/processes.go index 15fe4a83c07c..5ae4f3679776 100644 --- a/pkg/ebpf/controlplane/processes.go +++ b/pkg/ebpf/controlplane/processes.go @@ -187,6 +187,10 @@ func (ctrl *Controller) procTreeExitProcessor(args []trace.Argument) error { return nil // process tree is disabled } + // NOTE: Currently FeedFromExit is only using TaskHash and TimeStamp from the ExitFeed. + // So the other fields are commented out for now. + // + // TODO: Analyze if the other fields will be needed in the future. var err error // NOTE: override all the fields of the exitFeed, to avoid any previous data. @@ -205,24 +209,24 @@ func (ctrl *Controller) procTreeExitProcessor(args []trace.Argument) error { if err != nil { return err } - exitFeed.ParentHash, err = parse.ArgVal[uint32](args, "parent_hash") - if err != nil { - return err - } - exitFeed.LeaderHash, err = parse.ArgVal[uint32](args, "leader_hash") - if err != nil { - return err - } - - // Exit logic arguments - exitFeed.ExitCode, err = parse.ArgVal[int64](args, "exit_code") - if err != nil { - return err - } - exitFeed.Group, err = parse.ArgVal[bool](args, "process_group_exit") - if err != nil { - return err - } + // exitFeed.ParentHash, err = parse.ArgVal[uint32](args, "parent_hash") + // if err != nil { + // return err + // } + // exitFeed.LeaderHash, err = parse.ArgVal[uint32](args, "leader_hash") + // if err != nil { + // return err + // } + + // // Exit logic arguments + // exitFeed.ExitCode, err = parse.ArgVal[int64](args, "exit_code") + // if err != nil { + // return err + // } + // exitFeed.Group, err = parse.ArgVal[bool](args, "process_group_exit") + // if err != nil { + // return err + // } return ctrl.processTree.FeedFromExit(exitFeed) } diff --git a/pkg/ebpf/processor_proctree.go b/pkg/ebpf/processor_proctree.go index aebf0ee08822..acb2e981994a 100644 --- a/pkg/ebpf/processor_proctree.go +++ b/pkg/ebpf/processor_proctree.go @@ -186,26 +186,30 @@ func (t *Tracee) procTreeExitProcessor(event *trace.Event) error { return nil // chek FeedFromExec for TODO of execve() handled by threads } - var err error + // NOTE: Currently FeedFromExit is only using TaskHash and TimeStamp from the ExitFeed. + // So the other fields are commented out for now. + // + // TODO: Analyze if the other fields will be needed in the future. + // var err error // NOTE: override all the fields of the exitFeed, to avoid any previous data. exitFeed := t.processTree.GetExitFeedFromPool() defer t.processTree.PutExitFeedInPool(exitFeed) - // Exit logic arguments - exitFeed.ExitCode, err = parse.ArgVal[int64](event.Args, "exit_code") - if err != nil { - return err - } - exitFeed.Group, err = parse.ArgVal[bool](event.Args, "process_group_exit") - if err != nil { - return err - } + // // Exit logic arguments + // exitFeed.ExitCode, err = parse.ArgVal[int64](event.Args, "exit_code") + // if err != nil { + // return err + // } + // exitFeed.Group, err = parse.ArgVal[bool](event.Args, "process_group_exit") + // if err != nil { + // return err + // } exitFeed.TimeStamp = uint64(event.Timestamp) // time of exit is already a timestamp exitFeed.TaskHash = utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) - exitFeed.ParentHash = 0 // regular pipeline does not have parent hash - exitFeed.LeaderHash = 0 // regular pipeline does not have leader hash + // exitFeed.ParentHash = 0 // regular pipeline does not have parent hash + // exitFeed.LeaderHash = 0 // regular pipeline does not have leader hash return t.processTree.FeedFromExit(exitFeed) } diff --git a/pkg/proctree/proctree_feed.go b/pkg/proctree/proctree_feed.go index a5c8454818cc..2f045a04f976 100644 --- a/pkg/proctree/proctree_feed.go +++ b/pkg/proctree/proctree_feed.go @@ -289,6 +289,11 @@ type ExitFeed struct { func (pt *ProcessTree) FeedFromExit(feed *ExitFeed) error { // Always create a tree node because the events might be received out of order. + // NOTE: Currently FeedFromExit is only using TaskHash and TimeStamp from the ExitFeed. + // So the other fields are commented out for now. + // + // TODO: Analyze if the other fields will be needed in the future. + thread := pt.GetOrCreateThreadByHash(feed.TaskHash) thread.GetInfo().SetExitTime(feed.TimeStamp) From f6d324df6c7945fd88916b0d92dba9a8cbf7e2c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Thu, 16 Jan 2025 08:03:03 -0300 Subject: [PATCH 20/25] chore(proctree): remove leftover --- pkg/proctree/proctree.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/proctree/proctree.go b/pkg/proctree/proctree.go index ff233415db78..9e50cd9fd844 100644 --- a/pkg/proctree/proctree.go +++ b/pkg/proctree/proctree.go @@ -37,8 +37,6 @@ import ( const ( DefaultProcessCacheSize = 16384 DefaultThreadCacheSize = 32768 - DefaultProcessCacheTTL = time.Second * 120 - DefaultThreadCacheTTL = time.Second * 120 ) type SourceType int From c3447a64a1efc21925a2dbd5cdb767c3ce33bbd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Thu, 16 Jan 2025 10:35:58 -0300 Subject: [PATCH 21/25] perf(proctree): reduce lock contention Reuse the same TaskInfo reference avoiding the need to lock to fetch it. This also reorders the creation of the process and thread. --- pkg/proctree/process.go | 17 ++--------------- pkg/proctree/proctree.go | 28 +++++++++++++++++++--------- pkg/proctree/proctree_test.go | 7 ++++--- pkg/proctree/thread.go | 14 ++------------ 4 files changed, 27 insertions(+), 39 deletions(-) diff --git a/pkg/proctree/process.go b/pkg/proctree/process.go index 6b8b1a631e44..a3c7a1d4b5b8 100644 --- a/pkg/proctree/process.go +++ b/pkg/proctree/process.go @@ -20,21 +20,8 @@ type Process struct { mutex *sync.RWMutex // mutex to protect the process } -// NewProcess creates a new process. -func NewProcess(hash uint32) *Process { - return &Process{ - processHash: hash, - parentHash: 0, - info: NewTaskInfo(), - executable: NewFileInfo(), - children: make(map[uint32]struct{}), - threads: make(map[uint32]struct{}), - mutex: &sync.RWMutex{}, - } -} - -// NewProcessWithInfo creates a new thread with an initialized task info. -func NewProcessWithInfo(hash uint32, info *TaskInfo) *Process { +// NewProcess creates a new thread with an initialized task info. +func NewProcess(hash uint32, info *TaskInfo) *Process { return &Process{ processHash: hash, parentHash: 0, diff --git a/pkg/proctree/proctree.go b/pkg/proctree/proctree.go index 9e50cd9fd844..b9a75493f61f 100644 --- a/pkg/proctree/proctree.go +++ b/pkg/proctree/proctree.go @@ -200,20 +200,25 @@ func (pt *ProcessTree) GetProcessByHash(hash uint32) (*Process, bool) { func (pt *ProcessTree) GetOrCreateProcessByHash(hash uint32) *Process { process, ok := pt.processes.Get(hash) if !ok { + var taskInfo *TaskInfo + // Each process must have a thread with thread ID matching its process ID. // Both share the same info as both represent the same task in the kernel. thread, ok := pt.threads.Get(hash) - if !ok { - process = NewProcess(hash) // create a new process - thread = NewThreadWithInfo(hash, process.GetInfo()) - pt.threads.Add(hash, thread) + if ok { + taskInfo = thread.GetInfo() } else { - process = NewProcessWithInfo(hash, thread.GetInfo()) + taskInfo = NewTaskInfo() + thread = NewThread(hash, taskInfo) // create a new thread + pt.threads.Add(hash, thread) } - pt.processes.Add(hash, process) - process.AddThread(hash) + thread.SetLeaderHash(hash) + process = NewProcess(hash, taskInfo) // create a new process + process.AddThread(hash) + pt.processes.Add(hash, process) + return process } @@ -234,15 +239,20 @@ func (pt *ProcessTree) GetThreadByHash(hash uint32) (*Thread, bool) { func (pt *ProcessTree) GetOrCreateThreadByHash(hash uint32) *Thread { thread, ok := pt.threads.Get(hash) if !ok { + var taskInfo *TaskInfo + // Create a new thread // If the thread is a leader task, sync its info with the process instance info. process, ok := pt.processes.Get(hash) if ok { - thread = NewThreadWithInfo(hash, process.GetInfo()) + taskInfo = process.GetInfo() } else { - thread = NewThread(hash) + taskInfo = NewTaskInfo() } + + thread = NewThread(hash, taskInfo) // create a new thread pt.threads.Add(hash, thread) + return thread } diff --git a/pkg/proctree/proctree_test.go b/pkg/proctree/proctree_test.go index d11a5788e921..6c91aa4ce3cc 100644 --- a/pkg/proctree/proctree_test.go +++ b/pkg/proctree/proctree_test.go @@ -6,9 +6,10 @@ import ( "testing" ) +// TestProcessTreeConcurrency tests the ProcessTree for concurrent access. +// Enable data race detection with `go test -race`. func TestProcessTreeConcurrency(t *testing.T) { t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -41,13 +42,13 @@ func TestProcessTreeConcurrency(t *testing.T) { } // Run tests concurrently for different hashes - for i := 0; i < 1000; i++ { + for i := 0; i < 3000; i++ { wg.Add(1) go testFunc(uint32(i)) } // Run tests concurrently for the same hash - for i := 0; i < 1000; i++ { + for i := 0; i < 3000; i++ { wg.Add(1) go testFunc(42) } diff --git a/pkg/proctree/thread.go b/pkg/proctree/thread.go index cc8308ab3b9c..b00ff7ac8aa8 100644 --- a/pkg/proctree/thread.go +++ b/pkg/proctree/thread.go @@ -20,18 +20,8 @@ type Thread struct { // log, for a thread, is needed, the thread group leader hash will be used to find the process so it // can be logged as a process artifact. -// NewThread creates a new thread. -func NewThread(hash uint32) *Thread { - return &Thread{ - threadHash: hash, - parentHash: 0, - info: NewTaskInfo(), - mutex: &sync.RWMutex{}, - } -} - -// NewThreadWithInfo creates a new thread with an initialized task info. -func NewThreadWithInfo(hash uint32, info *TaskInfo) *Thread { +// NewThread creates a new thread with an initialized task info. +func NewThread(hash uint32, info *TaskInfo) *Thread { return &Thread{ threadHash: hash, parentHash: 0, From 5f326d5c129398638fd1e0c3fc2b298fbcd30973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Thu, 16 Jan 2025 11:35:20 -0300 Subject: [PATCH 22/25] perf(proctree): change Thread concurrency control Mutex is a heavy lock, and it's not necessary to use it in the Thread concurrency control. This change replaces the mutex with atomic operations to reduce contention, what also reduces memory footprint. --- pkg/proctree/thread.go | 36 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/pkg/proctree/thread.go b/pkg/proctree/thread.go index b00ff7ac8aa8..2132ad450190 100644 --- a/pkg/proctree/thread.go +++ b/pkg/proctree/thread.go @@ -1,17 +1,13 @@ package proctree -import ( - "sync" -) +import "sync/atomic" // Thread represents a thread. type Thread struct { - threadHash uint32 // hash of thread + threadHash uint32 // hash of thread (immutable, so no need of concurrency control) parentHash uint32 // hash of parent leaderHash uint32 // hash of thread group leader - info *TaskInfo // task info - // Control fields - mutex *sync.RWMutex // mutex to protect the thread + info *TaskInfo // task info (immutable pointer) } // NOTE: The importance of having the thread group leader hash to each thread is the following: the @@ -25,8 +21,8 @@ func NewThread(hash uint32, info *TaskInfo) *Thread { return &Thread{ threadHash: hash, parentHash: 0, + leaderHash: 0, info: info, - mutex: &sync.RWMutex{}, } } @@ -34,44 +30,32 @@ func NewThread(hash uint32, info *TaskInfo) *Thread { // GetHash returns the hash of the thread. func (t *Thread) GetHash() uint32 { - t.mutex.RLock() - defer t.mutex.RUnlock() - return t.threadHash + return t.threadHash // immutable } // GetParentHash returns the hash of the parent. func (t *Thread) GetParentHash() uint32 { - t.mutex.RLock() - defer t.mutex.RUnlock() - return t.parentHash + return atomic.LoadUint32(&t.parentHash) } // GEtLeaderHash returns the hash of the thread group leader. func (t *Thread) GetLeaderHash() uint32 { - t.mutex.RLock() - defer t.mutex.RUnlock() - return t.leaderHash + return atomic.LoadUint32(&t.leaderHash) } // GetInfo returns a instanced task info. func (t *Thread) GetInfo() *TaskInfo { - t.mutex.RLock() - defer t.mutex.RUnlock() - return t.info + return t.info // immutable pointer } // Setters // SetParentHash sets the hash of the parent. func (t *Thread) SetParentHash(parentHash uint32) { - t.mutex.Lock() - defer t.mutex.Unlock() - t.parentHash = parentHash + atomic.StoreUint32(&t.parentHash, parentHash) } // SetLeaderHash sets the hash of the thread group leader. func (t *Thread) SetLeaderHash(leaderHash uint32) { - t.mutex.Lock() - defer t.mutex.Unlock() - t.leaderHash = leaderHash + atomic.StoreUint32(&t.leaderHash, leaderHash) } From b1c5d24f1ab82a66bf3698245e4550195fb9ae53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Thu, 16 Jan 2025 11:35:38 -0300 Subject: [PATCH 23/25] perf(proctree): improve Process concurrency ctrl --- pkg/proctree/process.go | 77 +++++++++++++---------------------------- 1 file changed, 24 insertions(+), 53 deletions(-) diff --git a/pkg/proctree/process.go b/pkg/proctree/process.go index a3c7a1d4b5b8..05e8ffdcc9af 100644 --- a/pkg/proctree/process.go +++ b/pkg/proctree/process.go @@ -2,6 +2,7 @@ package proctree import ( "sync" + "sync/atomic" ) // @@ -10,11 +11,11 @@ import ( // Process represents a process. type Process struct { - processHash uint32 // hash of process + processHash uint32 // hash of process (immutable, so no need of concurrency control) parentHash uint32 // hash of parent - info *TaskInfo // task info - executable *FileInfo // executable info - children map[uint32]struct{} // hash of childrens + info *TaskInfo // task info (immutable pointer) + executable *FileInfo // executable info (immutable pointer) + children map[uint32]struct{} // hash of children threads map[uint32]struct{} // hash of threads // Control fields mutex *sync.RWMutex // mutex to protect the process @@ -37,39 +38,29 @@ func NewProcess(hash uint32, info *TaskInfo) *Process { // GetHash returns the hash of the process. func (p *Process) GetHash() uint32 { - p.mutex.RLock() - defer p.mutex.RUnlock() - return p.processHash + return p.processHash // immutable } // GetParentHash returns the hash of the parent. func (p *Process) GetParentHash() uint32 { - p.mutex.RLock() - defer p.mutex.RUnlock() - return p.parentHash + return atomic.LoadUint32(&p.parentHash) } // GetInfo returns a instanced task info. func (p *Process) GetInfo() *TaskInfo { - p.mutex.RLock() - defer p.mutex.RUnlock() - return p.info + return p.info // immutable pointer } // GetExecutable returns a instanced executable info. func (p *Process) GetExecutable() *FileInfo { - p.mutex.RLock() - defer p.mutex.RUnlock() - return p.executable + return p.executable // immutable pointer } // Setters // SetParentHash sets the hash of the parent. func (p *Process) SetParentHash(parentHash uint32) { - p.mutex.Lock() - defer p.mutex.Unlock() - p.parentHash = parentHash + atomic.StoreUint32(&p.parentHash, parentHash) } // @@ -80,26 +71,28 @@ func (p *Process) SetParentHash(parentHash uint32) { func (p *Process) AddChild(childHash uint32) { p.mutex.Lock() defer p.mutex.Unlock() - p.addChild(childHash) + + p.children[childHash] = struct{}{} } // AddThread adds a thread to the process. func (p *Process) AddThread(threadHash uint32) { p.mutex.Lock() defer p.mutex.Unlock() - p.addThread(threadHash) + + p.threads[threadHash] = struct{}{} } // GetChildren returns the children of the process. func (p *Process) GetChildren() []uint32 { p.mutex.RLock() defer p.mutex.RUnlock() - children := make([]uint32, len(p.children)) - i := 0 + + children := make([]uint32, 0, len(p.children)) for child := range p.children { - children[i] = child - i++ + children = append(children, child) } + return children } @@ -107,12 +100,12 @@ func (p *Process) GetChildren() []uint32 { func (p *Process) GetThreads() []uint32 { p.mutex.RLock() defer p.mutex.RUnlock() - threads := make([]uint32, len(p.threads)) - i := 0 + + threads := make([]uint32, 0, len(p.threads)) for thread := range p.threads { - threads[i] = thread - i++ + threads = append(threads, thread) } + return threads } @@ -120,36 +113,14 @@ func (p *Process) GetThreads() []uint32 { func (p *Process) DelChild(childHash uint32) { p.mutex.Lock() defer p.mutex.Unlock() - p.delChild(childHash) + + delete(p.children, childHash) } // DelThread deletes a thread from the process. func (p *Process) DelThread(threadHash uint32) { p.mutex.Lock() defer p.mutex.Unlock() - p.delThread(threadHash) -} - -// addChild adds a child to the process. -func (p *Process) addChild(childHash uint32) { - if _, ok := p.children[childHash]; !ok { - p.children[childHash] = struct{}{} - } -} - -// addThread adds a thread to the process. -func (p *Process) addThread(threadHash uint32) { - if _, ok := p.threads[threadHash]; !ok { - p.threads[threadHash] = struct{}{} - } -} - -// delChild deletes a child from the process. -func (p *Process) delChild(childHash uint32) { - delete(p.children, childHash) -} -// delThread deletes a thread from the process. -func (p *Process) delThread(threadHash uint32) { delete(p.threads, threadHash) } From 5e3ad59d40eeed8af09e57162bba5d5d5b3ef5e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Fri, 17 Jan 2025 07:42:59 -0300 Subject: [PATCH 24/25] perf(controlplane): introduce signal pool It helps to reduce the stack dynamic growth and the number of allocations, which is good for performance. --- pkg/ebpf/controlplane/controller.go | 34 ++++++++++++++++++++++++++--- pkg/ebpf/controlplane/signal.go | 4 ++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/pkg/ebpf/controlplane/controller.go b/pkg/ebpf/controlplane/controller.go index 2112d52af753..981496054078 100644 --- a/pkg/ebpf/controlplane/controller.go +++ b/pkg/ebpf/controlplane/controller.go @@ -3,6 +3,7 @@ package controlplane import ( "context" "fmt" + "sync" "time" "github.com/aquasecurity/libbpfgo" @@ -25,6 +26,7 @@ type Controller struct { lostSignalChan chan uint64 bpfModule *libbpfgo.Module signalBuffer *libbpfgo.PerfBuffer + signalPool *sync.Pool cgroupManager *containers.Containers processTree *proctree.ProcessTree enrichDisabled bool @@ -43,6 +45,11 @@ func NewController( signalChan: make(chan []byte, 100), lostSignalChan: make(chan uint64), bpfModule: bpfModule, + signalPool: &sync.Pool{ + New: func() interface{} { + return &signal{} + }, + }, cgroupManager: cgroupManager, processTree: procTree, enrichDisabled: enrichDisabled, @@ -69,16 +76,22 @@ func (ctrl *Controller) Run(ctx context.Context) { for { select { case signalData := <-ctrl.signalChan: - signal := signal{} + signal := ctrl.getSignalFromPool() + + // NOTE: override all the fields of the signal, to avoid any previous data. err := signal.Unmarshal(signalData) if err != nil { logger.Errorw("error unmarshaling signal ebpf buffer", "error", err) + ctrl.putSignalInPool(signal) continue } + err = ctrl.processSignal(signal) if err != nil { logger.Errorw("error processing control plane signal", "error", err) } + + ctrl.putSignalInPool(signal) case lost := <-ctrl.lostSignalChan: logger.Warnw(fmt.Sprintf("Lost %d control plane signals", lost)) case <-ctrl.ctx.Done(): @@ -93,8 +106,10 @@ func (ctrl *Controller) Stop() error { return nil } +// Private + // processSignal processes a signal from the control plane. -func (ctrl *Controller) processSignal(signal signal) error { +func (ctrl *Controller) processSignal(signal *signal) error { switch signal.id { case events.SignalCgroupMkdir: return ctrl.processCgroupMkdir(signal.args) @@ -111,7 +126,20 @@ func (ctrl *Controller) processSignal(signal signal) error { return nil } -// Private +// getSignalFromPool gets a signal from the pool. +// signal certainly contains old data, so it must be updated before use. +func (ctrl *Controller) getSignalFromPool() *signal { + // revive:disable:unchecked-type-assertion + sig := ctrl.signalPool.Get().(*signal) + // revive:enable:unchecked-type-assertion + + return sig +} + +// putSignalInPool puts a signal back in the pool. +func (ctrl *Controller) putSignalInPool(sig *signal) { + ctrl.signalPool.Put(sig) +} // debug prints the process tree every 5 seconds (for debugging purposes). func (ctrl *Controller) debug(enable bool) { diff --git a/pkg/ebpf/controlplane/signal.go b/pkg/ebpf/controlplane/signal.go index da5aa9cea294..33360d82c981 100644 --- a/pkg/ebpf/controlplane/signal.go +++ b/pkg/ebpf/controlplane/signal.go @@ -19,7 +19,9 @@ func (sig *signal) Unmarshal(buffer []byte) error { if err != nil { return errfmt.Errorf("failed to decode signal event ID: %v", err) } + sig.id = events.ID(eventIdUint32) + var argnum uint8 err = ebpfDecoder.DecodeUint8(&argnum) if err != nil { @@ -33,7 +35,9 @@ func (sig *signal) Unmarshal(buffer []byte) error { evtFields := eventDefinition.GetFields() evtName := eventDefinition.GetName() + sig.args = make([]trace.Argument, len(evtFields)) + err = ebpfDecoder.DecodeArguments(sig.args, int(argnum), evtFields, evtName, sig.id) if err != nil { return errfmt.Errorf("failed to decode signal arguments: %v", err) From 314326ea96d3ae4b766c7bcb03512e0e521cb6c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Sat, 18 Jan 2025 17:28:39 -0300 Subject: [PATCH 25/25] chore(cmd): add proctree disable-procfs --- .../docs/advanced/data-sources/builtin/process-tree.md | 3 ++- pkg/cmd/flags/proctree.go | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/docs/advanced/data-sources/builtin/process-tree.md b/docs/docs/advanced/data-sources/builtin/process-tree.md index 8d0eb400c55d..e94a2e25f87c 100644 --- a/docs/docs/advanced/data-sources/builtin/process-tree.md +++ b/docs/docs/advanced/data-sources/builtin/process-tree.md @@ -37,7 +37,8 @@ Example: --proctree thread-cache=16384 | will cache up to 16384 threads in the tree (LRU cache). --proctree process-cache-ttl=60 | will set the process cache element TTL to 60 seconds. --proctree thread-cache-ttl=60 | will set the thread cache element TTL to 60 seconds. - --proctree disable-procfs-query | Will disable procfs quering during runtime + --proctree disable-procfs | will disable procfs entirely. + --proctree disable-procfs-query | will disable procfs quering during runtime. Use comma OR use the flag multiple times to choose multiple options: --proctree source=A,process-cache=B,thread-cache=C diff --git a/pkg/cmd/flags/proctree.go b/pkg/cmd/flags/proctree.go index 020c86f369e6..30d8f10b7169 100644 --- a/pkg/cmd/flags/proctree.go +++ b/pkg/cmd/flags/proctree.go @@ -20,7 +20,8 @@ Example: both | process tree is built from both events and signals. --proctree process-cache=8192 | will cache up to 8192 processes in the tree (LRU cache). --proctree thread-cache=4096 | will cache up to 4096 threads in the tree (LRU cache). - --proctree disable-procfs-query | Will disable procfs queries during runtime + --proctree disable-procfs | will disable procfs entirely. + --proctree disable-procfs-query | will disable procfs quering during runtime. Use comma OR use the flag multiple times to choose multiple options: --proctree source=A,process-cache=B,thread-cache=C @@ -93,7 +94,12 @@ func PrepareProcTree(cacheSlice []string) (proctree.ProcTreeConfig, error) { cacheSet = true continue } - if strings.HasPrefix(value, "disable-procfs-query") { + if value == "disable-procfs" { + config.ProcfsInitialization = false + config.ProcfsQuerying = false + continue + } + if value == "disable-procfs-query" { config.ProcfsQuerying = false continue }