Chore: Move stats service into a standalone packge from sqlstore (#59574)

* move original stats service into a separate package

* add stats service to wire

* move GetAdminStats

* switch to using stats.Service

* add missing package

* fix api tests
This commit is contained in:
Serge Zaitsev 2022-11-30 18:11:07 +01:00 committed by GitHub
parent a343defe64
commit 9cdb6b07c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 202 additions and 109 deletions

View File

@ -47,7 +47,7 @@ func (hs *HTTPServer) AdminGetSettings(c *models.ReqContext) response.Response {
func (hs *HTTPServer) AdminGetStats(c *models.ReqContext) response.Response { func (hs *HTTPServer) AdminGetStats(c *models.ReqContext) response.Response {
statsQuery := models.GetAdminStatsQuery{} statsQuery := models.GetAdminStatsQuery{}
if err := hs.SQLStore.GetAdminStats(c.Req.Context(), &statsQuery); err != nil { if err := hs.statsService.GetAdminStats(c.Req.Context(), &statsQuery); err != nil {
return response.Error(500, "Failed to get admin stats from database", err) return response.Error(500, "Failed to get admin stats from database", err)
} }

View File

@ -54,6 +54,7 @@ import (
"github.com/grafana/grafana/pkg/services/searchusers/filters" "github.com/grafana/grafana/pkg/services/searchusers/filters"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/services/stats/statsimpl"
"github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/team/teamimpl" "github.com/grafana/grafana/pkg/services/team/teamimpl"
@ -251,6 +252,7 @@ func (s *fakeRenderService) Init() error {
func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url string, permissions []accesscontrol.Permission) (*scenarioContext, *HTTPServer) { func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url string, permissions []accesscontrol.Permission) (*scenarioContext, *HTTPServer) {
store := sqlstore.InitTestDB(t) store := sqlstore.InitTestDB(t)
statsService := statsimpl.ProvideService(store)
hs := &HTTPServer{ hs := &HTTPServer{
Cfg: cfg, Cfg: cfg,
Live: newTestLive(t, store), Live: newTestLive(t, store),
@ -262,6 +264,7 @@ func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url strin
searchUsersService: searchusers.ProvideUsersService(filters.ProvideOSSSearchUserFilter(), usertest.NewUserServiceFake()), searchUsersService: searchusers.ProvideUsersService(filters.ProvideOSSSearchUserFilter(), usertest.NewUserServiceFake()),
ldapGroups: ldap.ProvideGroupsService(), ldapGroups: ldap.ProvideGroupsService(),
accesscontrolService: actest.FakeService{}, accesscontrolService: actest.FakeService{},
statsService: statsService,
} }
sc := setupScenarioContext(t, url) sc := setupScenarioContext(t, url)

View File

@ -20,6 +20,7 @@ import (
"github.com/grafana/grafana/pkg/services/oauthtoken" "github.com/grafana/grafana/pkg/services/oauthtoken"
"github.com/grafana/grafana/pkg/services/querylibrary" "github.com/grafana/grafana/pkg/services/querylibrary"
"github.com/grafana/grafana/pkg/services/searchV2" "github.com/grafana/grafana/pkg/services/searchV2"
"github.com/grafana/grafana/pkg/services/stats"
"github.com/grafana/grafana/pkg/services/store/object/httpobjectstore" "github.com/grafana/grafana/pkg/services/store/object/httpobjectstore"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@ -207,6 +208,7 @@ type HTTPServer struct {
annotationsRepo annotations.Repository annotationsRepo annotations.Repository
tagService tag.Service tagService tag.Service
oauthTokenService oauthtoken.OAuthTokenService oauthTokenService oauthtoken.OAuthTokenService
statsService stats.Service
} }
type ServerOptions struct { type ServerOptions struct {
@ -249,6 +251,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
accesscontrolService accesscontrol.Service, dashboardThumbsService thumbs.DashboardThumbService, navTreeService navtree.Service, accesscontrolService accesscontrol.Service, dashboardThumbsService thumbs.DashboardThumbService, navTreeService navtree.Service,
annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService, annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService,
queryLibraryHTTPService querylibrary.HTTPService, queryLibraryService querylibrary.Service, oauthTokenService oauthtoken.OAuthTokenService, queryLibraryHTTPService querylibrary.HTTPService, queryLibraryService querylibrary.Service, oauthTokenService oauthtoken.OAuthTokenService,
statsService stats.Service,
) (*HTTPServer, error) { ) (*HTTPServer, error) {
web.Env = cfg.Env web.Env = cfg.Env
m := web.New() m := web.New()
@ -353,6 +356,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
QueryLibraryHTTPService: queryLibraryHTTPService, QueryLibraryHTTPService: queryLibraryHTTPService,
QueryLibraryService: queryLibraryService, QueryLibraryService: queryLibraryService,
oauthTokenService: oauthTokenService, oauthTokenService: oauthTokenService,
statsService: statsService,
} }
if hs.Listener != nil { if hs.Listener != nil {
hs.log.Debug("Using provided listener") hs.log.Debug("Using provided listener")

View File

@ -12,12 +12,14 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/stats/statsimpl"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
func TestConcurrentUsersMetrics(t *testing.T) { func TestConcurrentUsersMetrics(t *testing.T) {
sqlStore, cfg := db.InitTestDBwithCfg(t) sqlStore, cfg := db.InitTestDBwithCfg(t)
s := createService(t, cfg, sqlStore) statsService := statsimpl.ProvideService(sqlStore)
s := createService(t, cfg, sqlStore, statsService)
createConcurrentTokens(t, sqlStore) createConcurrentTokens(t, sqlStore)
@ -34,7 +36,8 @@ func TestConcurrentUsersMetrics(t *testing.T) {
func TestConcurrentUsersStats(t *testing.T) { func TestConcurrentUsersStats(t *testing.T) {
sqlStore, cfg := db.InitTestDBwithCfg(t) sqlStore, cfg := db.InitTestDBwithCfg(t)
s := createService(t, cfg, sqlStore) statsService := statsimpl.ProvideService(sqlStore)
s := createService(t, cfg, sqlStore, statsService)
createConcurrentTokens(t, sqlStore) createConcurrentTokens(t, sqlStore)

View File

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/services/stats/statstest"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -33,10 +34,12 @@ func TestDetectPrometheusVariant(t *testing.T) {
t.Cleanup(cortex.Close) t.Cleanup(cortex.Close)
sqlStore := mockstore.NewSQLStoreMock() sqlStore := mockstore.NewSQLStoreMock()
statsService := statstest.NewFakeService()
s := createService( s := createService(
t, t,
setting.NewCfg(), setting.NewCfg(),
sqlStore, sqlStore,
statsService,
withDatasources(mockDatasourceService{datasources: []*datasources.DataSource{ withDatasources(mockDatasourceService{datasources: []*datasources.DataSource{
{ {
Id: 1, Id: 1,

View File

@ -19,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/stats"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -28,6 +29,7 @@ type Service struct {
plugins plugins.Store plugins plugins.Store
social social.Service social social.Service
usageStats usagestats.Service usageStats usagestats.Service
statsService stats.Service
features *featuremgmt.FeatureManager features *featuremgmt.FeatureManager
datasources datasources.DataSourceService datasources datasources.DataSourceService
httpClientProvider httpclient.Provider httpClientProvider httpclient.Provider
@ -42,6 +44,7 @@ type Service struct {
func ProvideService( func ProvideService(
us usagestats.Service, us usagestats.Service,
statsService stats.Service,
cfg *setting.Cfg, cfg *setting.Cfg,
store sqlstore.Store, store sqlstore.Store,
social social.Service, social social.Service,
@ -56,6 +59,7 @@ func ProvideService(
plugins: plugins, plugins: plugins,
social: social, social: social,
usageStats: us, usageStats: us,
statsService: statsService,
features: features, features: features,
datasources: datasourceService, datasources: datasourceService,
httpClientProvider: httpClientProvider, httpClientProvider: httpClientProvider,
@ -106,7 +110,7 @@ func (s *Service) collectSystemStats(ctx context.Context) (map[string]interface{
m := map[string]interface{}{} m := map[string]interface{}{}
statsQuery := models.GetSystemStatsQuery{} statsQuery := models.GetSystemStatsQuery{}
if err := s.sqlstore.GetSystemStats(ctx, &statsQuery); err != nil { if err := s.statsService.GetSystemStats(ctx, &statsQuery); err != nil {
s.log.Error("Failed to get system stats", "error", err) s.log.Error("Failed to get system stats", "error", err)
return nil, err return nil, err
} }
@ -219,7 +223,7 @@ func (s *Service) collectAlertNotifierStats(ctx context.Context) (map[string]int
m := map[string]interface{}{} m := map[string]interface{}{}
// get stats about alert notifier usage // get stats about alert notifier usage
anStats := models.GetAlertNotifierUsageStatsQuery{} anStats := models.GetAlertNotifierUsageStatsQuery{}
if err := s.sqlstore.GetAlertNotifiersUsageStats(ctx, &anStats); err != nil { if err := s.statsService.GetAlertNotifiersUsageStats(ctx, &anStats); err != nil {
s.log.Error("Failed to get alert notification stats", "error", err) s.log.Error("Failed to get alert notification stats", "error", err)
return nil, err return nil, err
} }
@ -233,7 +237,7 @@ func (s *Service) collectAlertNotifierStats(ctx context.Context) (map[string]int
func (s *Service) collectDatasourceStats(ctx context.Context) (map[string]interface{}, error) { func (s *Service) collectDatasourceStats(ctx context.Context) (map[string]interface{}, error) {
m := map[string]interface{}{} m := map[string]interface{}{}
dsStats := models.GetDataSourceStatsQuery{} dsStats := models.GetDataSourceStatsQuery{}
if err := s.sqlstore.GetDataSourceStats(ctx, &dsStats); err != nil { if err := s.statsService.GetDataSourceStats(ctx, &dsStats); err != nil {
s.log.Error("Failed to get datasource stats", "error", err) s.log.Error("Failed to get datasource stats", "error", err)
return nil, err return nil, err
} }
@ -280,7 +284,7 @@ func (s *Service) collectDatasourceAccess(ctx context.Context) (map[string]inter
// fetch datasource access stats // fetch datasource access stats
dsAccessStats := models.GetDataSourceAccessStatsQuery{} dsAccessStats := models.GetDataSourceAccessStatsQuery{}
if err := s.sqlstore.GetDataSourceAccessStats(ctx, &dsAccessStats); err != nil { if err := s.statsService.GetDataSourceAccessStats(ctx, &dsAccessStats); err != nil {
s.log.Error("Failed to get datasource access stats", "error", err) s.log.Error("Failed to get datasource access stats", "error", err)
return nil, err return nil, err
} }
@ -316,7 +320,7 @@ func (s *Service) updateTotalStats(ctx context.Context) bool {
} }
statsQuery := models.GetSystemStatsQuery{} statsQuery := models.GetSystemStatsQuery{}
if err := s.sqlstore.GetSystemStats(ctx, &statsQuery); err != nil { if err := s.statsService.GetSystemStats(ctx, &statsQuery); err != nil {
s.log.Error("Failed to get system stats", "error", err) s.log.Error("Failed to get system stats", "error", err)
return false return false
} }
@ -351,7 +355,7 @@ func (s *Service) updateTotalStats(ctx context.Context) bool {
metrics.MStatTotalPublicDashboards.Set(float64(statsQuery.Result.PublicDashboards)) metrics.MStatTotalPublicDashboards.Set(float64(statsQuery.Result.PublicDashboards))
dsStats := models.GetDataSourceStatsQuery{} dsStats := models.GetDataSourceStatsQuery{}
if err := s.sqlstore.GetDataSourceStats(ctx, &dsStats); err != nil { if err := s.statsService.GetDataSourceStats(ctx, &dsStats); err != nil {
s.log.Error("Failed to get datasource stats", "error", err) s.log.Error("Failed to get datasource stats", "error", err)
return true return true
} }

View File

@ -24,16 +24,19 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/services/stats"
"github.com/grafana/grafana/pkg/services/stats/statstest"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
func TestTotalStatsUpdate(t *testing.T) { func TestTotalStatsUpdate(t *testing.T) {
sqlStore := mockstore.NewSQLStoreMock() sqlStore := mockstore.NewSQLStoreMock()
s := createService(t, setting.NewCfg(), sqlStore) statsService := statstest.NewFakeService()
s := createService(t, setting.NewCfg(), sqlStore, statsService)
s.cfg.MetricsEndpointEnabled = true s.cfg.MetricsEndpointEnabled = true
s.cfg.MetricsEndpointDisableTotalStats = false s.cfg.MetricsEndpointDisableTotalStats = false
sqlStore.ExpectedSystemStats = &models.SystemStats{} statsService.ExpectedSystemStats = &models.SystemStats{}
tests := []struct { tests := []struct {
MetricsEndpointEnabled bool MetricsEndpointEnabled bool
@ -94,8 +97,9 @@ func TestUsageStatsProviders(t *testing.T) {
provider2 := &dummyUsageStatProvider{stats: map[string]interface{}{"my_stat_x": "valx", "my_stat_z": "valz"}} provider2 := &dummyUsageStatProvider{stats: map[string]interface{}{"my_stat_x": "valx", "my_stat_z": "valz"}}
store := mockstore.NewSQLStoreMock() store := mockstore.NewSQLStoreMock()
mockSystemStats(store) statsService := statstest.NewFakeService()
s := createService(t, setting.NewCfg(), store) mockSystemStats(statsService)
s := createService(t, setting.NewCfg(), store, statsService)
s.RegisterProviders([]registry.ProvidesUsageStats{provider1, provider2}) s.RegisterProviders([]registry.ProvidesUsageStats{provider1, provider2})
m, err := s.collectAdditionalMetrics(context.Background()) m, err := s.collectAdditionalMetrics(context.Background())
@ -109,8 +113,9 @@ func TestUsageStatsProviders(t *testing.T) {
func TestFeatureUsageStats(t *testing.T) { func TestFeatureUsageStats(t *testing.T) {
store := mockstore.NewSQLStoreMock() store := mockstore.NewSQLStoreMock()
mockSystemStats(store) statsService := statstest.NewFakeService()
s := createService(t, setting.NewCfg(), store) mockSystemStats(statsService)
s := createService(t, setting.NewCfg(), store, statsService)
m, err := s.collectSystemStats(context.Background()) m, err := s.collectSystemStats(context.Background())
require.NoError(t, err, "Expected no error") require.NoError(t, err, "Expected no error")
@ -121,6 +126,7 @@ func TestFeatureUsageStats(t *testing.T) {
func TestCollectingUsageStats(t *testing.T) { func TestCollectingUsageStats(t *testing.T) {
sqlStore := mockstore.NewSQLStoreMock() sqlStore := mockstore.NewSQLStoreMock()
statsService := statstest.NewFakeService()
expectedDataSources := []*datasources.DataSource{ expectedDataSources := []*datasources.DataSource{
{ {
JsonData: simplejson.NewFromAny(map[string]interface{}{ JsonData: simplejson.NewFromAny(map[string]interface{}{
@ -148,12 +154,12 @@ func TestCollectingUsageStats(t *testing.T) {
AuthProxyEnabled: true, AuthProxyEnabled: true,
Packaging: "deb", Packaging: "deb",
ReportingDistributor: "hosted-grafana", ReportingDistributor: "hosted-grafana",
}, sqlStore, }, sqlStore, statsService,
withDatasources(mockDatasourceService{datasources: expectedDataSources})) withDatasources(mockDatasourceService{datasources: expectedDataSources}))
s.startTime = time.Now().Add(-1 * time.Minute) s.startTime = time.Now().Add(-1 * time.Minute)
mockSystemStats(sqlStore) mockSystemStats(statsService)
createConcurrentTokens(t, sqlStore) createConcurrentTokens(t, sqlStore)
@ -203,6 +209,7 @@ func TestCollectingUsageStats(t *testing.T) {
func TestElasticStats(t *testing.T) { func TestElasticStats(t *testing.T) {
sqlStore := mockstore.NewSQLStoreMock() sqlStore := mockstore.NewSQLStoreMock()
statsService := statstest.NewFakeService()
expectedDataSources := []*datasources.DataSource{ expectedDataSources := []*datasources.DataSource{
{ {
@ -231,7 +238,7 @@ func TestElasticStats(t *testing.T) {
AuthProxyEnabled: true, AuthProxyEnabled: true,
Packaging: "deb", Packaging: "deb",
ReportingDistributor: "hosted-grafana", ReportingDistributor: "hosted-grafana",
}, sqlStore, }, sqlStore, statsService,
withDatasources(mockDatasourceService{datasources: expectedDataSources})) withDatasources(mockDatasourceService{datasources: expectedDataSources}))
metrics, err := s.collectElasticStats(context.Background()) metrics, err := s.collectElasticStats(context.Background())
@ -242,11 +249,12 @@ func TestElasticStats(t *testing.T) {
} }
func TestDatasourceStats(t *testing.T) { func TestDatasourceStats(t *testing.T) {
sqlStore := mockstore.NewSQLStoreMock() sqlStore := mockstore.NewSQLStoreMock()
s := createService(t, &setting.Cfg{}, sqlStore) statsService := statstest.NewFakeService()
s := createService(t, &setting.Cfg{}, sqlStore, statsService)
setupSomeDataSourcePlugins(t, s) setupSomeDataSourcePlugins(t, s)
sqlStore.ExpectedDataSourceStats = []*models.DataSourceStats{ statsService.ExpectedDataSourceStats = []*models.DataSourceStats{
{ {
Type: datasources.DS_ES, Type: datasources.DS_ES,
Count: 9, Count: 9,
@ -283,7 +291,7 @@ func TestDatasourceStats(t *testing.T) {
}, },
} }
sqlStore.ExpectedDataSourcesAccessStats = []*models.DataSourceAccessStats{ statsService.ExpectedDataSourcesAccessStats = []*models.DataSourceAccessStats{
{ {
Type: datasources.DS_ES, Type: datasources.DS_ES,
Access: "direct", Access: "direct",
@ -349,9 +357,10 @@ func TestDatasourceStats(t *testing.T) {
func TestAlertNotifiersStats(t *testing.T) { func TestAlertNotifiersStats(t *testing.T) {
sqlStore := mockstore.NewSQLStoreMock() sqlStore := mockstore.NewSQLStoreMock()
s := createService(t, &setting.Cfg{}, sqlStore) statsService := statstest.NewFakeService()
s := createService(t, &setting.Cfg{}, sqlStore, statsService)
sqlStore.ExpectedNotifierUsageStats = []*models.NotifierUsageStats{ statsService.ExpectedNotifierUsageStats = []*models.NotifierUsageStats{
{ {
Type: "slack", Type: "slack",
Count: 1, Count: 1,
@ -369,8 +378,8 @@ func TestAlertNotifiersStats(t *testing.T) {
assert.EqualValues(t, 2, metrics["stats.alert_notifiers.webhook.count"]) assert.EqualValues(t, 2, metrics["stats.alert_notifiers.webhook.count"])
} }
func mockSystemStats(sqlStore *mockstore.SQLStoreMock) { func mockSystemStats(statsService *statstest.FakeService) {
sqlStore.ExpectedSystemStats = &models.SystemStats{ statsService.ExpectedSystemStats = &models.SystemStats{
Dashboards: 1, Dashboards: 1,
Datasources: 2, Datasources: 2,
Users: 3, Users: 3,
@ -437,7 +446,7 @@ func setupSomeDataSourcePlugins(t *testing.T, s *Service) {
} }
} }
func createService(t testing.TB, cfg *setting.Cfg, store sqlstore.Store, opts ...func(*serviceOptions)) *Service { func createService(t testing.TB, cfg *setting.Cfg, store sqlstore.Store, statsService stats.Service, opts ...func(*serviceOptions)) *Service {
t.Helper() t.Helper()
o := &serviceOptions{datasources: mockDatasourceService{}} o := &serviceOptions{datasources: mockDatasourceService{}}
@ -448,6 +457,7 @@ func createService(t testing.TB, cfg *setting.Cfg, store sqlstore.Store, opts ..
return ProvideService( return ProvideService(
&usagestats.UsageStatsMock{}, &usagestats.UsageStatsMock{},
statsService,
cfg, cfg,
store, store,
&mockSocial{}, &mockSocial{},

View File

@ -124,6 +124,7 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/services/star/starimpl" "github.com/grafana/grafana/pkg/services/star/starimpl"
"github.com/grafana/grafana/pkg/services/stats/statsimpl"
"github.com/grafana/grafana/pkg/services/store" "github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/store/kind" "github.com/grafana/grafana/pkg/services/store/kind"
"github.com/grafana/grafana/pkg/services/store/object/httpobjectstore" "github.com/grafana/grafana/pkg/services/store/object/httpobjectstore"
@ -353,6 +354,7 @@ var wireBasicSet = wire.NewSet(
publicdashboardsApi.ProvideApi, publicdashboardsApi.ProvideApi,
userimpl.ProvideService, userimpl.ProvideService,
orgimpl.ProvideService, orgimpl.ProvideService,
statsimpl.ProvideService,
grpccontext.ProvideContextHandler, grpccontext.ProvideContextHandler,
grpcserver.ProvideService, grpcserver.ProvideService,
grpcserver.ProvideHealthService, grpcserver.ProvideHealthService,

View File

@ -1,20 +0,0 @@
package sqlstore
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/require"
)
func TestIntegration_GetAdminStats(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
sqlStore := InitTestDB(t)
query := models.GetAdminStatsQuery{}
err := sqlStore.GetAdminStats(context.Background(), &query)
require.NoError(t, err)
}

View File

@ -12,13 +12,8 @@ import (
) )
type Store interface { type Store interface {
GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error
GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error
GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error
GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error
GetDialect() migrator.Dialect GetDialect() migrator.Dialect
GetDBType() core.DbType GetDBType() core.DbType
GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error
CreateUser(ctx context.Context, cmd user.CreateUserCommand) (*user.User, error) CreateUser(ctx context.Context, cmd user.CreateUserCommand) (*user.User, error)
WithDbSession(ctx context.Context, callback DBTransactionFunc) error WithDbSession(ctx context.Context, callback DBTransactionFunc) error
WithNewDbSession(ctx context.Context, callback DBTransactionFunc) error WithNewDbSession(ctx context.Context, callback DBTransactionFunc) error

View File

@ -0,0 +1,16 @@
package stats
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
type Service interface {
GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error
GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error
GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error
GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error
GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error
GetSystemUserCountStats(ctx context.Context, query *models.GetSystemUserCountStatsQuery) error
}

View File

@ -1,39 +1,48 @@
package sqlstore package statsimpl
import ( import (
"context" "context"
"strconv" "strconv"
"time" "time"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/stats"
) )
const activeUserTimeLimit = time.Hour * 24 * 30 const activeUserTimeLimit = time.Hour * 24 * 30
const dailyActiveUserTimeLimit = time.Hour * 24 const dailyActiveUserTimeLimit = time.Hour * 24
func (ss *SQLStore) GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error { func ProvideService(db db.DB) stats.Service {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error { return &sqlStatsService{db: db}
var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + dialect.Quote("alert_notification") + ` GROUP BY type` }
type sqlStatsService struct{ db db.DB }
func (ss *sqlStatsService) GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error {
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + ss.db.GetDialect().Quote("alert_notification") + ` GROUP BY type`
query.Result = make([]*models.NotifierUsageStats, 0) query.Result = make([]*models.NotifierUsageStats, 0)
err := dbSession.SQL(rawSQL).Find(&query.Result) err := dbSession.SQL(rawSQL).Find(&query.Result)
return err return err
}) })
} }
func (ss *SQLStore) GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error { func (ss *sqlStatsService) GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error { return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + dialect.Quote("data_source") + ` GROUP BY type` var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + ss.db.GetDialect().Quote("data_source") + ` GROUP BY type`
query.Result = make([]*models.DataSourceStats, 0) query.Result = make([]*models.DataSourceStats, 0)
err := dbSession.SQL(rawSQL).Find(&query.Result) err := dbSession.SQL(rawSQL).Find(&query.Result)
return err return err
}) })
} }
func (ss *SQLStore) GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error { func (ss *sqlStatsService) GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error { return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
var rawSQL = `SELECT COUNT(*) AS count, type, access FROM ` + dialect.Quote("data_source") + ` GROUP BY type, access` var rawSQL = `SELECT COUNT(*) AS count, type, access FROM ` + ss.db.GetDialect().Quote("data_source") + ` GROUP BY type, access`
query.Result = make([]*models.DataSourceAccessStats, 0) query.Result = make([]*models.DataSourceAccessStats, 0)
err := dbSession.SQL(rawSQL).Find(&query.Result) err := dbSession.SQL(rawSQL).Find(&query.Result)
return err return err
@ -45,10 +54,11 @@ func notServiceAccount(dialect migrator.Dialect) string {
dialect.BooleanStr(false) dialect.BooleanStr(false)
} }
func (ss *SQLStore) GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error { func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error { return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
sb := &SQLBuilder{} sb := &sqlstore.SQLBuilder{}
sb.Write("SELECT ") sb.Write("SELECT ")
dialect := ss.db.GetDialect()
sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("user") + ` WHERE ` + notServiceAccount(dialect) + `) AS users,`) sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("user") + ` WHERE ` + notServiceAccount(dialect) + `) AS users,`)
sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("org") + `) AS orgs,`) sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("org") + `) AS orgs,`)
sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("data_source") + `) AS datasources,`) sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("data_source") + `) AS datasources,`)
@ -88,10 +98,10 @@ func (ss *SQLStore) GetSystemStats(ctx context.Context, query *models.GetSystemS
WHERE d.is_folder = ? WHERE d.is_folder = ?
) AS folder_permissions,`, dialect.BooleanStr(true)) ) AS folder_permissions,`, dialect.BooleanStr(true))
sb.Write(viewersPermissionsCounterSQL("dashboards_viewers_can_edit", false, models.PERMISSION_EDIT)) sb.Write(viewersPermissionsCounterSQL(ss.db, "dashboards_viewers_can_edit", false, models.PERMISSION_EDIT))
sb.Write(viewersPermissionsCounterSQL("dashboards_viewers_can_admin", false, models.PERMISSION_ADMIN)) sb.Write(viewersPermissionsCounterSQL(ss.db, "dashboards_viewers_can_admin", false, models.PERMISSION_ADMIN))
sb.Write(viewersPermissionsCounterSQL("folders_viewers_can_edit", true, models.PERMISSION_EDIT)) sb.Write(viewersPermissionsCounterSQL(ss.db, "folders_viewers_can_edit", true, models.PERMISSION_EDIT))
sb.Write(viewersPermissionsCounterSQL("folders_viewers_can_admin", true, models.PERMISSION_ADMIN)) sb.Write(viewersPermissionsCounterSQL(ss.db, "folders_viewers_can_admin", true, models.PERMISSION_ADMIN))
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_provisioning") + `) AS provisioned_dashboards,`) sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_provisioning") + `) AS provisioned_dashboards,`)
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_snapshot") + `) AS snapshots,`) sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_snapshot") + `) AS snapshots,`)
@ -112,7 +122,7 @@ func (ss *SQLStore) GetSystemStats(ctx context.Context, query *models.GetSystemS
sb.Write(ss.roleCounterSQL(ctx)) sb.Write(ss.roleCounterSQL(ctx))
var stats models.SystemStats var stats models.SystemStats
_, err := dbSession.SQL(sb.GetSQLString(), sb.params...).Get(&stats) _, err := dbSession.SQL(sb.GetSQLString(), sb.GetParams()...).Get(&stats)
if err != nil { if err != nil {
return err return err
} }
@ -123,7 +133,7 @@ func (ss *SQLStore) GetSystemStats(ctx context.Context, query *models.GetSystemS
}) })
} }
func (ss *SQLStore) roleCounterSQL(ctx context.Context) string { func (ss *sqlStatsService) roleCounterSQL(ctx context.Context) string {
const roleCounterTimeout = 20 * time.Second const roleCounterTimeout = 20 * time.Second
ctx, cancel := context.WithTimeout(ctx, roleCounterTimeout) ctx, cancel := context.WithTimeout(ctx, roleCounterTimeout)
defer cancel() defer cancel()
@ -142,7 +152,8 @@ func (ss *SQLStore) roleCounterSQL(ctx context.Context) string {
return sqlQuery return sqlQuery
} }
func viewersPermissionsCounterSQL(statName string, isFolder bool, permission models.PermissionType) string { func viewersPermissionsCounterSQL(db db.DB, statName string, isFolder bool, permission models.PermissionType) string {
dialect := db.GetDialect()
return `( return `(
SELECT COUNT(*) SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard_acl") + ` AS acl FROM ` + dialect.Quote("dashboard_acl") + ` AS acl
@ -154,8 +165,9 @@ func viewersPermissionsCounterSQL(statName string, isFolder bool, permission mod
) AS ` + statName + `, ` ) AS ` + statName + `, `
} }
func (ss *SQLStore) GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error { func (ss *sqlStatsService) GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error { return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
dialect := ss.db.GetDialect()
now := time.Now() now := time.Now()
activeEndDate := now.Add(-activeUserTimeLimit) activeEndDate := now.Add(-activeUserTimeLimit)
dailyActiveEndDate := now.Add(-dailyActiveUserTimeLimit) dailyActiveEndDate := now.Add(-dailyActiveUserTimeLimit)
@ -231,9 +243,9 @@ func (ss *SQLStore) GetAdminStats(ctx context.Context, query *models.GetAdminSta
}) })
} }
func (ss *SQLStore) GetSystemUserCountStats(ctx context.Context, query *models.GetSystemUserCountStatsQuery) error { func (ss *sqlStatsService) GetSystemUserCountStats(ctx context.Context, query *models.GetSystemUserCountStatsQuery) error {
return ss.WithDbSession(ctx, func(sess *DBSession) error { return ss.db.WithDbSession(ctx, func(sess *db.Session) error {
var rawSQL = `SELECT COUNT(id) AS Count FROM ` + dialect.Quote("user") var rawSQL = `SELECT COUNT(id) AS Count FROM ` + ss.db.GetDialect().Quote("user")
var stats models.SystemUserCountStats var stats models.SystemUserCountStats
_, err := sess.SQL(rawSQL).Get(&stats) _, err := sess.SQL(rawSQL).Get(&stats)
if err != nil { if err != nil {
@ -246,7 +258,7 @@ func (ss *SQLStore) GetSystemUserCountStats(ctx context.Context, query *models.G
}) })
} }
func (ss *SQLStore) updateUserRoleCountsIfNecessary(ctx context.Context, forced bool) error { func (ss *sqlStatsService) updateUserRoleCountsIfNecessary(ctx context.Context, forced bool) error {
memoizationPeriod := time.Now().Add(-userStatsCacheLimetime) memoizationPeriod := time.Now().Add(-userStatsCacheLimetime)
if forced || userStatsCache.memoized.Before(memoizationPeriod) { if forced || userStatsCache.memoized.Before(memoizationPeriod) {
err := ss.updateUserRoleCounts(ctx) err := ss.updateUserRoleCounts(ctx)
@ -270,8 +282,8 @@ var (
userStatsCacheLimetime = 5 * time.Minute userStatsCacheLimetime = 5 * time.Minute
) )
func (ss *SQLStore) updateUserRoleCounts(ctx context.Context) error { func (ss *sqlStatsService) updateUserRoleCounts(ctx context.Context) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error { return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
query := ` query := `
SELECT role AS bitrole, active, COUNT(role) AS count FROM SELECT role AS bitrole, active, COUNT(role) AS count FROM
(SELECT last_seen_at>? AS active, last_seen_at>? AS daily_active, SUM(role) AS role (SELECT last_seen_at>? AS active, last_seen_at>? AS daily_active, SUM(role) AS role
@ -283,7 +295,7 @@ SELECT role AS bitrole, active, COUNT(role) AS count FROM
ELSE 1 ELSE 1
END AS role, END AS role,
u.last_seen_at u.last_seen_at
FROM ` + dialect.Quote("user") + ` AS u INNER JOIN org_user ON org_user.user_id = u.id FROM ` + ss.db.GetDialect().Quote("user") + ` AS u INNER JOIN org_user ON org_user.user_id = u.id
GROUP BY u.id, u.last_seen_at, org_user.role) AS t2 GROUP BY u.id, u.last_seen_at, org_user.role) AS t2
GROUP BY id, last_seen_at) AS t1 GROUP BY id, last_seen_at) AS t1
GROUP BY active, daily_active, role;` GROUP BY active, daily_active, role;`

View File

@ -1,28 +1,31 @@
package sqlstore package statsimpl
import ( import (
"context" "context"
"fmt" "fmt"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestIntegrationStatsDataAccess(t *testing.T) { func TestIntegrationStatsDataAccess(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
sqlStore := InitTestDB(t) db := sqlstore.InitTestDB(t)
populateDB(t, sqlStore) statsService := &sqlStatsService{db: db}
populateDB(t, db)
t.Run("Get system stats should not results in error", func(t *testing.T) { t.Run("Get system stats should not results in error", func(t *testing.T) {
query := models.GetSystemStatsQuery{} query := models.GetSystemStatsQuery{}
err := sqlStore.GetSystemStats(context.Background(), &query) err := statsService.GetSystemStats(context.Background(), &query)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, int64(3), query.Result.Users) assert.Equal(t, int64(3), query.Result.Users)
assert.Equal(t, int64(0), query.Result.Editors) assert.Equal(t, int64(0), query.Result.Editors)
@ -35,38 +38,40 @@ func TestIntegrationStatsDataAccess(t *testing.T) {
t.Run("Get system user count stats should not results in error", func(t *testing.T) { t.Run("Get system user count stats should not results in error", func(t *testing.T) {
query := models.GetSystemUserCountStatsQuery{} query := models.GetSystemUserCountStatsQuery{}
err := sqlStore.GetSystemUserCountStats(context.Background(), &query) err := statsService.GetSystemUserCountStats(context.Background(), &query)
assert.NoError(t, err) assert.NoError(t, err)
}) })
t.Run("Get datasource stats should not results in error", func(t *testing.T) { t.Run("Get datasource stats should not results in error", func(t *testing.T) {
query := models.GetDataSourceStatsQuery{} query := models.GetDataSourceStatsQuery{}
err := sqlStore.GetDataSourceStats(context.Background(), &query) err := statsService.GetDataSourceStats(context.Background(), &query)
assert.NoError(t, err) assert.NoError(t, err)
}) })
t.Run("Get datasource access stats should not results in error", func(t *testing.T) { t.Run("Get datasource access stats should not results in error", func(t *testing.T) {
query := models.GetDataSourceAccessStatsQuery{} query := models.GetDataSourceAccessStatsQuery{}
err := sqlStore.GetDataSourceAccessStats(context.Background(), &query) err := statsService.GetDataSourceAccessStats(context.Background(), &query)
assert.NoError(t, err) assert.NoError(t, err)
}) })
t.Run("Get alert notifier stats should not results in error", func(t *testing.T) { t.Run("Get alert notifier stats should not results in error", func(t *testing.T) {
query := models.GetAlertNotifierUsageStatsQuery{} query := models.GetAlertNotifierUsageStatsQuery{}
err := sqlStore.GetAlertNotifiersUsageStats(context.Background(), &query) err := statsService.GetAlertNotifiersUsageStats(context.Background(), &query)
assert.NoError(t, err) assert.NoError(t, err)
}) })
t.Run("Get admin stats should not result in error", func(t *testing.T) { t.Run("Get admin stats should not result in error", func(t *testing.T) {
query := models.GetAdminStatsQuery{} query := models.GetAdminStatsQuery{}
err := sqlStore.GetAdminStats(context.Background(), &query) err := statsService.GetAdminStats(context.Background(), &query)
assert.NoError(t, err) assert.NoError(t, err)
}) })
} }
func populateDB(t *testing.T, sqlStore *SQLStore) { func populateDB(t *testing.T, sqlStore *sqlstore.SQLStore) {
t.Helper() t.Helper()
orgService, _ := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotatest.New(false, nil))
users := make([]user.User, 3) users := make([]user.User, 3)
for i := range users { for i := range users {
cmd := user.CreateUserCommand{ cmd := user.CreateUserCommand{
@ -81,33 +86,41 @@ func populateDB(t *testing.T, sqlStore *SQLStore) {
} }
// add 2nd user as editor // add 2nd user as editor
cmd := &models.AddOrgUserCommand{ cmd := &org.AddOrgUserCommand{
OrgId: users[0].OrgID, OrgID: users[0].OrgID,
UserId: users[1].ID, UserID: users[1].ID,
Role: org.RoleEditor, Role: org.RoleEditor,
} }
err := sqlStore.addOrgUser(context.Background(), cmd) err := orgService.AddOrgUser(context.Background(), cmd)
require.NoError(t, err) require.NoError(t, err)
// add 3rd user as viewer // add 3rd user as viewer
cmd = &models.AddOrgUserCommand{ cmd = &org.AddOrgUserCommand{
OrgId: users[0].OrgID, OrgID: users[0].OrgID,
UserId: users[2].ID, UserID: users[2].ID,
Role: org.RoleViewer, Role: org.RoleViewer,
} }
err = sqlStore.addOrgUser(context.Background(), cmd) err = orgService.AddOrgUser(context.Background(), cmd)
require.NoError(t, err) require.NoError(t, err)
// add 1st user as admin // add 1st user as admin
cmd = &models.AddOrgUserCommand{ cmd = &org.AddOrgUserCommand{
OrgId: users[1].OrgID, OrgID: users[1].OrgID,
UserId: users[0].ID, UserID: users[0].ID,
Role: org.RoleAdmin, Role: org.RoleAdmin,
} }
err = sqlStore.addOrgUser(context.Background(), cmd) err = orgService.AddOrgUser(context.Background(), cmd)
require.NoError(t, err) require.NoError(t, err)
}
// force renewal of user stats
err = sqlStore.updateUserRoleCountsIfNecessary(context.Background(), true) func TestIntegration_GetAdminStats(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
db := sqlstore.InitTestDB(t)
statsService := ProvideService(db)
query := models.GetAdminStatsQuery{}
err := statsService.GetAdminStats(context.Background(), &query)
require.NoError(t, err) require.NoError(t, err)
} }

View File

@ -0,0 +1,48 @@
package statstest
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
type FakeService struct {
ExpectedSystemStats *models.SystemStats
ExpectedDataSourceStats []*models.DataSourceStats
ExpectedDataSourcesAccessStats []*models.DataSourceAccessStats
ExpectedNotifierUsageStats []*models.NotifierUsageStats
ExpectedError error
}
func NewFakeService() *FakeService {
return &FakeService{}
}
func (s *FakeService) GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error {
return s.ExpectedError
}
func (s *FakeService) GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error {
query.Result = s.ExpectedNotifierUsageStats
return s.ExpectedError
}
func (s *FakeService) GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error {
query.Result = s.ExpectedDataSourceStats
return s.ExpectedError
}
func (s *FakeService) GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error {
query.Result = s.ExpectedDataSourcesAccessStats
return s.ExpectedError
}
func (s *FakeService) GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error {
query.Result = s.ExpectedSystemStats
return s.ExpectedError
}
func (s *FakeService) GetSystemUserCountStats(ctx context.Context, query *models.GetSystemUserCountStatsQuery) error {
return s.ExpectedError
}