github.com/kubeshark/ebpf@v0.9.2/perf/reader_test.go (about) 1 package perf 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "os" 9 "syscall" 10 "testing" 11 "time" 12 13 "github.com/kubeshark/ebpf" 14 "github.com/kubeshark/ebpf/asm" 15 "github.com/kubeshark/ebpf/internal" 16 "github.com/kubeshark/ebpf/internal/testutils" 17 "github.com/kubeshark/ebpf/internal/unix" 18 19 qt "github.com/frankban/quicktest" 20 ) 21 22 var ( 23 readTimeout = 250 * time.Millisecond 24 ) 25 26 func TestPerfReader(t *testing.T) { 27 prog, events := mustOutputSamplesProg(t, 5) 28 defer prog.Close() 29 defer events.Close() 30 31 rd, err := NewReader(events, 4096) 32 if err != nil { 33 t.Fatal(err) 34 } 35 defer rd.Close() 36 37 ret, _, err := prog.Test(make([]byte, 14)) 38 testutils.SkipIfNotSupported(t, err) 39 if err != nil { 40 t.Fatal(err) 41 } 42 43 if errno := syscall.Errno(-int32(ret)); errno != 0 { 44 t.Fatal("Expected 0 as return value, got", errno) 45 } 46 47 record, err := rd.Read() 48 if err != nil { 49 t.Fatal("Can't read samples:", err) 50 } 51 52 want := []byte{1, 2, 3, 4, 4, 0, 0, 0, 0, 0, 0, 0} 53 if !bytes.Equal(record.RawSample, want) { 54 t.Log(record.RawSample) 55 t.Error("Sample doesn't match expected output") 56 } 57 58 if record.CPU < 0 { 59 t.Error("Record has invalid CPU number") 60 } 61 } 62 63 func outputSamplesProg(sampleSizes ...int) (*ebpf.Program, *ebpf.Map, error) { 64 const bpfFCurrentCPU = 0xffffffff 65 66 events, err := ebpf.NewMap(&ebpf.MapSpec{ 67 Type: ebpf.PerfEventArray, 68 }) 69 if err != nil { 70 return nil, nil, err 71 } 72 73 var maxSampleSize int 74 for _, sampleSize := range sampleSizes { 75 if sampleSize > maxSampleSize { 76 maxSampleSize = sampleSize 77 } 78 } 79 80 // Fill a buffer on the stack, and stash context somewhere 81 insns := asm.Instructions{ 82 asm.LoadImm(asm.R0, 0x0102030404030201, asm.DWord), 83 asm.Mov.Reg(asm.R9, asm.R1), 84 } 85 86 bufDwords := (maxSampleSize / 8) + 1 87 for i := 0; i < bufDwords; i++ { 88 insns = append(insns, 89 asm.StoreMem(asm.RFP, int16(i+1)*-8, asm.R0, asm.DWord), 90 ) 91 } 92 93 for _, sampleSize := range sampleSizes { 94 insns = append(insns, 95 asm.Mov.Reg(asm.R1, asm.R9), 96 asm.LoadMapPtr(asm.R2, events.FD()), 97 asm.LoadImm(asm.R3, bpfFCurrentCPU, asm.DWord), 98 asm.Mov.Reg(asm.R4, asm.RFP), 99 asm.Add.Imm(asm.R4, int32(bufDwords*-8)), 100 asm.Mov.Imm(asm.R5, int32(sampleSize)), 101 asm.FnPerfEventOutput.Call(), 102 ) 103 } 104 105 insns = append(insns, asm.Return()) 106 107 prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ 108 License: "GPL", 109 Type: ebpf.XDP, 110 Instructions: insns, 111 }) 112 if err != nil { 113 events.Close() 114 return nil, nil, err 115 } 116 117 return prog, events, nil 118 } 119 120 func mustOutputSamplesProg(tb testing.TB, sampleSizes ...int) (*ebpf.Program, *ebpf.Map) { 121 tb.Helper() 122 123 // Requires at least 4.9 (0515e5999a46 "bpf: introduce BPF_PROG_TYPE_PERF_EVENT program type") 124 testutils.SkipOnOldKernel(tb, "4.9", "perf events support") 125 126 prog, events, err := outputSamplesProg(sampleSizes...) 127 if err != nil { 128 tb.Fatal(err) 129 } 130 131 return prog, events 132 } 133 134 func TestPerfReaderLostSample(t *testing.T) { 135 // To generate a lost sample perf record: 136 // 137 // 1. Fill the perf ring buffer almost completely, with the output_large program. 138 // The buffer is sized in number of pages, which are architecture dependant. 139 // 140 // 2. Write an extra event that doesn't fit in the space remaining. 141 // 142 // 3. Write a smaller event that does fit, with output_single program. 143 // Lost sample records are generated opportunistically, when the kernel 144 // is writing an event and realizes that there were events lost previously. 145 // 146 // The event size is hardcoded in the test BPF programs, there's no way 147 // to parametrize it without rebuilding the programs. 148 // 149 // The event size needs to be selected so that, for any page size, there are at least 150 // 48 bytes left in the perf ring page after filling it with a whole number of events: 151 // 152 // - PERF_RECORD_LOST: 8 (perf_event_header) + 16 (PERF_RECORD_LOST) 153 // 154 // - output_single: 8 (perf_event_header) + 4 (size) + 5 (payload) + 7 (padding to 64bits) 155 // 156 // By selecting an event size of the form 2^n + 2^(n+1), for any page size 2^(n+m), m >= 0, 157 // the number of bytes left, x, after filling a page with a whole number of events is: 158 // 159 // 2^(n+m) 2^n * 2^m 160 // x = 2^n * frac(---------------) <=> x = 2^n * frac(---------------) 161 // 2^n + 2^(n+1) 2^n + 2^n * 2 162 // 163 // 2^n * 2^m 164 // <=> x = 2^n * frac(---------------) 165 // 2^n * (1 + 2) 166 // 167 // 2^m 168 // <=> x = 2^n * frac(-----) 169 // 3 170 // 171 // 1 2 172 // <=> x = 2^n * - or x = 2^n * - 173 // 3 3 174 // 175 // Selecting n = 6, we have: 176 // 177 // x = 64 or x = 128, no matter the page size 2^(6+m) 178 // 179 // event size = 2^6 + 2^7 = 192 180 // 181 // Accounting for perf headers, output_large uses a 180 byte payload: 182 // 183 // 8 (perf_event_header) + 4 (size) + 180 (payload) 184 const ( 185 eventSize = 192 186 ) 187 188 var ( 189 pageSize = os.Getpagesize() 190 maxEvents = (pageSize / eventSize) 191 ) 192 if remainder := pageSize % eventSize; remainder != 64 && remainder != 128 { 193 // Page size isn't 2^(6+m), m >= 0 194 t.Fatal("unsupported page size:", pageSize) 195 } 196 197 var sampleSizes []int 198 // Fill the ring with the maximum number of output_large events that will fit, 199 // and generate a lost event by writing an additional event. 200 for i := 0; i < maxEvents+1; i++ { 201 sampleSizes = append(sampleSizes, 180) 202 } 203 204 // Generate a small event to trigger the lost record 205 sampleSizes = append(sampleSizes, 5) 206 207 prog, events := mustOutputSamplesProg(t, sampleSizes...) 208 defer prog.Close() 209 defer events.Close() 210 211 rd, err := NewReader(events, pageSize) 212 if err != nil { 213 t.Fatal(err) 214 } 215 defer rd.Close() 216 217 ret, _, err := prog.Test(make([]byte, 14)) 218 testutils.SkipIfNotSupported(t, err) 219 if err != nil { 220 t.Fatal(err) 221 } 222 223 if errno := syscall.Errno(-int32(ret)); errno != 0 { 224 t.Fatal("Expected 0 as return value, got", errno) 225 } 226 227 for range sampleSizes { 228 record, err := rd.Read() 229 if err != nil { 230 t.Fatal(err) 231 } 232 233 if record.RawSample == nil && record.LostSamples != 1 { 234 t.Fatal("Expected a record with LostSamples 1, got", record.LostSamples) 235 } 236 } 237 } 238 239 func TestPerfReaderClose(t *testing.T) { 240 prog, events := mustOutputSamplesProg(t, 5) 241 defer prog.Close() 242 defer events.Close() 243 244 rd, err := NewReader(events, 4096) 245 if err != nil { 246 t.Fatal(err) 247 } 248 defer rd.Close() 249 250 errs := make(chan error, 1) 251 waiting := make(chan struct{}) 252 go func() { 253 close(waiting) 254 _, err := rd.Read() 255 errs <- err 256 }() 257 258 <-waiting 259 260 // Close should interrupt Read 261 if err := rd.Close(); err != nil { 262 t.Fatal(err) 263 } 264 265 select { 266 case <-errs: 267 case <-time.After(time.Second): 268 t.Fatal("Close doesn't interrupt Read") 269 } 270 271 // And we should be able to call it multiple times 272 if err := rd.Close(); err != nil { 273 t.Fatal(err) 274 } 275 276 if _, err := rd.Read(); err == nil { 277 t.Fatal("Read on a closed PerfReader doesn't return an error") 278 } 279 } 280 281 func TestCreatePerfEvent(t *testing.T) { 282 fd, err := createPerfEvent(0, 1) 283 if err != nil { 284 t.Fatal("Can't create perf event:", err) 285 } 286 unix.Close(fd) 287 } 288 289 func TestReadRecord(t *testing.T) { 290 var buf bytes.Buffer 291 292 err := binary.Write(&buf, internal.NativeEndian, &perfEventHeader{}) 293 if err != nil { 294 t.Fatal(err) 295 } 296 297 var rec Record 298 err = readRecord(&buf, &rec, make([]byte, perfEventHeaderSize)) 299 if !IsUnknownEvent(err) { 300 t.Error("readRecord should return unknown event error, got", err) 301 } 302 } 303 304 func TestPause(t *testing.T) { 305 t.Parallel() 306 307 prog, events := mustOutputSamplesProg(t, 5) 308 defer prog.Close() 309 defer events.Close() 310 311 rd, err := NewReader(events, 4096) 312 if err != nil { 313 t.Fatal(err) 314 } 315 defer rd.Close() 316 317 // Reader is already unpaused by default. It should be idempotent. 318 if err = rd.Resume(); err != nil { 319 t.Fatal(err) 320 } 321 322 // Write a sample. The reader should read it. 323 ret, _, err := prog.Test(make([]byte, 14)) 324 testutils.SkipIfNotSupported(t, err) 325 if err != nil || ret != 0 { 326 t.Fatal("Can't write sample") 327 } 328 if _, err := rd.Read(); err != nil { 329 t.Fatal(err) 330 } 331 332 // Pause. No notification should trigger. 333 if err = rd.Pause(); err != nil { 334 t.Fatal(err) 335 } 336 errChan := make(chan error, 1) 337 go func() { 338 // Read one notification then send any errors and exit. 339 _, err := rd.Read() 340 errChan <- err 341 }() 342 ret, _, err = prog.Test(make([]byte, 14)) 343 if err == nil && ret == 0 { 344 t.Fatal("Unexpectedly wrote sample while paused") 345 } // else Success 346 select { 347 case err := <-errChan: 348 // Failure: Pause was unsuccessful. 349 t.Fatalf("received notification on paused reader: %s", err) 350 case <-time.After(readTimeout): 351 // Success 352 } 353 354 // Pause should be idempotent. 355 if err = rd.Pause(); err != nil { 356 t.Fatal(err) 357 } 358 359 // Resume. Now notifications should continue. 360 if err = rd.Resume(); err != nil { 361 t.Fatal(err) 362 } 363 ret, _, err = prog.Test(make([]byte, 14)) 364 if err != nil || ret != 0 { 365 t.Fatal("Can't write sample") 366 } 367 select { 368 case err := <-errChan: 369 if err != nil { 370 t.Fatal(err) 371 } // else Success 372 case <-time.After(readTimeout): 373 t.Fatal("timed out waiting for notification after resume") 374 } 375 376 if err = rd.Close(); err != nil { 377 t.Fatal(err) 378 } 379 380 // Pause/Resume after close should be no-op. 381 err = rd.Pause() 382 qt.Assert(t, err, qt.Not(qt.Equals), ErrClosed, qt.Commentf("returns unwrapped ErrClosed")) 383 qt.Assert(t, errors.Is(err, ErrClosed), qt.IsTrue, qt.Commentf("doesn't wrap ErrClosed")) 384 385 err = rd.Resume() 386 qt.Assert(t, err, qt.Not(qt.Equals), ErrClosed, qt.Commentf("returns unwrapped ErrClosed")) 387 qt.Assert(t, errors.Is(err, ErrClosed), qt.IsTrue, qt.Commentf("doesn't wrap ErrClosed")) 388 } 389 390 func BenchmarkReader(b *testing.B) { 391 prog, events := mustOutputSamplesProg(b, 80) 392 defer prog.Close() 393 defer events.Close() 394 395 rd, err := NewReader(events, 4096) 396 if err != nil { 397 b.Fatal(err) 398 } 399 defer rd.Close() 400 401 buf := make([]byte, 14) 402 403 b.ResetTimer() 404 b.ReportAllocs() 405 for i := 0; i < b.N; i++ { 406 ret, _, err := prog.Test(buf) 407 if err != nil { 408 b.Fatal(err) 409 } else if errno := syscall.Errno(-int32(ret)); errno != 0 { 410 b.Fatal("Expected 0 as return value, got", errno) 411 } 412 413 if _, err = rd.Read(); err != nil { 414 b.Fatal(err) 415 } 416 } 417 } 418 419 func BenchmarkReadInto(b *testing.B) { 420 prog, events := mustOutputSamplesProg(b, 80) 421 defer prog.Close() 422 defer events.Close() 423 424 rd, err := NewReader(events, 4096) 425 if err != nil { 426 b.Fatal(err) 427 } 428 defer rd.Close() 429 430 buf := make([]byte, 14) 431 432 b.ResetTimer() 433 b.ReportAllocs() 434 435 var rec Record 436 for i := 0; i < b.N; i++ { 437 // NB: Submitting samples into the perf event ring dominates 438 // the benchmark time unfortunately. 439 ret, _, err := prog.Test(buf) 440 if err != nil { 441 b.Fatal(err) 442 } else if errno := syscall.Errno(-int32(ret)); errno != 0 { 443 b.Fatal("Expected 0 as return value, got", errno) 444 } 445 446 if err := rd.ReadInto(&rec); err != nil { 447 b.Fatal(err) 448 } 449 } 450 } 451 452 // This exists just to make the example below nicer. 453 func bpfPerfEventOutputProgram() (*ebpf.Program, *ebpf.Map) { 454 prog, events, err := outputSamplesProg(5) 455 if err != nil { 456 panic(err) 457 } 458 return prog, events 459 } 460 461 // ExamplePerfReader submits a perf event using BPF, 462 // and then reads it in user space. 463 // 464 // The BPF will look something like this: 465 // 466 // struct map events __section("maps") = { 467 // .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 468 // }; 469 // 470 // __section("xdp") int output_single(void *ctx) { 471 // unsigned char buf[] = { 472 // 1, 2, 3, 4, 5 473 // }; 474 // 475 // return perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &buf[0], 5); 476 // } 477 // 478 // Also see BPF_F_CTXLEN_MASK if you want to sample packet data 479 // from SKB or XDP programs. 480 func ExampleReader() { 481 prog, events := bpfPerfEventOutputProgram() 482 defer prog.Close() 483 defer events.Close() 484 485 rd, err := NewReader(events, 4096) 486 if err != nil { 487 panic(err) 488 } 489 defer rd.Close() 490 491 // Writes out a sample with content 1,2,3,4,4 492 ret, _, err := prog.Test(make([]byte, 14)) 493 if err != nil || ret != 0 { 494 panic("Can't write sample") 495 } 496 497 record, err := rd.Read() 498 if err != nil { 499 panic(err) 500 } 501 502 // Data is padded with 0 for alignment 503 fmt.Println("Sample:", record.RawSample) 504 } 505 506 // ReadRecord allows reducing memory allocations. 507 func ExampleReader_ReadInto() { 508 prog, events := bpfPerfEventOutputProgram() 509 defer prog.Close() 510 defer events.Close() 511 512 rd, err := NewReader(events, 4096) 513 if err != nil { 514 panic(err) 515 } 516 defer rd.Close() 517 518 for i := 0; i < 2; i++ { 519 // Write out two samples 520 ret, _, err := prog.Test(make([]byte, 14)) 521 if err != nil || ret != 0 { 522 panic("Can't write sample") 523 } 524 } 525 526 var rec Record 527 for i := 0; i < 2; i++ { 528 if err := rd.ReadInto(&rec); err != nil { 529 panic(err) 530 } 531 532 fmt.Println("Sample:", rec.RawSample[:5]) 533 } 534 }