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 }