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  }