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