github.com/grafana/pyroscope@v1.18.0/pkg/pyroscope/modules.go (about)

     1  package pyroscope
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"slices"
    10  	"strings"
    11  	"time"
    12  
    13  	"connectrpc.com/connect"
    14  	"google.golang.org/genproto/googleapis/api/httpbody"
    15  	"google.golang.org/grpc/health/grpc_health_v1"
    16  	"google.golang.org/protobuf/encoding/protojson"
    17  	"gopkg.in/yaml.v3"
    18  
    19  	"github.com/go-kit/log"
    20  	"github.com/go-kit/log/level"
    21  	"github.com/grafana/dskit/dns"
    22  	"github.com/grafana/dskit/kv/codec"
    23  	"github.com/grafana/dskit/kv/memberlist"
    24  	"github.com/grafana/dskit/middleware"
    25  	"github.com/grafana/dskit/ring"
    26  	"github.com/grafana/dskit/runtimeconfig"
    27  	"github.com/grafana/dskit/server"
    28  	"github.com/grafana/dskit/services"
    29  	grpcgw "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    30  	"github.com/opentracing/opentracing-go"
    31  	"github.com/pkg/errors"
    32  	"github.com/prometheus/client_golang/prometheus"
    33  	"github.com/prometheus/client_golang/prometheus/collectors"
    34  	"github.com/prometheus/client_golang/prometheus/collectors/version"
    35  	objstoretracing "github.com/thanos-io/objstore/tracing/opentracing"
    36  	"golang.org/x/net/http2"
    37  	"golang.org/x/net/http2/h2c"
    38  
    39  	statusv1 "github.com/grafana/pyroscope/api/gen/proto/go/status/v1"
    40  	"github.com/grafana/pyroscope/pkg/adhocprofiles"
    41  	apiversion "github.com/grafana/pyroscope/pkg/api/version"
    42  	"github.com/grafana/pyroscope/pkg/compactor"
    43  	"github.com/grafana/pyroscope/pkg/distributor"
    44  	"github.com/grafana/pyroscope/pkg/embedded/grafana"
    45  	"github.com/grafana/pyroscope/pkg/featureflags"
    46  	"github.com/grafana/pyroscope/pkg/ingester"
    47  	objstoreclient "github.com/grafana/pyroscope/pkg/objstore/client"
    48  	"github.com/grafana/pyroscope/pkg/objstore/providers/filesystem"
    49  	"github.com/grafana/pyroscope/pkg/operations"
    50  	phlarecontext "github.com/grafana/pyroscope/pkg/pyroscope/context"
    51  	"github.com/grafana/pyroscope/pkg/querier"
    52  	"github.com/grafana/pyroscope/pkg/querier/worker"
    53  	"github.com/grafana/pyroscope/pkg/querybackend"
    54  	"github.com/grafana/pyroscope/pkg/scheduler"
    55  	"github.com/grafana/pyroscope/pkg/settings"
    56  	"github.com/grafana/pyroscope/pkg/storegateway"
    57  	"github.com/grafana/pyroscope/pkg/usagestats"
    58  	"github.com/grafana/pyroscope/pkg/util"
    59  	"github.com/grafana/pyroscope/pkg/util/build"
    60  	httputil "github.com/grafana/pyroscope/pkg/util/http"
    61  	"github.com/grafana/pyroscope/pkg/validation"
    62  	"github.com/grafana/pyroscope/pkg/validation/exporter"
    63  )
    64  
    65  // The various modules that make up Pyroscope.
    66  const (
    67  	All               string = "all"
    68  	API               string = "api"
    69  	Version           string = "version"
    70  	Distributor       string = "distributor"
    71  	Server            string = "server"
    72  	IngesterRing      string = "ring"
    73  	Ingester          string = "ingester"
    74  	MemberlistKV      string = "memberlist-kv"
    75  	Querier           string = "querier"
    76  	StoreGateway      string = "store-gateway"
    77  	GRPCGateway       string = "grpc-gateway"
    78  	Storage           string = "storage"
    79  	UsageReport       string = "usage-stats"
    80  	QueryFrontend     string = "query-frontend"
    81  	QueryScheduler    string = "query-scheduler"
    82  	RuntimeConfig     string = "runtime-config"
    83  	Overrides         string = "overrides"
    84  	OverridesExporter string = "overrides-exporter"
    85  	Compactor         string = "compactor"
    86  	Admin             string = "admin"
    87  	TenantSettings    string = "tenant-settings"
    88  	AdHocProfiles     string = "ad-hoc-profiles"
    89  	EmbeddedGrafana   string = "embedded-grafana"
    90  	FeatureFlags      string = "feature-flags"
    91  
    92  	// V2 modules.
    93  
    94  	Metastore            string = "metastore"
    95  	MetastoreClient      string = "metastore-client"
    96  	MetastoreAdmin       string = "metastore-admin"
    97  	SegmentWriter        string = "segment-writer"
    98  	SegmentWriterRing    string = "segment-writer-ring"
    99  	SegmentWriterClient  string = "segment-writer-client"
   100  	QueryBackend         string = "query-backend"
   101  	QueryBackendClient   string = "query-backend-client"
   102  	CompactionWorker     string = "compaction-worker"
   103  	PlacementAgent       string = "placement-agent"
   104  	PlacementManager     string = "placement-manager"
   105  	HealthServer         string = "health-server"
   106  	RecordingRulesClient string = "recording-rules-client"
   107  	Symbolizer           string = "symbolizer"
   108  )
   109  
   110  var objectStoreTypeStats = usagestats.NewString("store_object_type")
   111  
   112  func (f *Pyroscope) initRuntimeConfig() (services.Service, error) {
   113  	if len(f.Cfg.RuntimeConfig.LoadPath) == 0 {
   114  		// no need to initialize module if load path is empty
   115  		return nil, nil
   116  	}
   117  
   118  	f.Cfg.RuntimeConfig.Loader = func(r io.Reader) (interface{}, error) {
   119  		return validation.LoadRuntimeConfig(r)
   120  	}
   121  
   122  	// make sure to set default limits before we start loading configuration into memory
   123  	validation.SetDefaultLimitsForYAMLUnmarshalling(f.Cfg.LimitsConfig)
   124  
   125  	serv, err := runtimeconfig.New(
   126  		f.Cfg.RuntimeConfig,
   127  		"pyroscope",
   128  		prometheus.WrapRegistererWithPrefix("pyroscope_", f.reg),
   129  		log.With(f.logger, "component", "runtime-config"),
   130  	)
   131  	if err == nil {
   132  		// TenantLimits just delegates to RuntimeConfig and doesn't have any state or need to do
   133  		// anything in the start/stopping phase. Thus we can create it as part of runtime config
   134  		// setup without any service instance of its own.
   135  		f.TenantLimits = newTenantLimits(serv)
   136  	}
   137  
   138  	f.RuntimeConfig = serv
   139  	f.API.RegisterRuntimeConfig(
   140  		runtimeConfigHandler(f.RuntimeConfig, f.Cfg.LimitsConfig),
   141  		validation.TenantLimitsHandler(f.Cfg.LimitsConfig, f.TenantLimits),
   142  	)
   143  
   144  	return serv, err
   145  }
   146  
   147  func (f *Pyroscope) initTenantSettings() (services.Service, error) {
   148  	settings, err := settings.New(f.Cfg.TenantSettings, f.storageBucket, log.With(f.logger, "component", TenantSettings), f.Overrides)
   149  	if err != nil {
   150  		return nil, errors.Wrap(err, "failed to init settings service")
   151  	}
   152  
   153  	f.API.RegisterTenantSettings(settings)
   154  	return settings, nil
   155  }
   156  
   157  func (f *Pyroscope) initAdHocProfiles() (services.Service, error) {
   158  	if f.storageBucket == nil {
   159  		level.Warn(f.logger).Log("msg", "no storage bucket configured, ad hoc profiles will not be loaded")
   160  		return nil, nil
   161  	}
   162  
   163  	a := adhocprofiles.NewAdHocProfiles(f.storageBucket, f.logger, f.Overrides)
   164  	f.API.RegisterAdHocProfiles(a)
   165  	return a, nil
   166  }
   167  
   168  func (f *Pyroscope) initOverrides() (serv services.Service, err error) {
   169  	f.Overrides, err = validation.NewOverrides(f.Cfg.LimitsConfig, f.TenantLimits)
   170  	// overrides don't have operational state, nor do they need to do anything more in starting/stopping phase,
   171  	// so there is no need to return any service.
   172  	return nil, err
   173  }
   174  
   175  func (f *Pyroscope) initOverridesExporter() (services.Service, error) {
   176  	overridesExporter, err := exporter.NewOverridesExporter(
   177  		f.Cfg.OverridesExporter,
   178  		&f.Cfg.LimitsConfig,
   179  		f.TenantLimits,
   180  		log.With(f.logger, "component", "overrides-exporter"),
   181  		f.reg,
   182  	)
   183  	if err != nil {
   184  		return nil, errors.Wrap(err, "failed to instantiate overrides-exporter")
   185  	}
   186  	if f.reg != nil {
   187  		f.reg.MustRegister(overridesExporter)
   188  	}
   189  
   190  	f.API.RegisterOverridesExporter(overridesExporter)
   191  
   192  	return overridesExporter, nil
   193  }
   194  
   195  func (f *Pyroscope) initQueryScheduler() (services.Service, error) {
   196  	f.Cfg.QueryScheduler.ServiceDiscovery.SchedulerRing.ListenPort = f.Cfg.Server.HTTPListenPort
   197  
   198  	s, err := scheduler.NewScheduler(f.Cfg.QueryScheduler, f.Overrides, log.With(f.logger, "component", "scheduler"), f.reg)
   199  	if err != nil {
   200  		return nil, errors.Wrap(err, "query-scheduler init")
   201  	}
   202  
   203  	f.API.RegisterQueryScheduler(s)
   204  
   205  	return s, nil
   206  }
   207  
   208  func (f *Pyroscope) initCompactor() (serv services.Service, err error) {
   209  	f.Cfg.Compactor.ShardingRing.Common.ListenPort = f.Cfg.Server.HTTPListenPort
   210  
   211  	if f.storageBucket == nil {
   212  		return nil, nil
   213  	}
   214  
   215  	f.Compactor, err = compactor.NewMultitenantCompactor(
   216  		f.Cfg.Compactor,
   217  		f.storageBucket,
   218  		f.Overrides,
   219  		log.With(f.logger, "component", "compactor"),
   220  		f.reg,
   221  	)
   222  	if err != nil {
   223  		return
   224  	}
   225  
   226  	// Expose HTTP endpoints.
   227  	f.API.RegisterCompactor(f.Compactor)
   228  	return f.Compactor, nil
   229  }
   230  
   231  // setupWorkerTimeout sets the max loop duration for the querier worker and frontend worker
   232  // to 90% of the read or write http timeout, whichever is smaller.
   233  // This is to ensure that the worker doesn't timeout before the http handler and that the connection
   234  // is refreshed.
   235  func (f *Pyroscope) setupWorkerTimeout() {
   236  	timeout := f.Cfg.Server.HTTPServerReadTimeout
   237  	if f.Cfg.Server.HTTPServerWriteTimeout < timeout {
   238  		timeout = f.Cfg.Server.HTTPServerWriteTimeout
   239  	}
   240  
   241  	if timeout > 0 {
   242  		f.Cfg.Worker.MaxLoopDuration = time.Duration(float64(timeout) * 0.9)
   243  		f.Cfg.Frontend.MaxLoopDuration = time.Duration(float64(timeout) * 0.9)
   244  	}
   245  }
   246  
   247  func (f *Pyroscope) initQuerier() (services.Service, error) {
   248  	newQuerierParams := &querier.NewQuerierParams{
   249  		Cfg:             f.Cfg.Querier,
   250  		StoreGatewayCfg: f.Cfg.StoreGateway,
   251  		Overrides:       f.Overrides,
   252  		CfgProvider:     f.Overrides,
   253  		StorageBucket:   f.storageBucket,
   254  		IngestersRing:   f.ingesterRing,
   255  		Reg:             f.reg,
   256  		Logger:          log.With(f.logger, "component", "querier"),
   257  		ClientOptions:   []connect.ClientOption{f.auth},
   258  	}
   259  	querierSvc, err := querier.New(newQuerierParams)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  
   264  	if !f.isModuleActive(QueryFrontend) {
   265  		f.API.RegisterPyroscopeHandlers(querierSvc)
   266  		f.API.RegisterQuerierServiceHandler(querierSvc)
   267  	}
   268  
   269  	qWorker, err := worker.NewQuerierWorker(
   270  		f.Cfg.Worker,
   271  		querier.NewGRPCHandler(querierSvc, f.Cfg.SelfProfiling.UseK6Middleware),
   272  		log.With(f.logger, "component", "querier-worker"),
   273  		f.reg,
   274  	)
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	sm, err := services.NewManager(querierSvc, qWorker)
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  	w := services.NewFailureWatcher()
   284  	w.WatchManager(sm)
   285  
   286  	return services.NewBasicService(func(ctx context.Context) error {
   287  		err := sm.StartAsync(ctx)
   288  		if err != nil {
   289  			return err
   290  		}
   291  		return sm.AwaitHealthy(ctx)
   292  	}, func(ctx context.Context) error {
   293  		select {
   294  		case <-ctx.Done():
   295  			return nil
   296  		case err := <-w.Chan():
   297  			return err
   298  		}
   299  	}, func(failureCase error) error {
   300  		sm.StopAsync()
   301  		return sm.AwaitStopped(context.Background())
   302  	}), nil
   303  }
   304  
   305  func (f *Pyroscope) initGRPCGateway() (services.Service, error) {
   306  	f.grpcGatewayMux = grpcgw.NewServeMux(
   307  		grpcgw.WithMarshalerOption("application/json+pretty", &grpcgw.JSONPb{
   308  			MarshalOptions: protojson.MarshalOptions{
   309  				Indent:    "  ",
   310  				Multiline: true, // Optional, implied by presence of "Indent".
   311  			},
   312  			UnmarshalOptions: protojson.UnmarshalOptions{
   313  				DiscardUnknown: true,
   314  			},
   315  		}),
   316  	)
   317  	return nil, nil
   318  }
   319  
   320  func (f *Pyroscope) initDistributor() (services.Service, error) {
   321  	f.Cfg.Distributor.DistributorRing.ListenPort = f.Cfg.Server.HTTPListenPort
   322  	logger := log.With(f.logger, "component", "distributor")
   323  	d, err := distributor.New(f.Cfg.Distributor, f.ingesterRing, nil, f.Overrides, f.reg, logger, f.segmentWriterClient, f.auth)
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  	f.API.RegisterDistributor(d, f.Overrides, f.Cfg.Server)
   328  	return d, nil
   329  }
   330  
   331  func (f *Pyroscope) initMemberlistKV() (services.Service, error) {
   332  	f.Cfg.MemberlistKV.Codecs = []codec.Codec{
   333  		ring.GetCodec(),
   334  		usagestats.JSONCodec,
   335  		apiversion.GetCodec(),
   336  	}
   337  
   338  	dnsProviderReg := prometheus.WrapRegistererWithPrefix(
   339  		"pyroscope_",
   340  		prometheus.WrapRegistererWith(
   341  			prometheus.Labels{"name": "memberlist"},
   342  			f.reg,
   343  		),
   344  	)
   345  	dnsProvider := dns.NewProvider(f.logger, dnsProviderReg, dns.GolangResolverType)
   346  
   347  	f.MemberlistKV = memberlist.NewKVInitService(&f.Cfg.MemberlistKV, f.logger, dnsProvider, f.reg)
   348  
   349  	f.Cfg.Distributor.DistributorRing.KVStore.MemberlistKV = f.MemberlistKV.GetMemberlistKV
   350  	f.Cfg.Ingester.LifecyclerConfig.RingConfig.KVStore.MemberlistKV = f.MemberlistKV.GetMemberlistKV
   351  	f.Cfg.SegmentWriter.LifecyclerConfig.RingConfig.KVStore.MemberlistKV = f.MemberlistKV.GetMemberlistKV
   352  	f.Cfg.QueryScheduler.ServiceDiscovery.SchedulerRing.KVStore.MemberlistKV = f.MemberlistKV.GetMemberlistKV
   353  	f.Cfg.OverridesExporter.Ring.Ring.KVStore.MemberlistKV = f.MemberlistKV.GetMemberlistKV
   354  	f.Cfg.StoreGateway.ShardingRing.Ring.KVStore.MemberlistKV = f.MemberlistKV.GetMemberlistKV
   355  	f.Cfg.Compactor.ShardingRing.Common.KVStore.MemberlistKV = f.MemberlistKV.GetMemberlistKV
   356  	f.Cfg.Frontend.QuerySchedulerDiscovery = f.Cfg.QueryScheduler.ServiceDiscovery
   357  	f.Cfg.Worker.QuerySchedulerDiscovery = f.Cfg.QueryScheduler.ServiceDiscovery
   358  
   359  	f.API.RegisterMemberlistKV("", f.MemberlistKV)
   360  
   361  	return f.MemberlistKV, nil
   362  }
   363  
   364  func (f *Pyroscope) initIngesterRing() (_ services.Service, err error) {
   365  	f.ingesterRing, err = ring.New(
   366  		f.Cfg.Ingester.LifecyclerConfig.RingConfig,
   367  		"ingester",
   368  		"ring",
   369  		log.With(f.logger, "component", "ring"),
   370  		prometheus.WrapRegistererWithPrefix("pyroscope_", f.reg),
   371  	)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  	f.API.RegisterIngesterRing(f.ingesterRing)
   376  	return f.ingesterRing, nil
   377  }
   378  
   379  func (f *Pyroscope) initStorage() (_ services.Service, err error) {
   380  	objectStoreTypeStats.Set(f.Cfg.Storage.Bucket.Backend)
   381  	if cfg := f.Cfg.Storage.Bucket; cfg.Backend != objstoreclient.None {
   382  		if cfg.Backend == objstoreclient.Filesystem {
   383  			level.Warn(f.logger).
   384  				Log("msg", "when running with storage.backend 'filesystem' it is important that all replicas/components share the same filesystem")
   385  		}
   386  		b, err := objstoreclient.NewBucket(
   387  			f.context(),
   388  			cfg,
   389  			"storage",
   390  		)
   391  		if err != nil {
   392  			return nil, errors.Wrap(err, "unable to initialise bucket")
   393  		}
   394  		f.storageBucket = b
   395  	}
   396  
   397  	if !slices.Contains(f.Cfg.Target, All) && f.storageBucket == nil {
   398  		return nil, errors.New("storage bucket configuration is required when running in microservices mode")
   399  	}
   400  
   401  	return nil, nil
   402  }
   403  
   404  // TODO: This should be passed to all other services and could also be used to signal shutdown
   405  func (f *Pyroscope) context() context.Context {
   406  	phlarectx := phlarecontext.WithLogger(context.Background(), f.logger)
   407  	return phlarecontext.WithRegistry(phlarectx, f.reg)
   408  }
   409  
   410  func (f *Pyroscope) initIngester() (_ services.Service, err error) {
   411  	f.Cfg.Ingester.LifecyclerConfig.ListenPort = f.Cfg.Server.HTTPListenPort
   412  
   413  	svc, err := ingester.New(
   414  		f.context(),
   415  		f.Cfg.Ingester,
   416  		f.Cfg.PhlareDB,
   417  		f.storageBucket,
   418  		f.Overrides,
   419  		f.Cfg.Querier.QueryStoreAfter,
   420  	)
   421  	if err != nil {
   422  		return nil, err
   423  	}
   424  
   425  	f.API.RegisterIngester(svc)
   426  	f.ingester = svc
   427  
   428  	return svc, nil
   429  }
   430  
   431  func (f *Pyroscope) initStoreGateway() (serv services.Service, err error) {
   432  	f.Cfg.StoreGateway.ShardingRing.Ring.ListenPort = f.Cfg.Server.HTTPListenPort
   433  	if f.storageBucket == nil {
   434  		return nil, nil
   435  	}
   436  
   437  	svc, err := storegateway.NewStoreGateway(f.Cfg.StoreGateway, f.storageBucket, f.Overrides, f.logger, f.reg)
   438  	if err != nil {
   439  		return nil, err
   440  	}
   441  	f.API.RegisterStoreGateway(svc)
   442  	return svc, nil
   443  }
   444  
   445  var objstoreTracerMiddleware = middleware.Func(func(next http.Handler) http.Handler {
   446  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   447  		ctx := r.Context()
   448  		if tracer := opentracing.GlobalTracer(); tracer != nil {
   449  			ctx = objstoretracing.ContextWithTracer(ctx, opentracing.GlobalTracer())
   450  		}
   451  		next.ServeHTTP(w, r.WithContext(ctx))
   452  	})
   453  })
   454  
   455  func (f *Pyroscope) initServer() (services.Service, error) {
   456  	f.reg.MustRegister(version.NewCollector("pyroscope"))
   457  	f.reg.Unregister(collectors.NewGoCollector())
   458  	// register collector with additional metrics
   459  	f.reg.MustRegister(collectors.NewGoCollector(
   460  		collectors.WithGoCollectorRuntimeMetrics(collectors.MetricsAll),
   461  	))
   462  	DisableSignalHandling(&f.Cfg.Server)
   463  	f.Cfg.Server.Registerer = prometheus.WrapRegistererWithPrefix("pyroscope_", f.reg)
   464  	// Not all default middleware works with http2 so we'll add then manually.
   465  	// see https://github.com/grafana/pyroscope/issues/231
   466  	f.Cfg.Server.DoNotAddDefaultHTTPMiddleware = true
   467  	f.Cfg.Server.ExcludeRequestInLog = true // gRPC-specific.
   468  	f.Cfg.Server.GRPCMiddleware = append(f.Cfg.Server.GRPCMiddleware,
   469  		util.RecoveryInterceptorGRPC,
   470  		featureflags.ClientCapabilitiesGRPCMiddleware(),
   471  	)
   472  
   473  	if f.Cfg.V2 {
   474  		f.Cfg.Server.MetricsNativeHistogramFactor = 1.1 // 10% increase from bucket to bucket
   475  		if slices.Contains(f.Cfg.Target, QueryBackend) {
   476  			concurrencyInterceptor, err := querybackend.CreateConcurrencyInterceptor(f.logger)
   477  			if err != nil {
   478  				return nil, err
   479  			}
   480  			f.Cfg.Server.GRPCMiddleware = append(f.Cfg.Server.GRPCMiddleware, concurrencyInterceptor)
   481  		}
   482  	}
   483  
   484  	f.setupWorkerTimeout()
   485  	if f.isModuleActive(QueryScheduler) {
   486  		// to ensure that the query scheduler is always able to handle the request, we need to double the timeout
   487  		f.Cfg.Server.HTTPServerReadTimeout = 2 * f.Cfg.Server.HTTPServerReadTimeout
   488  		f.Cfg.Server.HTTPServerWriteTimeout = 2 * f.Cfg.Server.HTTPServerWriteTimeout
   489  	}
   490  	var err error
   491  	if f.Server, err = server.New(f.Cfg.Server); err != nil {
   492  		return nil, err
   493  	}
   494  
   495  	if f.healthServer != nil {
   496  		grpc_health_v1.RegisterHealthServer(f.Server.GRPC, f.healthServer)
   497  	}
   498  
   499  	servicesToWaitFor := func() []services.Service {
   500  		svs := []services.Service(nil)
   501  		for m, s := range f.serviceMap {
   502  			// Server should not wait for itself.
   503  			if m != Server {
   504  				svs = append(svs, s)
   505  			}
   506  		}
   507  		return svs
   508  	}
   509  
   510  	httpMetric, err := util.NewHTTPMetricMiddleware(f.Server.HTTP, f.Cfg.Server.MetricsNamespace, f.Cfg.Server.Registerer)
   511  	if err != nil {
   512  		return nil, err
   513  	}
   514  	defaultHTTPMiddleware := []middleware.Interface{
   515  		middleware.Tracer{},
   516  		// https://github.com/grafana/dskit/pull/527
   517  		middleware.RouteInjector{
   518  			RouteMatcher: f.Server.HTTP,
   519  		},
   520  		&util.Log{
   521  			Log:                      f.Server.Log,
   522  			LogRequestAtInfoLevel:    f.Cfg.Server.LogRequestAtInfoLevel,
   523  			LogRequestHeaders:        f.Cfg.Server.LogRequestHeaders,
   524  			LogRequestExcludeHeaders: strings.Split(f.Cfg.Server.LogRequestExcludeHeadersList, ","),
   525  		},
   526  		httpMetric,
   527  		objstoreTracerMiddleware,
   528  		httputil.K6Middleware(),
   529  		featureflags.ClientCapabilitiesHttpMiddleware(),
   530  	}
   531  	if f.Cfg.SelfProfiling.UseK6Middleware {
   532  		defaultHTTPMiddleware = append(defaultHTTPMiddleware, httputil.K6Middleware())
   533  	}
   534  
   535  	f.Server.HTTPServer.Handler = middleware.Merge(defaultHTTPMiddleware...).Wrap(f.Server.HTTP)
   536  
   537  	s := NewServerService(f.Server, servicesToWaitFor, f.logger)
   538  	// todo configure http2
   539  	f.Server.HTTPServer.Handler = h2c.NewHandler(f.Server.HTTPServer.Handler, &http2.Server{})
   540  	f.Server.HTTPServer.Handler = util.RecoveryHTTPMiddleware.Wrap(f.Server.HTTPServer.Handler)
   541  
   542  	return s, nil
   543  }
   544  
   545  func (f *Pyroscope) initUsageReport() (services.Service, error) {
   546  	if !f.Cfg.Analytics.Enabled {
   547  		return nil, nil
   548  	}
   549  	f.Cfg.Analytics.Leader = false
   550  	// ingester is the only component that can be a leader
   551  	if f.isModuleActive(Ingester) {
   552  		f.Cfg.Analytics.Leader = true
   553  	}
   554  
   555  	usagestats.Target(f.Cfg.Target.String())
   556  
   557  	b := f.storageBucket
   558  	if f.storageBucket == nil {
   559  		if err := os.MkdirAll(f.Cfg.PhlareDB.DataPath, 0o777); err != nil {
   560  			return nil, fmt.Errorf("mkdir %s: %w", f.Cfg.PhlareDB.DataPath, err)
   561  		}
   562  		fs, err := filesystem.NewBucket(f.Cfg.PhlareDB.DataPath)
   563  		if err != nil {
   564  			return nil, err
   565  		}
   566  		b = fs
   567  	}
   568  
   569  	if b == nil {
   570  		level.Warn(f.logger).Log("msg", "no storage bucket configured, usage report will not be sent")
   571  		return nil, nil
   572  	}
   573  
   574  	ur, err := usagestats.NewReporter(f.Cfg.Analytics, f.Cfg.Ingester.LifecyclerConfig.RingConfig.KVStore, b, f.logger, f.reg)
   575  	if err != nil {
   576  		level.Info(f.logger).Log("msg", "failed to initialize usage report", "err", err)
   577  		return nil, nil
   578  	}
   579  	f.usageReport = ur
   580  	return ur, nil
   581  }
   582  
   583  func (f *Pyroscope) initAdmin() (services.Service, error) {
   584  	if f.Cfg.V2 {
   585  		// For v2 storage, use metastore-based admin
   586  		if f.metastoreClient == nil {
   587  			level.Warn(f.logger).Log("msg", "v2 enabled but no metastore client configured, the admin component will not be loaded")
   588  			return nil, nil
   589  		}
   590  		return f.initAdminV2()
   591  	}
   592  
   593  	if f.storageBucket == nil {
   594  		level.Warn(f.logger).Log("msg", "no storage bucket configured, the admin component will not be loaded")
   595  		return nil, nil
   596  	}
   597  
   598  	a, err := operations.NewAdmin(f.storageBucket, f.logger, f.Cfg.PhlareDB.MaxBlockDuration)
   599  	if err != nil {
   600  		level.Info(f.logger).Log("msg", "failed to initialize admin", "err", err)
   601  		return nil, nil
   602  	}
   603  	f.admin = a
   604  	f.API.RegisterAdmin(a)
   605  	return a, nil
   606  }
   607  
   608  func (f *Pyroscope) initEmbeddedGrafana() (services.Service, error) {
   609  	return grafana.New(f.Cfg.EmbeddedGrafana, f.logger)
   610  }
   611  
   612  type statusService struct {
   613  	statusv1.UnimplementedStatusServiceServer
   614  	defaultConfig *Config
   615  	actualConfig  *Config
   616  }
   617  
   618  func (s *statusService) GetBuildInfo(
   619  	ctx context.Context,
   620  	req *statusv1.GetBuildInfoRequest,
   621  ) (*statusv1.GetBuildInfoResponse, error) {
   622  	version := build.GetVersion()
   623  	return &statusv1.GetBuildInfoResponse{
   624  		Status: "success",
   625  		Data: &statusv1.GetBuildInfoData{
   626  			Version:   version.Version,
   627  			Revision:  build.Revision,
   628  			Branch:    version.Branch,
   629  			GoVersion: version.GoVersion,
   630  		},
   631  	}, nil
   632  }
   633  
   634  const (
   635  	// There is not standardised and generally used content-type for YAML,
   636  	// text/plain ensures the YAML is displayed in the browser instead of
   637  	// offered as a download
   638  	yamlContentType = "text/plain; charset=utf-8"
   639  )
   640  
   641  func (s *statusService) GetConfig(ctx context.Context, req *statusv1.GetConfigRequest) (*httpbody.HttpBody, error) {
   642  	body, err := yaml.Marshal(s.actualConfig)
   643  	if err != nil {
   644  		return nil, err
   645  	}
   646  
   647  	return &httpbody.HttpBody{
   648  		ContentType: yamlContentType,
   649  		Data:        body,
   650  	}, nil
   651  }
   652  
   653  func (s *statusService) GetDefaultConfig(ctx context.Context, req *statusv1.GetConfigRequest) (*httpbody.HttpBody, error) {
   654  	body, err := yaml.Marshal(s.defaultConfig)
   655  	if err != nil {
   656  		return nil, err
   657  	}
   658  
   659  	return &httpbody.HttpBody{
   660  		ContentType: yamlContentType,
   661  		Data:        body,
   662  	}, nil
   663  }
   664  
   665  func (s *statusService) GetDiffConfig(ctx context.Context, req *statusv1.GetConfigRequest) (*httpbody.HttpBody, error) {
   666  	aBody, err := yaml.Marshal(s.actualConfig)
   667  	if err != nil {
   668  		return nil, err
   669  	}
   670  	aCfg := map[interface{}]interface{}{}
   671  	if err := yaml.Unmarshal(aBody, &aCfg); err != nil {
   672  		return nil, err
   673  	}
   674  
   675  	dBody, err := yaml.Marshal(s.defaultConfig)
   676  	if err != nil {
   677  		return nil, err
   678  	}
   679  	dCfg := map[interface{}]interface{}{}
   680  	if err := yaml.Unmarshal(dBody, &dCfg); err != nil {
   681  		return nil, err
   682  	}
   683  
   684  	diff, err := util.DiffConfig(dCfg, aCfg)
   685  	if err != nil {
   686  		return nil, err
   687  	}
   688  
   689  	body, err := yaml.Marshal(diff)
   690  	if err != nil {
   691  		return nil, err
   692  	}
   693  
   694  	return &httpbody.HttpBody{
   695  		ContentType: yamlContentType,
   696  		Data:        body,
   697  	}, nil
   698  }
   699  
   700  func (f *Pyroscope) statusService() statusv1.StatusServiceServer {
   701  	return &statusService{
   702  		actualConfig:  &f.Cfg,
   703  		defaultConfig: newDefaultConfig(),
   704  	}
   705  }
   706  
   707  func (f *Pyroscope) isModuleActive(m string) bool {
   708  	for _, target := range f.Cfg.Target {
   709  		if target == m {
   710  			return true
   711  		}
   712  		if f.recursiveIsModuleActive(target, m) {
   713  			return true
   714  		}
   715  	}
   716  	return false
   717  }
   718  
   719  func (f *Pyroscope) recursiveIsModuleActive(target, m string) bool {
   720  	if targetDeps, ok := f.deps[target]; ok {
   721  		for _, dep := range targetDeps {
   722  			if dep == m {
   723  				return true
   724  			}
   725  			if f.recursiveIsModuleActive(dep, m) {
   726  				return true
   727  			}
   728  		}
   729  	}
   730  	return false
   731  }
   732  
   733  // NewServerService constructs service from Server component.
   734  // servicesToWaitFor is called when server is stopping, and should return all
   735  // services that need to terminate before server actually stops.
   736  // N.B.: this function is NOT Cortex specific, please let's keep it that way.
   737  // Passed server should not react on signals. Early return from Run function is considered to be an error.
   738  func NewServerService(serv *server.Server, servicesToWaitFor func() []services.Service, log log.Logger) services.Service {
   739  	serverDone := make(chan error, 1)
   740  
   741  	runFn := func(ctx context.Context) error {
   742  		go func() {
   743  			defer close(serverDone)
   744  			serverDone <- serv.Run()
   745  		}()
   746  
   747  		select {
   748  		case <-ctx.Done():
   749  			return nil
   750  		case err := <-serverDone:
   751  			if err != nil {
   752  				return fmt.Errorf("server stopped unexpectedly: %w", err)
   753  			}
   754  			return nil
   755  		}
   756  	}
   757  
   758  	stoppingFn := func(_ error) error {
   759  		// wait until all modules are done, and then shutdown server.
   760  		for _, s := range servicesToWaitFor() {
   761  			_ = s.AwaitTerminated(context.Background())
   762  		}
   763  
   764  		// shutdown HTTP and gRPC servers (this also unblocks Run)
   765  		serv.Shutdown()
   766  
   767  		// if not closed yet, wait until server stops.
   768  		<-serverDone
   769  		level.Info(log).Log("msg", "server stopped")
   770  		return nil
   771  	}
   772  
   773  	return services.NewBasicService(nil, runFn, stoppingFn)
   774  }
   775  
   776  // DisableSignalHandling puts a dummy signal handler
   777  func DisableSignalHandling(config *server.Config) {
   778  	config.SignalHandler = make(ignoreSignalHandler)
   779  }
   780  
   781  type ignoreSignalHandler chan struct{}
   782  
   783  func (dh ignoreSignalHandler) Loop() {
   784  	<-dh
   785  }
   786  
   787  func (dh ignoreSignalHandler) Stop() {
   788  	close(dh)
   789  }