mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PublicDashboards: metrics collected in background service (#65836)
This commit is contained in:
parent
1ed75f9709
commit
92e591d2e1
@ -24,6 +24,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
plugindashboardsservice "github.com/grafana/grafana/pkg/services/plugindashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
publicdashboardsmetric "github.com/grafana/grafana/pkg/services/publicdashboards/metric"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2"
|
||||
secretsMigrations "github.com/grafana/grafana/pkg/services/secrets/kvstore/migrations"
|
||||
@ -50,6 +51,7 @@ func ProvideBackgroundServiceRegistry(
|
||||
saService *samanager.ServiceAccountsService, authInfoService *authinfoservice.Implementation,
|
||||
grpcServerProvider grpcserver.Provider, secretMigrationProvider secretsMigrations.SecretMigrationProvider, loginAttemptService *loginattemptimpl.Service,
|
||||
bundleService *supportbundlesimpl.Service,
|
||||
publicDashboardsMetric *publicdashboardsmetric.Service,
|
||||
// Need to make sure these are initialized, is there a better place to put them?
|
||||
_ dashboardsnapshots.Service, _ *alerting.AlertNotificationService,
|
||||
_ serviceaccounts.Service, _ *guardian.Provider,
|
||||
@ -86,6 +88,7 @@ func ProvideBackgroundServiceRegistry(
|
||||
secretMigrationProvider,
|
||||
loginAttemptService,
|
||||
bundleService,
|
||||
publicDashboardsMetric,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -96,6 +96,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||
publicdashboardsApi "github.com/grafana/grafana/pkg/services/publicdashboards/api"
|
||||
publicdashboardsStore "github.com/grafana/grafana/pkg/services/publicdashboards/database"
|
||||
publicdashboardsmetric "github.com/grafana/grafana/pkg/services/publicdashboards/metric"
|
||||
publicdashboardsService "github.com/grafana/grafana/pkg/services/publicdashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/query"
|
||||
"github.com/grafana/grafana/pkg/services/queryhistory"
|
||||
@ -327,6 +328,7 @@ var wireBasicSet = wire.NewSet(
|
||||
wire.Bind(new(publicdashboards.Service), new(*publicdashboardsService.PublicDashboardServiceImpl)),
|
||||
publicdashboardsStore.ProvideStore,
|
||||
wire.Bind(new(publicdashboards.Store), new(*publicdashboardsStore.PublicDashboardStoreImpl)),
|
||||
publicdashboardsmetric.ProvideService,
|
||||
publicdashboardsApi.ProvideApi,
|
||||
starApi.ProvideApi,
|
||||
userimpl.ProvideService,
|
||||
|
@ -285,3 +285,17 @@ func (d *PublicDashboardStoreImpl) FindByDashboardFolder(ctx context.Context, da
|
||||
|
||||
return pubdashes, nil
|
||||
}
|
||||
|
||||
func (d *PublicDashboardStoreImpl) GetMetrics(ctx context.Context) (*Metrics, error) {
|
||||
metrics := &Metrics{
|
||||
TotalPublicDashboards: []*TotalPublicDashboard{},
|
||||
}
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
return sess.SQL("SELECT COUNT(*) as total_count, is_enabled, share as share_type FROM dashboard_public GROUP BY is_enabled, share").Find(&metrics.TotalPublicDashboards)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return metrics, nil
|
||||
}
|
||||
|
@ -47,9 +47,9 @@ func TestIntegrationListPublicDashboard(t *testing.T) {
|
||||
cDash := insertTestDashboard(t, dashboardStore, "c", orgId, 0, true)
|
||||
|
||||
// these are in order of how they should be returned from ListPUblicDashboards
|
||||
a := insertPublicDashboard(t, publicdashboardStore, bDash.UID, orgId, true)
|
||||
b := insertPublicDashboard(t, publicdashboardStore, cDash.UID, orgId, true)
|
||||
c := insertPublicDashboard(t, publicdashboardStore, aDash.UID, orgId, false)
|
||||
a := insertPublicDashboard(t, publicdashboardStore, bDash.UID, orgId, true, PublicShareType)
|
||||
b := insertPublicDashboard(t, publicdashboardStore, cDash.UID, orgId, true, PublicShareType)
|
||||
c := insertPublicDashboard(t, publicdashboardStore, aDash.UID, orgId, false, PublicShareType)
|
||||
|
||||
// this is case that can happen as of now, however, postgres and mysql sort
|
||||
// null in the exact opposite fashion and there is no shared syntax to sort
|
||||
@ -57,7 +57,7 @@ func TestIntegrationListPublicDashboard(t *testing.T) {
|
||||
//d := insertPublicDashboard(t, publicdashboardStore, "missing", orgId, false)
|
||||
|
||||
// should not be included in response
|
||||
_ = insertPublicDashboard(t, publicdashboardStore, "wrongOrgId", 777, false)
|
||||
_ = insertPublicDashboard(t, publicdashboardStore, "wrongOrgId", 777, false, PublicShareType)
|
||||
|
||||
resp, err := publicdashboardStore.FindAll(context.Background(), orgId)
|
||||
require.NoError(t, err)
|
||||
@ -258,7 +258,7 @@ func TestIntegrationFindByDashboardUid(t *testing.T) {
|
||||
|
||||
t.Run("returns public dashboard by dashboardUid", func(t *testing.T) {
|
||||
setup()
|
||||
savedPubdash := insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, false)
|
||||
savedPubdash := insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, false, PublicShareType)
|
||||
pubdash, err := publicdashboardStore.FindByDashboardUid(context.Background(), savedDashboard.OrgID, savedDashboard.UID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, savedPubdash, pubdash)
|
||||
@ -325,7 +325,7 @@ func TestIntegrationFindByAccessToken(t *testing.T) {
|
||||
|
||||
t.Run("returns public dashboard by accessToken", func(t *testing.T) {
|
||||
setup()
|
||||
savedPubdash := insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, false)
|
||||
savedPubdash := insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, false, PublicShareType)
|
||||
pubdash, err := publicdashboardStore.FindByAccessToken(context.Background(), savedPubdash.AccessToken)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, savedPubdash, pubdash)
|
||||
@ -392,7 +392,7 @@ func TestIntegrationCreatePublicDashboard(t *testing.T) {
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, true)
|
||||
insertPublicDashboard(t, publicdashboardStore, savedDashboard2.UID, savedDashboard2.OrgID, false)
|
||||
insertPublicDashboard(t, publicdashboardStore, savedDashboard2.UID, savedDashboard2.OrgID, false, PublicShareType)
|
||||
}
|
||||
|
||||
t.Run("saves new public dashboard", func(t *testing.T) {
|
||||
@ -646,7 +646,7 @@ func TestIntegrationDelete(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
savedPublicDashboard = insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, true)
|
||||
savedPublicDashboard = insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, true, PublicShareType)
|
||||
}
|
||||
|
||||
t.Run("Delete success", func(t *testing.T) {
|
||||
@ -697,10 +697,10 @@ func TestGetDashboardByFolder(t *testing.T) {
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
pubdashStore := ProvideStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "title", 1, 1, true)
|
||||
pubdash := insertPublicDashboard(t, pubdashStore, dashboard.UID, dashboard.OrgID, true)
|
||||
dashboard2 := insertTestDashboard(t, dashboardStore, "title", 1, 2, true)
|
||||
_ = insertPublicDashboard(t, pubdashStore, dashboard2.UID, dashboard2.OrgID, true)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "title", 1, 1, true, PublicShareType)
|
||||
pubdash := insertPublicDashboard(t, pubdashStore, dashboard.UID, dashboard.OrgID, true, PublicShareType)
|
||||
dashboard2 := insertTestDashboard(t, dashboardStore, "title", 1, 2, true, PublicShareType)
|
||||
_ = insertPublicDashboard(t, pubdashStore, dashboard2.UID, dashboard2.OrgID, true, PublicShareType)
|
||||
|
||||
pubdashes, err := pubdashStore.FindByDashboardFolder(context.Background(), dashboard)
|
||||
|
||||
@ -710,6 +710,69 @@ func TestGetDashboardByFolder(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetMetrics(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
var sqlStore db.DB
|
||||
var cfg *setting.Cfg
|
||||
var dashboardStore dashboards.Store
|
||||
var publicdashboardStore *PublicDashboardStoreImpl
|
||||
var savedDashboard *dashboards.Dashboard
|
||||
var savedDashboard2 *dashboards.Dashboard
|
||||
var savedDashboard3 *dashboards.Dashboard
|
||||
var savedDashboard4 *dashboards.Dashboard
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}})
|
||||
quotaService := quotatest.New(false, nil)
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, false)
|
||||
savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, false)
|
||||
savedDashboard3 = insertTestDashboard(t, dashboardStore, "testDashie3", 2, 0, false)
|
||||
savedDashboard4 = insertTestDashboard(t, dashboardStore, "testDashie4", 2, 0, false)
|
||||
insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, true, PublicShareType)
|
||||
insertPublicDashboard(t, publicdashboardStore, savedDashboard2.UID, savedDashboard2.OrgID, true, PublicShareType)
|
||||
insertPublicDashboard(t, publicdashboardStore, savedDashboard3.UID, savedDashboard3.OrgID, true, EmailShareType)
|
||||
insertPublicDashboard(t, publicdashboardStore, savedDashboard4.UID, savedDashboard4.OrgID, false, EmailShareType)
|
||||
}
|
||||
|
||||
t.Run("returns correct list of metrics", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
metrics, err := publicdashboardStore.GetMetrics(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 3, len(metrics.TotalPublicDashboards))
|
||||
enabledAndPublicCount := -1
|
||||
enabledAndEmailCount := -1
|
||||
disabledAndEmailCount := -1
|
||||
disabledAndPublicCount := -1
|
||||
for _, metric := range metrics.TotalPublicDashboards {
|
||||
if metric.ShareType == string(PublicShareType) && metric.IsEnabled {
|
||||
enabledAndPublicCount = int(metric.TotalCount)
|
||||
}
|
||||
if metric.ShareType == string(PublicShareType) && !metric.IsEnabled {
|
||||
disabledAndPublicCount = int(metric.TotalCount)
|
||||
}
|
||||
if metric.ShareType == string(EmailShareType) && metric.IsEnabled {
|
||||
enabledAndEmailCount = int(metric.TotalCount)
|
||||
}
|
||||
if metric.ShareType == string(EmailShareType) && !metric.IsEnabled {
|
||||
disabledAndEmailCount = int(metric.TotalCount)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, 2, enabledAndPublicCount)
|
||||
assert.Equal(t, 1, enabledAndEmailCount)
|
||||
assert.Equal(t, 1, disabledAndEmailCount)
|
||||
assert.Equal(t, -1, disabledAndPublicCount)
|
||||
})
|
||||
}
|
||||
|
||||
// helper function to insert a dashboard
|
||||
func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64,
|
||||
folderId int64, isFolder bool, tags ...interface{}) *dashboards.Dashboard {
|
||||
@ -733,7 +796,7 @@ func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title st
|
||||
}
|
||||
|
||||
// helper function to insert a public dashboard
|
||||
func insertPublicDashboard(t *testing.T, publicdashboardStore *PublicDashboardStoreImpl, dashboardUid string, orgId int64, isEnabled bool) *PublicDashboard {
|
||||
func insertPublicDashboard(t *testing.T, publicdashboardStore *PublicDashboardStoreImpl, dashboardUid string, orgId int64, isEnabled bool, shareType ShareType) *PublicDashboard {
|
||||
ctx := context.Background()
|
||||
|
||||
uid := util.GenerateShortUID()
|
||||
@ -751,6 +814,7 @@ func insertPublicDashboard(t *testing.T, publicdashboardStore *PublicDashboardSt
|
||||
CreatedBy: 1,
|
||||
CreatedAt: time.Now(),
|
||||
AccessToken: accessToken,
|
||||
Share: shareType,
|
||||
},
|
||||
}
|
||||
|
||||
|
74
pkg/services/publicdashboards/metric/metric.go
Normal file
74
pkg/services/publicdashboards/metric/metric.go
Normal file
@ -0,0 +1,74 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
store publicdashboards.Store
|
||||
Metrics *Metrics
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func ProvideService(
|
||||
store publicdashboards.Store,
|
||||
prom prometheus.Registerer,
|
||||
) (*Service, error) {
|
||||
s := &Service{
|
||||
store: store,
|
||||
Metrics: newMetrics(),
|
||||
log: log.New("publicdashboards.metric"),
|
||||
}
|
||||
|
||||
if err := s.registerMetrics(prom); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Service) registerMetrics(prom prometheus.Registerer) error {
|
||||
err := prom.Register(s.Metrics.PublicDashboardsAmount)
|
||||
var alreadyRegisterErr prometheus.AlreadyRegisteredError
|
||||
if errors.As(err, &alreadyRegisterErr) {
|
||||
if alreadyRegisterErr.ExistingCollector == alreadyRegisterErr.NewCollector {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) Run(ctx context.Context) error {
|
||||
s.recordMetrics(ctx)
|
||||
|
||||
ticker := time.NewTicker(12 * time.Hour)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
s.recordMetrics(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) recordMetrics(ctx context.Context) {
|
||||
records, err := s.store.GetMetrics(ctx)
|
||||
if err != nil {
|
||||
s.log.Error("error collecting background metrics", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
s.Metrics.PublicDashboardsAmount.Reset()
|
||||
for _, r := range records.TotalPublicDashboards {
|
||||
s.Metrics.PublicDashboardsAmount.WithLabelValues(strconv.FormatBool(r.IsEnabled), r.ShareType).Set(r.TotalCount)
|
||||
}
|
||||
}
|
23
pkg/services/publicdashboards/metric/metrics.go
Normal file
23
pkg/services/publicdashboards/metric/metrics.go
Normal file
@ -0,0 +1,23 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
namespace = "grafana"
|
||||
)
|
||||
|
||||
type Metrics struct {
|
||||
PublicDashboardsAmount *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
func newMetrics() *Metrics {
|
||||
return &Metrics{
|
||||
PublicDashboardsAmount: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "public_dashboards_amount",
|
||||
Help: "Total amount of public dashboards",
|
||||
}, []string{"is_enabled", "share_type"}),
|
||||
}
|
||||
}
|
11
pkg/services/publicdashboards/models/metrics.go
Normal file
11
pkg/services/publicdashboards/models/metrics.go
Normal file
@ -0,0 +1,11 @@
|
||||
package models
|
||||
|
||||
type TotalPublicDashboard struct {
|
||||
TotalCount float64
|
||||
IsEnabled bool
|
||||
ShareType string
|
||||
}
|
||||
|
||||
type Metrics struct {
|
||||
TotalPublicDashboards []*TotalPublicDashboard
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.16.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.14.0. DO NOT EDIT.
|
||||
|
||||
package publicdashboards
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.16.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.14.0. DO NOT EDIT.
|
||||
|
||||
package publicdashboards
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.16.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.14.0. DO NOT EDIT.
|
||||
|
||||
package publicdashboards
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
|
||||
dashboards "github.com/grafana/grafana/pkg/services/dashboards"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
models "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
)
|
||||
|
||||
@ -238,6 +237,29 @@ func (_m *FakePublicDashboardStore) FindDashboard(ctx context.Context, orgId int
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetMetrics provides a mock function with given fields: ctx
|
||||
func (_m *FakePublicDashboardStore) GetMetrics(ctx context.Context) (*models.Metrics, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
var r0 *models.Metrics
|
||||
if rf, ok := ret.Get(0).(func(context.Context) *models.Metrics); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.Metrics)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetOrgIdByAccessToken provides a mock function with given fields: ctx, accessToken
|
||||
func (_m *FakePublicDashboardStore) GetOrgIdByAccessToken(ctx context.Context, accessToken string) (int64, error) {
|
||||
ret := _m.Called(ctx, accessToken)
|
||||
|
@ -61,4 +61,5 @@ type Store interface {
|
||||
FindByDashboardFolder(ctx context.Context, dashboard *dashboards.Dashboard) ([]*PublicDashboard, error)
|
||||
ExistsEnabledByAccessToken(ctx context.Context, accessToken string) (bool, error)
|
||||
ExistsEnabledByDashboardUid(ctx context.Context, dashboardUid string) (bool, error)
|
||||
GetMetrics(ctx context.Context) (*Metrics, error)
|
||||
}
|
||||
|
@ -124,9 +124,7 @@ func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *stats.GetS
|
||||
sb.Write(`(SELECT COUNT(id) FROM `+dialect.Quote("library_element")+` WHERE kind = ?) AS library_variables,`, model.VariableElement)
|
||||
sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("data_keys") + `) AS data_keys,`)
|
||||
sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("data_keys") + `WHERE active = true) AS active_data_keys,`)
|
||||
|
||||
// TODO: table name will change and filter should check only for is_enabled = true
|
||||
sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("dashboard_public") + `WHERE is_enabled = true) AS public_dashboards,`)
|
||||
sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("dashboard_public") + `) AS public_dashboards,`)
|
||||
|
||||
sb.Write(ss.roleCounterSQL(ctx))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user