github.com/grafana/pyroscope@v1.18.0/pkg/api/api.go (about) 1 // SPDX-License-Identifier: AGPL-3.0-only 2 // Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/api/api.go 3 // Provenance-includes-license: Apache-2.0 4 // Provenance-includes-copyright: The Cortex Authors. 5 6 package api 7 8 import ( 9 "context" 10 "flag" 11 "fmt" 12 "net/http" 13 14 "connectrpc.com/connect" 15 16 "github.com/felixge/fgprof" 17 "github.com/go-kit/log" 18 "github.com/grafana/dskit/kv/memberlist" 19 "github.com/grafana/dskit/middleware" 20 "github.com/grafana/dskit/server" 21 grpcgw "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 22 23 "github.com/grafana/pyroscope/public" 24 25 "github.com/grafana/pyroscope/pkg/validation" 26 27 "github.com/grafana/pyroscope/api/gen/proto/go/adhocprofiles/v1/adhocprofilesv1connect" 28 "github.com/grafana/pyroscope/api/gen/proto/go/capabilities/v1/capabilitiesv1connect" 29 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1/ingesterv1connect" 30 "github.com/grafana/pyroscope/api/gen/proto/go/push/v1/pushv1connect" 31 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1/querierv1connect" 32 "github.com/grafana/pyroscope/api/gen/proto/go/settings/v1/settingsv1connect" 33 statusv1 "github.com/grafana/pyroscope/api/gen/proto/go/status/v1" 34 "github.com/grafana/pyroscope/api/gen/proto/go/storegateway/v1/storegatewayv1connect" 35 "github.com/grafana/pyroscope/api/gen/proto/go/vcs/v1/vcsv1connect" 36 "github.com/grafana/pyroscope/api/gen/proto/go/version/v1/versionv1connect" 37 "github.com/grafana/pyroscope/api/openapiv2" 38 "github.com/grafana/pyroscope/pkg/adhocprofiles" 39 "github.com/grafana/pyroscope/pkg/compactor" 40 "github.com/grafana/pyroscope/pkg/distributor" 41 "github.com/grafana/pyroscope/pkg/frontend" 42 "github.com/grafana/pyroscope/pkg/frontend/frontendpb/frontendpbconnect" 43 "github.com/grafana/pyroscope/pkg/ingester" 44 "github.com/grafana/pyroscope/pkg/ingester/otlp" 45 "github.com/grafana/pyroscope/pkg/ingester/pyroscope" 46 "github.com/grafana/pyroscope/pkg/querier" 47 "github.com/grafana/pyroscope/pkg/scheduler" 48 "github.com/grafana/pyroscope/pkg/scheduler/schedulerpb/schedulerpbconnect" 49 "github.com/grafana/pyroscope/pkg/settings" 50 "github.com/grafana/pyroscope/pkg/storegateway" 51 "github.com/grafana/pyroscope/pkg/validation/exporter" 52 ) 53 54 type Config struct { 55 // The following configs are injected by the upstream caller. 56 HTTPAuthMiddleware middleware.Interface `yaml:"-"` 57 GrpcAuthMiddleware connect.Option `yaml:"-"` 58 BaseURL string `yaml:"base-url"` 59 } 60 61 type API struct { 62 server *server.Server 63 httpAuthMiddleware middleware.Interface 64 grpcGatewayMux *grpcgw.ServeMux 65 66 cfg Config 67 logger log.Logger 68 indexPage *IndexPageContent 69 } 70 71 func New(cfg Config, s *server.Server, grpcGatewayMux *grpcgw.ServeMux, logger log.Logger) (*API, error) { 72 api := &API{ 73 cfg: cfg, 74 httpAuthMiddleware: cfg.HTTPAuthMiddleware, 75 server: s, 76 logger: logger, 77 indexPage: NewIndexPageContent(), 78 grpcGatewayMux: grpcGatewayMux, 79 } 80 81 // If no authentication middleware is present in the config, use the default authentication middleware. 82 if cfg.HTTPAuthMiddleware == nil { 83 api.httpAuthMiddleware = middleware.AuthenticateUser 84 } 85 86 return api, nil 87 } 88 89 // registerRoute registers an HTTP handler with the main HTTP server. 90 // 91 // Register Options allow to filter the HTTP methods and apply middlewares. 92 func (a *API) RegisterRoute(path string, handler http.Handler, registerOpts ...RegisterOption) { 93 registerRoute(a.logger, a.server.HTTP, path, handler, registerOpts...) 94 } 95 96 // RegisterAPI registers the standard endpoints associated with a running Pyroscope. 97 func (a *API) RegisterAPI(statusService statusv1.StatusServiceServer) error { 98 // register admin page 99 a.RegisterRoute("/admin", indexHandler("", a.indexPage), a.registerOptionsPublicAccess()...) 100 // expose openapiv2 definition 101 openapiv2Handler, err := openapiv2.Handler() 102 if err != nil { 103 return fmt.Errorf("unable to initialize openapiv2 handler: %w", err) 104 } 105 a.RegisterRoute("/api/swagger.json", openapiv2Handler, a.registerOptionsPublicAccess()...) 106 a.indexPage.AddLinks(openAPIDefinitionWeight, "OpenAPI definition", []IndexPageLink{ 107 {Desc: "Swagger JSON", Path: "/api/swagger.json"}, 108 }) 109 // register grpc-gateway api 110 publicAccessPrefixAllMethods := []RegisterOption{WithGzipMiddleware(), WithPrefix()} 111 a.RegisterRoute("/api", a.grpcGatewayMux, publicAccessPrefixAllMethods...) 112 // register fgprof 113 a.RegisterRoute("/debug/fgprof", fgprof.Handler(), a.registerOptionsPublicAccess()...) 114 // register static assets 115 a.RegisterRoute("/static/", http.FileServer(http.FS(staticFiles)), a.registerOptionsPrefixPublicAccess()...) 116 // register ui 117 uiAssets, err := public.Assets() 118 if err != nil { 119 return fmt.Errorf("unable to initialize the ui: %w", err) 120 } 121 122 // The UI used to be at /ui, but now it's at /. 123 a.RegisterRoute("/ui", http.RedirectHandler("/", http.StatusFound), a.registerOptionsPrefixPublicAccess()...) 124 // All assets are served as static files 125 a.RegisterRoute("/assets/", http.FileServer(uiAssets), a.registerOptionsPrefixPublicAccess()...) 126 127 // register status service providing config and buildinfo at grpc gateway 128 if err := statusv1.RegisterStatusServiceHandlerServer(context.Background(), a.grpcGatewayMux, statusService); err != nil { 129 return err 130 } 131 a.indexPage.AddLinks(buildInfoWeight, "Build information", []IndexPageLink{ 132 {Desc: "Build information", Path: "/api/v1/status/buildinfo"}, 133 }) 134 a.indexPage.AddLinks(configWeight, "Current config", []IndexPageLink{ 135 {Desc: "Including the default values", Path: "/api/v1/status/config"}, 136 {Desc: "Only values that differ from the defaults", Path: "/api/v1/status/config/diff"}, 137 {Desc: "Default values", Path: "/api/v1/status/config/default"}, 138 }) 139 return nil 140 } 141 142 func (a *API) RegisterRedirectToAdmin() { 143 a.RegisterRoute("/", http.RedirectHandler("/admin", http.StatusFound), a.registerOptionsPublicAccess()...) 144 } 145 146 func (a *API) RegisterCatchAll() error { 147 uiIndexHandler, err := public.NewIndexHandler(a.cfg.BaseURL) 148 if err != nil { 149 return fmt.Errorf("unable to initialize the ui: %w", err) 150 } 151 152 // Serve index to known paths 153 // This should be kept in sync with routes in public/app/pages/routes.ts 154 for _, path := range []string{"/", "/explore", "/comparison", "/comparison-diff"} { 155 a.RegisterRoute(path, uiIndexHandler, a.registerOptionsPublicAccess()...) 156 } 157 158 a.indexPage.AddLinks(defaultWeight, "User interface", []IndexPageLink{ 159 {Desc: "User interface", Path: "/"}, 160 }) 161 162 return nil 163 } 164 165 // RegisterRuntimeConfig registers the endpoints associates with the runtime configuration 166 func (a *API) RegisterRuntimeConfig(runtimeConfigHandler http.HandlerFunc, userLimitsHandler http.HandlerFunc) { 167 a.RegisterRoute("/runtime_config", runtimeConfigHandler, a.registerOptionsPublicAccess()...) 168 a.RegisterRoute("/api/v1/tenant_limits", userLimitsHandler, a.registerOptionsTenantPath()...) 169 a.indexPage.AddLinks(runtimeConfigWeight, "Current runtime config", []IndexPageLink{ 170 {Desc: "Entire runtime config (including overrides)", Path: "/runtime_config"}, 171 {Desc: "Only values that differ from the defaults", Path: "/runtime_config?mode=diff"}, 172 }) 173 } 174 175 func (a *API) RegisterTenantSettings(ts *settings.TenantSettings) { 176 connectOptions := a.connectOptionsAuthRecovery() 177 settingsv1connect.RegisterSettingsServiceHandler(a.server.HTTP, ts, connectOptions...) 178 179 _, isUnimplemented := ts.RecordingRulesServiceHandler.(*settingsv1connect.UnimplementedRecordingRulesServiceHandler) 180 if !isUnimplemented { 181 settingsv1connect.RegisterRecordingRulesServiceHandler(a.server.HTTP, ts, connectOptions...) 182 } 183 } 184 185 // RegisterOverridesExporter registers the endpoints associated with the overrides exporter. 186 func (a *API) RegisterOverridesExporter(oe *exporter.OverridesExporter) { 187 a.RegisterRoute("/overrides-exporter/ring", http.HandlerFunc(oe.RingHandler), a.registerOptionsRingPage()...) 188 a.indexPage.AddLinks(defaultWeight, "Overrides-exporter", []IndexPageLink{ 189 {Desc: "Ring status", Path: "/overrides-exporter/ring"}, 190 }) 191 } 192 193 // RegisterDistributor registers the endpoints associated with the distributor. 194 func (a *API) RegisterDistributor(d *distributor.Distributor, limits *validation.Overrides, cfg server.Config) { 195 writePathOpts := a.registerOptionsWritePath(limits) 196 pyroscopeHandler := pyroscope.NewPyroscopeIngestHandler(d, limits, a.logger) 197 otlpHandler := otlp.NewOTLPIngestHandler(cfg, d, a.logger, limits) 198 199 a.RegisterRoute("/ingest", pyroscopeHandler, writePathOpts...) 200 a.RegisterRoute("/pyroscope/ingest", pyroscopeHandler, writePathOpts...) 201 pushv1connect.RegisterPusherServiceHandler(a.server.HTTP, d, a.connectOptionsAuthDelayRecovery(limits)...) 202 a.RegisterRoute("/distributor/ring", d, a.registerOptionsRingPage()...) 203 a.indexPage.AddLinks(defaultWeight, "Distributor", []IndexPageLink{ 204 {Desc: "Ring status", Path: "/distributor/ring"}, 205 }) 206 207 a.RegisterRoute("/opentelemetry.proto.collector.profiles.v1development.ProfilesService/Export", otlpHandler, writePathOpts...) 208 a.RegisterRoute("/v1development/profiles", otlpHandler, writePathOpts...) 209 } 210 211 // RegisterMemberlistKV registers the endpoints associated with the memberlist KV store. 212 func (a *API) RegisterMemberlistKV(pathPrefix string, kvs *memberlist.KVInitService) { 213 a.RegisterRoute("/memberlist", MemberlistStatusHandler(pathPrefix, kvs), a.registerOptionsPublicAccess()...) 214 a.indexPage.AddLinks(memberlistWeight, "Memberlist", []IndexPageLink{ 215 {Desc: "Status", Path: "/memberlist"}, 216 }) 217 } 218 219 // RegisterIngesterRing registers the ring UI page associated with the distributor for writes. 220 func (a *API) RegisterIngesterRing(r http.Handler) { 221 a.RegisterRoute("/ring", r, a.registerOptionsRingPage()...) 222 a.indexPage.AddLinks(defaultWeight, "Ingester", []IndexPageLink{ 223 {Desc: "Ring status", Path: "/ring"}, 224 }) 225 } 226 227 func (a *API) RegisterQuerierServiceHandler(svc querierv1connect.QuerierServiceHandler) { 228 querierv1connect.RegisterQuerierServiceHandler(a.server.HTTP, svc, a.connectOptionsAuthLogRecovery()...) 229 } 230 231 func (a *API) RegisterVCSServiceHandler(svc vcsv1connect.VCSServiceHandler) { 232 vcsv1connect.RegisterVCSServiceHandler(a.server.HTTP, svc, a.connectOptionsAuthLogRecovery()...) 233 } 234 235 func (a *API) RegisterFeatureFlagsServiceHandler(svc capabilitiesv1connect.FeatureFlagsServiceHandler) { 236 capabilitiesv1connect.RegisterFeatureFlagsServiceHandler(a.server.HTTP, svc, a.connectOptionsAuthLogRecovery()...) 237 } 238 239 func (a *API) RegisterPyroscopeHandlers(client querierv1connect.QuerierServiceClient) { 240 handlers := querier.NewHTTPHandlers(client) 241 a.RegisterRoute("/pyroscope/render", http.HandlerFunc(handlers.Render), a.registerOptionsReadPath()...) 242 a.RegisterRoute("/pyroscope/render-diff", http.HandlerFunc(handlers.RenderDiff), a.registerOptionsReadPath()...) 243 a.RegisterRoute("/pyroscope/label-values", http.HandlerFunc(handlers.LabelValues), a.registerOptionsReadPath()...) 244 } 245 246 // RegisterIngester registers the endpoints associated with the ingester. 247 func (a *API) RegisterIngester(svc *ingester.Ingester) { 248 ingesterv1connect.RegisterIngesterServiceHandler(a.server.HTTP, svc, a.connectOptionsAuthRecovery()...) 249 } 250 251 func (a *API) RegisterReadyHandler(handler http.Handler) { 252 a.RegisterRoute("/ready", handler, WithMethod("GET")) 253 } 254 255 func (a *API) RegisterStoreGateway(svc *storegateway.StoreGateway) { 256 storegatewayv1connect.RegisterStoreGatewayServiceHandler(a.server.HTTP, svc, a.connectOptionsAuthRecovery()...) 257 258 a.indexPage.AddLinks(defaultWeight, "Store-gateway", []IndexPageLink{ 259 {Desc: "Ring status", Path: "/store-gateway/ring"}, 260 {Desc: "Tenants & Blocks", Path: "/store-gateway/tenants"}, 261 }) 262 a.RegisterRoute("/store-gateway/tenants", http.HandlerFunc(svc.RingHandler), a.registerOptionsRingPage()...) 263 a.RegisterRoute("/store-gateway/tenants", http.HandlerFunc(svc.TenantsHandler), a.registerOptionsPublicAccess()...) 264 a.RegisterRoute("/store-gateway/tenant/{tenant}/blocks", http.HandlerFunc(svc.BlocksHandler), a.registerOptionsPublicAccess()...) 265 } 266 267 // RegisterCompactor registers routes associated with the compactor. 268 func (a *API) RegisterCompactor(c *compactor.MultitenantCompactor) { 269 a.indexPage.AddLinks(defaultWeight, "Compactor", []IndexPageLink{ 270 {Desc: "Ring status", Path: "/compactor/ring"}, 271 }) 272 a.RegisterRoute("/compactor/ring", http.HandlerFunc(c.RingHandler), a.registerOptionsRingPage()...) 273 } 274 275 // RegisterFrontendForQuerierHandler registers the endpoints associated with the query frontend. 276 func (a *API) RegisterFrontendForQuerierHandler(frontendSvc *frontend.Frontend) { 277 frontendpbconnect.RegisterFrontendForQuerierHandler(a.server.HTTP, frontendSvc, a.connectOptionsAuthRecovery()...) 278 } 279 280 // RegisterVersion registers the endpoints associated with the versions service. 281 func (a *API) RegisterVersion(svc versionv1connect.VersionHandler) { 282 versionv1connect.RegisterVersionHandler(a.server.HTTP, svc, a.connectOptionsRecovery()...) 283 } 284 285 // RegisterQueryScheduler registers the endpoints associated with the query scheduler. 286 func (a *API) RegisterQueryScheduler(s *scheduler.Scheduler) { 287 schedulerpbconnect.RegisterSchedulerForFrontendHandler(a.server.HTTP, s, a.connectOptionsRecovery()...) 288 schedulerpbconnect.RegisterSchedulerForQuerierHandler(a.server.HTTP, s, a.connectOptionsRecovery()...) 289 } 290 291 // RegisterFlags registers api-related flags. 292 func (cfg *Config) RegisterFlags(fs *flag.FlagSet) { 293 fs.StringVar( 294 &cfg.BaseURL, 295 "api.base-url", 296 "", 297 "base URL for when the server is behind a reverse proxy with a different path", 298 ) 299 } 300 301 // AdminService is an interface for admin handlers (v1 and v2) 302 type AdminService interface { 303 TenantsHandler(w http.ResponseWriter, r *http.Request) 304 BlocksHandler(w http.ResponseWriter, r *http.Request) 305 BlockHandler(w http.ResponseWriter, r *http.Request) 306 DatasetHandler(w http.ResponseWriter, r *http.Request) 307 DatasetProfilesHandler(w http.ResponseWriter, r *http.Request) 308 ProfileDownloadHandler(w http.ResponseWriter, r *http.Request) 309 ProfileCallTreeHandler(w http.ResponseWriter, r *http.Request) 310 DatasetTSDBIndexHandler(w http.ResponseWriter, r *http.Request) 311 DatasetSymbolsHandler(w http.ResponseWriter, r *http.Request) 312 } 313 314 func (a *API) RegisterAdmin(ad AdminService) { 315 a.RegisterRoute("/ops/object-store/tenants", http.HandlerFunc(ad.TenantsHandler), a.registerOptionsPublicAccess()...) 316 a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks", http.HandlerFunc(ad.BlocksHandler), a.registerOptionsPublicAccess()...) 317 a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks/{block}", http.HandlerFunc(ad.BlockHandler), a.registerOptionsPublicAccess()...) 318 a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks/{block}/datasets", http.HandlerFunc(ad.DatasetHandler), a.registerOptionsPublicAccess()...) 319 a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks/{block}/datasets/profiles", http.HandlerFunc(ad.DatasetProfilesHandler), a.registerOptionsPublicAccess()...) 320 a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks/{block}/datasets/profiles/download", http.HandlerFunc(ad.ProfileDownloadHandler), a.registerOptionsPublicAccess()...) 321 a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks/{block}/datasets/profiles/call-tree", http.HandlerFunc(ad.ProfileCallTreeHandler), a.registerOptionsPublicAccess()...) 322 a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks/{block}/datasets/index", http.HandlerFunc(ad.DatasetTSDBIndexHandler), a.registerOptionsPublicAccess()...) 323 a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks/{block}/datasets/symbols", http.HandlerFunc(ad.DatasetSymbolsHandler), a.registerOptionsPublicAccess()...) 324 325 a.indexPage.AddLinks(defaultWeight, "Admin", []IndexPageLink{ 326 {Desc: "Object Storage Tenants & Blocks", Path: "/ops/object-store/tenants"}, 327 }) 328 } 329 330 func (a *API) RegisterAdHocProfiles(ahp *adhocprofiles.AdHocProfiles) { 331 adhocprofilesv1connect.RegisterAdHocProfileServiceHandler(a.server.HTTP, ahp, a.connectOptionsAuthLogRecovery()...) 332 }