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  }