mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
a343defe64
commit
9cdb6b07c7
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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")
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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{},
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
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
|
||||||
|
16
pkg/services/stats/stats.go
Normal file
16
pkg/services/stats/stats.go
Normal 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
|
||||||
|
}
|
@ -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;`
|
@ -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)
|
||||||
}
|
}
|
48
pkg/services/stats/statstest/stats.go
Normal file
48
pkg/services/stats/statstest/stats.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user