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

     1  package pyroscope
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"slices"
     7  	"time"
     8  
     9  	"github.com/go-kit/log"
    10  	"github.com/go-kit/log/level"
    11  	"github.com/grafana/dskit/middleware"
    12  	"github.com/grafana/dskit/netutil"
    13  	"github.com/grafana/dskit/ring"
    14  	"github.com/grafana/dskit/services"
    15  	otgrpc "github.com/opentracing-contrib/go-grpc"
    16  	"github.com/opentracing/opentracing-go"
    17  	"github.com/prometheus/client_golang/prometheus"
    18  	"google.golang.org/grpc"
    19  	grpchealth "google.golang.org/grpc/health"
    20  
    21  	"github.com/grafana/pyroscope/pkg/compactionworker"
    22  	"github.com/grafana/pyroscope/pkg/frontend"
    23  	"github.com/grafana/pyroscope/pkg/frontend/readpath"
    24  	"github.com/grafana/pyroscope/pkg/frontend/readpath/queryfrontend"
    25  	"github.com/grafana/pyroscope/pkg/frontend/vcs"
    26  	"github.com/grafana/pyroscope/pkg/metastore"
    27  	metastoreadmin "github.com/grafana/pyroscope/pkg/metastore/admin"
    28  	metastoreclient "github.com/grafana/pyroscope/pkg/metastore/client"
    29  	"github.com/grafana/pyroscope/pkg/metastore/discovery"
    30  	"github.com/grafana/pyroscope/pkg/metrics"
    31  	"github.com/grafana/pyroscope/pkg/objstore"
    32  	operationsv2 "github.com/grafana/pyroscope/pkg/operations/v2"
    33  	"github.com/grafana/pyroscope/pkg/querybackend"
    34  	querybackendclient "github.com/grafana/pyroscope/pkg/querybackend/client"
    35  	"github.com/grafana/pyroscope/pkg/segmentwriter"
    36  	segmentwriterclient "github.com/grafana/pyroscope/pkg/segmentwriter/client"
    37  	placement "github.com/grafana/pyroscope/pkg/segmentwriter/client/distributor/placement/adaptiveplacement"
    38  	recordingrulesclient "github.com/grafana/pyroscope/pkg/settings/recording/client"
    39  	"github.com/grafana/pyroscope/pkg/symbolizer"
    40  	"github.com/grafana/pyroscope/pkg/tenant"
    41  	"github.com/grafana/pyroscope/pkg/util"
    42  	"github.com/grafana/pyroscope/pkg/util/health"
    43  	"github.com/grafana/pyroscope/pkg/util/spanlogger"
    44  )
    45  
    46  func (f *Pyroscope) initQueryFrontend() (services.Service, error) {
    47  	var err error
    48  	if f.Cfg.Frontend.Addr, err = f.getFrontendAddress(); err != nil {
    49  		return nil, fmt.Errorf("failed to get frontend address: %w", err)
    50  	}
    51  	if f.Cfg.Frontend.Port == 0 {
    52  		f.Cfg.Frontend.Port = f.Cfg.Server.HTTPListenPort
    53  	}
    54  	if !f.Cfg.V2 {
    55  		return f.initQueryFrontendV1()
    56  	}
    57  	// If the new read path is enabled globally by default,
    58  	// the old query frontend is not used. Tenant-specific overrides
    59  	// are ignored — all tenants use the new read path.
    60  	//
    61  	// If the old read path is still in use, we configure the router
    62  	// to use both the old and new query frontends.
    63  	c := f.Overrides.ReadPathOverrides(tenant.DefaultTenantID)
    64  	switch {
    65  	case !c.EnableQueryBackend:
    66  		return f.initQueryFrontendV1()
    67  	case c.EnableQueryBackend && c.EnableQueryBackendFrom.IsZero():
    68  		return f.initQueryFrontendV2()
    69  	case c.EnableQueryBackend && !c.EnableQueryBackendFrom.IsZero():
    70  		return f.initQueryFrontendV12()
    71  	default:
    72  		return nil, fmt.Errorf("invalid query backend configuration: %v", c)
    73  	}
    74  }
    75  
    76  func (f *Pyroscope) initQueryFrontendV1() (services.Service, error) {
    77  	queryFrontendLogger := log.With(f.logger, "component", "frontend")
    78  	var err error
    79  	f.frontend, err = frontend.NewFrontend(f.Cfg.Frontend, f.Overrides, queryFrontendLogger, f.reg)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	f.API.RegisterFrontendForQuerierHandler(f.frontend)
    84  	f.API.RegisterQuerierServiceHandler(spanlogger.NewLogSpanParametersWrapper(f.frontend, queryFrontendLogger))
    85  	f.API.RegisterPyroscopeHandlers(spanlogger.NewLogSpanParametersWrapper(f.frontend, queryFrontendLogger))
    86  	f.API.RegisterVCSServiceHandler(f.frontend)
    87  	return f.frontend, nil
    88  }
    89  
    90  func (f *Pyroscope) initQueryFrontendV2() (services.Service, error) {
    91  	queryFrontendLogger := log.With(f.logger, "component", "query-frontend")
    92  	queryFrontend := queryfrontend.NewQueryFrontend(
    93  		queryFrontendLogger,
    94  		f.Overrides,
    95  		f.metastoreClient,
    96  		f.metastoreClient,
    97  		f.queryBackendClient,
    98  		f.symbolizer,
    99  	)
   100  
   101  	vcsService := vcs.New(
   102  		log.With(f.logger, "component", "vcs-service"),
   103  		f.reg,
   104  	)
   105  
   106  	f.API.RegisterQuerierServiceHandler(spanlogger.NewLogSpanParametersWrapper(queryFrontend, queryFrontendLogger))
   107  	f.API.RegisterPyroscopeHandlers(spanlogger.NewLogSpanParametersWrapper(queryFrontend, queryFrontendLogger))
   108  	f.API.RegisterVCSServiceHandler(vcsService)
   109  
   110  	// New query frontend does not have any state.
   111  	// For simplicity, we return a no-op service.
   112  	svc := services.NewIdleService(
   113  		func(context.Context) error { return nil },
   114  		func(error) error { return nil },
   115  	)
   116  
   117  	return svc, nil
   118  }
   119  
   120  func (f *Pyroscope) initQueryFrontendV12() (services.Service, error) {
   121  	var err error
   122  	f.frontend, err = frontend.NewFrontend(f.Cfg.Frontend, f.Overrides, log.With(f.logger, "component", "frontend"), f.reg)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	queryFrontendLogger := log.With(f.logger, "component", "query-frontend")
   128  	newFrontend := queryfrontend.NewQueryFrontend(
   129  		queryFrontendLogger,
   130  		f.Overrides,
   131  		f.metastoreClient,
   132  		f.metastoreClient,
   133  		f.queryBackendClient,
   134  		f.symbolizer,
   135  	)
   136  
   137  	handler := readpath.NewRouter(
   138  		log.With(f.logger, "component", "read-path-router"),
   139  		f.Overrides,
   140  		f.frontend,
   141  		newFrontend,
   142  	)
   143  
   144  	vcsService := vcs.New(
   145  		log.With(f.logger, "component", "vcs-service"),
   146  		f.reg,
   147  	)
   148  
   149  	f.API.RegisterFrontendForQuerierHandler(f.frontend)
   150  	f.API.RegisterQuerierServiceHandler(spanlogger.NewLogSpanParametersWrapper(handler, queryFrontendLogger))
   151  	f.API.RegisterPyroscopeHandlers(spanlogger.NewLogSpanParametersWrapper(handler, queryFrontendLogger))
   152  	f.API.RegisterVCSServiceHandler(vcsService)
   153  
   154  	return f.frontend, nil
   155  }
   156  
   157  func (f *Pyroscope) getFrontendAddress() (addr string, err error) {
   158  	addr = f.Cfg.Frontend.Addr
   159  	if f.Cfg.Frontend.AddrOld != "" {
   160  		addr = f.Cfg.Frontend.AddrOld
   161  	}
   162  	if addr != "" {
   163  		return addr, nil
   164  	}
   165  	return netutil.GetFirstAddressOf(f.Cfg.Frontend.InfNames, f.logger, f.Cfg.Frontend.EnableIPv6)
   166  }
   167  
   168  func (f *Pyroscope) initSegmentWriterRing() (_ services.Service, err error) {
   169  	if err = f.Cfg.SegmentWriter.Validate(); err != nil {
   170  		return nil, err
   171  	}
   172  	logger := log.With(f.logger, "component", "segment-writer-ring")
   173  	reg := prometheus.WrapRegistererWithPrefix("pyroscope_", f.reg)
   174  	f.segmentWriterRing, err = ring.New(
   175  		f.Cfg.SegmentWriter.LifecyclerConfig.RingConfig,
   176  		segmentwriter.RingName,
   177  		segmentwriter.RingKey,
   178  		logger, reg,
   179  	)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	f.API.RegisterSegmentWriterRing(f.segmentWriterRing)
   184  	return f.segmentWriterRing, nil
   185  }
   186  
   187  func (f *Pyroscope) initSegmentWriter() (services.Service, error) {
   188  	f.Cfg.SegmentWriter.LifecyclerConfig.ListenPort = f.Cfg.Server.GRPCListenPort
   189  	if err := f.Cfg.SegmentWriter.Validate(); err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	logger := log.With(f.logger, "component", "segment-writer")
   194  	healthService := health.NewGRPCHealthService(f.healthServer, logger, "pyroscope.segment-writer")
   195  	segmentWriter, err := segmentwriter.New(
   196  		f.reg,
   197  		logger,
   198  		f.Cfg.SegmentWriter,
   199  		f.Overrides,
   200  		healthService,
   201  		f.storageBucket,
   202  		f.metastoreClient,
   203  	)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	f.segmentWriter = segmentWriter
   209  	f.API.RegisterSegmentWriter(segmentWriter)
   210  	return f.segmentWriter, nil
   211  }
   212  
   213  func (f *Pyroscope) initSegmentWriterClient() (_ services.Service, err error) {
   214  	f.Cfg.SegmentWriter.GRPCClientConfig.Middleware = f.grpcClientInterceptors()
   215  	// Validation of the config is not required since
   216  	// it's already validated in initSegmentWriterRing.
   217  	logger := log.With(f.logger, "component", "segment-writer-client")
   218  	placement := f.placementAgent.Placement()
   219  	client, err := segmentwriterclient.NewSegmentWriterClient(
   220  		f.Cfg.SegmentWriter.GRPCClientConfig,
   221  		logger, f.reg,
   222  		f.segmentWriterRing,
   223  		placement,
   224  	)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	f.segmentWriterClient = client
   229  	return client.Service(), nil
   230  }
   231  
   232  func (f *Pyroscope) initCompactionWorker() (svc services.Service, err error) {
   233  	logger := log.With(f.logger, "component", "compaction-worker")
   234  	registerer := prometheus.WrapRegistererWithPrefix("pyroscope_compaction_worker_", f.reg)
   235  
   236  	var ruler metrics.Ruler
   237  	var exporter metrics.Exporter
   238  	if f.Cfg.CompactionWorker.MetricsExporter.Enabled {
   239  		if f.recordingRulesClient != nil {
   240  			ruler, err = metrics.NewCachedRemoteRuler(f.recordingRulesClient, f.logger)
   241  			if err != nil {
   242  				return nil, err
   243  			}
   244  		} else {
   245  			ruler = metrics.NewStaticRulerFromOverrides(f.Overrides)
   246  		}
   247  
   248  		exporter, err = metrics.NewExporter(f.Cfg.CompactionWorker.MetricsExporter.RemoteWriteAddress, f.logger, f.reg)
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  	}
   253  
   254  	w, err := compactionworker.New(
   255  		logger,
   256  		f.Cfg.CompactionWorker,
   257  		f.metastoreClient,
   258  		f.storageBucket,
   259  		registerer,
   260  		ruler,
   261  		exporter,
   262  	)
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	f.compactionWorker = w
   267  	return w.Service(), nil
   268  }
   269  
   270  func (f *Pyroscope) initMetastore() (services.Service, error) {
   271  	if err := f.Cfg.Metastore.Validate(); err != nil {
   272  		return nil, err
   273  	}
   274  
   275  	logger := log.With(f.logger, "component", "metastore")
   276  	healthService := health.NewGRPCHealthService(f.healthServer, logger, "pyroscope.metastore")
   277  	registerer := prometheus.WrapRegistererWithPrefix("pyroscope_metastore_", f.reg)
   278  	m, err := metastore.New(
   279  		f.Cfg.Metastore,
   280  		f.Overrides,
   281  		logger,
   282  		registerer,
   283  		healthService,
   284  		f.metastoreClient,
   285  		f.storageBucket,
   286  		f.placementManager,
   287  	)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	m.Register(f.Server.GRPC)
   293  	f.metastore = m
   294  	return m.Service(), nil
   295  }
   296  
   297  func (f *Pyroscope) initMetastoreClient() (services.Service, error) {
   298  	if err := f.Cfg.Metastore.Validate(); err != nil {
   299  		return nil, err
   300  	}
   301  
   302  	disc, err := discovery.NewDiscovery(f.logger, f.Cfg.Metastore.Address, f.reg)
   303  	if err != nil {
   304  		return nil, fmt.Errorf("failed to create discovery: %w %s", err, f.Cfg.Metastore.Address)
   305  	}
   306  
   307  	f.Cfg.Metastore.GRPCClientConfig.Middleware = f.grpcClientInterceptors()
   308  	f.metastoreClient = metastoreclient.New(
   309  		f.logger,
   310  		f.Cfg.Metastore.GRPCClientConfig,
   311  		disc,
   312  	)
   313  	return f.metastoreClient.Service(), nil
   314  }
   315  
   316  func (f *Pyroscope) initMetastoreAdmin() (services.Service, error) {
   317  	level.Info(f.logger).Log("msg", "initializing metastore admin")
   318  	if err := f.Cfg.Metastore.Validate(); err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	var err error
   323  	f.metastoreAdmin, err = metastoreadmin.New(f.metastoreClient, f.logger, f.Cfg.Metastore.Address, f.metastoreClient)
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  	level.Info(f.logger).Log("msg", "registering metastore admin routes")
   328  	f.API.RegisterMetastoreAdmin(f.metastoreAdmin)
   329  	return f.metastoreAdmin.Service(), nil
   330  }
   331  
   332  func (f *Pyroscope) initAdminV2() (services.Service, error) {
   333  	level.Info(f.logger).Log("msg", "initializing v2 admin (metastore-based)")
   334  
   335  	a, err := operationsv2.NewAdmin(f.metastoreClient, f.storageBucket, f.logger)
   336  	if err != nil {
   337  		level.Info(f.logger).Log("msg", "failed to initialize v2 admin", "err", err)
   338  		return nil, nil
   339  	}
   340  	f.admin = a
   341  	f.API.RegisterAdmin(a)
   342  	return a, nil
   343  }
   344  
   345  func (f *Pyroscope) initQueryBackend() (services.Service, error) {
   346  	if err := f.Cfg.QueryBackend.Validate(); err != nil {
   347  		return nil, err
   348  	}
   349  	logger := log.With(f.logger, "component", "query-backend")
   350  	b, err := querybackend.New(
   351  		f.Cfg.QueryBackend,
   352  		logger,
   353  		f.reg,
   354  		f.queryBackendClient,
   355  		querybackend.NewBlockReader(f.logger, f.storageBucket, f.reg),
   356  	)
   357  	if err != nil {
   358  		return nil, err
   359  	}
   360  	f.API.RegisterQueryBackend(b)
   361  	return b.Service(), nil
   362  }
   363  
   364  func (f *Pyroscope) initQueryBackendClient() (services.Service, error) {
   365  	if err := f.Cfg.QueryBackend.Validate(); err != nil {
   366  		return nil, err
   367  	}
   368  	f.Cfg.QueryBackend.GRPCClientConfig.Middleware = f.grpcClientInterceptors()
   369  	c, err := querybackendclient.New(
   370  		f.Cfg.QueryBackend.Address,
   371  		f.Cfg.QueryBackend.GRPCClientConfig,
   372  		f.Cfg.QueryBackend.ClientTimeout,
   373  	)
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  	f.queryBackendClient = c
   378  	return c.Service(), nil
   379  }
   380  
   381  func (f *Pyroscope) initRecordingRulesClient() (services.Service, error) {
   382  	if err := f.Cfg.CompactionWorker.MetricsExporter.Validate(); err != nil {
   383  		return nil, err
   384  	}
   385  	if !f.Cfg.CompactionWorker.MetricsExporter.Enabled ||
   386  		f.Cfg.CompactionWorker.MetricsExporter.RulesSource.ClientAddress == "" {
   387  		return nil, nil
   388  	}
   389  	c, err := recordingrulesclient.NewClient(f.Cfg.CompactionWorker.MetricsExporter.RulesSource.ClientAddress, f.logger, f.auth)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	f.recordingRulesClient = c
   394  	return c.Service(), nil
   395  }
   396  
   397  func (f *Pyroscope) initSymbolizer() (services.Service, error) {
   398  	prefixedBucket := objstore.NewPrefixedBucket(f.storageBucket, "symbolizer")
   399  
   400  	sym, err := symbolizer.New(
   401  		f.logger,
   402  		f.Cfg.Symbolizer,
   403  		f.reg,
   404  		prefixedBucket,
   405  		f.Overrides,
   406  	)
   407  	if err != nil {
   408  		return nil, fmt.Errorf("failed to create symbolizer: %w", err)
   409  	}
   410  
   411  	f.symbolizer = sym
   412  
   413  	return nil, nil
   414  }
   415  
   416  func (f *Pyroscope) initPlacementAgent() (services.Service, error) {
   417  	f.placementAgent = placement.NewAgent(
   418  		f.logger,
   419  		f.reg,
   420  		f.Cfg.AdaptivePlacement,
   421  		f.Overrides,
   422  		f.adaptivePlacementStore(),
   423  	)
   424  	return f.placementAgent.Service(), nil
   425  }
   426  
   427  func (f *Pyroscope) initPlacementManager() (services.Service, error) {
   428  	f.placementManager = placement.NewManager(
   429  		f.logger,
   430  		f.reg,
   431  		f.Cfg.AdaptivePlacement,
   432  		f.Overrides,
   433  		f.adaptivePlacementStore(),
   434  	)
   435  	return f.placementManager.Service(), nil
   436  }
   437  
   438  func (f *Pyroscope) adaptivePlacementStore() placement.Store {
   439  	if slices.Contains(f.Cfg.Target, All) {
   440  		// Disables sharding in all-in-one scenario.
   441  		return placement.NewEmptyStore()
   442  	}
   443  	return placement.NewStore(f.storageBucket)
   444  }
   445  
   446  func (f *Pyroscope) initHealthServer() (services.Service, error) {
   447  	f.healthServer = grpchealth.NewServer()
   448  	return nil, nil
   449  }
   450  
   451  func (f *Pyroscope) grpcClientInterceptors() []grpc.UnaryClientInterceptor {
   452  	requestDuration := util.RegisterOrGet(f.reg, prometheus.NewHistogramVec(prometheus.HistogramOpts{
   453  		Namespace:                       "pyroscope",
   454  		Subsystem:                       "grpc_client",
   455  		Name:                            "request_duration_seconds",
   456  		Help:                            "Time (in seconds) spent waiting for gRPC response.",
   457  		Buckets:                         prometheus.DefBuckets,
   458  		NativeHistogramBucketFactor:     1.1,
   459  		NativeHistogramMaxBucketNumber:  50,
   460  		NativeHistogramMinResetDuration: time.Hour,
   461  	}, []string{"method", "status_code"}))
   462  
   463  	return []grpc.UnaryClientInterceptor{
   464  		middleware.UnaryClientInstrumentInterceptor(requestDuration, middleware.ReportGRPCStatusOption),
   465  		otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer()),
   466  	}
   467  }