github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/ringbuf/reader_test.go (about) 1 package ringbuf 2 3 import ( 4 "errors" 5 "os" 6 "syscall" 7 "testing" 8 "time" 9 10 "github.com/cilium/ebpf" 11 "github.com/cilium/ebpf/asm" 12 "github.com/cilium/ebpf/internal" 13 "github.com/cilium/ebpf/internal/testutils" 14 "github.com/cilium/ebpf/internal/testutils/fdtrace" 15 "github.com/cilium/ebpf/internal/unix" 16 "github.com/google/go-cmp/cmp" 17 ) 18 19 type sampleMessage struct { 20 size int 21 flags int32 22 } 23 24 func TestMain(m *testing.M) { 25 fdtrace.TestMain(m) 26 } 27 28 func TestRingbufReader(t *testing.T) { 29 testutils.SkipOnOldKernel(t, "5.8", "BPF ring buffer") 30 31 readerTests := []struct { 32 name string 33 messages []sampleMessage 34 want map[int][]byte 35 }{ 36 { 37 name: "send one short sample", 38 messages: []sampleMessage{{size: 5}}, 39 want: map[int][]byte{ 40 5: {1, 2, 3, 4, 4}, 41 }, 42 }, 43 { 44 name: "send three short samples, the second is discarded", 45 messages: []sampleMessage{{size: 5}, {size: 10}, {size: 15}}, 46 want: map[int][]byte{ 47 5: {1, 2, 3, 4, 4}, 48 15: {1, 2, 3, 4, 4, 3, 2, 1, 1, 2, 3, 4, 4, 3, 2}, 49 }, 50 }, 51 } 52 for _, tt := range readerTests { 53 t.Run(tt.name, func(t *testing.T) { 54 prog, events := mustOutputSamplesProg(t, tt.messages...) 55 56 rd, err := NewReader(events) 57 if err != nil { 58 t.Fatal(err) 59 } 60 defer rd.Close() 61 62 if uint32(rd.BufferSize()) != 2*events.MaxEntries() { 63 t.Errorf("expected %d BufferSize, got %d", events.MaxEntries(), rd.BufferSize()) 64 } 65 66 ret, _, err := prog.Test(internal.EmptyBPFContext) 67 testutils.SkipIfNotSupported(t, err) 68 if err != nil { 69 t.Fatal(err) 70 } 71 72 if errno := syscall.Errno(-int32(ret)); errno != 0 { 73 t.Fatal("Expected 0 as return value, got", errno) 74 } 75 76 raw := make(map[int][]byte) 77 78 for len(raw) < len(tt.want) { 79 record, err := rd.Read() 80 if err != nil { 81 t.Fatal("Can't read samples:", err) 82 } 83 raw[len(record.RawSample)] = record.RawSample 84 if len(raw) == len(tt.want) { 85 if record.Remaining != 0 { 86 t.Errorf("expected 0 Remaining, got %d", record.Remaining) 87 } 88 } else { 89 if record.Remaining == 0 { 90 t.Error("expected non-zero Remaining, got 0") 91 } 92 } 93 } 94 95 if diff := cmp.Diff(tt.want, raw); diff != "" { 96 t.Errorf("Read samples mismatch (-want +got):\n%s", diff) 97 } 98 }) 99 } 100 } 101 102 func outputSamplesProg(sampleMessages ...sampleMessage) (*ebpf.Program, *ebpf.Map, error) { 103 events, err := ebpf.NewMap(&ebpf.MapSpec{ 104 Type: ebpf.RingBuf, 105 MaxEntries: 4096, 106 }) 107 if err != nil { 108 return nil, nil, err 109 } 110 111 var maxSampleSize int 112 for _, sampleMessage := range sampleMessages { 113 if sampleMessage.size > maxSampleSize { 114 maxSampleSize = sampleMessage.size 115 } 116 } 117 118 insns := asm.Instructions{ 119 asm.LoadImm(asm.R0, 0x0102030404030201, asm.DWord), 120 asm.Mov.Reg(asm.R9, asm.R1), 121 } 122 123 bufDwords := (maxSampleSize / 8) + 1 124 for i := 0; i < bufDwords; i++ { 125 insns = append(insns, 126 asm.StoreMem(asm.RFP, int16(i+1)*-8, asm.R0, asm.DWord), 127 ) 128 } 129 130 for sampleIdx, sampleMessage := range sampleMessages { 131 insns = append(insns, 132 asm.LoadMapPtr(asm.R1, events.FD()), 133 asm.Mov.Imm(asm.R2, int32(sampleMessage.size)), 134 asm.Mov.Imm(asm.R3, int32(0)), 135 asm.FnRingbufReserve.Call(), 136 asm.JEq.Imm(asm.R0, 0, "exit"), 137 asm.Mov.Reg(asm.R5, asm.R0), 138 ) 139 for i := 0; i < sampleMessage.size; i++ { 140 insns = append(insns, 141 asm.LoadMem(asm.R4, asm.RFP, int16(i+1)*-1, asm.Byte), 142 asm.StoreMem(asm.R5, int16(i), asm.R4, asm.Byte), 143 ) 144 } 145 146 // discard every even sample 147 if sampleIdx&1 != 0 { 148 insns = append(insns, 149 asm.Mov.Reg(asm.R1, asm.R5), 150 asm.Mov.Imm(asm.R2, sampleMessage.flags), 151 asm.FnRingbufDiscard.Call(), 152 ) 153 } else { 154 insns = append(insns, 155 asm.Mov.Reg(asm.R1, asm.R5), 156 asm.Mov.Imm(asm.R2, sampleMessage.flags), 157 asm.FnRingbufSubmit.Call(), 158 ) 159 } 160 } 161 162 insns = append(insns, 163 asm.Mov.Imm(asm.R0, int32(0)).WithSymbol("exit"), 164 asm.Return(), 165 ) 166 167 prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ 168 License: "MIT", 169 Type: ebpf.XDP, 170 Instructions: insns, 171 }) 172 if err != nil { 173 events.Close() 174 return nil, nil, err 175 } 176 177 return prog, events, nil 178 } 179 180 func mustOutputSamplesProg(tb testing.TB, sampleMessages ...sampleMessage) (*ebpf.Program, *ebpf.Map) { 181 tb.Helper() 182 183 prog, events, err := outputSamplesProg(sampleMessages...) 184 if err != nil { 185 tb.Fatal(err) 186 } 187 188 tb.Cleanup(func() { 189 prog.Close() 190 events.Close() 191 }) 192 193 return prog, events 194 } 195 196 func TestReaderBlocking(t *testing.T) { 197 testutils.SkipOnOldKernel(t, "5.8", "BPF ring buffer") 198 199 prog, events := mustOutputSamplesProg(t, sampleMessage{size: 5, flags: 0}) 200 ret, _, err := prog.Test(internal.EmptyBPFContext) 201 testutils.SkipIfNotSupported(t, err) 202 if err != nil { 203 t.Fatal(err) 204 } 205 206 if errno := syscall.Errno(-int32(ret)); errno != 0 { 207 t.Fatal("Expected 0 as return value, got", errno) 208 } 209 210 rd, err := NewReader(events) 211 if err != nil { 212 t.Fatal(err) 213 } 214 defer rd.Close() 215 216 if _, err := rd.Read(); err != nil { 217 t.Fatal("Can't read first sample:", err) 218 } 219 220 errs := make(chan error, 1) 221 go func() { 222 _, err := rd.Read() 223 errs <- err 224 }() 225 226 select { 227 case err := <-errs: 228 t.Fatal("Read returns error instead of blocking:", err) 229 case <-time.After(100 * time.Millisecond): 230 } 231 232 // Close should interrupt blocking Read 233 if err := rd.Close(); err != nil { 234 t.Fatal(err) 235 } 236 237 select { 238 case err := <-errs: 239 if !errors.Is(err, ErrClosed) { 240 t.Fatal("Expected os.ErrClosed from interrupted Read, got:", err) 241 } 242 case <-time.After(time.Second): 243 t.Fatal("Close doesn't interrupt Read") 244 } 245 246 // And we should be able to call it multiple times 247 if err := rd.Close(); err != nil { 248 t.Fatal(err) 249 } 250 251 if _, err := rd.Read(); !errors.Is(err, ErrClosed) { 252 t.Fatal("Second Read on a closed RingbufReader doesn't return ErrClosed") 253 } 254 } 255 256 func TestReaderNoWakeup(t *testing.T) { 257 testutils.SkipOnOldKernel(t, "5.8", "BPF ring buffer") 258 259 prog, events := mustOutputSamplesProg(t, 260 sampleMessage{size: 5, flags: unix.BPF_RB_NO_WAKEUP}, // Read after timeout 261 sampleMessage{size: 6, flags: unix.BPF_RB_NO_WAKEUP}, // Discard 262 sampleMessage{size: 7, flags: unix.BPF_RB_NO_WAKEUP}) // Read won't block 263 264 rd, err := NewReader(events) 265 if err != nil { 266 t.Fatal(err) 267 } 268 defer rd.Close() 269 270 ret, _, err := prog.Test(internal.EmptyBPFContext) 271 testutils.SkipIfNotSupported(t, err) 272 if err != nil { 273 t.Fatal(err) 274 } 275 276 if errno := syscall.Errno(-int32(ret)); errno != 0 { 277 t.Fatal("Expected 0 as return value, got", errno) 278 } 279 280 rd.SetDeadline(time.Now()) 281 record, err := rd.Read() 282 283 if err != nil { 284 t.Error("Expected no error from first Read, got:", err) 285 } 286 if len(record.RawSample) != 5 { 287 t.Errorf("Expected to read 5 bytes bot got %d", len(record.RawSample)) 288 } 289 290 record, err = rd.Read() 291 292 if err != nil { 293 t.Error("Expected no error from second Read, got:", err) 294 } 295 if len(record.RawSample) != 7 { 296 t.Errorf("Expected to read 7 bytes bot got %d", len(record.RawSample)) 297 } 298 } 299 300 func TestReaderSetDeadline(t *testing.T) { 301 testutils.SkipOnOldKernel(t, "5.8", "BPF ring buffer") 302 303 _, events := mustOutputSamplesProg(t, sampleMessage{size: 5, flags: 0}) 304 rd, err := NewReader(events) 305 if err != nil { 306 t.Fatal(err) 307 } 308 defer rd.Close() 309 310 rd.SetDeadline(time.Now().Add(-time.Second)) 311 if _, err := rd.Read(); !errors.Is(err, os.ErrDeadlineExceeded) { 312 t.Error("Expected os.ErrDeadlineExceeded from first Read, got:", err) 313 } 314 if _, err := rd.Read(); !errors.Is(err, os.ErrDeadlineExceeded) { 315 t.Error("Expected os.ErrDeadlineExceeded from second Read, got:", err) 316 } 317 } 318 319 func BenchmarkReader(b *testing.B) { 320 testutils.SkipOnOldKernel(b, "5.8", "BPF ring buffer") 321 322 readerBenchmarks := []struct { 323 name string 324 flags int32 325 }{ 326 { 327 name: "normal epoll with timeout -1", 328 }, 329 } 330 331 for _, bm := range readerBenchmarks { 332 b.Run(bm.name, func(b *testing.B) { 333 prog, events := mustOutputSamplesProg(b, sampleMessage{size: 80, flags: bm.flags}) 334 335 rd, err := NewReader(events) 336 if err != nil { 337 b.Fatal(err) 338 } 339 defer rd.Close() 340 341 buf := internal.EmptyBPFContext 342 343 b.ResetTimer() 344 b.ReportAllocs() 345 346 for i := 0; i < b.N; i++ { 347 ret, _, err := prog.Test(buf) 348 if err != nil { 349 b.Fatal(err) 350 } else if errno := syscall.Errno(-int32(ret)); errno != 0 { 351 b.Fatal("Expected 0 as return value, got", errno) 352 } 353 _, err = rd.Read() 354 if err != nil { 355 b.Fatal("Can't read samples:", err) 356 } 357 } 358 }) 359 } 360 } 361 362 func BenchmarkReadInto(b *testing.B) { 363 testutils.SkipOnOldKernel(b, "5.8", "BPF ring buffer") 364 365 prog, events := mustOutputSamplesProg(b, sampleMessage{size: 80, flags: 0}) 366 367 rd, err := NewReader(events) 368 if err != nil { 369 b.Fatal(err) 370 } 371 defer rd.Close() 372 373 buf := internal.EmptyBPFContext 374 375 b.ResetTimer() 376 b.ReportAllocs() 377 378 var rec Record 379 for i := 0; i < b.N; i++ { 380 ret, _, err := prog.Test(buf) 381 if err != nil { 382 b.Fatal(err) 383 } else if errno := syscall.Errno(-int32(ret)); errno != 0 { 384 b.Fatal("Expected 0 as return value, got", errno) 385 } 386 387 if err := rd.ReadInto(&rec); err != nil { 388 b.Fatal("Can't read samples:", err) 389 } 390 } 391 }