github.com/cilium/ebpf@v0.10.0/info_test.go (about) 1 package ebpf 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "testing" 8 9 "github.com/cilium/ebpf/asm" 10 "github.com/cilium/ebpf/internal" 11 "github.com/cilium/ebpf/internal/sys" 12 "github.com/cilium/ebpf/internal/testutils" 13 "github.com/cilium/ebpf/internal/unix" 14 qt "github.com/frankban/quicktest" 15 ) 16 17 func TestMapInfoFromProc(t *testing.T) { 18 hash, err := NewMap(&MapSpec{ 19 Name: "testing", 20 Type: Hash, 21 KeySize: 4, 22 ValueSize: 5, 23 MaxEntries: 2, 24 Flags: unix.BPF_F_NO_PREALLOC, 25 }) 26 testutils.SkipIfNotSupported(t, err) 27 if err != nil { 28 t.Fatal(err) 29 } 30 defer hash.Close() 31 32 info, err := newMapInfoFromProc(hash.fd) 33 testutils.SkipIfNotSupported(t, err) 34 if err != nil { 35 t.Fatal("Can't get map info:", err) 36 } 37 38 if info.Type != Hash { 39 t.Error("Expected Hash, got", info.Type) 40 } 41 42 if info.KeySize != 4 { 43 t.Error("Expected KeySize of 4, got", info.KeySize) 44 } 45 46 if info.ValueSize != 5 { 47 t.Error("Expected ValueSize of 5, got", info.ValueSize) 48 } 49 50 if info.MaxEntries != 2 { 51 t.Error("Expected MaxEntries of 2, got", info.MaxEntries) 52 } 53 54 if info.Flags != unix.BPF_F_NO_PREALLOC { 55 t.Errorf("Expected Flags to be %d, got %d", unix.BPF_F_NO_PREALLOC, info.Flags) 56 } 57 58 if info.Name != "" && info.Name != "testing" { 59 t.Error("Expected name to be testing, got", info.Name) 60 } 61 62 if _, ok := info.ID(); ok { 63 t.Error("Expected ID to not be available") 64 } 65 66 nested, err := NewMap(&MapSpec{ 67 Type: ArrayOfMaps, 68 KeySize: 4, 69 MaxEntries: 2, 70 InnerMap: &MapSpec{ 71 Type: Array, 72 KeySize: 4, 73 ValueSize: 4, 74 MaxEntries: 2, 75 }, 76 }) 77 testutils.SkipIfNotSupported(t, err) 78 if err != nil { 79 t.Fatal(err) 80 } 81 defer nested.Close() 82 83 _, err = newMapInfoFromProc(nested.fd) 84 if err != nil { 85 t.Fatal("Can't get nested map info from /proc:", err) 86 } 87 } 88 89 func TestProgramInfo(t *testing.T) { 90 prog := mustSocketFilter(t) 91 92 for name, fn := range map[string]func(*sys.FD) (*ProgramInfo, error){ 93 "generic": newProgramInfoFromFd, 94 "proc": newProgramInfoFromProc, 95 } { 96 t.Run(name, func(t *testing.T) { 97 info, err := fn(prog.fd) 98 testutils.SkipIfNotSupported(t, err) 99 if err != nil { 100 t.Fatal("Can't get program info:", err) 101 } 102 103 if info.Type != SocketFilter { 104 t.Error("Expected Type to be SocketFilter, got", info.Type) 105 } 106 107 if info.Name != "" && info.Name != "test" { 108 t.Error("Expected Name to be test, got", info.Name) 109 } 110 111 if want := "d7edec644f05498d"; info.Tag != want { 112 t.Errorf("Expected Tag to be %s, got %s", want, info.Tag) 113 } 114 115 if id, ok := info.ID(); ok && id == 0 { 116 t.Error("Expected a valid ID:", id) 117 } else if name == "proc" && ok { 118 t.Error("Expected ID to not be available") 119 } 120 }) 121 } 122 } 123 124 func TestProgramInfoMapIDs(t *testing.T) { 125 testutils.SkipOnOldKernel(t, "4.10", "reading program info") 126 127 arr, err := NewMap(&MapSpec{ 128 Type: Array, 129 KeySize: 4, 130 ValueSize: 4, 131 MaxEntries: 1, 132 }) 133 qt.Assert(t, err, qt.IsNil) 134 defer arr.Close() 135 136 prog, err := NewProgram(&ProgramSpec{ 137 Type: SocketFilter, 138 Instructions: asm.Instructions{ 139 asm.LoadMapPtr(asm.R0, arr.FD()), 140 asm.LoadImm(asm.R0, 2, asm.DWord), 141 asm.Return(), 142 }, 143 License: "MIT", 144 }) 145 qt.Assert(t, err, qt.IsNil) 146 defer prog.Close() 147 148 info, err := prog.Info() 149 qt.Assert(t, err, qt.IsNil) 150 151 ids, ok := info.MapIDs() 152 if testutils.MustKernelVersion().Less(internal.Version{4, 15, 0}) { 153 qt.Assert(t, ok, qt.IsFalse) 154 qt.Assert(t, ids, qt.HasLen, 0) 155 } else { 156 qt.Assert(t, ok, qt.IsTrue) 157 qt.Assert(t, ids, qt.HasLen, 1) 158 159 mapInfo, err := arr.Info() 160 qt.Assert(t, err, qt.IsNil) 161 mapID, ok := mapInfo.ID() 162 qt.Assert(t, ok, qt.IsTrue) 163 qt.Assert(t, ids[0], qt.Equals, mapID) 164 } 165 } 166 167 func TestScanFdInfoReader(t *testing.T) { 168 tests := []struct { 169 fields map[string]interface{} 170 valid bool 171 }{ 172 {nil, true}, 173 {map[string]interface{}{"foo": new(string)}, true}, 174 {map[string]interface{}{"zap": new(string)}, false}, 175 {map[string]interface{}{"foo": new(int)}, false}, 176 } 177 178 for _, test := range tests { 179 err := scanFdInfoReader(strings.NewReader("foo:\tbar\n"), test.fields) 180 if test.valid { 181 if err != nil { 182 t.Errorf("fields %v returns an error: %s", test.fields, err) 183 } 184 } else { 185 if err == nil { 186 t.Errorf("fields %v doesn't return an error", test.fields) 187 } 188 } 189 } 190 } 191 192 // TestStats loads a BPF program once and executes back-to-back test runs 193 // of the program. See testStats for details. 194 func TestStats(t *testing.T) { 195 testutils.SkipOnOldKernel(t, "5.8", "BPF_ENABLE_STATS") 196 197 prog := mustSocketFilter(t) 198 199 pi, err := prog.Info() 200 if err != nil { 201 t.Errorf("failed to get ProgramInfo: %v", err) 202 } 203 204 rc, ok := pi.RunCount() 205 if !ok { 206 t.Errorf("expected run count info to be available") 207 } 208 if rc != 0 { 209 t.Errorf("expected a run count of 0 but got %d", rc) 210 } 211 212 rt, ok := pi.Runtime() 213 if !ok { 214 t.Errorf("expected runtime info to be available") 215 } 216 if rt != 0 { 217 t.Errorf("expected a runtime of 0ns but got %v", rt) 218 } 219 220 if err := testStats(prog); err != nil { 221 t.Error(err) 222 } 223 } 224 225 // BenchmarkStats is a benchmark of TestStats. See testStats for details. 226 func BenchmarkStats(b *testing.B) { 227 testutils.SkipOnOldKernel(b, "5.8", "BPF_ENABLE_STATS") 228 229 prog := mustSocketFilter(b) 230 231 for n := 0; n < b.N; n++ { 232 if err := testStats(prog); err != nil { 233 b.Fatal(fmt.Errorf("iter %d: %w", n, err)) 234 } 235 } 236 } 237 238 // testStats implements the behaviour under test for TestStats 239 // and BenchmarkStats. First, a test run is executed with runtime statistics 240 // enabled, followed by another with runtime stats disabled. Counters are only 241 // expected to increase on the runs where runtime stats are enabled. 242 // 243 // Due to runtime behaviour on Go 1.14 and higher, the syscall backing 244 // (*Program).Test() could be invoked multiple times for each call to Test(), 245 // resulting in RunCount incrementing by more than one. Expecting RunCount to 246 // be of a specific value after a call to Test() is therefore not possible. 247 // See https://golang.org/doc/go1.14#runtime for more details. 248 func testStats(prog *Program) error { 249 in := internal.EmptyBPFContext 250 251 stats, err := EnableStats(uint32(unix.BPF_STATS_RUN_TIME)) 252 if err != nil { 253 return fmt.Errorf("failed to enable stats: %v", err) 254 } 255 defer stats.Close() 256 257 // Program execution with runtime statistics enabled. 258 // Should increase both runtime and run counter. 259 if _, _, err := prog.Test(in); err != nil { 260 return fmt.Errorf("failed to trigger program: %v", err) 261 } 262 263 pi, err := prog.Info() 264 if err != nil { 265 return fmt.Errorf("failed to get ProgramInfo: %v", err) 266 } 267 268 rc, ok := pi.RunCount() 269 if !ok { 270 return errors.New("expected run count info to be available") 271 } 272 if rc < 1 { 273 return fmt.Errorf("expected a run count of at least 1 but got %d", rc) 274 } 275 // Store the run count for the next invocation. 276 lc := rc 277 278 rt, ok := pi.Runtime() 279 if !ok { 280 return errors.New("expected runtime info to be available") 281 } 282 if rt == 0 { 283 return errors.New("expected a runtime other than 0ns") 284 } 285 // Store the runtime value for the next invocation. 286 lt := rt 287 288 if err := stats.Close(); err != nil { 289 return fmt.Errorf("failed to disable statistics: %v", err) 290 } 291 292 // Second program execution, with runtime statistics gathering disabled. 293 // Total runtime and run counters are not expected to increase. 294 if _, _, err := prog.Test(in); err != nil { 295 return fmt.Errorf("failed to trigger program: %v", err) 296 } 297 298 pi, err = prog.Info() 299 if err != nil { 300 return fmt.Errorf("failed to get ProgramInfo: %v", err) 301 } 302 303 rc, ok = pi.RunCount() 304 if !ok { 305 return errors.New("expected run count info to be available") 306 } 307 if rc != lc { 308 return fmt.Errorf("run count unexpectedly increased over previous value (current: %v, prev: %v)", rc, lc) 309 } 310 311 rt, ok = pi.Runtime() 312 if !ok { 313 return errors.New("expected runtime info to be available") 314 } 315 if rt != lt { 316 return fmt.Errorf("runtime unexpectedly increased over the previous value (current: %v, prev: %v)", rt, lt) 317 } 318 319 return nil 320 }