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