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