github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/snapshot/process/tracer/tracer_test.go (about) 1 // Copyright 2022-2023 The Inspektor Gadget authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //go:build linux 16 // +build linux 17 18 package tracer 19 20 import ( 21 "fmt" 22 "os" 23 "os/exec" 24 "reflect" 25 "testing" 26 27 utilstest "github.com/inspektor-gadget/inspektor-gadget/internal/test" 28 containerutils "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils" 29 "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets" 30 snapshotProcessTypes "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/snapshot/process/types" 31 eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types" 32 ) 33 34 type collectorFunc func(config *Config, enricher gadgets.DataEnricherByMntNs) ([]*snapshotProcessTypes.Event, error) 35 36 func BenchmarkSnapshotProcessEBPFTracer(b *testing.B) { 37 benchmarkTracer(b, runeBPFCollector) 38 } 39 40 func BenchmarkSnapshotProcessProcfsTracer(b *testing.B) { 41 benchmarkTracer(b, runProcfsCollector) 42 } 43 44 func benchmarkTracer(b *testing.B, runCollector collectorFunc) { 45 utilstest.RequireRoot(b) 46 47 for n := 0; n < b.N; n++ { 48 _, err := runCollector(&Config{}, nil) 49 if err != nil { 50 b.Fatalf("benchmarking collector: %s", err) 51 } 52 } 53 } 54 55 func TestSnapshotProcessEBPFTracer(t *testing.T) { 56 testTracer(t, runeBPFCollector) 57 } 58 59 func TestSnapshotProcessProcfsTracer(t *testing.T) { 60 testTracer(t, runProcfsCollector) 61 } 62 63 func testTracer(t *testing.T, runCollector collectorFunc) { 64 t.Parallel() 65 66 utilstest.RequireRoot(t) 67 utilstest.HostInit(t) 68 69 type testDefinition struct { 70 getTracerConfig func(info *utilstest.RunnerInfo) *Config 71 runnerConfig *utilstest.RunnerConfig 72 generateEvent func() (int, error) 73 validateEvent func(t *testing.T, info *utilstest.RunnerInfo, sleepPid int, events []snapshotProcessTypes.Event) 74 } 75 76 for name, test := range map[string]testDefinition{ 77 "captures_all_events_with_no_filters_configured": { 78 getTracerConfig: func(info *utilstest.RunnerInfo) *Config { 79 return &Config{} 80 }, 81 generateEvent: generateEvent, 82 validateEvent: utilstest.ExpectAtLeastOneEvent(func(info *utilstest.RunnerInfo, sleepPid int) *snapshotProcessTypes.Event { 83 return &snapshotProcessTypes.Event{ 84 Event: eventtypes.Event{ 85 Type: eventtypes.NORMAL, 86 }, 87 Command: "sleep", 88 Pid: sleepPid, 89 Tid: sleepPid, 90 ParentPid: 0, 91 WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID}, 92 } 93 }), 94 }, 95 "captures_no_events_with_no_matching_filter": { 96 getTracerConfig: func(info *utilstest.RunnerInfo) *Config { 97 // We can't use 0 as the mntnsid because the tracer will collect 98 // some defunct processes that don't have any mntnsid defined 99 // anymore. Then, we set the network namespace inode id to be sure 100 // there is not any mount namespace using that same inode. 101 mntns, _ := containerutils.GetNetNs(os.Getpid()) 102 return &Config{ 103 MountnsMap: utilstest.CreateMntNsFilterMap(t, mntns), 104 } 105 }, 106 generateEvent: generateEvent, 107 validateEvent: utilstest.ExpectNoEvent[snapshotProcessTypes.Event, int], 108 }, 109 "captures_events_with_matching_filter": { 110 getTracerConfig: func(info *utilstest.RunnerInfo) *Config { 111 return &Config{ 112 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 113 } 114 }, 115 generateEvent: generateEvent, 116 // We have to use ExpectAtLeastOneEvent because it's possible that the 117 // golang thread that executes this test is also captured 118 validateEvent: utilstest.ExpectAtLeastOneEvent(func(info *utilstest.RunnerInfo, sleepPid int) *snapshotProcessTypes.Event { 119 return &snapshotProcessTypes.Event{ 120 Event: eventtypes.Event{ 121 Type: eventtypes.NORMAL, 122 }, 123 Command: "sleep", 124 Pid: sleepPid, 125 Tid: sleepPid, 126 WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID}, 127 } 128 }), 129 }, 130 // This is a hacky way to test this: one of the threads of the goroutine is moved to 131 // the mount namespace created for testing, also the sleep process we execute is 132 // there. That's why 2 events are expected. A better way would be to execute a 133 // command that creates multiple threads and check if we capture all of them, but so 134 // far I haven't found an easy way to do so. One idea is to use python but it seems 135 // too complicated and will introduce another dependency for testing. 136 "captures_events_with_matching_filter_threads": { 137 getTracerConfig: func(info *utilstest.RunnerInfo) *Config { 138 return &Config{ 139 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 140 ShowThreads: true, 141 } 142 }, 143 generateEvent: generateEvent, 144 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, sleepPid int, events []snapshotProcessTypes.Event) { 145 if len(events) != 2 { 146 t.Fatalf("%d events expected, found: %d", 2, len(events)) 147 } 148 149 expectedEvent := &snapshotProcessTypes.Event{ 150 Event: eventtypes.Event{ 151 Type: eventtypes.NORMAL, 152 }, 153 Command: "sleep", 154 Pid: sleepPid, 155 Tid: sleepPid, 156 ParentPid: 0, 157 WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID}, 158 } 159 160 for _, event := range events { 161 if reflect.DeepEqual(expectedEvent, &event) { 162 return 163 } 164 } 165 166 t.Fatalf("Event wasn't captured") 167 }, 168 }, 169 "no_threads_are_captured": { 170 getTracerConfig: func(info *utilstest.RunnerInfo) *Config { 171 return &Config{} 172 }, 173 generateEvent: generateEvent, 174 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, sleepPid int, events []snapshotProcessTypes.Event) { 175 if len(events) == 0 { 176 t.Fatalf("no events were captured") 177 } 178 for _, event := range events { 179 if event.Pid != event.Tid { 180 t.Fatalf("thread %d was captured", event.Tid) 181 } 182 } 183 }, 184 }, 185 } { 186 test := test 187 188 t.Run(name, func(t *testing.T) { 189 t.Parallel() 190 191 runner := utilstest.NewRunnerWithTest(t, test.runnerConfig) 192 193 var sleepPid int 194 195 utilstest.RunWithRunner(t, runner, func() error { 196 var err error 197 sleepPid, err = test.generateEvent() 198 return err 199 }) 200 201 events, err := runCollector(test.getTracerConfig(runner.Info), nil) 202 if err != nil { 203 t.Fatalf("running collector: %s", err) 204 } 205 206 // TODO: This won't be required once we pass pointers everywhere 207 validateEvents := []snapshotProcessTypes.Event{} 208 for _, event := range events { 209 // Normalize parent PID to avoid failing tests as this is not trivial to 210 // guess the parent PID. 211 event.ParentPid = 0 212 213 validateEvents = append(validateEvents, *event) 214 } 215 216 test.validateEvent(t, runner.Info, sleepPid, validateEvents) 217 }) 218 } 219 } 220 221 // Function that runs a "sleep" process. 222 func generateEvent() (int, error) { 223 cmd := exec.Command("/bin/sleep", "5") 224 if err := cmd.Start(); err != nil { 225 return 0, fmt.Errorf("running command: %w", err) 226 } 227 228 return cmd.Process.Pid, nil 229 }