Skip to content

Commit

Permalink
K8s: Refactor metrics to share k8s registry (grafana#79106)
Browse files Browse the repository at this point in the history
  • Loading branch information
toddtreece authored Dec 6, 2023
1 parent c631261 commit 2a2a132
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 36 deletions.
6 changes: 4 additions & 2 deletions pkg/api/http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ type HTTPServer struct {
authnService authn.Service
starApi *starApi.API
promRegister prometheus.Registerer
promGatherer prometheus.Gatherer
clientConfigProvider grafanaapiserver.DirectRestConfigProvider
namespacer request.NamespaceMapper
anonService anonymous.Service
Expand Down Expand Up @@ -256,7 +257,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
loginAttemptService loginAttempt.Service, orgService org.Service, teamService team.Service,
accesscontrolService accesscontrol.Service, navTreeService navtree.Service,
annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService, oauthTokenService oauthtoken.OAuthTokenService,
statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service,
statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service, promGatherer prometheus.Gatherer,
starApi *starApi.API, promRegister prometheus.Registerer, clientConfigProvider grafanaapiserver.DirectRestConfigProvider, anonService anonymous.Service,
) (*HTTPServer, error) {
web.Env = cfg.Env
Expand Down Expand Up @@ -356,6 +357,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
pluginsCDNService: pluginsCDNService,
starApi: starApi,
promRegister: promRegister,
promGatherer: promGatherer,
clientConfigProvider: clientConfigProvider,
namespacer: request.GetNamespaceMapper(cfg),
anonService: anonService,
Expand Down Expand Up @@ -720,7 +722,7 @@ func (hs *HTTPServer) metricsEndpoint(ctx *web.Context) {
}

promhttp.
HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true}).
HandlerFor(hs.promGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true}).
ServeHTTP(ctx.Resp, ctx.Req)
}

Expand Down
11 changes: 6 additions & 5 deletions pkg/cmd/grafana-server/commands/buildinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@ import (
"time"

"github.com/grafana/grafana/pkg/extensions"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/setting"
)

func setBuildInfo(opts ServerOptions) {
func getBuildstamp(opts ServerOptions) int64 {
buildstampInt64, err := strconv.ParseInt(opts.BuildStamp, 10, 64)
if err != nil || buildstampInt64 == 0 {
buildstampInt64 = time.Now().Unix()
}
return buildstampInt64
}

func setBuildInfo(opts ServerOptions) {
setting.BuildVersion = opts.Version
setting.BuildCommit = opts.Commit
setting.EnterpriseBuildCommit = opts.EnterpriseCommit
setting.BuildStamp = buildstampInt64
setting.BuildStamp = getBuildstamp(opts)
setting.BuildBranch = opts.BuildBranch
setting.IsEnterprise = extensions.IsEnterprise
setting.Packaging = validPackaging(Packaging)

metrics.SetBuildInformation(opts.Version, opts.Commit, opts.BuildBranch, buildstampInt64)
}
3 changes: 3 additions & 0 deletions pkg/cmd/grafana-server/commands/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/api"
gcli "github.com/grafana/grafana/pkg/cmd/grafana-cli/commands"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/infra/process"
"github.com/grafana/grafana/pkg/server"
_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
Expand Down Expand Up @@ -111,6 +112,8 @@ func RunServer(opts ServerOptions) error {
return err
}

metrics.SetBuildInformation(metrics.ProvideRegisterer(cfg), opts.Version, opts.Commit, opts.BuildBranch, getBuildstamp(opts))

s, err := server.Initialize(
cfg,
server.Options{
Expand Down
3 changes: 3 additions & 0 deletions pkg/cmd/grafana-server/commands/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/grafana/grafana/pkg/api"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/server"
"github.com/grafana/grafana/pkg/setting"
)
Expand Down Expand Up @@ -88,6 +89,8 @@ func RunTargetServer(opts ServerOptions) error {
return err
}

metrics.SetBuildInformation(metrics.ProvideRegisterer(cfg), opts.Version, opts.Commit, opts.BuildBranch, getBuildstamp(opts))

s, err := server.InitializeModuleServer(
cfg,
server.Options{
Expand Down
18 changes: 9 additions & 9 deletions pkg/infra/metrics/frontendmetrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type FrontendMetricsRecorder func(event FrontendMetricEvent)
// FrontendMetrics contains all the valid frontend metrics and a handler function for recording events
var FrontendMetrics map[string]FrontendMetricsRecorder = map[string]FrontendMetricsRecorder{}

func registerFrontendHistogram(name string, help string) {
func registerFrontendHistogram(reg prometheus.Registerer, name string, help string) {
defBuckets := []float64{.1, .25, .5, 1, 1.5, 2, 5, 10, 20, 40}

histogram := prometheus.NewHistogram(prometheus.HistogramOpts{
Expand All @@ -33,14 +33,14 @@ func registerFrontendHistogram(name string, help string) {
histogram.Observe(event.Value)
}

prometheus.MustRegister(histogram)
reg.MustRegister(histogram)
}

func initFrontendMetrics() {
registerFrontendHistogram("frontend_boot_load_time_seconds", "Frontend boot time measurement")
registerFrontendHistogram("frontend_boot_first_paint_time_seconds", "Frontend boot first paint")
registerFrontendHistogram("frontend_boot_first_contentful_paint_time_seconds", "Frontend boot first contentful paint")
registerFrontendHistogram("frontend_boot_js_done_time_seconds", "Frontend boot initial js load")
registerFrontendHistogram("frontend_boot_css_time_seconds", "Frontend boot initial css load")
registerFrontendHistogram("frontend_plugins_preload_ms", "Frontend preload plugin time measurement")
func initFrontendMetrics(r prometheus.Registerer) {
registerFrontendHistogram(r, "frontend_boot_load_time_seconds", "Frontend boot time measurement")
registerFrontendHistogram(r, "frontend_boot_first_paint_time_seconds", "Frontend boot first paint")
registerFrontendHistogram(r, "frontend_boot_first_contentful_paint_time_seconds", "Frontend boot first contentful paint")
registerFrontendHistogram(r, "frontend_boot_js_done_time_seconds", "Frontend boot initial js load")
registerFrontendHistogram(r, "frontend_boot_css_time_seconds", "Frontend boot initial css load")
registerFrontendHistogram(r, "frontend_plugins_preload_ms", "Frontend preload plugin time measurement")
}
12 changes: 6 additions & 6 deletions pkg/infra/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ func init() {
}

// SetBuildInformation sets the build information for this binary
func SetBuildInformation(version, revision, branch string, buildTimestamp int64) {
func SetBuildInformation(reg prometheus.Registerer, version, revision, branch string, buildTimestamp int64) {
edition := "oss"
if setting.IsEnterprise {
edition = "enterprise"
Expand All @@ -631,15 +631,15 @@ func SetBuildInformation(version, revision, branch string, buildTimestamp int64)
Namespace: ExporterName,
}, []string{"version", "revision", "branch", "goversion", "edition"})

prometheus.MustRegister(grafanaBuildVersion, grafanaBuildTimestamp)
reg.MustRegister(grafanaBuildVersion, grafanaBuildTimestamp)

grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version(), edition).Set(1)
grafanaBuildTimestamp.WithLabelValues(version, revision, branch, runtime.Version(), edition).Set(float64(buildTimestamp))
}

// SetEnvironmentInformation exposes environment values provided by the operators as an `_info` metric.
// If there are no environment metrics labels configured, this metric will not be exposed.
func SetEnvironmentInformation(labels map[string]string) error {
func SetEnvironmentInformation(reg prometheus.Registerer, labels map[string]string) error {
if len(labels) == 0 {
return nil
}
Expand All @@ -651,7 +651,7 @@ func SetEnvironmentInformation(labels map[string]string) error {
ConstLabels: labels,
})

prometheus.MustRegister(grafanaEnvironmentInfo)
reg.MustRegister(grafanaEnvironmentInfo)

grafanaEnvironmentInfo.Set(1)
return nil
Expand All @@ -661,8 +661,8 @@ func SetPluginBuildInformation(pluginID, pluginType, version, signatureStatus st
grafanaPluginBuildInfoDesc.WithLabelValues(pluginID, pluginType, version, signatureStatus).Set(1)
}

func initMetricVars() {
prometheus.MustRegister(
func initMetricVars(reg prometheus.Registerer) {
reg.MustRegister(
MInstanceStart,
MPageStatus,
MApiStatus,
Expand Down
81 changes: 73 additions & 8 deletions pkg/infra/metrics/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ package metrics

import (
"context"
"errors"
"regexp"

"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"k8s.io/component-base/metrics/legacyregistry"

"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics/graphitebridge"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
)

var metricsLogger log.Logger = log.New("metrics")
Expand All @@ -19,12 +25,10 @@ func (lw *logWrapper) Println(v ...any) {
lw.logger.Info("graphite metric bridge", v...)
}

func init() {
initMetricVars()
initFrontendMetrics()
}
func ProvideService(cfg *setting.Cfg, reg prometheus.Registerer) (*InternalMetricsService, error) {
initMetricVars(reg)
initFrontendMetrics(reg)

func ProvideService(cfg *setting.Cfg) (*InternalMetricsService, error) {
s := &InternalMetricsService{
Cfg: cfg,
}
Expand Down Expand Up @@ -55,5 +59,66 @@ func (im *InternalMetricsService) Run(ctx context.Context) error {
return ctx.Err()
}

func ProvideRegisterer() prometheus.Registerer { return prometheus.DefaultRegisterer }
func ProvideRegistererForTest() prometheus.Registerer { return prometheus.NewRegistry() }
func ProvideRegisterer(cfg *setting.Cfg) prometheus.Registerer {
if cfg.IsFeatureToggleEnabled(featuremgmt.FlagGrafanaAPIServer) {
return legacyregistry.Registerer()
}
return prometheus.DefaultRegisterer
}

func ProvideGatherer(cfg *setting.Cfg) prometheus.Gatherer {
if cfg.IsFeatureToggleEnabled(featuremgmt.FlagGrafanaAPIServer) {
return newAddPrefixWrapper(legacyregistry.DefaultGatherer)
}
return prometheus.DefaultGatherer
}

func ProvideRegistererForTest() prometheus.Registerer {
return prometheus.NewRegistry()
}

func ProvideGathererForTest(reg prometheus.Registerer) prometheus.Gatherer {
// the registerer provided by ProvideRegistererForTest
// is a *prometheus.Registry, so it also implements prometheus.Gatherer
return reg.(*prometheus.Registry)
}

var _ prometheus.Gatherer = (*addPrefixWrapper)(nil)

// addPrefixWrapper wraps a prometheus.Gatherer, and ensures that all metric names are prefixed with `grafana_`.
// metrics with the prefix `grafana_` or `go_` are not modified.
type addPrefixWrapper struct {
orig prometheus.Gatherer
reg *regexp.Regexp
}

func newAddPrefixWrapper(orig prometheus.Gatherer) *addPrefixWrapper {
return &addPrefixWrapper{
orig: orig,
reg: regexp.MustCompile("^((?:grafana_|go_).*)"),
}
}

func (g *addPrefixWrapper) Gather() ([]*dto.MetricFamily, error) {
mf, err := g.orig.Gather()
if err != nil {
return nil, err
}

names := make(map[string]struct{})

for i := 0; i < len(mf); i++ {
m := mf[i]
if m.Name != nil && !g.reg.MatchString(*m.Name) {
*m.Name = "grafana_" + *m.Name
// since we are modifying the name, we need to check for duplicates in the gatherer
if _, exists := names[*m.Name]; exists {
return nil, errors.New("duplicate metric name: " + *m.Name)
}
}
// keep track of names to detect duplicates
names[*m.Name] = struct{}{}
}

return mf, nil
}
61 changes: 61 additions & 0 deletions pkg/infra/metrics/service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package metrics

import (
"testing"

dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/require"
)

func TestK8sGathererWrapper_Gather(t *testing.T) {
orig := &mockGatherer{}
g := newAddPrefixWrapper(orig)

t.Run("metrics with grafana and go prefix are not modified", func(t *testing.T) {
originalMF := []*dto.MetricFamily{
{Name: strptr("grafana_metric1")},
{Name: strptr("metric2")},
{Name: strptr("go_metric1")},
}

orig.GatherFunc = func() ([]*dto.MetricFamily, error) {
return originalMF, nil
}

expectedMF := []*dto.MetricFamily{
{Name: strptr("grafana_metric1")},
{Name: strptr("grafana_metric2")},
{Name: strptr("go_metric1")},
}

mf, err := g.Gather()
require.NoError(t, err)
require.Equal(t, expectedMF, mf)
})

t.Run("duplicate metrics result in an error", func(t *testing.T) {
originalMF := []*dto.MetricFamily{
{Name: strptr("grafana_metric1")},
{Name: strptr("metric1")},
}

orig.GatherFunc = func() ([]*dto.MetricFamily, error) {
return originalMF, nil
}

_, err := g.Gather()
require.Error(t, err)
})
}

type mockGatherer struct {
GatherFunc func() ([]*dto.MetricFamily, error)
}

func (m *mockGatherer) Gather() ([]*dto.MetricFamily, error) {
return m.GatherFunc()
}

func strptr(s string) *string {
return &s
}
17 changes: 17 additions & 0 deletions pkg/infra/metrics/wireset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package metrics

import (
"github.com/google/wire"
)

var WireSet = wire.NewSet(
ProvideService,
ProvideRegisterer,
ProvideGatherer,
)

var WireSetForTest = wire.NewSet(
ProvideService,
ProvideRegistererForTest,
ProvideGathererForTest,
)
Loading

0 comments on commit 2a2a132

Please sign in to comment.