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  }