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
pull/59604/head
Serge Zaitsev 3 years ago committed by GitHub
parent a343defe64
commit 9cdb6b07c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      pkg/api/admin.go
  2. 3
      pkg/api/common_test.go
  3. 4
      pkg/api/http_server.go
  4. 7
      pkg/infra/usagestats/statscollector/concurrent_users_test.go
  5. 3
      pkg/infra/usagestats/statscollector/prometheus_flavor_test.go
  6. 16
      pkg/infra/usagestats/statscollector/service.go
  7. 44
      pkg/infra/usagestats/statscollector/service_test.go
  8. 2
      pkg/server/wire.go
  9. 20
      pkg/services/sqlstore/stats_integration_test.go
  10. 5
      pkg/services/sqlstore/store.go
  11. 16
      pkg/services/stats/stats.go
  12. 70
      pkg/services/stats/statsimpl/stats.go
  13. 67
      pkg/services/stats/statsimpl/stats_test.go
  14. 48
      pkg/services/stats/statstest/stats.go

@ -47,7 +47,7 @@ func (hs *HTTPServer) AdminGetSettings(c *models.ReqContext) response.Response {
func (hs *HTTPServer) AdminGetStats(c *models.ReqContext) response.Response {
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)
}

@ -54,6 +54,7 @@ import (
"github.com/grafana/grafana/pkg/services/searchusers/filters"
"github.com/grafana/grafana/pkg/services/sqlstore"
"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/team"
"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) {
store := sqlstore.InitTestDB(t)
statsService := statsimpl.ProvideService(store)
hs := &HTTPServer{
Cfg: cfg,
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()),
ldapGroups: ldap.ProvideGroupsService(),
accesscontrolService: actest.FakeService{},
statsService: statsService,
}
sc := setupScenarioContext(t, url)

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

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

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

@ -19,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/stats"
"github.com/grafana/grafana/pkg/setting"
)
@ -28,6 +29,7 @@ type Service struct {
plugins plugins.Store
social social.Service
usageStats usagestats.Service
statsService stats.Service
features *featuremgmt.FeatureManager
datasources datasources.DataSourceService
httpClientProvider httpclient.Provider
@ -42,6 +44,7 @@ type Service struct {
func ProvideService(
us usagestats.Service,
statsService stats.Service,
cfg *setting.Cfg,
store sqlstore.Store,
social social.Service,
@ -56,6 +59,7 @@ func ProvideService(
plugins: plugins,
social: social,
usageStats: us,
statsService: statsService,
features: features,
datasources: datasourceService,
httpClientProvider: httpClientProvider,
@ -106,7 +110,7 @@ func (s *Service) collectSystemStats(ctx context.Context) (map[string]interface{
m := map[string]interface{}{}
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)
return nil, err
}
@ -219,7 +223,7 @@ func (s *Service) collectAlertNotifierStats(ctx context.Context) (map[string]int
m := map[string]interface{}{}
// get stats about alert notifier usage
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)
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) {
m := map[string]interface{}{}
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)
return nil, err
}
@ -280,7 +284,7 @@ func (s *Service) collectDatasourceAccess(ctx context.Context) (map[string]inter
// fetch datasource access stats
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)
return nil, err
}
@ -316,7 +320,7 @@ func (s *Service) updateTotalStats(ctx context.Context) bool {
}
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)
return false
}
@ -351,7 +355,7 @@ func (s *Service) updateTotalStats(ctx context.Context) bool {
metrics.MStatTotalPublicDashboards.Set(float64(statsQuery.Result.PublicDashboards))
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)
return true
}

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

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

@ -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)
}

@ -12,13 +12,8 @@ import (
)
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
GetDBType() core.DbType
GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error
CreateUser(ctx context.Context, cmd user.CreateUserCommand) (*user.User, error)
WithDbSession(ctx context.Context, callback DBTransactionFunc) error
WithNewDbSession(ctx context.Context, callback DBTransactionFunc) error

@ -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
}

@ -1,39 +1,48 @@
package sqlstore
package statsimpl
import (
"context"
"strconv"
"time"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/models"
"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/stats"
)
const activeUserTimeLimit = time.Hour * 24 * 30
const dailyActiveUserTimeLimit = time.Hour * 24
func (ss *SQLStore) GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + dialect.Quote("alert_notification") + ` GROUP BY type`
func ProvideService(db db.DB) stats.Service {
return &sqlStatsService{db: db}
}
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)
err := dbSession.SQL(rawSQL).Find(&query.Result)
return err
})
}
func (ss *SQLStore) GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + dialect.Quote("data_source") + ` GROUP BY type`
func (ss *sqlStatsService) GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error {
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + ss.db.GetDialect().Quote("data_source") + ` GROUP BY type`
query.Result = make([]*models.DataSourceStats, 0)
err := dbSession.SQL(rawSQL).Find(&query.Result)
return err
})
}
func (ss *SQLStore) GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
var rawSQL = `SELECT COUNT(*) AS count, type, access FROM ` + dialect.Quote("data_source") + ` GROUP BY type, access`
func (ss *sqlStatsService) GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error {
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
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)
err := dbSession.SQL(rawSQL).Find(&query.Result)
return err
@ -45,10 +54,11 @@ func notServiceAccount(dialect migrator.Dialect) string {
dialect.BooleanStr(false)
}
func (ss *SQLStore) GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
sb := &SQLBuilder{}
func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error {
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
sb := &sqlstore.SQLBuilder{}
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("org") + `) AS orgs,`)
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 = ?
) AS folder_permissions,`, dialect.BooleanStr(true))
sb.Write(viewersPermissionsCounterSQL("dashboards_viewers_can_edit", false, models.PERMISSION_EDIT))
sb.Write(viewersPermissionsCounterSQL("dashboards_viewers_can_admin", false, models.PERMISSION_ADMIN))
sb.Write(viewersPermissionsCounterSQL("folders_viewers_can_edit", true, models.PERMISSION_EDIT))
sb.Write(viewersPermissionsCounterSQL("folders_viewers_can_admin", true, models.PERMISSION_ADMIN))
sb.Write(viewersPermissionsCounterSQL(ss.db, "dashboards_viewers_can_edit", false, models.PERMISSION_EDIT))
sb.Write(viewersPermissionsCounterSQL(ss.db, "dashboards_viewers_can_admin", false, models.PERMISSION_ADMIN))
sb.Write(viewersPermissionsCounterSQL(ss.db, "folders_viewers_can_edit", true, models.PERMISSION_EDIT))
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_snapshot") + `) AS snapshots,`)
@ -112,7 +122,7 @@ func (ss *SQLStore) GetSystemStats(ctx context.Context, query *models.GetSystemS
sb.Write(ss.roleCounterSQL(ctx))
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 {
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
ctx, cancel := context.WithTimeout(ctx, roleCounterTimeout)
defer cancel()
@ -142,7 +152,8 @@ func (ss *SQLStore) roleCounterSQL(ctx context.Context) string {
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 `(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard_acl") + ` AS acl
@ -154,8 +165,9 @@ func viewersPermissionsCounterSQL(statName string, isFolder bool, permission mod
) AS ` + statName + `, `
}
func (ss *SQLStore) GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
func (ss *sqlStatsService) GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error {
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
dialect := ss.db.GetDialect()
now := time.Now()
activeEndDate := now.Add(-activeUserTimeLimit)
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 {
return ss.WithDbSession(ctx, func(sess *DBSession) error {
var rawSQL = `SELECT COUNT(id) AS Count FROM ` + dialect.Quote("user")
func (ss *sqlStatsService) GetSystemUserCountStats(ctx context.Context, query *models.GetSystemUserCountStatsQuery) error {
return ss.db.WithDbSession(ctx, func(sess *db.Session) error {
var rawSQL = `SELECT COUNT(id) AS Count FROM ` + ss.db.GetDialect().Quote("user")
var stats models.SystemUserCountStats
_, err := sess.SQL(rawSQL).Get(&stats)
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)
if forced || userStatsCache.memoized.Before(memoizationPeriod) {
err := ss.updateUserRoleCounts(ctx)
@ -270,8 +282,8 @@ var (
userStatsCacheLimetime = 5 * time.Minute
)
func (ss *SQLStore) updateUserRoleCounts(ctx context.Context) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
func (ss *sqlStatsService) updateUserRoleCounts(ctx context.Context) error {
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
query := `
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
@ -283,7 +295,7 @@ SELECT role AS bitrole, active, COUNT(role) AS count FROM
ELSE 1
END AS role,
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 id, last_seen_at) AS t1
GROUP BY active, daily_active, role;`

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

@ -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
}
Loading…
Cancel
Save