github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/cli/server.go (about) 1 package cli 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "time" 8 9 "github.com/davecgh/go-spew/spew" 10 "github.com/prometheus/client_golang/prometheus" 11 "github.com/pyroscope-io/client/pyroscope" 12 "github.com/sirupsen/logrus" 13 "golang.org/x/sync/errgroup" 14 "gopkg.in/yaml.v2" 15 16 // revive:disable:blank-imports register discoverer 17 "github.com/pyroscope-io/pyroscope/pkg/baseurl" 18 "github.com/pyroscope-io/pyroscope/pkg/remotewrite" 19 _ "github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/aws" 20 _ "github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/consul" 21 _ "github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/file" 22 _ "github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/http" 23 _ "github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/kubernetes" 24 25 "github.com/pyroscope-io/pyroscope/pkg/admin" 26 "github.com/pyroscope-io/pyroscope/pkg/analytics" 27 "github.com/pyroscope-io/pyroscope/pkg/config" 28 "github.com/pyroscope-io/pyroscope/pkg/exporter" 29 "github.com/pyroscope-io/pyroscope/pkg/health" 30 "github.com/pyroscope-io/pyroscope/pkg/ingestion" 31 "github.com/pyroscope-io/pyroscope/pkg/parser" 32 "github.com/pyroscope-io/pyroscope/pkg/scrape" 33 sc "github.com/pyroscope-io/pyroscope/pkg/scrape/config" 34 "github.com/pyroscope-io/pyroscope/pkg/scrape/discovery" 35 "github.com/pyroscope-io/pyroscope/pkg/selfprofiling" 36 "github.com/pyroscope-io/pyroscope/pkg/server" 37 "github.com/pyroscope-io/pyroscope/pkg/service" 38 "github.com/pyroscope-io/pyroscope/pkg/sqlstore" 39 "github.com/pyroscope-io/pyroscope/pkg/storage" 40 "github.com/pyroscope-io/pyroscope/pkg/util/debug" 41 ) 42 43 type Server struct { 44 svc *serverService 45 } 46 47 type serverService struct { 48 config *config.Server 49 logger *logrus.Logger 50 controller *server.Controller 51 storage *storage.Storage 52 // queue used to ingest data into the storage 53 storageQueue *storage.IngestionQueue 54 analyticsService *analytics.Service 55 selfProfiling *pyroscope.Session 56 debugReporter *debug.Reporter 57 healthController *health.Controller 58 adminServer *admin.Server 59 discoveryManager *discovery.Manager 60 scrapeManager *scrape.Manager 61 database *sqlstore.SQLStore 62 remoteWriteQueue []*remotewrite.IngestionQueue 63 64 stopped chan struct{} 65 done chan struct{} 66 group *errgroup.Group 67 } 68 69 func newServerService(c *config.Server) (*serverService, error) { 70 logLevel, err := logrus.ParseLevel(c.LogLevel) 71 if err != nil { 72 return nil, err 73 } 74 75 logger := logrus.StandardLogger() 76 logger.SetLevel(logLevel) 77 78 if err = loadScrapeConfigsFromFile(c); err != nil { 79 return nil, fmt.Errorf("could not load scrape config: %w", err) 80 } 81 82 svc := serverService{ 83 config: c, 84 logger: logger, 85 stopped: make(chan struct{}), 86 done: make(chan struct{}), 87 } 88 89 diskPressure := health.DiskPressure{ 90 Threshold: c.MinFreeSpacePercentage, 91 Path: c.StoragePath, 92 } 93 94 svc.database, err = sqlstore.Open(c) 95 if err != nil { 96 return nil, fmt.Errorf("can't open database %q: %w", c.Database.URL, err) 97 } 98 99 svc.healthController = health.NewController(svc.logger, time.Minute, diskPressure) 100 101 var appMetadataSaver storage.ApplicationMetadataSaver = service.NewApplicationMetadataService(svc.database.DB()) 102 appMetadataSaver = service.NewApplicationMetadataCacheService(service.ApplicationMetadataCacheServiceConfig{}, appMetadataSaver) 103 104 storageConfig := storage.NewConfig(svc.config) 105 svc.storage, err = storage.New(storageConfig, svc.logger, prometheus.DefaultRegisterer, svc.healthController, appMetadataSaver) 106 if err != nil { 107 return nil, fmt.Errorf("new storage: %w", err) 108 } 109 110 svc.debugReporter = debug.NewReporter(svc.logger, svc.storage, prometheus.DefaultRegisterer) 111 112 if svc.config.Auth.JWTSecret == "" { 113 if svc.config.Auth.JWTSecret, err = svc.storage.JWT(); err != nil { 114 return nil, err 115 } 116 } 117 118 appMetadataSvc := service.NewApplicationMetadataService(svc.database.DB()) 119 migrator := NewAppMetadataMigrator(logger, svc.storage, appMetadataSvc) 120 err = migrator.Migrate() 121 if err != nil { 122 svc.logger.Error(err) 123 } 124 appSvc := service.NewApplicationService(appMetadataSvc, svc.storage) 125 126 // this needs to happen after storage is initiated! 127 if svc.config.EnableExperimentalAdmin { 128 socketPath := svc.config.AdminSocketPath 129 userService := service.NewUserService(svc.database.DB()) 130 adminController := admin.NewController(svc.logger, appSvc, userService, svc.storage) 131 httpClient, err := admin.NewHTTPOverUDSClient(socketPath) 132 if err != nil { 133 return nil, fmt.Errorf("admin: %w", err) 134 } 135 136 adminHTTPOverUDS, err := admin.NewUdsHTTPServer(socketPath, httpClient) 137 if err != nil { 138 return nil, fmt.Errorf("admin: %w", err) 139 } 140 141 svc.adminServer, err = admin.NewServer( 142 svc.logger, 143 adminController, 144 adminHTTPOverUDS, 145 ) 146 if err != nil { 147 return nil, fmt.Errorf("admin: %w", err) 148 } 149 } 150 151 exportedMetricsRegistry := prometheus.NewRegistry() 152 metricsExporter, err := exporter.NewExporter(svc.config.MetricsExportRules, exportedMetricsRegistry) 153 if err != nil { 154 return nil, fmt.Errorf("new metric exporter: %w", err) 155 } 156 157 svc.storageQueue = storage.NewIngestionQueue(svc.logger, svc.storage, prometheus.DefaultRegisterer, storageConfig) 158 159 defaultMetricsRegistry := prometheus.DefaultRegisterer 160 161 var ingester ingestion.Ingester 162 if !svc.config.RemoteWrite.Enabled || !svc.config.RemoteWrite.DisableLocalWrites { 163 ingester = parser.New(svc.logger, svc.storageQueue, metricsExporter) 164 } else { 165 ingester = ingestion.NewNoopIngester() 166 } 167 168 // If remote write is available, let's write to both local storage and to the remote server 169 if svc.config.RemoteWrite.Enabled { 170 err = loadRemoteWriteTargetConfigsFromFile(svc.config) 171 if err != nil { 172 return nil, err 173 } 174 175 if len(svc.config.RemoteWrite.Targets) <= 0 { 176 return nil, fmt.Errorf("remote write is enabled but no targets are set up") 177 } 178 179 remoteClients := make([]ingestion.Ingester, len(svc.config.RemoteWrite.Targets)) 180 svc.remoteWriteQueue = make([]*remotewrite.IngestionQueue, len(svc.config.RemoteWrite.Targets)) 181 182 i := 0 183 for targetName, t := range svc.config.RemoteWrite.Targets { 184 targetLogger := logger.WithField("remote_target", targetName) 185 targetLogger.Debug("Initializing remote write target") 186 187 remoteClient := remotewrite.NewClient(targetLogger, defaultMetricsRegistry, targetName, t) 188 q := remotewrite.NewIngestionQueue(targetLogger, defaultMetricsRegistry, remoteClient, targetName, t) 189 190 remoteClients[i] = q 191 svc.remoteWriteQueue[i] = q 192 i++ 193 } 194 195 ingesters := append([]ingestion.Ingester{ingester}, remoteClients...) 196 ingester = ingestion.NewParallelizer(svc.logger, ingesters...) 197 } 198 if !svc.config.NoSelfProfiling { 199 svc.selfProfiling = selfprofiling.NewSession(svc.logger, ingester, "pyroscope.server", svc.config.SelfProfilingTags) 200 } 201 202 svc.scrapeManager = scrape.NewManager( 203 svc.logger.WithField("component", "scrape-manager"), 204 ingester, 205 defaultMetricsRegistry) 206 207 svc.controller, err = server.New(server.Config{ 208 Configuration: svc.config, 209 Storage: svc.storage, 210 Ingester: ingester, 211 Notifier: svc.healthController, 212 Logger: svc.logger, 213 MetricsRegisterer: defaultMetricsRegistry, 214 ExportedMetricsRegistry: exportedMetricsRegistry, 215 ScrapeManager: svc.scrapeManager, 216 DB: svc.database.DB(), 217 }) 218 if err != nil { 219 return nil, fmt.Errorf("new server: %w", err) 220 } 221 222 svc.discoveryManager = discovery.NewManager( 223 svc.logger.WithField("component", "discovery-manager")) 224 225 if !c.AnalyticsOptOut { 226 svc.analyticsService = analytics.NewService(c, svc.storage, svc.controller) 227 } 228 229 if os.Getenv("PYROSCOPE_CONFIG_DEBUG") != "" { 230 fmt.Println("parsed config:") 231 spew.Dump(svc.config) 232 } 233 234 return &svc, nil 235 } 236 237 func (svc *serverService) Start() error { 238 g, ctx := errgroup.WithContext(context.Background()) 239 svc.group = g 240 g.Go(func() error { 241 // if you ever change this line, make sure to update this homebrew test: 242 // https://github.com/pyroscope-io/homebrew-brew/blob/main/Formula/pyroscope.rb#L94 243 svc.logger.Info("starting HTTP server") 244 return svc.controller.Start() 245 }) 246 if svc.config.EnableExperimentalAdmin { 247 g.Go(func() error { 248 svc.logger.Info("starting admin server") 249 return svc.adminServer.Start() 250 }) 251 } 252 253 if svc.config.BaseURLBindAddr != "" { 254 go baseurl.Start(svc.config) 255 } 256 go svc.debugReporter.Start() 257 if svc.analyticsService != nil { 258 go svc.analyticsService.Start() 259 } 260 261 svc.healthController.Start() 262 if !svc.config.NoSelfProfiling { 263 if err := svc.selfProfiling.Start(); err != nil { 264 svc.logger.WithError(err).Error("failed to start self-profiling") 265 } 266 } 267 268 // Scrape and Discovery managers have to be initialized 269 // with ApplyConfig before starting running. 270 if err := svc.applyScrapeConfigs(svc.config); err != nil { 271 return err 272 } 273 g.Go(func() error { 274 svc.logger.Debug("starting discovery manager") 275 return svc.discoveryManager.Run() 276 }) 277 g.Go(func() error { 278 svc.logger.Debug("starting scrape manager") 279 return svc.scrapeManager.Run(svc.discoveryManager.SyncCh()) 280 }) 281 282 defer close(svc.done) 283 select { 284 case <-svc.stopped: 285 case <-ctx.Done(): 286 // The context is canceled the first time a function passed to Go 287 // returns a non-nil error. 288 } 289 // N.B. internal components are de-initialized/disposed (if applicable) 290 // regardless of the exit reason. Once server is stopped, wait for all 291 // Go goroutines to finish. 292 svc.stop() 293 return svc.group.Wait() 294 } 295 296 func (svc *serverService) Stop() { 297 close(svc.stopped) 298 <-svc.done 299 } 300 301 //revive:disable-next-line:confusing-naming methods are different 302 func (svc *serverService) stop() { 303 if svc.config.EnableExperimentalAdmin { 304 svc.logger.Debug("stopping admin server") 305 if err := svc.adminServer.Stop(); err != nil { 306 svc.logger.WithError(err).Error("admin server stop") 307 } 308 } 309 svc.controller.Drain() 310 svc.logger.Debug("stopping discovery manager") 311 svc.discoveryManager.Stop() 312 svc.logger.Debug("stopping scrape manager") 313 svc.scrapeManager.Stop() 314 svc.logger.Debug("stopping debug reporter") 315 svc.debugReporter.Stop() 316 svc.healthController.Stop() 317 if svc.analyticsService != nil { 318 svc.logger.Debug("stopping analytics service") 319 svc.analyticsService.Stop() 320 } 321 322 if !svc.config.NoSelfProfiling { 323 svc.logger.Debug("stopping self profiling") 324 svc.selfProfiling.Stop() 325 } 326 327 if svc.config.RemoteWrite.Enabled { 328 svc.logger.Debug("stopping remote queues") 329 for _, q := range svc.remoteWriteQueue { 330 q.Stop() 331 } 332 } 333 334 svc.logger.Debug("stopping ingestion queue") 335 svc.storageQueue.Stop() 336 svc.logger.Debug("stopping storage") 337 if err := svc.storage.Close(); err != nil { 338 svc.logger.WithError(err).Error("storage close") 339 } 340 svc.logger.Debug("closing database") 341 if err := svc.database.Close(); err != nil { 342 svc.logger.WithError(err).Error("database close") 343 } 344 // we stop the http server as the last thing due to: 345 // 1. we may still want to bserve metric values while storage is closing 346 // 2. we want the /healthz endpoint to still be responding while server is shutting down 347 // (we are thinking in a k8s context here, but maybe 'terminationGracePeriodSeconds' makes this unnecessary) 348 svc.logger.Debug("stopping http server") 349 if err := svc.controller.Stop(); err != nil { 350 svc.logger.WithError(err).Error("controller stop") 351 } 352 } 353 354 func (svc *serverService) ApplyConfig(c *config.Server) error { 355 return svc.applyScrapeConfigs(c) 356 } 357 358 func (svc *serverService) applyScrapeConfigs(c *config.Server) error { 359 if err := loadScrapeConfigsFromFile(c); err != nil { 360 return fmt.Errorf("could not load scrape configs from %s: %w", c.Config, err) 361 } 362 if err := svc.discoveryManager.ApplyConfig(discoveryConfigs(c.ScrapeConfigs)); err != nil { 363 // discoveryManager.ApplyConfig never return errors. 364 return err 365 } 366 return svc.scrapeManager.ApplyConfig(c.ScrapeConfigs) 367 } 368 369 func discoveryConfigs(cfg []*sc.Config) map[string]discovery.Configs { 370 c := make(map[string]discovery.Configs) 371 for _, x := range cfg { 372 c[x.JobName] = x.ServiceDiscoveryConfigs 373 } 374 return c 375 } 376 377 func loadScrapeConfigsFromFile(c *config.Server) error { 378 b, err := os.ReadFile(c.Config) 379 switch { 380 case err == nil: 381 case os.IsNotExist(err): 382 return nil 383 default: 384 return err 385 } 386 type scrapeConfig struct { 387 ScrapeConfigs []*sc.Config `yaml:"scrape-configs" mapstructure:"-"` 388 } 389 var s scrapeConfig 390 if err = yaml.Unmarshal([]byte(performSubstitutions(b)), &s); err != nil { 391 return err 392 } 393 // Populate scrape configs. 394 c.ScrapeConfigs = s.ScrapeConfigs 395 return nil 396 } 397 398 func loadRemoteWriteTargetConfigsFromFile(c *config.Server) error { 399 b, err := os.ReadFile(c.Config) 400 switch { 401 case err == nil: 402 case os.IsNotExist(err): 403 return nil 404 default: 405 return err 406 } 407 408 type cfg struct { 409 RemoteWrite struct { 410 Targets map[string]config.RemoteWriteTarget `yaml:"targets" mapstructure:"-"` 411 } `yaml:"remote-write"` 412 } 413 414 var s cfg 415 if err = yaml.Unmarshal([]byte(performSubstitutions(b)), &s); err != nil { 416 return err 417 } 418 419 c.RemoteWrite.Targets = s.RemoteWrite.Targets 420 421 return nil 422 }