...

Source file src/github.com/cilium/ebpf/perf/reader_test.go

Documentation: github.com/cilium/ebpf/perf

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

View as plain text