Skip to content

Commit 5a0992d

Browse files
committed
Add Remaining and BufferSize to perf/ring buffers
Signed-off-by: Bryce Kahle <[email protected]> Signed-off-by: Lorenz Bauer <[email protected]>
1 parent ed9bf3b commit 5a0992d

6 files changed

Lines changed: 71 additions & 5 deletions

File tree

perf/reader.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ type Record struct {
4848
// The number of samples which could not be output, since
4949
// the ring buffer was full.
5050
LostSamples uint64
51+
52+
// The minimum number of bytes remaining in the per-CPU buffer after this Record has been read.
53+
// Negative for overwritable buffers.
54+
Remaining int
5155
}
5256

5357
// Read a record from a reader and tag it as being from the given CPU.
@@ -158,6 +162,8 @@ type Reader struct {
158162

159163
paused bool
160164
overwritable bool
165+
166+
bufferSize int
161167
}
162168

163169
// ReaderOptions control the behaviour of the user
@@ -216,6 +222,7 @@ func NewReaderWithOptions(array *ebpf.Map, perCPUBuffer int, opts ReaderOptions)
216222
// bpf_perf_event_output checks which CPU an event is enabled on,
217223
// but doesn't allow using a wildcard like -1 to specify "all CPUs".
218224
// Hence we have to create a ring for each CPU.
225+
bufferSize := 0
219226
for i := 0; i < nCPU; i++ {
220227
ring, err := newPerfEventRing(i, perCPUBuffer, opts.Watermark, opts.Overwritable)
221228
if errors.Is(err, unix.ENODEV) {
@@ -224,6 +231,7 @@ func NewReaderWithOptions(array *ebpf.Map, perCPUBuffer int, opts ReaderOptions)
224231
pauseFds = append(pauseFds, -1)
225232
continue
226233
}
234+
bufferSize = ring.size()
227235

228236
if err != nil {
229237
return nil, fmt.Errorf("failed to create perf ring for CPU %d: %v", i, err)
@@ -251,6 +259,7 @@ func NewReaderWithOptions(array *ebpf.Map, perCPUBuffer int, opts ReaderOptions)
251259
eventHeader: make([]byte, perfEventHeaderSize),
252260
pauseFds: pauseFds,
253261
overwritable: opts.Overwritable,
262+
bufferSize: bufferSize,
254263
}
255264
if err = pr.Resume(); err != nil {
256265
return nil, err
@@ -430,6 +439,11 @@ func (pr *Reader) Resume() error {
430439
return nil
431440
}
432441

442+
// BufferSize is the size in bytes of each per-CPU buffer
443+
func (pr *Reader) BufferSize() int {
444+
return pr.bufferSize
445+
}
446+
433447
// NB: Has to be preceded by a call to ring.loadHead.
434448
func (pr *Reader) readRecordFromRing(rec *Record, ring *perfEventRing) error {
435449
defer ring.writeTail()
@@ -439,6 +453,7 @@ func (pr *Reader) readRecordFromRing(rec *Record, ring *perfEventRing) error {
439453
if pr.overwritable && (errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)) {
440454
return errEOR
441455
}
456+
rec.Remaining = ring.remaining()
442457
return err
443458
}
444459

perf/reader_test.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,15 @@ func TestPerfReader(t *testing.T) {
3838
}
3939
defer rd.Close()
4040

41-
outputSamples(t, events, 5)
41+
qt.Assert(t, rd.BufferSize(), qt.Equals, 4096)
4242

43-
checkRecord(t, rd)
43+
outputSamples(t, events, 5, 5)
44+
45+
_, rem := checkRecord(t, rd)
46+
qt.Assert(t, rem >= 5, qt.IsTrue, qt.Commentf("expected at least 5 Remaining"))
47+
48+
_, rem = checkRecord(t, rd)
49+
qt.Assert(t, rem, qt.Equals, 0, qt.Commentf("expected zero Remaining"))
4450

4551
rd.SetDeadline(time.Now().Add(4 * time.Millisecond))
4652
_, err = rd.Read()
@@ -155,7 +161,7 @@ func outputSamplesProg(tb testing.TB, events *ebpf.Map, sampleSizes ...byte) *eb
155161
return prog
156162
}
157163

158-
func checkRecord(tb testing.TB, rd *Reader) (id int) {
164+
func checkRecord(tb testing.TB, rd *Reader) (id int, remaining int) {
159165
tb.Helper()
160166

161167
rec, err := rd.Read()
@@ -172,7 +178,7 @@ func checkRecord(tb testing.TB, rd *Reader) (id int) {
172178

173179
// padding is ignored since it's value is undefined.
174180

175-
return int(rec.RawSample[1])
181+
return int(rec.RawSample[1]), rec.Remaining
176182
}
177183

178184
func TestPerfReaderLostSample(t *testing.T) {
@@ -305,8 +311,9 @@ func TestPerfReaderOverwritable(t *testing.T) {
305311

306312
nextID := maxEvents
307313
for i := 0; i < maxEvents; i++ {
308-
id := checkRecord(t, rd)
314+
id, rem := checkRecord(t, rd)
309315
qt.Assert(t, id, qt.Equals, nextID)
316+
qt.Assert(t, rem, qt.Equals, -1)
310317
nextID--
311318
}
312319
}

perf/ring.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ func createPerfEvent(cpu, watermark int, overwritable bool) (int, error) {
127127
type ringReader interface {
128128
loadHead()
129129
size() int
130+
remaining() int
130131
writeTail()
131132
Read(p []byte) (int, error)
132133
}
@@ -157,6 +158,10 @@ func (rr *forwardReader) size() int {
157158
return len(rr.ring)
158159
}
159160

161+
func (rr *forwardReader) remaining() int {
162+
return int((rr.head - rr.tail) & rr.mask)
163+
}
164+
160165
func (rr *forwardReader) writeTail() {
161166
// Commit the new tail. This lets the kernel know that
162167
// the ring buffer has been consumed.
@@ -244,6 +249,12 @@ func (rr *reverseReader) size() int {
244249
return len(rr.ring)
245250
}
246251

252+
func (rr *reverseReader) remaining() int {
253+
// remaining data is inaccurate for overwritable buffers
254+
// once an overwrite happens, so return -1 here.
255+
return -1
256+
}
257+
247258
func (rr *reverseReader) writeTail() {
248259
// We do not care about tail for over writable perf buffer.
249260
// So, this function is noop.

ringbuf/reader.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ func (rh *ringbufHeader) dataLen() int {
4444

4545
type Record struct {
4646
RawSample []byte
47+
48+
// The minimum number of bytes remaining in the ring buffer after this Record has been read.
49+
Remaining int
4750
}
4851

4952
// Read a record from an event ring.
@@ -98,6 +101,7 @@ func readRecord(rd *ringbufEventRing, rec *Record, buf []byte) error {
98101

99102
rd.storeConsumer()
100103
rec.RawSample = rec.RawSample[:header.dataLen()]
104+
rec.Remaining = rd.remaining()
101105
return nil
102106
}
103107

@@ -231,3 +235,8 @@ func (r *Reader) ReadInto(rec *Record) error {
231235
}
232236
}
233237
}
238+
239+
// BufferSize returns the size in bytes of the ring buffer
240+
func (r *Reader) BufferSize() int {
241+
return r.ring.size()
242+
}

ringbuf/reader_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ func TestRingbufReader(t *testing.T) {
5959
}
6060
defer rd.Close()
6161

62+
if uint32(rd.BufferSize()) != 2*events.MaxEntries() {
63+
t.Errorf("expected %d BufferSize, got %d", events.MaxEntries(), rd.BufferSize())
64+
}
65+
6266
ret, _, err := prog.Test(internal.EmptyBPFContext)
6367
testutils.SkipIfNotSupported(t, err)
6468
if err != nil {
@@ -77,6 +81,15 @@ func TestRingbufReader(t *testing.T) {
7781
t.Fatal("Can't read samples:", err)
7882
}
7983
raw[len(record.RawSample)] = record.RawSample
84+
if len(raw) == len(tt.want) {
85+
if record.Remaining != 0 {
86+
t.Errorf("expected 0 Remaining, got %d", record.Remaining)
87+
}
88+
} else {
89+
if record.Remaining == 0 {
90+
t.Error("expected non-zero Remaining, got 0")
91+
}
92+
}
8093
}
8194

8295
if diff := cmp.Diff(tt.want, raw); diff != "" {

ringbuf/ring.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@ func (rr *ringReader) isEmpty() bool {
9898
return prod == cons
9999
}
100100

101+
func (rr *ringReader) size() int {
102+
return cap(rr.ring)
103+
}
104+
105+
func (rr *ringReader) remaining() int {
106+
cons := atomic.LoadUint64(rr.cons_pos)
107+
prod := atomic.LoadUint64(rr.prod_pos)
108+
109+
return int((prod - cons) & rr.mask)
110+
}
111+
101112
func (rr *ringReader) Read(p []byte) (int, error) {
102113
prod := atomic.LoadUint64(rr.prod_pos)
103114

0 commit comments

Comments
 (0)