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 }