github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/adhoc/push.go (about)

     1  package adhoc
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"net/http"
     7  	"os"
     8  	"os/exec"
     9  	"os/signal"
    10  	"time"
    11  
    12  	kitLogrus "github.com/go-kit/kit/log/logrus"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"github.com/sirupsen/logrus"
    15  
    16  	"github.com/pyroscope-io/pyroscope/pkg/config"
    17  	"github.com/pyroscope-io/pyroscope/pkg/exporter"
    18  	"github.com/pyroscope-io/pyroscope/pkg/ingestion"
    19  	"github.com/pyroscope-io/pyroscope/pkg/parser"
    20  	"github.com/pyroscope-io/pyroscope/pkg/server"
    21  	"github.com/pyroscope-io/pyroscope/pkg/server/httputils"
    22  	"github.com/pyroscope-io/pyroscope/pkg/storage"
    23  	"github.com/pyroscope-io/pyroscope/pkg/util/process"
    24  )
    25  
    26  type push struct {
    27  	args    []string
    28  	handler http.Handler
    29  	logger  *logrus.Logger
    30  }
    31  
    32  func newPush(_ *config.Adhoc, args []string, st *storage.Storage, logger *logrus.Logger) (runner, error) {
    33  	e, err := exporter.NewExporter(config.MetricsExportRules{}, prometheus.DefaultRegisterer)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	p := parser.New(logger, st, e)
    38  	return push{
    39  		args:    args,
    40  		handler: server.NewIngestHandler(kitLogrus.NewLogger(logger), p, func(*ingestion.IngestInput) {}, httputils.NewDefaultHelper(logger)),
    41  		logger:  logger,
    42  	}, nil
    43  }
    44  
    45  func (p push) Run() error {
    46  	http.Handle("/ingest", p.handler)
    47  	listener, err := net.Listen("tcp", ":0")
    48  	if err != nil {
    49  		return err
    50  	}
    51  	p.logger.Debugf("Ingester listening to port %d", listener.Addr().(*net.TCPAddr).Port)
    52  
    53  	done := make(chan error)
    54  	go func() {
    55  		done <- http.Serve(listener, nil)
    56  	}()
    57  
    58  	// start command
    59  	c := make(chan os.Signal, 10)
    60  	// Note that we don't specify which signals to be sent: any signal to be
    61  	// relayed to the child process (including SIGINT and SIGTERM).
    62  	signal.Notify(c)
    63  	env := fmt.Sprintf("PYROSCOPE_ADHOC_SERVER_ADDRESS=http://localhost:%d", listener.Addr().(*net.TCPAddr).Port)
    64  	cmd := exec.Command(p.args[0], p.args[1:]...)
    65  	cmd.Env = append(os.Environ(), env)
    66  	cmd.Stderr = os.Stderr
    67  	cmd.Stdout = os.Stdout
    68  	cmd.Stdin = os.Stdin
    69  	if err := cmd.Start(); err != nil {
    70  		return err
    71  	}
    72  	defer func() {
    73  		signal.Stop(c)
    74  		close(c)
    75  	}()
    76  	ticker := time.NewTicker(time.Second)
    77  	defer ticker.Stop()
    78  	for {
    79  		select {
    80  		case s := <-c:
    81  			_ = process.SendSignal(cmd.Process, s)
    82  		case err := <-done:
    83  			return err
    84  		case <-ticker.C:
    85  			if !process.Exists(cmd.Process.Pid) {
    86  				logrus.Debug("child process exited")
    87  				return cmd.Wait()
    88  			}
    89  		}
    90  	}
    91  }