github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/agent/ebpfspy/session_linux.go (about)

     1  //go:build ebpfspy
     2  
     3  // Package ebpfspy provides integration with Linux eBPF. It is a rough copy of profile.py from BCC tools:
     4  //
     5  //	https://github.com/iovisor/bcc/blob/master/tools/profile.py
     6  package ebpfspy
     7  
     8  import "C"
     9  import (
    10  	"bytes"
    11  	"context"
    12  	_ "embed"
    13  	"encoding/binary"
    14  	"fmt"
    15  	"github.com/pyroscope-io/pyroscope/pkg/agent/ebpfspy/cpuonline"
    16  	"github.com/pyroscope-io/pyroscope/pkg/agent/ebpfspy/sd"
    17  	"github.com/pyroscope-io/pyroscope/pkg/agent/log"
    18  	"github.com/pyroscope-io/pyroscope/pkg/agent/spy"
    19  	"golang.org/x/sys/unix"
    20  	"sync"
    21  	"syscall"
    22  	"unsafe"
    23  
    24  	bpf "github.com/aquasecurity/libbpfgo"
    25  )
    26  
    27  //#cgo CFLAGS: -I./bpf/
    28  //#include <linux/types.h>
    29  //#include "profile.bpf.h"
    30  import "C"
    31  
    32  type Session struct {
    33  	logger           log.Logger
    34  	pid              int
    35  	sampleRate       uint32
    36  	symbolCacheSize  int
    37  	serviceDiscovery sd.ServiceDiscovery
    38  	onlyServices     bool
    39  
    40  	perfEventFds []int
    41  
    42  	symCache *symbolCache
    43  
    44  	module    *bpf.Module
    45  	mapCounts *bpf.BPFMap
    46  	mapStacks *bpf.BPFMap
    47  	mapArgs   *bpf.BPFMap
    48  	prog      *bpf.BPFProg
    49  	link      *bpf.BPFLink
    50  
    51  	modMutex sync.Mutex
    52  
    53  	roundNumber int
    54  }
    55  
    56  const btf = "should not be used" // canary to detect we got relocations
    57  
    58  func NewSession(logger log.Logger, pid int, sampleRate uint32, symbolCacheSize int, serviceDiscovery sd.ServiceDiscovery, onlyServices bool) *Session {
    59  	return &Session{
    60  		logger:           logger,
    61  		pid:              pid,
    62  		sampleRate:       sampleRate,
    63  		symbolCacheSize:  symbolCacheSize,
    64  		serviceDiscovery: serviceDiscovery,
    65  		onlyServices:     onlyServices,
    66  	}
    67  }
    68  
    69  func (s *Session) Start() error {
    70  	var err error
    71  	if err = unix.Setrlimit(unix.RLIMIT_MEMLOCK, &unix.Rlimit{
    72  		Cur: unix.RLIM_INFINITY,
    73  		Max: unix.RLIM_INFINITY,
    74  	}); err != nil {
    75  		return err
    76  	}
    77  
    78  	s.modMutex.Lock()
    79  	defer s.modMutex.Unlock()
    80  
    81  	if s.symCache, err = newSymbolCache(s.symbolCacheSize); err != nil {
    82  		return err
    83  	}
    84  	args := bpf.NewModuleArgs{BPFObjBuff: profileBpf,
    85  		BTFObjPath: btf}
    86  	if s.module, err = bpf.NewModuleFromBufferArgs(args); err != nil {
    87  		return err
    88  	}
    89  	if err = s.module.BPFLoadObject(); err != nil {
    90  		return err
    91  	}
    92  	if s.prog, err = s.module.GetProgram("do_perf_event"); err != nil {
    93  		return err
    94  	}
    95  	if err = s.findMaps(); err != nil {
    96  		return err
    97  	}
    98  	if err = s.initArgs(); err != nil {
    99  		return err
   100  	}
   101  	if err = s.attachPerfEvent(); err != nil {
   102  		return err
   103  	}
   104  	return nil
   105  }
   106  
   107  func (s *Session) Reset(cb func(labels *spy.Labels, name []byte, value uint64, pid uint32) error) error {
   108  	s.logger.Debugf("ebpf session reset")
   109  	s.modMutex.Lock()
   110  	defer s.modMutex.Unlock()
   111  
   112  	s.roundNumber += 1
   113  
   114  	refreshResult := make(chan error)
   115  	go func() {
   116  		refreshResult <- s.serviceDiscovery.Refresh(context.TODO())
   117  	}()
   118  
   119  	keys, values, batch, err := s.getCountsMapValues()
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	err = <-refreshResult
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	type sf struct {
   130  		pid    uint32
   131  		count  uint32
   132  		kStack []byte
   133  		uStack []byte
   134  		comm   string
   135  		labels *spy.Labels
   136  	}
   137  	var sfs []sf
   138  	knownStacks := map[uint32]bool{}
   139  	for i, key := range keys {
   140  		ck := (*C.struct_profile_key_t)(unsafe.Pointer(&key[0]))
   141  		value := values[i]
   142  
   143  		pid := uint32(ck.pid)
   144  		kStackID := int64(ck.kern_stack)
   145  		uStackID := int64(ck.user_stack)
   146  		count := binary.LittleEndian.Uint32(value)
   147  		var comm string = C.GoString(&ck.comm[0])
   148  		if uStackID >= 0 {
   149  			knownStacks[uint32(uStackID)] = true
   150  		}
   151  		if kStackID >= 0 {
   152  			knownStacks[uint32(kStackID)] = true
   153  		}
   154  		labels := s.serviceDiscovery.GetLabels(pid)
   155  		if labels == nil && s.onlyServices {
   156  			continue
   157  		}
   158  		uStack := s.getStack(uStackID)
   159  		kStack := s.getStack(kStackID)
   160  		sfs = append(sfs, sf{pid: pid, uStack: uStack, kStack: kStack, count: count, comm: comm, labels: labels})
   161  	}
   162  	for _, it := range sfs {
   163  		buf := bytes.NewBuffer(nil)
   164  		buf.Write([]byte(it.comm))
   165  		buf.Write([]byte{';'})
   166  		s.walkStack(buf, it.uStack, it.pid, true)
   167  		s.walkStack(buf, it.kStack, 0, false)
   168  		err = cb(it.labels, buf.Bytes(), uint64(it.count), it.pid)
   169  		if err != nil {
   170  			return err
   171  		}
   172  	}
   173  	if err = s.clearCountsMap(keys, batch); err != nil {
   174  		return err
   175  	}
   176  	if err = s.clearStacksMap(knownStacks); err != nil {
   177  		return err
   178  	}
   179  	return nil
   180  }
   181  
   182  func (s *Session) Stop() {
   183  	s.symCache.clear()
   184  	for fd := range s.perfEventFds {
   185  		_ = syscall.Close(fd)
   186  	}
   187  	s.module.Close()
   188  }
   189  
   190  func (s *Session) findMaps() error {
   191  	var err error
   192  	if s.mapArgs, err = s.module.GetMap("args"); err != nil {
   193  		return err
   194  	}
   195  	if s.mapCounts, err = s.module.GetMap("counts"); err != nil {
   196  		return err
   197  	}
   198  	if s.mapStacks, err = s.module.GetMap("stacks"); err != nil {
   199  		return err
   200  	}
   201  	return nil
   202  }
   203  func (s *Session) initArgs() error {
   204  	var zero uint32
   205  	var err error
   206  	var tgidFilter uint32
   207  	if s.pid <= 0 {
   208  		tgidFilter = 0
   209  	} else {
   210  		tgidFilter = uint32(s.pid)
   211  	}
   212  	args := C.struct_profile_bss_args_t{
   213  		tgid_filter: C.uint(tgidFilter),
   214  	}
   215  	err = s.mapArgs.UpdateValueFlags(unsafe.Pointer(&zero), unsafe.Pointer(&args), 0)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	return nil
   220  }
   221  
   222  func (s *Session) attachPerfEvent() error {
   223  	var cpus []uint
   224  	var err error
   225  	if cpus, err = cpuonline.Get(); err != nil {
   226  		return err
   227  	}
   228  	for _, cpu := range cpus {
   229  		attr := unix.PerfEventAttr{
   230  			Type:   unix.PERF_TYPE_SOFTWARE,
   231  			Config: unix.PERF_COUNT_SW_CPU_CLOCK,
   232  			Bits:   unix.PerfBitFreq,
   233  			Sample: uint64(s.sampleRate),
   234  		}
   235  		fd, err := unix.PerfEventOpen(&attr, -1, int(cpu), -1, unix.PERF_FLAG_FD_CLOEXEC)
   236  		if err != nil {
   237  			return err
   238  		}
   239  		s.perfEventFds = append(s.perfEventFds, fd)
   240  		if _, err = s.prog.AttachPerfEvent(fd); err != nil {
   241  			return err
   242  		}
   243  	}
   244  	return nil
   245  }
   246  
   247  func (s *Session) getStack(stackId int64) []byte {
   248  	if stackId < 0 {
   249  		return nil
   250  	}
   251  	stackIdU32 := uint32(stackId)
   252  	key := unsafe.Pointer(&stackIdU32)
   253  	stack, err := s.mapStacks.GetValue(key)
   254  	if err != nil {
   255  		return nil
   256  	}
   257  	return stack
   258  
   259  }
   260  func (s *Session) walkStack(line *bytes.Buffer, stack []byte, pid uint32, userspace bool) {
   261  	if len(stack) == 0 {
   262  		return
   263  	}
   264  	var stackFrames []string
   265  	for i := 0; i < 127; i++ {
   266  		it := stack[i*8 : i*8+8]
   267  		ip := binary.LittleEndian.Uint64(it)
   268  		if ip == 0 {
   269  			break
   270  		}
   271  		sym := s.symCache.bccResolve(pid, ip, s.roundNumber)
   272  		if !userspace && sym.Name == "" {
   273  			continue
   274  		}
   275  		name := sym.Name
   276  		if sym.Name == "" {
   277  			if sym.Module != "" {
   278  				name = fmt.Sprintf("%s+0x%x", sym.Module, sym.Offset)
   279  			} else {
   280  				name = "[unknown]"
   281  			}
   282  		}
   283  		stackFrames = append(stackFrames, name+";")
   284  	}
   285  	reverse(stackFrames)
   286  	for _, s := range stackFrames {
   287  		line.Write([]byte(s))
   288  	}
   289  }
   290  
   291  func reverse(s []string) {
   292  	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
   293  		s[i], s[j] = s[j], s[i]
   294  	}
   295  }
   296  
   297  //go:embed bpf/profile.bpf.o
   298  var profileBpf []byte