github.com/grafana/pyroscope@v1.18.0/pkg/pyroscope/pyroscope.go (about) 1 package pyroscope 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "flag" 8 "fmt" 9 "io" 10 "net/http" 11 "os" 12 "runtime" 13 "runtime/debug" 14 "slices" 15 "sort" 16 "strings" 17 "time" 18 19 "connectrpc.com/connect" 20 "github.com/go-kit/log" 21 "github.com/go-kit/log/level" 22 "github.com/gorilla/mux" 23 "github.com/grafana/dskit/flagext" 24 "github.com/grafana/dskit/grpcutil" 25 "github.com/grafana/dskit/kv/memberlist" 26 dslog "github.com/grafana/dskit/log" 27 "github.com/grafana/dskit/modules" 28 "github.com/grafana/dskit/ring" 29 "github.com/grafana/dskit/runtimeconfig" 30 "github.com/grafana/dskit/server" 31 "github.com/grafana/dskit/services" 32 "github.com/grafana/dskit/signals" 33 "github.com/grafana/dskit/spanlogger" 34 "github.com/grafana/dskit/spanprofiler" 35 wwtracing "github.com/grafana/dskit/tracing" 36 "github.com/grafana/pyroscope-go" 37 grpcgw "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 38 "github.com/opentracing/opentracing-go" 39 "github.com/prometheus/client_golang/prometheus" 40 "github.com/prometheus/common/version" 41 "github.com/samber/lo" 42 "google.golang.org/grpc/health" 43 44 "github.com/grafana/pyroscope/pkg/api" 45 apiversion "github.com/grafana/pyroscope/pkg/api/version" 46 "github.com/grafana/pyroscope/pkg/cfg" 47 "github.com/grafana/pyroscope/pkg/compactionworker" 48 "github.com/grafana/pyroscope/pkg/compactor" 49 "github.com/grafana/pyroscope/pkg/distributor" 50 "github.com/grafana/pyroscope/pkg/embedded/grafana" 51 "github.com/grafana/pyroscope/pkg/frontend" 52 "github.com/grafana/pyroscope/pkg/ingester" 53 "github.com/grafana/pyroscope/pkg/metastore" 54 metastoreadmin "github.com/grafana/pyroscope/pkg/metastore/admin" 55 metastoreclient "github.com/grafana/pyroscope/pkg/metastore/client" 56 phlareobj "github.com/grafana/pyroscope/pkg/objstore" 57 objstoreclient "github.com/grafana/pyroscope/pkg/objstore/client" 58 "github.com/grafana/pyroscope/pkg/phlaredb" 59 "github.com/grafana/pyroscope/pkg/querier" 60 "github.com/grafana/pyroscope/pkg/querier/worker" 61 "github.com/grafana/pyroscope/pkg/querybackend" 62 querybackendclient "github.com/grafana/pyroscope/pkg/querybackend/client" 63 "github.com/grafana/pyroscope/pkg/scheduler" 64 "github.com/grafana/pyroscope/pkg/scheduler/schedulerdiscovery" 65 "github.com/grafana/pyroscope/pkg/segmentwriter" 66 segmentwriterclient "github.com/grafana/pyroscope/pkg/segmentwriter/client" 67 placement "github.com/grafana/pyroscope/pkg/segmentwriter/client/distributor/placement/adaptiveplacement" 68 "github.com/grafana/pyroscope/pkg/settings" 69 recordingrulesclient "github.com/grafana/pyroscope/pkg/settings/recording/client" 70 "github.com/grafana/pyroscope/pkg/storegateway" 71 "github.com/grafana/pyroscope/pkg/symbolizer" 72 "github.com/grafana/pyroscope/pkg/tenant" 73 "github.com/grafana/pyroscope/pkg/tracing" 74 "github.com/grafana/pyroscope/pkg/usagestats" 75 "github.com/grafana/pyroscope/pkg/util" 76 "github.com/grafana/pyroscope/pkg/util/cli" 77 "github.com/grafana/pyroscope/pkg/validation" 78 "github.com/grafana/pyroscope/pkg/validation/exporter" 79 ) 80 81 type Config struct { 82 Target flagext.StringSliceCSV `yaml:"target,omitempty"` 83 API api.Config `yaml:"api"` 84 Server server.Config `yaml:"server,omitempty"` 85 Distributor distributor.Config `yaml:"distributor,omitempty"` 86 Querier querier.Config `yaml:"querier,omitempty"` 87 Frontend frontend.Config `yaml:"frontend,omitempty"` 88 Worker worker.Config `yaml:"frontend_worker"` 89 LimitsConfig validation.Limits `yaml:"limits"` 90 QueryScheduler scheduler.Config `yaml:"query_scheduler"` 91 Ingester ingester.Config `yaml:"ingester,omitempty"` 92 StoreGateway storegateway.Config `yaml:"store_gateway,omitempty"` 93 MemberlistKV memberlist.KVConfig `yaml:"memberlist"` 94 PhlareDB phlaredb.Config `yaml:"pyroscopedb,omitempty"` 95 Tracing tracing.Config `yaml:"tracing"` 96 OverridesExporter exporter.Config `yaml:"overrides_exporter" doc:"hidden"` 97 RuntimeConfig runtimeconfig.Config `yaml:"runtime_config"` 98 Compactor compactor.Config `yaml:"compactor"` 99 TenantSettings settings.Config `yaml:"tenant_settings"` 100 101 Storage StorageConfig `yaml:"storage"` 102 SelfProfiling SelfProfilingConfig `yaml:"self_profiling,omitempty"` 103 104 MultitenancyEnabled bool `yaml:"multitenancy_enabled,omitempty"` 105 Analytics usagestats.Config `yaml:"analytics"` 106 ShowBanner bool `yaml:"show_banner,omitempty"` 107 ShutdownDelay time.Duration `yaml:"shutdown_delay,omitempty"` 108 109 EmbeddedGrafana grafana.Config `yaml:"embedded_grafana,omitempty"` 110 111 ConfigFile string `yaml:"-"` 112 ConfigExpandEnv bool `yaml:"-"` 113 114 V2 bool `yaml:"-" doc:"hidden"` 115 SegmentWriter segmentwriter.Config `yaml:"segment_writer" doc:"hidden"` 116 Metastore metastore.Config `yaml:"metastore" doc:"hidden"` 117 QueryBackend querybackend.Config `yaml:"query_backend" doc:"hidden"` 118 CompactionWorker compactionworker.Config `yaml:"compaction_worker" doc:"hidden"` 119 AdaptivePlacement placement.Config `yaml:"adaptive_placement" doc:"hidden"` 120 Symbolizer symbolizer.Config `yaml:"symbolizer" doc:"hidden"` 121 } 122 123 func newDefaultConfig() *Config { 124 defaultConfig := &Config{} 125 defaultFS := flag.NewFlagSet("", flag.PanicOnError) 126 defaultConfig.RegisterFlags(defaultFS) 127 return defaultConfig 128 } 129 130 type StorageConfig struct { 131 Bucket objstoreclient.Config `yaml:",inline"` 132 } 133 134 func (c *StorageConfig) RegisterFlags(f *flag.FlagSet) { 135 c.Bucket.RegisterFlagsWithPrefix("storage.", f) 136 } 137 138 type SelfProfilingConfig struct { 139 DisablePush bool `yaml:"disable_push,omitempty"` 140 MutexProfileFraction int `yaml:"mutex_profile_fraction,omitempty"` 141 BlockProfileRate int `yaml:"block_profile_rate,omitempty"` 142 UseK6Middleware bool `yaml:"use_k6_middleware,omitempty"` 143 } 144 145 func (c *SelfProfilingConfig) RegisterFlags(f *flag.FlagSet) { 146 // these are values that worked well in OG Pyroscope Cloud without adding much overhead 147 f.IntVar(&c.MutexProfileFraction, "self-profiling.mutex-profile-fraction", 5, "") 148 f.IntVar(&c.BlockProfileRate, "self-profiling.block-profile-rate", 5, "") 149 f.BoolVar( 150 &c.DisablePush, 151 "self-profiling.disable-push", 152 false, 153 "When running in single binary (--target=all) Pyroscope will push (Go SDK) profiles to itself. Set to true to disable self-profiling.", 154 ) 155 f.BoolVar( 156 &c.UseK6Middleware, 157 "self-profiling.use-k6-middleware", 158 false, 159 "Read k6 labels from request headers and set them as dynamic profile tags.", 160 ) 161 } 162 163 func (c *Config) RegisterFlags(f *flag.FlagSet) { 164 c.RegisterFlagsWithContext(f) 165 } 166 167 // RegisterFlagsWithContext registers flag. 168 func (c *Config) RegisterFlagsWithContext(f *flag.FlagSet) { 169 // Set the default module list to 'all' 170 c.Target = []string{All} 171 f.StringVar(&c.ConfigFile, "config.file", "", "yaml file to load") 172 f.Var(&c.Target, "target", "Comma-separated list of Pyroscope modules to load. "+ 173 "The alias 'all' can be used in the list to load a number of core modules and will enable single-binary mode. ") 174 f.BoolVar( 175 &c.MultitenancyEnabled, 176 "auth.multitenancy-enabled", 177 false, 178 "When set to true, incoming HTTP requests must specify tenant ID in HTTP X-Scope-OrgId header. When set to false, tenant ID anonymous is used instead.", 179 ) 180 f.BoolVar( 181 &c.ConfigExpandEnv, 182 "config.expand-env", 183 false, 184 "Expands ${var} in config according to the values of the environment variables.", 185 ) 186 f.BoolVar(&c.ShowBanner, "config.show_banner", true, "Prints the application banner at startup.") 187 f.DurationVar(&c.ShutdownDelay, "shutdown-delay", 0, "Wait time before shutting down after a termination signal.") 188 189 c.registerServerFlagsWithChangedDefaultValues(f) 190 c.MemberlistKV.RegisterFlags(f) 191 c.Querier.RegisterFlags(f) 192 c.StoreGateway.RegisterFlags(f, util.Logger) 193 c.PhlareDB.RegisterFlags(f) 194 c.Tracing.RegisterFlags(f) 195 c.SelfProfiling.RegisterFlags(f) 196 c.RuntimeConfig.RegisterFlags(f) 197 c.Analytics.RegisterFlags(f) 198 c.LimitsConfig.RegisterFlags(f) 199 c.Compactor.RegisterFlags(f, log.NewLogfmtLogger(os.Stderr)) 200 c.API.RegisterFlags(f) 201 c.EmbeddedGrafana.RegisterFlags(f) 202 c.TenantSettings.RegisterFlags(f) 203 } 204 205 // registerServerFlagsWithChangedDefaultValues registers *Config.Server flags, but overrides some defaults set by the dskit package. 206 func (c *Config) registerServerFlagsWithChangedDefaultValues(fs *flag.FlagSet) { 207 throwaway := flag.NewFlagSet("throwaway", flag.PanicOnError) 208 209 // Register to throwaway flags first. Default values are remembered during registration and cannot be changed, 210 // but we can take values from throwaway flag set and reregister into supplied flags with new default values. 211 c.Storage.RegisterFlags(throwaway) 212 c.Server.RegisterFlags(throwaway) 213 c.Ingester.RegisterFlags(throwaway) 214 c.Distributor.RegisterFlags(throwaway, log.NewLogfmtLogger(os.Stderr)) 215 c.Frontend.RegisterFlags(throwaway, log.NewLogfmtLogger(os.Stderr)) 216 c.QueryScheduler.RegisterFlags(throwaway, log.NewLogfmtLogger(os.Stderr)) 217 c.Worker.RegisterFlags(throwaway) 218 c.OverridesExporter.RegisterFlags(throwaway, log.NewLogfmtLogger(os.Stderr)) 219 220 overrides := map[string]string{ 221 "server.http-listen-port": "4040", 222 "distributor.replication-factor": "1", 223 "query-scheduler.service-discovery-mode": schedulerdiscovery.ModeRing, 224 } 225 226 if c.V2 { 227 for k, v := range map[string]string{ 228 "server.grpc-max-recv-msg-size-bytes": "104857600", 229 "server.grpc-max-send-msg-size-bytes": "104857600", 230 "server.grpc.keepalive.min-time-between-pings": "1s", 231 "segment-writer.grpc-client-config.connect-timeout": "1s", 232 "segment-writer.num-tokens": "4", 233 "segment-writer.heartbeat-timeout": "1m", 234 "segment-writer.unregister-on-shutdown": "false", 235 "segment-writer.min-ready-duration": "30s", 236 "storage.s3.http.idle-conn-timeout": "10m", 237 "storage.s3.max-idle-connections-per-host": "1000", 238 "storage.gcs.http.idle-conn-timeout": "10m", 239 "storage.gcs.max-idle-connections-per-host": "1000", 240 "compaction-worker.metrics-exporter.rules-source.static": "[]", 241 } { 242 overrides[k] = v 243 } 244 245 c.Metastore.RegisterFlags(throwaway) 246 c.SegmentWriter.RegisterFlags(throwaway) 247 c.QueryBackend.RegisterFlags(throwaway) 248 c.CompactionWorker.RegisterFlags(throwaway) 249 c.AdaptivePlacement.RegisterFlags(throwaway) 250 c.LimitsConfig.WritePathOverrides.RegisterFlags(throwaway) 251 c.LimitsConfig.ReadPathOverrides.RegisterFlags(throwaway) 252 c.LimitsConfig.AdaptivePlacementLimits.RegisterFlags(throwaway) 253 c.LimitsConfig.Retention.RegisterFlags(throwaway) 254 c.LimitsConfig.RecordingRules.RegisterFlags(throwaway) 255 c.LimitsConfig.Symbolizer.RegisterFlags(throwaway) 256 c.Symbolizer.RegisterFlags(throwaway) 257 } 258 259 throwaway.VisitAll(func(f *flag.Flag) { 260 if v, ok := overrides[f.Name]; ok { 261 // Ignore errors when setting new values. We have a test to verify that it works. 262 _ = f.Value.Set(v) 263 } 264 fs.Var(f.Value, f.Name, f.Usage) 265 }) 266 } 267 268 func (c *Config) Validate() error { 269 if len(c.Target) == 0 { 270 return errors.New("no modules specified") 271 } 272 273 if err := c.Frontend.Validate(); err != nil { 274 return err 275 } 276 277 if err := c.Worker.Validate(util.Logger); err != nil { 278 return err 279 } 280 281 if err := c.LimitsConfig.Validate(); err != nil { 282 return err 283 } 284 285 if err := c.QueryScheduler.Validate(); err != nil { 286 return err 287 } 288 289 if err := c.Ingester.Validate(); err != nil { 290 return err 291 } 292 293 if err := c.StoreGateway.Validate(c.LimitsConfig); err != nil { 294 return err 295 } 296 297 if err := c.OverridesExporter.Validate(); err != nil { 298 return err 299 } 300 301 if err := c.Compactor.Validate(c.PhlareDB.MaxBlockDuration); err != nil { 302 return err 303 } 304 305 if err := c.Storage.Bucket.Validate(util.Logger); err != nil { 306 return err 307 } 308 309 if err := c.TenantSettings.Validate(); err != nil { 310 return err 311 } 312 313 return nil 314 } 315 316 func (c *Config) ApplyDynamicConfig() cfg.Source { 317 c.Ingester.LifecyclerConfig.RingConfig.KVStore.Store = "memberlist" 318 c.SegmentWriter.LifecyclerConfig.RingConfig.KVStore.Store = "memberlist" 319 c.Distributor.DistributorRing.KVStore.Store = c.Ingester.LifecyclerConfig.RingConfig.KVStore.Store 320 c.OverridesExporter.Ring.Ring.KVStore.Store = c.Ingester.LifecyclerConfig.RingConfig.KVStore.Store 321 c.Frontend.QuerySchedulerDiscovery.SchedulerRing.KVStore.Store = c.Ingester.LifecyclerConfig.RingConfig.KVStore.Store 322 c.Worker.QuerySchedulerDiscovery.SchedulerRing.KVStore.Store = c.Ingester.LifecyclerConfig.RingConfig.KVStore.Store 323 c.QueryScheduler.ServiceDiscovery.SchedulerRing.KVStore.Store = c.Ingester.LifecyclerConfig.RingConfig.KVStore.Store 324 c.StoreGateway.ShardingRing.Ring.KVStore.Store = c.Ingester.LifecyclerConfig.RingConfig.KVStore.Store 325 c.Compactor.ShardingRing.Common.KVStore.Store = c.Ingester.LifecyclerConfig.RingConfig.KVStore.Store 326 327 return func(dst cfg.Cloneable) error { 328 return nil 329 } 330 } 331 332 func (c *Config) Clone() flagext.Registerer { 333 return func(c Config) *Config { 334 return &c 335 }(*c) 336 } 337 338 type Pyroscope struct { 339 Cfg Config 340 reg prometheus.Registerer 341 logger *logger 342 tracer io.Closer 343 344 ModuleManager *modules.Manager 345 serviceMap map[string]services.Service 346 deps map[string][]string 347 348 API *api.API 349 Server *server.Server 350 SignalHandler *signals.Handler 351 MemberlistKV *memberlist.KVInitService 352 ingesterRing *ring.Ring 353 usageReport *usagestats.Reporter 354 RuntimeConfig *runtimeconfig.Manager 355 Overrides *validation.Overrides 356 Compactor *compactor.MultitenantCompactor 357 admin api.AdminService 358 versions *apiversion.Service 359 serviceManager *services.Manager 360 361 TenantLimits validation.TenantLimits 362 363 storageBucket phlareobj.Bucket 364 365 grpcGatewayMux *grpcgw.ServeMux 366 367 auth connect.Option 368 ingester *ingester.Ingester 369 frontend *frontend.Frontend 370 371 // Experimental modules. 372 segmentWriter *segmentwriter.SegmentWriterService 373 segmentWriterClient *segmentwriterclient.Client 374 segmentWriterRing *ring.Ring 375 placementAgent *placement.Agent 376 placementManager *placement.Manager 377 metastore *metastore.Metastore 378 metastoreClient *metastoreclient.Client 379 metastoreAdmin *metastoreadmin.Admin 380 queryBackendClient *querybackendclient.Client 381 compactionWorker *compactionworker.Worker 382 healthServer *health.Server 383 recordingRulesClient *recordingrulesclient.Client 384 symbolizer *symbolizer.Symbolizer 385 } 386 387 func New(cfg Config) (*Pyroscope, error) { 388 logger := initLogger(cfg.Server.LogFormat, cfg.Server.LogLevel) 389 cfg.Server.Log = logger 390 usagestats.Edition("oss") 391 392 phlare := &Pyroscope{ 393 Cfg: cfg, 394 logger: logger, 395 reg: prometheus.DefaultRegisterer, 396 } 397 if err := cfg.Validate(); err != nil { 398 return nil, err 399 } 400 if err := phlare.setupModuleManager(); err != nil { 401 return nil, err 402 } 403 404 runtime.SetMutexProfileFraction(cfg.SelfProfiling.MutexProfileFraction) 405 runtime.SetBlockProfileRate(cfg.SelfProfiling.BlockProfileRate) 406 407 if cfg.Tracing.Enabled { 408 // Setting the environment variable JAEGER_AGENT_HOST enables tracing 409 name := os.Getenv("JAEGER_SERVICE_NAME") 410 if name == "" { 411 name = fmt.Sprintf("pyroscope-%s", cfg.Target) 412 } 413 trace, err := wwtracing.NewFromEnv(name) 414 if err != nil { 415 level.Error(logger).Log("msg", "error in initializing tracing. tracing will not be enabled", "err", err) 416 } 417 if cfg.Tracing.ProfilingEnabled { 418 opentracing.SetGlobalTracer(spanprofiler.NewTracer(opentracing.GlobalTracer())) 419 } 420 phlare.tracer = trace 421 } 422 423 phlare.auth = connect.WithInterceptors(tenant.NewAuthInterceptor(cfg.MultitenancyEnabled)) 424 phlare.Cfg.API.HTTPAuthMiddleware = util.AuthenticateUser(cfg.MultitenancyEnabled) 425 phlare.Cfg.API.GrpcAuthMiddleware = phlare.auth 426 427 return phlare, nil 428 } 429 430 func (f *Pyroscope) setupModuleManager() error { 431 mm := modules.NewManager(f.logger) 432 433 mm.RegisterModule(Storage, f.initStorage, modules.UserInvisibleModule) 434 mm.RegisterModule(GRPCGateway, f.initGRPCGateway, modules.UserInvisibleModule) 435 mm.RegisterModule(MemberlistKV, f.initMemberlistKV, modules.UserInvisibleModule) 436 mm.RegisterModule(IngesterRing, f.initIngesterRing, modules.UserInvisibleModule) 437 mm.RegisterModule(RuntimeConfig, f.initRuntimeConfig, modules.UserInvisibleModule) 438 mm.RegisterModule(Overrides, f.initOverrides, modules.UserInvisibleModule) 439 mm.RegisterModule(OverridesExporter, f.initOverridesExporter) 440 mm.RegisterModule(Ingester, f.initIngester) 441 mm.RegisterModule(Server, f.initServer, modules.UserInvisibleModule) 442 mm.RegisterModule(API, f.initAPI, modules.UserInvisibleModule) 443 mm.RegisterModule(Version, f.initVersion, modules.UserInvisibleModule) 444 mm.RegisterModule(Distributor, f.initDistributor) 445 mm.RegisterModule(Querier, f.initQuerier) 446 mm.RegisterModule(StoreGateway, f.initStoreGateway) 447 mm.RegisterModule(UsageReport, f.initUsageReport) 448 mm.RegisterModule(QueryFrontend, f.initQueryFrontend) 449 mm.RegisterModule(QueryScheduler, f.initQueryScheduler) 450 mm.RegisterModule(Compactor, f.initCompactor) 451 mm.RegisterModule(Admin, f.initAdmin) 452 mm.RegisterModule(All, nil) 453 mm.RegisterModule(TenantSettings, f.initTenantSettings) 454 mm.RegisterModule(AdHocProfiles, f.initAdHocProfiles) 455 mm.RegisterModule(EmbeddedGrafana, f.initEmbeddedGrafana) 456 mm.RegisterModule(FeatureFlags, f.initFeatureFlags) 457 458 // Add dependencies 459 deps := map[string][]string{ 460 All: { 461 Ingester, 462 Distributor, 463 QueryFrontend, 464 QueryScheduler, 465 Querier, 466 StoreGateway, 467 Compactor, 468 Admin, 469 TenantSettings, 470 AdHocProfiles, 471 }, 472 473 Server: {GRPCGateway}, 474 API: {Server}, 475 Distributor: {Overrides, IngesterRing, API, UsageReport}, 476 Querier: {Overrides, API, MemberlistKV, IngesterRing, UsageReport, Version, FeatureFlags}, 477 QueryFrontend: {OverridesExporter, API, MemberlistKV, UsageReport, Version, FeatureFlags}, 478 QueryScheduler: {Overrides, API, MemberlistKV, UsageReport}, 479 Ingester: {Overrides, API, MemberlistKV, Storage, UsageReport, Version}, 480 StoreGateway: {API, Storage, Overrides, MemberlistKV, UsageReport, Admin, Version}, 481 Compactor: {API, Storage, Overrides, MemberlistKV, UsageReport}, 482 UsageReport: {Storage, MemberlistKV}, 483 Overrides: {RuntimeConfig}, 484 OverridesExporter: {Overrides, MemberlistKV}, 485 RuntimeConfig: {API}, 486 IngesterRing: {API, MemberlistKV}, 487 MemberlistKV: {API}, 488 Admin: {API, Storage}, 489 Version: {API, MemberlistKV}, 490 TenantSettings: {API, Overrides, Storage}, 491 AdHocProfiles: {API, Overrides, Storage}, 492 EmbeddedGrafana: {API}, 493 FeatureFlags: {API}, 494 } 495 496 if f.Cfg.V2 { 497 v2Modules := map[string][]string{ 498 SegmentWriter: {Overrides, API, MemberlistKV, Storage, UsageReport, MetastoreClient}, 499 Metastore: {Overrides, API, MetastoreClient, Storage, PlacementManager}, 500 MetastoreAdmin: {API, MetastoreClient}, 501 CompactionWorker: {Overrides, API, Storage, MetastoreClient, RecordingRulesClient}, 502 QueryBackend: {Overrides, API, Storage, QueryBackendClient}, 503 SegmentWriterRing: {Overrides, API, MemberlistKV}, 504 SegmentWriterClient: {Overrides, API, SegmentWriterRing, PlacementAgent}, 505 PlacementAgent: {Overrides, API, Storage}, 506 PlacementManager: {Overrides, API, Storage}, 507 Symbolizer: {Overrides, Storage}, 508 } 509 for k, v := range v2Modules { 510 deps[k] = v 511 } 512 513 deps[All] = append(deps[All], SegmentWriter, Metastore, CompactionWorker, QueryBackend) 514 deps[QueryFrontend] = append(deps[QueryFrontend], MetastoreClient, QueryBackendClient, Symbolizer) 515 deps[Distributor] = append(deps[Distributor], SegmentWriterClient) 516 deps[Server] = append(deps[Server], HealthServer) 517 deps[Admin] = append(deps[Admin], MetastoreAdmin) 518 519 mm.RegisterModule(SegmentWriter, f.initSegmentWriter) 520 mm.RegisterModule(Metastore, f.initMetastore) 521 mm.RegisterModule(CompactionWorker, f.initCompactionWorker) 522 mm.RegisterModule(QueryBackend, f.initQueryBackend) 523 mm.RegisterModule(Symbolizer, f.initSymbolizer) 524 525 mm.RegisterModule(SegmentWriterRing, f.initSegmentWriterRing, modules.UserInvisibleModule) 526 mm.RegisterModule(SegmentWriterClient, f.initSegmentWriterClient, modules.UserInvisibleModule) 527 mm.RegisterModule(MetastoreClient, f.initMetastoreClient, modules.UserInvisibleModule) 528 mm.RegisterModule(MetastoreAdmin, f.initMetastoreAdmin, modules.UserInvisibleModule) 529 mm.RegisterModule(QueryBackendClient, f.initQueryBackendClient, modules.UserInvisibleModule) 530 mm.RegisterModule(PlacementAgent, f.initPlacementAgent, modules.UserInvisibleModule) 531 mm.RegisterModule(PlacementManager, f.initPlacementManager, modules.UserInvisibleModule) 532 mm.RegisterModule(HealthServer, f.initHealthServer, modules.UserInvisibleModule) 533 mm.RegisterModule(RecordingRulesClient, f.initRecordingRulesClient, modules.UserInvisibleModule) 534 } 535 536 for mod, targets := range deps { 537 if err := mm.AddDependency(mod, targets...); err != nil { 538 return err 539 } 540 } 541 542 f.deps = deps 543 f.ModuleManager = mm 544 545 return nil 546 } 547 548 // made here https://patorjk.com/software/taag/#p=display&f=Doom&t=grafana%20pyroscope 549 // also needed to replace all ` with ' 550 var banner = ` 551 / _| 552 __ _ _ __ __ _| |_ __ _ _ __ __ _ _ __ _ _ _ __ ___ ___ ___ ___ _ __ ___ 553 / _' | '__/ _' | _/ _' | '_ \ / _' | | '_ \| | | | '__/ _ \/ __|/ __/ _ \| '_ \ / _ \ 554 | (_| | | | (_| | || (_| | | | | (_| | | |_) | |_| | | | (_) \__ \ (_| (_) | |_) | __/ 555 \__, |_| \__,_|_| \__,_|_| |_|\__,_| | .__/ \__, |_| \___/|___/\___\___/| .__/ \___| 556 __/ | | | __/ | | | 557 |___/ |_| |___/ |_| 558 ` 559 560 func (f *Pyroscope) Run() error { 561 if f.Cfg.ShowBanner { 562 _ = cli.GradientBanner(banner, os.Stderr) 563 } 564 565 serviceMap, err := f.ModuleManager.InitModuleServices(f.Cfg.Target...) 566 if err != nil { 567 return err 568 } 569 570 f.serviceMap = serviceMap 571 var servs []services.Service 572 for _, s := range serviceMap { 573 servs = append(servs, s) 574 } 575 576 sm, err := services.NewManager(servs...) 577 if err != nil { 578 return err 579 } 580 f.serviceManager = sm 581 582 f.API.RegisterReadyHandler(f.readyHandler(sm)) 583 RegisterHealthServer(f.Server.HTTP, grpcutil.WithManager(sm)) 584 healthy := func() { 585 level.Info(f.logger).Log("msg", "Pyroscope started", "version", version.Info()) 586 if os.Getenv("PYROSCOPE_PRINT_ROUTES") != "" { 587 printRoutes(f.Server.HTTP) 588 } 589 590 // Start profiling when Pyroscope is ready 591 if !f.Cfg.SelfProfiling.DisablePush && slices.Contains(f.Cfg.Target, All) { 592 _, err := pyroscope.Start(pyroscope.Config{ 593 ApplicationName: "pyroscope", 594 ServerAddress: fmt.Sprintf("http://%s:%d", "localhost", f.Cfg.Server.HTTPListenPort), 595 Tags: map[string]string{ 596 "hostname": os.Getenv("HOSTNAME"), 597 "target": "all", 598 "service_git_ref": serviceGitRef(), 599 "service_repository": "https://github.com/grafana/pyroscope", 600 }, 601 ProfileTypes: []pyroscope.ProfileType{ 602 pyroscope.ProfileCPU, 603 pyroscope.ProfileAllocObjects, 604 pyroscope.ProfileAllocSpace, 605 pyroscope.ProfileInuseObjects, 606 pyroscope.ProfileInuseSpace, 607 pyroscope.ProfileGoroutines, 608 pyroscope.ProfileMutexCount, 609 pyroscope.ProfileMutexDuration, 610 pyroscope.ProfileBlockCount, 611 pyroscope.ProfileBlockDuration, 612 }, 613 }) 614 if err != nil { 615 level.Warn(f.logger).Log("msg", "failed to start pyroscope", "err", err) 616 } 617 } 618 } 619 620 // only query-frontend, query-backend, querier and target all should register the UI 621 if slices.Contains(f.Cfg.Target, All) || 622 slices.Contains(f.Cfg.Target, QueryFrontend) || 623 slices.Contains(f.Cfg.Target, QueryBackend) || 624 slices.Contains(f.Cfg.Target, Querier) { 625 626 if err = f.API.RegisterCatchAll(); err != nil { 627 return err 628 } 629 } else { 630 f.API.RegisterRedirectToAdmin() 631 } 632 633 serviceFailed := func(service services.Service) { 634 // if any service fails, stop entire Pyroscope 635 sm.StopAsync() 636 637 // let's find out which module failed 638 for m, s := range serviceMap { 639 if s == service { 640 if service.FailureCase() == modules.ErrStopProcess { 641 level.Info(f.logger). 642 Log("msg", "received stop signal via return error", "module", m, "error", service.FailureCase()) 643 } else { 644 level.Error(f.logger).Log("msg", "module failed", "module", m, "error", service.FailureCase()) 645 } 646 return 647 } 648 } 649 650 level.Error(f.logger).Log("msg", "module failed", "module", "unknown", "error", service.FailureCase()) 651 } 652 653 sm.AddListener(services.NewManagerListener(healthy, f.stopped, serviceFailed)) 654 655 // Setup signal handler. If signal arrives, we stop the manager, which stops all the services. 656 f.SignalHandler = signals.NewHandler(f.Server.Log) 657 go func() { 658 f.SignalHandler.Loop() 659 if f.Cfg.ShutdownDelay > 0 { 660 level.Info(f.logger).Log("msg", "shutdown delayed", "delay", f.Cfg.ShutdownDelay) 661 time.Sleep(f.Cfg.ShutdownDelay) 662 } 663 sm.StopAsync() 664 }() 665 666 // Start all services. This can really only fail if some service is already 667 // in other state than New, which should not be the case. 668 err = sm.StartAsync(context.Background()) 669 if err == nil { 670 // Wait until service manager stops. It can stop in two ways: 671 // 1) Signal is received and manager is stopped. 672 // 2) Any service fails. 673 err = sm.AwaitStopped(context.Background()) 674 } 675 if f.versions != nil { 676 f.versions.Shutdown() 677 } 678 // If there is no error yet (= service manager started and then stopped without problems), 679 // but any service failed, report that failure as an error to caller. 680 if err == nil { 681 if failed := sm.ServicesByState()[services.Failed]; len(failed) > 0 { 682 for _, f := range failed { 683 if f.FailureCase() != modules.ErrStopProcess { 684 // Details were reported via failure listener before 685 err = errors.New("failed services") 686 break 687 } 688 } 689 } 690 } 691 692 return err 693 } 694 695 func (f *Pyroscope) readyHandler(sm *services.Manager) http.HandlerFunc { 696 return func(w http.ResponseWriter, r *http.Request) { 697 if !sm.IsHealthy() { 698 msg := bytes.Buffer{} 699 msg.WriteString("Some services are not Running:\n") 700 701 byState := map[services.State][]string{} 702 for name, svc := range f.serviceMap { 703 state := svc.State() 704 byState[state] = append(byState[state], name) 705 } 706 707 states := lo.Keys(byState) 708 sort.Slice(states, func(i, j int) bool { return states[i] < states[j] }) 709 710 for _, st := range states { 711 sort.Strings(byState[st]) 712 msg.WriteString(fmt.Sprintf("%v: %v\n", st, byState[st])) 713 } 714 715 http.Error(w, msg.String(), http.StatusServiceUnavailable) 716 return 717 } 718 719 if f.metastore != nil { 720 if err := f.metastore.CheckReady(r.Context()); err != nil { 721 http.Error(w, "Metastore not ready: "+err.Error(), http.StatusServiceUnavailable) 722 return 723 } 724 } 725 726 if f.ingester != nil { 727 if err := f.ingester.CheckReady(r.Context()); err != nil { 728 http.Error(w, "Ingester not ready: "+err.Error(), http.StatusServiceUnavailable) 729 return 730 } 731 } 732 733 if f.segmentWriter != nil { 734 if err := f.segmentWriter.CheckReady(r.Context()); err != nil { 735 http.Error(w, "Segment Writer not ready: "+err.Error(), http.StatusServiceUnavailable) 736 return 737 } 738 } 739 740 if f.frontend != nil { 741 if err := f.frontend.CheckReady(r.Context()); err != nil { 742 http.Error(w, "Query Frontend not ready: "+err.Error(), http.StatusServiceUnavailable) 743 return 744 } 745 } 746 747 util.WriteTextResponse(w, "ready") 748 } 749 } 750 751 func (f *Pyroscope) Stop() func(context.Context) error { 752 if f.serviceManager == nil { 753 return func(context.Context) error { return nil } 754 } 755 f.serviceManager.StopAsync() 756 return f.serviceManager.AwaitStopped 757 } 758 759 func (f *Pyroscope) stopped() { 760 level.Info(f.logger).Log("msg", "Pyroscope stopped") 761 if f.tracer != nil { 762 if err := f.tracer.Close(); err != nil { 763 level.Error(f.logger).Log("msg", "error closing tracing", "err", err) 764 } 765 } 766 if err := f.logger.w.Close(); err != nil { 767 _, _ = fmt.Fprintf(os.Stderr, "error closing log writer: %v\n", err) 768 } 769 } 770 771 func initLogger(logFormat string, logLevel dslog.Level) *logger { 772 w := util.NewAsyncWriter(os.Stderr, // Flush after: 773 256<<10, 20, // 256KiB buffer is full (keep 20 buffers). 774 1<<10, // 1K writes or 100ms. 775 100*time.Millisecond, 776 ) 777 778 // Use UTC timestamps and skip 5 stack frames. 779 l := dslog.NewGoKitWithWriter(logFormat, w) 780 l = log.With(l, "ts", log.DefaultTimestampUTC, "caller", spanlogger.Caller(5)) 781 782 // Must put the level filter last for efficiency. 783 l = level.NewFilter(l, logLevel.Option) 784 785 return &logger{w: w, Logger: l} 786 } 787 788 type logger struct { 789 w io.WriteCloser 790 log.Logger 791 } 792 793 func (f *Pyroscope) initAPI() (services.Service, error) { 794 a, err := api.New(f.Cfg.API, f.Server, f.grpcGatewayMux, f.Server.Log) 795 if err != nil { 796 return nil, err 797 } 798 f.API = a 799 800 if err := f.API.RegisterAPI(f.statusService()); err != nil { 801 return nil, err 802 } 803 804 return nil, nil 805 } 806 807 func (f *Pyroscope) initVersion() (services.Service, error) { 808 var err error 809 f.versions, err = apiversion.New(f.Cfg.Distributor.DistributorRing, f.logger, f.reg) 810 if err != nil { 811 return nil, err 812 } 813 f.API.RegisterVersion(f.versions) 814 return f.versions, nil 815 } 816 817 func printRoutes(r *mux.Router) { 818 err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { 819 path, err := route.GetPathRegexp() 820 if err != nil { 821 fmt.Printf("failed to get path regexp %s\n", err) 822 return nil 823 } 824 method, err := route.GetMethods() 825 if err != nil { 826 method = []string{"*"} 827 } 828 fmt.Printf("%s %s\n", strings.Join(method, ","), path) 829 return nil 830 }) 831 if err != nil { 832 fmt.Printf("failed to walk routes %s\n", err) 833 } 834 } 835 836 // serviceGitRef attempts to find the git revision of the service. Default to HEAD. 837 func serviceGitRef() string { 838 if version.Revision != "" { 839 return version.Revision 840 } 841 buildInfo, ok := debug.ReadBuildInfo() 842 if ok { 843 for _, setting := range buildInfo.Settings { 844 if setting.Key == "vcs.revision" { 845 return setting.Value 846 } 847 } 848 } 849 return "HEAD" 850 }