github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/exec/ebpf.go (about) 1 //go:build ebpfspy 2 3 package exec 4 5 import ( 6 "context" 7 "errors" 8 "fmt" 9 "github.com/pyroscope-io/pyroscope/pkg/agent/ebpfspy" 10 sd "github.com/pyroscope-io/pyroscope/pkg/agent/ebpfspy/sd" 11 "os" 12 "os/signal" 13 "syscall" 14 "time" 15 16 "github.com/pyroscope-io/pyroscope/pkg/agent" 17 "github.com/pyroscope-io/pyroscope/pkg/agent/spy" 18 "github.com/pyroscope-io/pyroscope/pkg/agent/types" 19 "github.com/pyroscope-io/pyroscope/pkg/agent/upstream/remote" 20 "github.com/pyroscope-io/pyroscope/pkg/config" 21 "github.com/pyroscope-io/pyroscope/pkg/util/process" 22 ) 23 24 func RunEBPF(cfg *config.EBPF) error { 25 if cfg.Pid == -1 && cfg.DetectSubprocesses { 26 return fmt.Errorf("pid -1 can only be used without dectecting subprocesses") 27 } 28 if !isRoot() { 29 return errors.New("when using eBPF you're required to run the agent with sudo") 30 } 31 32 logger := NewLogger(cfg.LogLevel, cfg.NoLogging) 33 34 rc := remote.RemoteConfig{ 35 AuthToken: cfg.AuthToken, 36 TenantID: cfg.TenantID, 37 HTTPHeaders: cfg.Headers, 38 BasicAuthUser: cfg.BasicAuthUser, 39 BasicAuthPassword: cfg.BasicAuthPassword, 40 UpstreamThreads: cfg.UpstreamThreads, 41 UpstreamAddress: cfg.ServerAddress, 42 UpstreamRequestTimeout: cfg.UpstreamRequestTimeout, 43 } 44 up, err := remote.New(rc, logger) 45 if err != nil { 46 return fmt.Errorf("new remote upstream: %v", err) 47 } 48 49 // if the sample rate is zero, use the default value 50 sampleRate := uint32(types.DefaultSampleRate) 51 if cfg.SampleRate != 0 { 52 sampleRate = uint32(cfg.SampleRate) 53 } 54 55 appName := CheckApplicationName(logger, cfg.ApplicationName, spy.EBPF, []string{}) 56 57 var serviceDiscovery sd.ServiceDiscovery = sd.NoopServiceDiscovery{} 58 if cfg.KubernetesNode != "" { 59 serviceDiscovery, err = sd.NewK8ServiceDiscovery(context.TODO(), logger, cfg.KubernetesNode) 60 if err != nil { 61 return err 62 } 63 } 64 65 logger.Debug("starting command") 66 67 // The channel buffer capacity should be sufficient to be keep up with 68 // the expected signal rate (in case of Exec all the signals to be relayed 69 // to the child process) 70 ch := make(chan os.Signal, 10) 71 signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) 72 defer func() { 73 signal.Stop(ch) 74 close(ch) 75 }() 76 77 sc := agent.SessionConfig{ 78 Upstream: up, 79 AppName: appName, 80 Tags: cfg.Tags, 81 ProfilingTypes: []spy.ProfileType{spy.ProfileCPU}, 82 SpyName: spy.EBPF, 83 SampleRate: sampleRate, 84 UploadRate: 10 * time.Second, 85 Pid: cfg.Pid, 86 WithSubprocesses: cfg.DetectSubprocesses, 87 Logger: logger, 88 } 89 session, err := agent.NewSessionWithSpyFactory(sc, func(pid int) ([]spy.Spy, error) { 90 s := ebpfspy.NewSession(logger, cfg.Pid, sampleRate, cfg.SymbolCacheSize, serviceDiscovery, cfg.OnlyServices) 91 err := s.Start() 92 if err != nil { 93 return nil, err 94 } 95 96 res := ebpfspy.NewEBPFSpy(s) 97 return []spy.Spy{res}, nil 98 }) 99 if err != nil { 100 return fmt.Errorf("new session: %w", err) 101 } 102 103 up.Start() 104 defer up.Stop() 105 106 if err = session.Start(); err != nil { 107 return fmt.Errorf("start session: %w", err) 108 } 109 defer session.Stop() 110 111 // wait for process to exit 112 // pid == -1 means we're profiling whole system 113 if cfg.Pid == -1 { 114 <-ch 115 return nil 116 } 117 ticker := time.NewTicker(time.Second) 118 defer ticker.Stop() 119 for { 120 select { 121 case <-ch: 122 return nil 123 case <-ticker.C: 124 if !process.Exists(cfg.Pid) { 125 logger.Debugf("child process exited") 126 return nil 127 } 128 } 129 } 130 }