diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 2d3bd11ab37..fa0b407aa2b 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -36,6 +36,7 @@ import ( "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/metrics/metricutil" "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login/social" @@ -202,26 +203,27 @@ type HTTPServer struct { pluginsCDNService *pluginscdn.Service managedPluginsService managedplugins.Manager - userService user.Service - tempUserService tempUser.Service - loginAttemptService loginAttempt.Service - orgService org.Service - orgDeletionService org.DeletionService - TeamService team.Service - accesscontrolService accesscontrol.Service - annotationsRepo annotations.Repository - tagService tag.Service - oauthTokenService oauthtoken.OAuthTokenService - statsService stats.Service - authnService authn.Service - starApi *starApi.API - promRegister prometheus.Registerer - promGatherer prometheus.Gatherer - clientConfigProvider grafanaapiserver.DirectRestConfigProvider - namespacer request.NamespaceMapper - anonService anonymous.Service - userVerifier user.Verifier - tlsCerts TLSCerts + userService user.Service + tempUserService tempUser.Service + loginAttemptService loginAttempt.Service + orgService org.Service + orgDeletionService org.DeletionService + TeamService team.Service + accesscontrolService accesscontrol.Service + annotationsRepo annotations.Repository + tagService tag.Service + oauthTokenService oauthtoken.OAuthTokenService + statsService stats.Service + authnService authn.Service + starApi *starApi.API + promRegister prometheus.Registerer + promGatherer prometheus.Gatherer + clientConfigProvider grafanaapiserver.DirectRestConfigProvider + namespacer request.NamespaceMapper + anonService anonymous.Service + userVerifier user.Verifier + tlsCerts TLSCerts + htmlHandlerRequestsDuration *prometheus.HistogramVec } type TLSCerts struct { @@ -375,7 +377,15 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi namespacer: request.GetNamespaceMapper(cfg), anonService: anonService, userVerifier: userVerifier, + htmlHandlerRequestsDuration: metricutil.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: "grafana", + Name: "html_handler_requests_duration_seconds", + Help: "Duration of requests handled by the index.go HTML handler", + }, []string{"handler"}), } + + promRegister.MustRegister(hs.htmlHandlerRequestsDuration) + if hs.Listener != nil { hs.log.Debug("Using provided listener") } diff --git a/pkg/api/index.go b/pkg/api/index.go index 1f85f2b0dbe..afbebb3b013 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -8,11 +8,13 @@ import ( "html/template" "net/http" "strings" + "time" claims "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/webassets" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/infra/metrics/metricutil" "github.com/grafana/grafana/pkg/middleware" ac "github.com/grafana/grafana/pkg/services/accesscontrol" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" @@ -272,6 +274,11 @@ func (hs *HTTPServer) Index(c *contextmodel.ReqContext) { c, span := hs.injectSpan(c, "api.Index") defer span.End() + start := time.Now() + defer func() { + metricutil.ObserveWithExemplar(c.Req.Context(), hs.htmlHandlerRequestsDuration.WithLabelValues("index"), time.Since(start).Seconds()) + }() + data, err := hs.setIndexViewData(c) if err != nil { c.Handle(hs.Cfg, http.StatusInternalServerError, "Failed to get settings", err) @@ -286,6 +293,11 @@ func (hs *HTTPServer) NotFoundHandler(c *contextmodel.ReqContext) { return } + start := time.Now() + defer func() { + metricutil.ObserveWithExemplar(c.Req.Context(), hs.htmlHandlerRequestsDuration.WithLabelValues("not_found"), time.Since(start).Seconds()) + }() + data, err := hs.setIndexViewData(c) if err != nil { c.Handle(hs.Cfg, http.StatusInternalServerError, "Failed to get settings", err) diff --git a/pkg/api/login.go b/pkg/api/login.go index 60b894d0c49..89f452cd103 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -9,11 +9,13 @@ import ( "net/url" "regexp" "strings" + "time" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/infra/metrics/metricutil" "github.com/grafana/grafana/pkg/infra/network" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware/cookies" @@ -101,6 +103,10 @@ func (hs *HTTPServer) LoginView(c *contextmodel.ReqContext) { return } + start := time.Now() + defer func() { + metricutil.ObserveWithExemplar(c.Req.Context(), hs.htmlHandlerRequestsDuration.WithLabelValues("login"), time.Since(start).Seconds()) + }() viewData, err := setIndexViewData(hs, c) if err != nil { c.Handle(hs.Cfg, http.StatusInternalServerError, "Failed to get settings", err) diff --git a/pkg/api/login_test.go b/pkg/api/login_test.go index 2407876995e..59a539608a7 100644 --- a/pkg/api/login_test.go +++ b/pkg/api/login_test.go @@ -12,6 +12,7 @@ import ( "strings" "testing" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -22,6 +23,7 @@ import ( "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/metrics/metricutil" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/models/usertoken" "github.com/grafana/grafana/pkg/services/auth/authtest" @@ -44,6 +46,18 @@ import ( const loginCookieName = "grafana_session" +// setupHTMLHandlerMetrics creates and registers the prometheus metrics needed for HTTPServer tests +// that call methods using htmlHandlerRequestsDuration (LoginView, Index, NotFoundHandler) +func setupHTMLHandlerMetrics() (prometheus.Registerer, *prometheus.HistogramVec) { + promRegister := prometheus.NewRegistry() + htmlHandlerRequestsDuration := metricutil.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: "grafana", + Name: "html_handler_requests_duration_seconds", + }, []string{"handler"}) + promRegister.MustRegister(htmlHandlerRequestsDuration) + return promRegister, htmlHandlerRequestsDuration +} + func fakeSetIndexViewData(t *testing.T) { origSetIndexViewData := setIndexViewData t.Cleanup(func() { @@ -109,13 +123,16 @@ func TestLoginErrorCookieAPIEndpoint(t *testing.T) { sc := setupScenarioContext(t, "/login") cfg := setting.NewCfg() secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) + promRegister, htmlHandlerRequestsDuration := setupHTMLHandlerMetrics() hs := &HTTPServer{ - Cfg: cfg, - SettingsProvider: &setting.OSSImpl{Cfg: cfg}, - License: &licensing.OSSLicensingService{}, - SocialService: &mockSocialService{}, - SecretsService: secretsService, - Features: featuremgmt.WithFeatures(), + Cfg: cfg, + SettingsProvider: &setting.OSSImpl{Cfg: cfg}, + License: &licensing.OSSLicensingService{}, + SocialService: &mockSocialService{}, + SecretsService: secretsService, + Features: featuremgmt.WithFeatures(), + promRegister: promRegister, + htmlHandlerRequestsDuration: htmlHandlerRequestsDuration, } sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response { @@ -156,13 +173,16 @@ func TestLoginViewRedirect(t *testing.T) { fakeViewIndex(t) sc := setupScenarioContext(t, "/login") cfg := setting.NewCfg() + promRegister, htmlHandlerRequestsDuration := setupHTMLHandlerMetrics() hs := &HTTPServer{ - Cfg: cfg, - SettingsProvider: &setting.OSSImpl{Cfg: cfg}, - License: &licensing.OSSLicensingService{}, - SocialService: &mockSocialService{}, - Features: featuremgmt.WithFeatures(), - log: log.NewNopLogger(), + Cfg: cfg, + SettingsProvider: &setting.OSSImpl{Cfg: cfg}, + License: &licensing.OSSLicensingService{}, + SocialService: &mockSocialService{}, + Features: featuremgmt.WithFeatures(), + log: log.NewNopLogger(), + promRegister: promRegister, + htmlHandlerRequestsDuration: htmlHandlerRequestsDuration, } hs.Cfg.CookieSecure = true @@ -492,13 +512,16 @@ func TestLoginOAuthRedirect(t *testing.T) { }, oAuthInfos: oAuthInfos, } + promRegister, htmlHandlerRequestsDuration := setupHTMLHandlerMetrics() hs := &HTTPServer{ - authnService: &authntest.FakeService{}, - Cfg: cfg, - SettingsProvider: &setting.OSSImpl{Cfg: cfg}, - License: &licensing.OSSLicensingService{}, - SocialService: mock, - Features: featuremgmt.WithFeatures(), + authnService: &authntest.FakeService{}, + Cfg: cfg, + SettingsProvider: &setting.OSSImpl{Cfg: cfg}, + License: &licensing.OSSLicensingService{}, + SocialService: mock, + Features: featuremgmt.WithFeatures(), + promRegister: promRegister, + htmlHandlerRequestsDuration: htmlHandlerRequestsDuration, } sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response { @@ -521,11 +544,14 @@ func TestLoginInternal(t *testing.T) { fakeViewIndex(t) sc := setupScenarioContext(t, "/login") + promRegister, htmlHandlerRequestsDuration := setupHTMLHandlerMetrics() hs := &HTTPServer{ - Cfg: setting.NewCfg(), - License: &licensing.OSSLicensingService{}, - log: log.New("test"), - Features: featuremgmt.WithFeatures(), + Cfg: setting.NewCfg(), + License: &licensing.OSSLicensingService{}, + log: log.New("test"), + Features: featuremgmt.WithFeatures(), + promRegister: promRegister, + htmlHandlerRequestsDuration: htmlHandlerRequestsDuration, } sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response { @@ -583,14 +609,17 @@ func TestAuthProxyLoginWithEnableLoginTokenAndEnabledOauthAutoLogin(t *testing.T sc := setupScenarioContext(t, "/login") sc.cfg.LoginCookieName = loginCookieName sc.cfg.OAuthAutoLogin = true + promRegister, htmlHandlerRequestsDuration := setupHTMLHandlerMetrics() hs := &HTTPServer{ - Cfg: sc.cfg, - SettingsProvider: &setting.OSSImpl{Cfg: sc.cfg}, - License: &licensing.OSSLicensingService{}, - AuthTokenService: authtest.NewFakeUserAuthTokenService(), - log: log.New("hello"), - SocialService: mock, - Features: featuremgmt.WithFeatures(), + Cfg: sc.cfg, + SettingsProvider: &setting.OSSImpl{Cfg: sc.cfg}, + License: &licensing.OSSLicensingService{}, + AuthTokenService: authtest.NewFakeUserAuthTokenService(), + log: log.New("hello"), + SocialService: mock, + Features: featuremgmt.WithFeatures(), + promRegister: promRegister, + htmlHandlerRequestsDuration: htmlHandlerRequestsDuration, } sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response { @@ -623,14 +652,17 @@ func setupAuthProxyLoginTest(t *testing.T, enableLoginToken bool) *scenarioConte sc := setupScenarioContext(t, "/login") sc.cfg.LoginCookieName = loginCookieName + promRegister, htmlHandlerRequestsDuration := setupHTMLHandlerMetrics() hs := &HTTPServer{ - Cfg: sc.cfg, - SettingsProvider: &setting.OSSImpl{Cfg: sc.cfg}, - License: &licensing.OSSLicensingService{}, - AuthTokenService: authtest.NewFakeUserAuthTokenService(), - log: log.New("hello"), - SocialService: &mockSocialService{}, - Features: featuremgmt.WithFeatures(), + Cfg: sc.cfg, + SettingsProvider: &setting.OSSImpl{Cfg: sc.cfg}, + License: &licensing.OSSLicensingService{}, + AuthTokenService: authtest.NewFakeUserAuthTokenService(), + log: log.New("hello"), + SocialService: &mockSocialService{}, + Features: featuremgmt.WithFeatures(), + promRegister: promRegister, + htmlHandlerRequestsDuration: htmlHandlerRequestsDuration, } sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response {