Chore: Add html_handler_requests metric (#113991)

* Chore: Add html_handler_requests metric for tracking requests handled by index.go

* make a member of HttpServer

* make it a histogram instead

* update test
This commit is contained in:
Josh Hunt
2025-11-19 16:40:54 -05:00
committed by GitHub
parent fabb4a603d
commit a4e1db66d3
4 changed files with 116 additions and 56 deletions
+30 -20
View File
@@ -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")
}
+12
View File
@@ -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)
+6
View File
@@ -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)
+68 -36
View File
@@ -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 {