github.com/cilium/ebpf@v0.10.0/ringbuf/reader_test.go (about)

     1  package ringbuf
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"syscall"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/cilium/ebpf"
    11  	"github.com/cilium/ebpf/asm"
    12  	"github.com/cilium/ebpf/internal"
    13  	"github.com/cilium/ebpf/internal/testutils"
    14  	"github.com/google/go-cmp/cmp"
    15  )
    16  
    17  func TestRingbufReader(t *testing.T) {
    18  	testutils.SkipOnOldKernel(t, "5.8", "BPF ring buffer")
    19  
    20  	readerTests := []struct {
    21  		name     string
    22  		messages []int
    23  		want     map[int][]byte
    24  	}{
    25  		{
    26  			name:     "send one short sample",
    27  			messages: []int{5},
    28  			want: map[int][]byte{
    29  				5: {1, 2, 3, 4, 4},
    30  			},
    31  		},
    32  		{
    33  			name:     "send three short samples, the second is discarded",
    34  			messages: []int{5, 10, 15},
    35  			want: map[int][]byte{
    36  				5:  {1, 2, 3, 4, 4},
    37  				15: {1, 2, 3, 4, 4, 3, 2, 1, 1, 2, 3, 4, 4, 3, 2},
    38  			},
    39  		},
    40  	}
    41  	for _, tt := range readerTests {
    42  		t.Run(tt.name, func(t *testing.T) {
    43  			prog, events := mustOutputSamplesProg(t, 0, tt.messages...)
    44  
    45  			rd, err := NewReader(events)
    46  			if err != nil {
    47  				t.Fatal(err)
    48  			}
    49  			defer rd.Close()
    50  
    51  			ret, _, err := prog.Test(internal.EmptyBPFContext)
    52  			testutils.SkipIfNotSupported(t, err)
    53  			if err != nil {
    54  				t.Fatal(err)
    55  			}
    56  
    57  			if errno := syscall.Errno(-int32(ret)); errno != 0 {
    58  				t.Fatal("Expected 0 as return value, got", errno)
    59  			}
    60  
    61  			raw := make(map[int][]byte)
    62  
    63  			for len(raw) < len(tt.want) {
    64  				record, err := rd.Read()
    65  				if err != nil {
    66  					t.Fatal("Can't read samples:", err)
    67  				}
    68  				raw[len(record.RawSample)] = record.RawSample
    69  			}
    70  
    71  			if diff := cmp.Diff(tt.want, raw); diff != "" {
    72  				t.Errorf("Read samples mismatch (-want +got):\n%s", diff)
    73  			}
    74  		})
    75  	}
    76  }
    77  
    78  func outputSamplesProg(flags int32, sampleSizes ...int) (*ebpf.Program, *ebpf.Map, error) {
    79  	events, err := ebpf.NewMap(&ebpf.MapSpec{
    80  		Type:       ebpf.RingBuf,
    81  		MaxEntries: 4096,
    82  	})
    83  	if err != nil {
    84  		return nil, nil, err
    85  	}
    86  
    87  	var maxSampleSize int
    88  	for _, sampleSize := range sampleSizes {
    89  		if sampleSize > maxSampleSize {
    90  			maxSampleSize = sampleSize
    91  		}
    92  	}
    93  
    94  	insns := asm.Instructions{
    95  		asm.LoadImm(asm.R0, 0x0102030404030201, asm.DWord),
    96  		asm.Mov.Reg(asm.R9, asm.R1),
    97  	}
    98  
    99  	bufDwords := (maxSampleSize / 8) + 1
   100  	for i := 0; i < bufDwords; i++ {
   101  		insns = append(insns,
   102  			asm.StoreMem(asm.RFP, int16(i+1)*-8, asm.R0, asm.DWord),
   103  		)
   104  	}
   105  
   106  	for sampleIdx, sampleSize := range sampleSizes {
   107  		insns = append(insns,
   108  			asm.LoadMapPtr(asm.R1, events.FD()),
   109  			asm.Mov.Imm(asm.R2, int32(sampleSize)),
   110  			asm.Mov.Imm(asm.R3, int32(0)),
   111  			asm.FnRingbufReserve.Call(),
   112  			asm.JEq.Imm(asm.R0, 0, "exit"),
   113  			asm.Mov.Reg(asm.R5, asm.R0),
   114  		)
   115  		for i := 0; i < sampleSize; i++ {
   116  			insns = append(insns,
   117  				asm.LoadMem(asm.R4, asm.RFP, int16(i+1)*-1, asm.Byte),
   118  				asm.StoreMem(asm.R5, int16(i), asm.R4, asm.Byte),
   119  			)
   120  		}
   121  
   122  		// discard every even sample
   123  		if sampleIdx&1 != 0 {
   124  			insns = append(insns,
   125  				asm.Mov.Reg(asm.R1, asm.R5),
   126  				asm.Mov.Imm(asm.R2, flags),
   127  				asm.FnRingbufDiscard.Call(),
   128  			)
   129  		} else {
   130  			insns = append(insns,
   131  				asm.Mov.Reg(asm.R1, asm.R5),
   132  				asm.Mov.Imm(asm.R2, flags),
   133  				asm.FnRingbufSubmit.Call(),
   134  			)
   135  		}
   136  	}
   137  
   138  	insns = append(insns,
   139  		asm.Mov.Imm(asm.R0, int32(0)).WithSymbol("exit"),
   140  		asm.Return(),
   141  	)
   142  
   143  	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
   144  		License:      "MIT",
   145  		Type:         ebpf.XDP,
   146  		Instructions: insns,
   147  	})
   148  	if err != nil {
   149  		events.Close()
   150  		return nil, nil, err
   151  	}
   152  
   153  	return prog, events, nil
   154  }
   155  
   156  func mustOutputSamplesProg(tb testing.TB, flags int32, sampleSizes ...int) (*ebpf.Program, *ebpf.Map) {
   157  	tb.Helper()
   158  
   159  	prog, events, err := outputSamplesProg(flags, sampleSizes...)
   160  	if err != nil {
   161  		tb.Fatal(err)
   162  	}
   163  
   164  	tb.Cleanup(func() {
   165  		prog.Close()
   166  		events.Close()
   167  	})
   168  
   169  	return prog, events
   170  }
   171  
   172  func TestReaderBlocking(t *testing.T) {
   173  	testutils.SkipOnOldKernel(t, "5.8", "BPF ring buffer")
   174  
   175  	prog, events := mustOutputSamplesProg(t, 0, 5)
   176  	ret, _, err := prog.Test(internal.EmptyBPFContext)
   177  	testutils.SkipIfNotSupported(t, err)
   178  	if err != nil {
   179  		t.Fatal(err)
   180  	}
   181  
   182  	if errno := syscall.Errno(-int32(ret)); errno != 0 {
   183  		t.Fatal("Expected 0 as return value, got", errno)
   184  	}
   185  
   186  	rd, err := NewReader(events)
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  	defer rd.Close()
   191  
   192  	if _, err := rd.Read(); err != nil {
   193  		t.Fatal("Can't read first sample:", err)
   194  	}
   195  
   196  	errs := make(chan error, 1)
   197  	go func() {
   198  		_, err := rd.Read()
   199  		errs <- err
   200  	}()
   201  
   202  	select {
   203  	case err := <-errs:
   204  		t.Fatal("Read returns error instead of blocking:", err)
   205  	case <-time.After(100 * time.Millisecond):
   206  	}
   207  
   208  	// Close should interrupt blocking Read
   209  	if err := rd.Close(); err != nil {
   210  		t.Fatal(err)
   211  	}
   212  
   213  	select {
   214  	case err := <-errs:
   215  		if !errors.Is(err, ErrClosed) {
   216  			t.Fatal("Expected os.ErrClosed from interrupted Read, got:", err)
   217  		}
   218  	case <-time.After(time.Second):
   219  		t.Fatal("Close doesn't interrupt Read")
   220  	}
   221  
   222  	// And we should be able to call it multiple times
   223  	if err := rd.Close(); err != nil {
   224  		t.Fatal(err)
   225  	}
   226  
   227  	if _, err := rd.Read(); !errors.Is(err, ErrClosed) {
   228  		t.Fatal("Second Read on a closed RingbufReader doesn't return ErrClosed")
   229  	}
   230  }
   231  
   232  func TestReaderSetDeadline(t *testing.T) {
   233  	testutils.SkipOnOldKernel(t, "5.8", "BPF ring buffer")
   234  
   235  	_, events := mustOutputSamplesProg(t, 0, 5)
   236  	rd, err := NewReader(events)
   237  	if err != nil {
   238  		t.Fatal(err)
   239  	}
   240  	defer rd.Close()
   241  
   242  	rd.SetDeadline(time.Now().Add(-time.Second))
   243  	if _, err := rd.Read(); !errors.Is(err, os.ErrDeadlineExceeded) {
   244  		t.Error("Expected os.ErrDeadlineExceeded from first Read, got:", err)
   245  	}
   246  	if _, err := rd.Read(); !errors.Is(err, os.ErrDeadlineExceeded) {
   247  		t.Error("Expected os.ErrDeadlineExceeded from second Read, got:", err)
   248  	}
   249  }
   250  
   251  func BenchmarkReader(b *testing.B) {
   252  	testutils.SkipOnOldKernel(b, "5.8", "BPF ring buffer")
   253  
   254  	readerBenchmarks := []struct {
   255  		name  string
   256  		flags int32
   257  	}{
   258  		{
   259  			name: "normal epoll with timeout -1",
   260  		},
   261  	}
   262  
   263  	for _, bm := range readerBenchmarks {
   264  		b.Run(bm.name, func(b *testing.B) {
   265  			prog, events := mustOutputSamplesProg(b, bm.flags, 80)
   266  
   267  			rd, err := NewReader(events)
   268  			if err != nil {
   269  				b.Fatal(err)
   270  			}
   271  			defer rd.Close()
   272  
   273  			buf := internal.EmptyBPFContext
   274  
   275  			b.ResetTimer()
   276  			b.ReportAllocs()
   277  
   278  			for i := 0; i < b.N; i++ {
   279  				ret, _, err := prog.Test(buf)
   280  				if err != nil {
   281  					b.Fatal(err)
   282  				} else if errno := syscall.Errno(-int32(ret)); errno != 0 {
   283  					b.Fatal("Expected 0 as return value, got", errno)
   284  				}
   285  				_, err = rd.Read()
   286  				if err != nil {
   287  					b.Fatal("Can't read samples:", err)
   288  				}
   289  			}
   290  		})
   291  	}
   292  }
   293  
   294  func BenchmarkReadInto(b *testing.B) {
   295  	testutils.SkipOnOldKernel(b, "5.8", "BPF ring buffer")
   296  
   297  	prog, events := mustOutputSamplesProg(b, 0, 80)
   298  
   299  	rd, err := NewReader(events)
   300  	if err != nil {
   301  		b.Fatal(err)
   302  	}
   303  	defer rd.Close()
   304  
   305  	buf := internal.EmptyBPFContext
   306  
   307  	b.ResetTimer()
   308  	b.ReportAllocs()
   309  
   310  	var rec Record
   311  	for i := 0; i < b.N; i++ {
   312  		ret, _, err := prog.Test(buf)
   313  		if err != nil {
   314  			b.Fatal(err)
   315  		} else if errno := syscall.Errno(-int32(ret)); errno != 0 {
   316  			b.Fatal("Expected 0 as return value, got", errno)
   317  		}
   318  
   319  		if err := rd.ReadInto(&rec); err != nil {
   320  			b.Fatal("Can't read samples:", err)
   321  		}
   322  	}
   323  }