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