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  }