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
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
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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
194 t.Fatal("unsupported page size:", pageSize)
195 }
196
197 var sampleSizes []int
198
199
200 for i := 0; i < maxEvents+1; i++ {
201 sampleSizes = append(sampleSizes, 180)
202 }
203
204
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
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
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
318 if err = rd.Resume(); err != nil {
319 t.Fatal(err)
320 }
321
322
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
333 if err = rd.Pause(); err != nil {
334 t.Fatal(err)
335 }
336 errChan := make(chan error, 1)
337 go func() {
338
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 }
346 select {
347 case err := <-errChan:
348
349 t.Fatalf("received notification on paused reader: %s", err)
350 case <-time.After(readTimeout):
351
352 }
353
354
355 if err = rd.Pause(); err != nil {
356 t.Fatal(err)
357 }
358
359
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 }
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
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
438
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
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
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
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
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
503 fmt.Println("Sample:", record.RawSample)
504 }
505
506
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
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