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 }