github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/ringbuf/reader.go (about) 1 package ringbuf 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "sync" 8 "time" 9 10 "github.com/cilium/ebpf" 11 "github.com/cilium/ebpf/internal/epoll" 12 "github.com/cilium/ebpf/internal/unix" 13 ) 14 15 var ( 16 ErrClosed = os.ErrClosed 17 errEOR = errors.New("end of ring") 18 errBusy = errors.New("sample not committed yet") 19 ) 20 21 // ringbufHeader from 'struct bpf_ringbuf_hdr' in kernel/bpf/ringbuf.c 22 type ringbufHeader struct { 23 Len uint32 24 _ uint32 // pg_off, only used by kernel internals 25 } 26 27 func (rh *ringbufHeader) isBusy() bool { 28 return rh.Len&unix.BPF_RINGBUF_BUSY_BIT != 0 29 } 30 31 func (rh *ringbufHeader) isDiscard() bool { 32 return rh.Len&unix.BPF_RINGBUF_DISCARD_BIT != 0 33 } 34 35 func (rh *ringbufHeader) dataLen() int { 36 return int(rh.Len & ^uint32(unix.BPF_RINGBUF_BUSY_BIT|unix.BPF_RINGBUF_DISCARD_BIT)) 37 } 38 39 type Record struct { 40 RawSample []byte 41 42 // The minimum number of bytes remaining in the ring buffer after this Record has been read. 43 Remaining int 44 } 45 46 // Reader allows reading bpf_ringbuf_output 47 // from user space. 48 type Reader struct { 49 poller *epoll.Poller 50 51 // mu protects read/write access to the Reader structure 52 mu sync.Mutex 53 ring *ringbufEventRing 54 epollEvents []unix.EpollEvent 55 haveData bool 56 deadline time.Time 57 bufferSize int 58 } 59 60 // NewReader creates a new BPF ringbuf reader. 61 func NewReader(ringbufMap *ebpf.Map) (*Reader, error) { 62 if ringbufMap.Type() != ebpf.RingBuf { 63 return nil, fmt.Errorf("invalid Map type: %s", ringbufMap.Type()) 64 } 65 66 maxEntries := int(ringbufMap.MaxEntries()) 67 if maxEntries == 0 || (maxEntries&(maxEntries-1)) != 0 { 68 return nil, fmt.Errorf("ringbuffer map size %d is zero or not a power of two", maxEntries) 69 } 70 71 poller, err := epoll.New() 72 if err != nil { 73 return nil, err 74 } 75 76 if err := poller.Add(ringbufMap.FD(), 0); err != nil { 77 poller.Close() 78 return nil, err 79 } 80 81 ring, err := newRingBufEventRing(ringbufMap.FD(), maxEntries) 82 if err != nil { 83 poller.Close() 84 return nil, fmt.Errorf("failed to create ringbuf ring: %w", err) 85 } 86 87 return &Reader{ 88 poller: poller, 89 ring: ring, 90 epollEvents: make([]unix.EpollEvent, 1), 91 bufferSize: ring.size(), 92 }, nil 93 } 94 95 // Close frees resources used by the reader. 96 // 97 // It interrupts calls to Read. 98 func (r *Reader) Close() error { 99 if err := r.poller.Close(); err != nil { 100 if errors.Is(err, os.ErrClosed) { 101 return nil 102 } 103 return err 104 } 105 106 // Acquire the lock. This ensures that Read isn't running. 107 r.mu.Lock() 108 defer r.mu.Unlock() 109 110 if r.ring != nil { 111 r.ring.Close() 112 r.ring = nil 113 } 114 115 return nil 116 } 117 118 // SetDeadline controls how long Read and ReadInto will block waiting for samples. 119 // 120 // Passing a zero time.Time will remove the deadline. 121 func (r *Reader) SetDeadline(t time.Time) { 122 r.mu.Lock() 123 defer r.mu.Unlock() 124 125 r.deadline = t 126 } 127 128 // Read the next record from the BPF ringbuf. 129 // 130 // Returns os.ErrClosed if Close is called on the Reader, or os.ErrDeadlineExceeded 131 // if a deadline was set and no valid entry was present. A producer might use BPF_RB_NO_WAKEUP 132 // which may cause the deadline to expire but a valid entry will be present. 133 func (r *Reader) Read() (Record, error) { 134 var rec Record 135 return rec, r.ReadInto(&rec) 136 } 137 138 // ReadInto is like Read except that it allows reusing Record and associated buffers. 139 func (r *Reader) ReadInto(rec *Record) error { 140 r.mu.Lock() 141 defer r.mu.Unlock() 142 143 if r.ring == nil { 144 return fmt.Errorf("ringbuffer: %w", ErrClosed) 145 } 146 147 for { 148 if !r.haveData { 149 _, err := r.poller.Wait(r.epollEvents[:cap(r.epollEvents)], r.deadline) 150 if errors.Is(err, os.ErrDeadlineExceeded) && !r.ring.isEmpty() { 151 // Ignoring this for reading a valid entry after timeout 152 // This can occur if the producer submitted to the ring buffer with BPF_RB_NO_WAKEUP 153 err = nil 154 } 155 if err != nil { 156 return err 157 } 158 r.haveData = true 159 } 160 161 for { 162 err := r.ring.readRecord(rec) 163 // Not using errors.Is which is quite a bit slower 164 // For a tight loop it might make a difference 165 if err == errBusy { 166 continue 167 } 168 if err == errEOR { 169 r.haveData = false 170 break 171 } 172 return err 173 } 174 } 175 } 176 177 // BufferSize returns the size in bytes of the ring buffer 178 func (r *Reader) BufferSize() int { 179 return r.bufferSize 180 }