github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/perf/reader_test.go (about) 1 package perf 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "math" 9 "os" 10 "syscall" 11 "testing" 12 "time" 13 14 "github.com/cilium/ebpf" 15 "github.com/cilium/ebpf/asm" 16 "github.com/cilium/ebpf/internal" 17 "github.com/cilium/ebpf/internal/testutils" 18 "github.com/cilium/ebpf/internal/testutils/fdtrace" 19 20 "github.com/go-quicktest/qt" 21 ) 22 23 var ( 24 readTimeout = 250 * time.Millisecond 25 ) 26 27 func TestMain(m *testing.M) { 28 fdtrace.TestMain(m) 29 } 30 31 func TestPerfReader(t *testing.T) { 32 events := perfEventArray(t) 33 34 rd, err := NewReader(events, 4096) 35 if err != nil { 36 t.Fatal(err) 37 } 38 defer rd.Close() 39 40 qt.Assert(t, qt.Equals(rd.BufferSize(), 4096)) 41 42 outputSamples(t, events, 5, 5) 43 44 _, rem := checkRecord(t, rd) 45 qt.Assert(t, qt.IsTrue(rem >= 5), qt.Commentf("expected at least 5 Remaining")) 46 47 _, rem = checkRecord(t, rd) 48 qt.Assert(t, qt.Equals(rem, 0), qt.Commentf("expected zero Remaining")) 49 50 rd.SetDeadline(time.Now().Add(4 * time.Millisecond)) 51 _, err = rd.Read() 52 qt.Assert(t, qt.ErrorIs(err, os.ErrDeadlineExceeded), qt.Commentf("expected os.ErrDeadlineExceeded")) 53 } 54 55 func TestReaderSetDeadline(t *testing.T) { 56 events := perfEventArray(t) 57 58 rd, err := NewReader(events, 4096) 59 if err != nil { 60 t.Fatal(err) 61 } 62 defer rd.Close() 63 64 rd.SetDeadline(time.Now().Add(-time.Second)) 65 if _, err := rd.Read(); !errors.Is(err, os.ErrDeadlineExceeded) { 66 t.Error("Expected os.ErrDeadlineExceeded from first Read, got:", err) 67 } 68 if _, err := rd.Read(); !errors.Is(err, os.ErrDeadlineExceeded) { 69 t.Error("Expected os.ErrDeadlineExceeded from second Read, got:", err) 70 } 71 } 72 73 func outputSamples(tb testing.TB, events *ebpf.Map, sampleSizes ...byte) { 74 prog := outputSamplesProg(tb, events, sampleSizes...) 75 76 ret, _, err := prog.Test(internal.EmptyBPFContext) 77 testutils.SkipIfNotSupported(tb, err) 78 if err != nil { 79 tb.Fatal(err) 80 } 81 82 if errno := syscall.Errno(-int32(ret)); errno != 0 { 83 tb.Fatal("Expected 0 as return value, got", errno) 84 } 85 } 86 87 // outputSamplesProg creates a program which submits a series of samples to a PerfEventArray. 88 // 89 // The format of each sample is: 90 // 91 // index: 0 1 2 3 ... size - 1 92 // content: size id 0xff 0xff ... 0xff [padding] 93 // 94 // padding is an implementation detail of the perf buffer and 1-7 bytes long. The 95 // contents are undefined. 96 func outputSamplesProg(tb testing.TB, events *ebpf.Map, sampleSizes ...byte) *ebpf.Program { 97 tb.Helper() 98 99 // Requires at least 4.9 (0515e5999a46 "bpf: introduce BPF_PROG_TYPE_PERF_EVENT program type") 100 testutils.SkipOnOldKernel(tb, "4.9", "perf events support") 101 102 const bpfFCurrentCPU = 0xffffffff 103 104 var maxSampleSize byte 105 for _, sampleSize := range sampleSizes { 106 if sampleSize < 2 { 107 tb.Fatalf("Sample size %d is too small to contain size and counter", sampleSize) 108 } 109 if sampleSize > maxSampleSize { 110 maxSampleSize = sampleSize 111 } 112 } 113 114 // Fill a buffer on the stack, and stash context somewhere 115 insns := asm.Instructions{ 116 asm.LoadImm(asm.R0, ^int64(0), asm.DWord), 117 asm.Mov.Reg(asm.R9, asm.R1), 118 } 119 120 bufDwords := int(maxSampleSize/8) + 1 121 for i := 0; i < bufDwords; i++ { 122 insns = append(insns, 123 asm.StoreMem(asm.RFP, int16(i+1)*-8, asm.R0, asm.DWord), 124 ) 125 } 126 127 for i, sampleSize := range sampleSizes { 128 insns = append(insns, 129 // Restore stashed context. 130 asm.Mov.Reg(asm.R1, asm.R9), 131 // map 132 asm.LoadMapPtr(asm.R2, events.FD()), 133 // flags 134 asm.LoadImm(asm.R3, bpfFCurrentCPU, asm.DWord), 135 // buffer 136 asm.Mov.Reg(asm.R4, asm.RFP), 137 asm.Add.Imm(asm.R4, int32(bufDwords*-8)), 138 // buffer[0] = size 139 asm.StoreImm(asm.R4, 0, int64(sampleSize), asm.Byte), 140 // buffer[1] = i 141 asm.StoreImm(asm.R4, 1, int64(i&math.MaxUint8), asm.Byte), 142 // size 143 asm.Mov.Imm(asm.R5, int32(sampleSize)), 144 asm.FnPerfEventOutput.Call(), 145 ) 146 } 147 148 insns = append(insns, asm.Return()) 149 150 prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ 151 License: "GPL", 152 Type: ebpf.XDP, 153 Instructions: insns, 154 }) 155 if err != nil { 156 tb.Fatal(err) 157 } 158 tb.Cleanup(func() { prog.Close() }) 159 160 return prog 161 } 162 163 func checkRecord(tb testing.TB, rd *Reader) (id int, remaining int) { 164 tb.Helper() 165 166 rec, err := rd.Read() 167 qt.Assert(tb, qt.IsNil(err)) 168 169 qt.Assert(tb, qt.IsTrue(rec.CPU >= 0), qt.Commentf("Record has invalid CPU number")) 170 171 size := int(rec.RawSample[0]) 172 qt.Assert(tb, qt.IsTrue(len(rec.RawSample) >= size), qt.Commentf("RawSample is at least size bytes")) 173 174 for i, v := range rec.RawSample[2:size] { 175 qt.Assert(tb, qt.Equals(v, 0xff), qt.Commentf("filler at position %d should match", i+2)) 176 } 177 178 // padding is ignored since it's value is undefined. 179 180 return int(rec.RawSample[1]), rec.Remaining 181 } 182 183 func TestPerfReaderLostSample(t *testing.T) { 184 // To generate a lost sample perf record: 185 // 186 // 1. Fill the perf ring buffer almost completely, with the output_large program. 187 // The buffer is sized in number of pages, which are architecture dependant. 188 // 189 // 2. Write an extra event that doesn't fit in the space remaining. 190 // 191 // 3. Write a smaller event that does fit, with output_single program. 192 // Lost sample records are generated opportunistically, when the kernel 193 // is writing an event and realizes that there were events lost previously. 194 // 195 // The event size is hardcoded in the test BPF programs, there's no way 196 // to parametrize it without rebuilding the programs. 197 // 198 // The event size needs to be selected so that, for any page size, there are at least 199 // 48 bytes left in the perf ring page after filling it with a whole number of events: 200 // 201 // - PERF_RECORD_LOST: 8 (perf_event_header) + 16 (PERF_RECORD_LOST) 202 // 203 // - output_single: 8 (perf_event_header) + 4 (size) + 5 (payload) + 7 (padding to 64bits) 204 // 205 // By selecting an event size of the form 2^n + 2^(n+1), for any page size 2^(n+m), m >= 0, 206 // the number of bytes left, x, after filling a page with a whole number of events is: 207 // 208 // 2^(n+m) 2^n * 2^m 209 // x = 2^n * frac(---------------) <=> x = 2^n * frac(---------------) 210 // 2^n + 2^(n+1) 2^n + 2^n * 2 211 // 212 // 2^n * 2^m 213 // <=> x = 2^n * frac(---------------) 214 // 2^n * (1 + 2) 215 // 216 // 2^m 217 // <=> x = 2^n * frac(-----) 218 // 3 219 // 220 // 1 2 221 // <=> x = 2^n * - or x = 2^n * - 222 // 3 3 223 // 224 // Selecting n = 6, we have: 225 // 226 // x = 64 or x = 128, no matter the page size 2^(6+m) 227 // 228 // event size = 2^6 + 2^7 = 192 229 // 230 // Accounting for perf headers, output_large uses a 180 byte payload: 231 // 232 // 8 (perf_event_header) + 4 (size) + 180 (payload) 233 const ( 234 eventSize = 192 235 ) 236 237 var ( 238 pageSize = os.Getpagesize() 239 maxEvents = (pageSize / eventSize) 240 ) 241 if remainder := pageSize % eventSize; remainder != 64 && remainder != 128 { 242 // Page size isn't 2^(6+m), m >= 0 243 t.Fatal("unsupported page size:", pageSize) 244 } 245 246 var sampleSizes []byte 247 // Fill the ring with the maximum number of output_large events that will fit, 248 // and generate a lost event by writing an additional event. 249 for i := 0; i < maxEvents+1; i++ { 250 sampleSizes = append(sampleSizes, 180) 251 } 252 253 // Generate a small event to trigger the lost record 254 sampleSizes = append(sampleSizes, 5) 255 256 events := perfEventArray(t) 257 258 rd, err := NewReader(events, pageSize) 259 if err != nil { 260 t.Fatal(err) 261 } 262 defer rd.Close() 263 264 outputSamples(t, events, sampleSizes...) 265 266 for range sampleSizes { 267 record, err := rd.Read() 268 if err != nil { 269 t.Fatal(err) 270 } 271 272 if record.RawSample == nil && record.LostSamples != 1 { 273 t.Fatal("Expected a record with LostSamples 1, got", record.LostSamples) 274 } 275 } 276 } 277 278 func TestPerfReaderOverwritable(t *testing.T) { 279 // Smallest buffer size. 280 pageSize := os.Getpagesize() 281 282 const sampleSize = math.MaxUint8 283 284 // Account for perf header (8) and size (4), align to 8 bytes as perf does. 285 realSampleSize := internal.Align(sampleSize+8+4, 8) 286 maxEvents := pageSize / realSampleSize 287 288 var sampleSizes []byte 289 for i := 0; i < maxEvents; i++ { 290 sampleSizes = append(sampleSizes, sampleSize) 291 } 292 // Append an extra sample that will overwrite the first sample. 293 sampleSizes = append(sampleSizes, sampleSize) 294 295 events := perfEventArray(t) 296 297 rd, err := NewReaderWithOptions(events, pageSize, ReaderOptions{Overwritable: true}) 298 if err != nil { 299 t.Fatal(err) 300 } 301 defer rd.Close() 302 303 _, err = rd.Read() 304 qt.Assert(t, qt.ErrorIs(err, errMustBePaused)) 305 306 outputSamples(t, events, sampleSizes...) 307 308 qt.Assert(t, qt.IsNil(rd.Pause())) 309 rd.SetDeadline(time.Now()) 310 311 nextID := maxEvents 312 for i := 0; i < maxEvents; i++ { 313 id, rem := checkRecord(t, rd) 314 qt.Assert(t, qt.Equals(id, nextID)) 315 qt.Assert(t, qt.Equals(rem, -1)) 316 nextID-- 317 } 318 } 319 320 func TestPerfReaderOverwritableEmpty(t *testing.T) { 321 events := perfEventArray(t) 322 rd, err := NewReaderWithOptions(events, os.Getpagesize(), ReaderOptions{Overwritable: true}) 323 if err != nil { 324 t.Fatal(err) 325 } 326 defer rd.Close() 327 328 err = rd.Pause() 329 if err != nil { 330 t.Fatal(err) 331 } 332 333 rd.SetDeadline(time.Now().Add(4 * time.Millisecond)) 334 _, err = rd.Read() 335 qt.Assert(t, qt.ErrorIs(err, os.ErrDeadlineExceeded), qt.Commentf("expected os.ErrDeadlineExceeded")) 336 337 err = rd.Resume() 338 if err != nil { 339 t.Fatal(err) 340 } 341 } 342 343 func TestPerfReaderClose(t *testing.T) { 344 events := perfEventArray(t) 345 346 rd, err := NewReader(events, 4096) 347 if err != nil { 348 t.Fatal(err) 349 } 350 defer rd.Close() 351 352 errs := make(chan error, 1) 353 waiting := make(chan struct{}) 354 go func() { 355 close(waiting) 356 _, err := rd.Read() 357 errs <- err 358 }() 359 360 <-waiting 361 362 // Close should interrupt Read 363 if err := rd.Close(); err != nil { 364 t.Fatal(err) 365 } 366 367 select { 368 case <-errs: 369 case <-time.After(time.Second): 370 t.Fatal("Close doesn't interrupt Read") 371 } 372 373 // And we should be able to call it multiple times 374 if err := rd.Close(); err != nil { 375 t.Fatal(err) 376 } 377 378 if _, err := rd.Read(); err == nil { 379 t.Fatal("Read on a closed PerfReader doesn't return an error") 380 } 381 } 382 383 func TestCreatePerfEvent(t *testing.T) { 384 fd, err := createPerfEvent(0, ReaderOptions{Watermark: 1, Overwritable: false}) 385 if err != nil { 386 t.Fatal("Can't create perf event:", err) 387 } 388 fd.Close() 389 } 390 391 func TestReadRecord(t *testing.T) { 392 var buf bytes.Buffer 393 394 err := binary.Write(&buf, internal.NativeEndian, &perfEventHeader{}) 395 if err != nil { 396 t.Fatal(err) 397 } 398 399 var rec Record 400 err = readRecord(&buf, &rec, make([]byte, perfEventHeaderSize), false) 401 if !IsUnknownEvent(err) { 402 t.Error("readRecord should return unknown event error, got", err) 403 } 404 } 405 406 func TestPause(t *testing.T) { 407 t.Parallel() 408 409 events := perfEventArray(t) 410 411 rd, err := NewReader(events, 4096) 412 if err != nil { 413 t.Fatal(err) 414 } 415 defer rd.Close() 416 417 // Reader is already unpaused by default. It should be idempotent. 418 if err = rd.Resume(); err != nil { 419 t.Fatal(err) 420 } 421 422 // Write a sample. The reader should read it. 423 prog := outputSamplesProg(t, events, 5) 424 ret, _, err := prog.Test(internal.EmptyBPFContext) 425 testutils.SkipIfNotSupported(t, err) 426 if err != nil || ret != 0 { 427 t.Fatal("Can't write sample") 428 } 429 if _, err := rd.Read(); err != nil { 430 t.Fatal(err) 431 } 432 433 // Pause. No notification should trigger. 434 if err = rd.Pause(); err != nil { 435 t.Fatal(err) 436 } 437 errChan := make(chan error, 1) 438 go func() { 439 // Read one notification then send any errors and exit. 440 _, err := rd.Read() 441 errChan <- err 442 }() 443 ret, _, err = prog.Test(internal.EmptyBPFContext) 444 if err == nil && ret == 0 { 445 t.Fatal("Unexpectedly wrote sample while paused") 446 } // else Success 447 select { 448 case err := <-errChan: 449 // Failure: Pause was unsuccessful. 450 t.Fatalf("received notification on paused reader: %s", err) 451 case <-time.After(readTimeout): 452 // Success 453 } 454 455 // Pause should be idempotent. 456 if err = rd.Pause(); err != nil { 457 t.Fatal(err) 458 } 459 460 // Resume. Now notifications should continue. 461 if err = rd.Resume(); err != nil { 462 t.Fatal(err) 463 } 464 ret, _, err = prog.Test(internal.EmptyBPFContext) 465 if err != nil || ret != 0 { 466 t.Fatal("Can't write sample") 467 } 468 select { 469 case err := <-errChan: 470 if err != nil { 471 t.Fatal(err) 472 } // else Success 473 case <-time.After(readTimeout): 474 t.Fatal("timed out waiting for notification after resume") 475 } 476 477 if err = rd.Close(); err != nil { 478 t.Fatal(err) 479 } 480 481 // Pause/Resume after close should be no-op. 482 err = rd.Pause() 483 qt.Assert(t, qt.Not(qt.Equals(err, ErrClosed)), qt.Commentf("returns unwrapped ErrClosed")) 484 qt.Assert(t, qt.ErrorIs(err, ErrClosed), qt.Commentf("doesn't wrap ErrClosed")) 485 486 err = rd.Resume() 487 qt.Assert(t, qt.Not(qt.Equals(err, ErrClosed)), qt.Commentf("returns unwrapped ErrClosed")) 488 qt.Assert(t, qt.ErrorIs(err, ErrClosed), qt.Commentf("doesn't wrap ErrClosed")) 489 } 490 491 func TestPerfReaderWakeupEvents(t *testing.T) { 492 testutils.LockOSThreadToSingleCPU(t) 493 494 events := perfEventArray(t) 495 496 numEvents := 2 497 rd, err := NewReaderWithOptions(events, 4096, ReaderOptions{WakeupEvents: numEvents}) 498 if err != nil { 499 t.Fatal(err) 500 } 501 defer rd.Close() 502 503 prog := outputSamplesProg(t, events, 5) 504 505 // Send enough events to trigger WakeupEvents. 506 for i := 0; i < numEvents; i++ { 507 _, _, err = prog.Test(internal.EmptyBPFContext) 508 testutils.SkipIfNotSupported(t, err) 509 qt.Assert(t, qt.IsNil(err)) 510 } 511 512 time.AfterFunc(5*time.Second, func() { 513 // Interrupt Read() in case the implementation is buggy. 514 rd.Close() 515 }) 516 517 for i := 0; i < numEvents; i++ { 518 checkRecord(t, rd) 519 } 520 } 521 522 func TestReadWithoutWakeup(t *testing.T) { 523 t.Parallel() 524 525 events := perfEventArray(t) 526 527 rd, err := NewReaderWithOptions(events, 1, ReaderOptions{WakeupEvents: 2}) 528 if err != nil { 529 t.Fatal(err) 530 } 531 defer rd.Close() 532 533 prog := outputSamplesProg(t, events, 5) 534 ret, _, err := prog.Test(internal.EmptyBPFContext) 535 testutils.SkipIfNotSupported(t, err) 536 qt.Assert(t, qt.IsNil(err)) 537 qt.Assert(t, qt.Equals(ret, 0)) 538 539 rd.SetDeadline(time.Now()) 540 checkRecord(t, rd) 541 } 542 543 func BenchmarkReader(b *testing.B) { 544 events := perfEventArray(b) 545 prog := outputSamplesProg(b, events, 80) 546 547 rd, err := NewReader(events, 4096) 548 if err != nil { 549 b.Fatal(err) 550 } 551 defer rd.Close() 552 553 buf := internal.EmptyBPFContext 554 555 b.ResetTimer() 556 b.ReportAllocs() 557 for i := 0; i < b.N; i++ { 558 ret, _, err := prog.Test(buf) 559 if err != nil { 560 b.Fatal(err) 561 } else if errno := syscall.Errno(-int32(ret)); errno != 0 { 562 b.Fatal("Expected 0 as return value, got", errno) 563 } 564 565 if _, err = rd.Read(); err != nil { 566 b.Fatal(err) 567 } 568 } 569 } 570 571 func BenchmarkReadInto(b *testing.B) { 572 events := perfEventArray(b) 573 prog := outputSamplesProg(b, events, 80) 574 575 rd, err := NewReader(events, 4096) 576 if err != nil { 577 b.Fatal(err) 578 } 579 defer rd.Close() 580 581 buf := internal.EmptyBPFContext 582 583 b.ResetTimer() 584 b.ReportAllocs() 585 586 var rec Record 587 for i := 0; i < b.N; i++ { 588 // NB: Submitting samples into the perf event ring dominates 589 // the benchmark time unfortunately. 590 ret, _, err := prog.Test(buf) 591 if err != nil { 592 b.Fatal(err) 593 } else if errno := syscall.Errno(-int32(ret)); errno != 0 { 594 b.Fatal("Expected 0 as return value, got", errno) 595 } 596 597 if err := rd.ReadInto(&rec); err != nil { 598 b.Fatal(err) 599 } 600 } 601 } 602 603 // This exists just to make the example below nicer. 604 func bpfPerfEventOutputProgram() (*ebpf.Program, *ebpf.Map) { 605 return nil, nil 606 } 607 608 // ExampleReader submits a perf event using BPF, 609 // and then reads it in user space. 610 // 611 // The BPF will look something like this: 612 // 613 // struct map events __section("maps") = { 614 // .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 615 // }; 616 // 617 // __section("xdp") int output_single(void *ctx) { 618 // unsigned char buf[] = { 619 // 1, 2, 3, 4, 5 620 // }; 621 // 622 // return perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &buf[0], 5); 623 // } 624 // 625 // Also see BPF_F_CTXLEN_MASK if you want to sample packet data 626 // from SKB or XDP programs. 627 func ExampleReader() { 628 prog, events := bpfPerfEventOutputProgram() 629 defer prog.Close() 630 defer events.Close() 631 632 rd, err := NewReader(events, 4096) 633 if err != nil { 634 panic(err) 635 } 636 defer rd.Close() 637 638 // Writes out a sample with content 1,2,3,4,4 639 ret, _, err := prog.Test(internal.EmptyBPFContext) 640 if err != nil || ret != 0 { 641 panic("Can't write sample") 642 } 643 644 record, err := rd.Read() 645 if err != nil { 646 panic(err) 647 } 648 649 // Data is padded with 0 for alignment 650 fmt.Println("Sample:", record.RawSample) 651 } 652 653 // ReadRecord allows reducing memory allocations. 654 func ExampleReader_ReadInto() { 655 prog, events := bpfPerfEventOutputProgram() 656 defer prog.Close() 657 defer events.Close() 658 659 rd, err := NewReader(events, 4096) 660 if err != nil { 661 panic(err) 662 } 663 defer rd.Close() 664 665 for i := 0; i < 2; i++ { 666 // Write out two samples 667 ret, _, err := prog.Test(internal.EmptyBPFContext) 668 if err != nil || ret != 0 { 669 panic("Can't write sample") 670 } 671 } 672 673 var rec Record 674 for i := 0; i < 2; i++ { 675 if err := rd.ReadInto(&rec); err != nil { 676 panic(err) 677 } 678 679 fmt.Println("Sample:", rec.RawSample[:5]) 680 } 681 } 682 683 func perfEventArray(tb testing.TB) *ebpf.Map { 684 events, err := ebpf.NewMap(&ebpf.MapSpec{ 685 Type: ebpf.PerfEventArray, 686 }) 687 if err != nil { 688 tb.Fatal(err) 689 } 690 tb.Cleanup(func() { events.Close() }) 691 return events 692 }