mirror of https://github.com/grafana/grafana
UsageStats: Move stats collection to separate service (#47381)
* Remove specific stats from usage stats service * Create statscollector service * refactor * Update and move tests Mostly equivalent tests to before, but they've been divided over the two services and removed the behavior driven legacy from GoConvey to reduce the complexity of the tests. * Collect featuremgmr metrics (copied over from #47407) I removed the metrics registration from the feature manager in the merge and re-add them in this commit. Separated to make things easier to review.pull/47506/head
parent
87383b1c8b
commit
3df625e9f4
@ -1,10 +0,0 @@ |
||||
package service |
||||
|
||||
type concurrentUsersStats struct { |
||||
BucketLE3 int32 `xorm:"bucket_le_3"` |
||||
BucketLE6 int32 `xorm:"bucket_le_6"` |
||||
BucketLE9 int32 `xorm:"bucket_le_9"` |
||||
BucketLE12 int32 `xorm:"bucket_le_12"` |
||||
BucketLE15 int32 `xorm:"bucket_le_15"` |
||||
BucketLEInf int32 `xorm:"bucket_le_inf"` |
||||
} |
||||
@ -0,0 +1,58 @@ |
||||
package statscollector |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
) |
||||
|
||||
const concurrentUserStatsCacheLifetime = time.Hour |
||||
|
||||
type concurrentUsersStats struct { |
||||
BucketLE3 int32 `xorm:"bucket_le_3"` |
||||
BucketLE6 int32 `xorm:"bucket_le_6"` |
||||
BucketLE9 int32 `xorm:"bucket_le_9"` |
||||
BucketLE12 int32 `xorm:"bucket_le_12"` |
||||
BucketLE15 int32 `xorm:"bucket_le_15"` |
||||
BucketLEInf int32 `xorm:"bucket_le_inf"` |
||||
} |
||||
|
||||
type memoConcurrentUserStats struct { |
||||
stats *concurrentUsersStats |
||||
|
||||
memoized time.Time |
||||
} |
||||
|
||||
func (s *Service) concurrentUsers(ctx context.Context) (*concurrentUsersStats, error) { |
||||
memoizationPeriod := time.Now().Add(-concurrentUserStatsCacheLifetime) |
||||
if !s.concurrentUserStatsCache.memoized.Before(memoizationPeriod) { |
||||
return s.concurrentUserStatsCache.stats, nil |
||||
} |
||||
|
||||
s.concurrentUserStatsCache.stats = &concurrentUsersStats{} |
||||
err := s.sqlstore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { |
||||
// Retrieves concurrent users stats as a histogram. Buckets are accumulative and upper bound is inclusive.
|
||||
rawSQL := ` |
||||
SELECT |
||||
COUNT(CASE WHEN tokens <= 3 THEN 1 END) AS bucket_le_3, |
||||
COUNT(CASE WHEN tokens <= 6 THEN 1 END) AS bucket_le_6, |
||||
COUNT(CASE WHEN tokens <= 9 THEN 1 END) AS bucket_le_9, |
||||
COUNT(CASE WHEN tokens <= 12 THEN 1 END) AS bucket_le_12, |
||||
COUNT(CASE WHEN tokens <= 15 THEN 1 END) AS bucket_le_15, |
||||
COUNT(1) AS bucket_le_inf |
||||
FROM (select count(1) as tokens from user_auth_token group by user_id) uat;` |
||||
_, err := sess.SQL(rawSQL).Get(s.concurrentUserStatsCache.stats) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
}) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to get concurrent users stats from database: %w", err) |
||||
} |
||||
|
||||
s.concurrentUserStatsCache.memoized = time.Now() |
||||
return s.concurrentUserStatsCache.stats, nil |
||||
} |
||||
@ -0,0 +1,321 @@ |
||||
package statscollector |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/infra/metrics" |
||||
"github.com/grafana/grafana/pkg/login/social" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/plugins" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
type Service struct { |
||||
cfg *setting.Cfg |
||||
sqlstore sqlstore.Store |
||||
plugins plugins.Store |
||||
social social.Service |
||||
usageStats usagestats.Service |
||||
features *featuremgmt.FeatureManager |
||||
|
||||
log log.Logger |
||||
|
||||
startTime time.Time |
||||
concurrentUserStatsCache memoConcurrentUserStats |
||||
} |
||||
|
||||
func ProvideService( |
||||
usagestats usagestats.Service, |
||||
cfg *setting.Cfg, |
||||
store sqlstore.Store, |
||||
social social.Service, |
||||
plugins plugins.Store, |
||||
features *featuremgmt.FeatureManager, |
||||
) *Service { |
||||
s := &Service{ |
||||
cfg: cfg, |
||||
sqlstore: store, |
||||
plugins: plugins, |
||||
social: social, |
||||
usageStats: usagestats, |
||||
features: features, |
||||
|
||||
startTime: time.Now(), |
||||
log: log.New("infra.usagestats.collector"), |
||||
} |
||||
|
||||
usagestats.RegisterMetricsFunc(s.collect) |
||||
|
||||
return s |
||||
} |
||||
|
||||
func (s *Service) Run(ctx context.Context) error { |
||||
s.updateTotalStats(ctx) |
||||
updateStatsTicker := time.NewTicker(time.Minute * 30) |
||||
defer updateStatsTicker.Stop() |
||||
|
||||
for { |
||||
select { |
||||
case <-updateStatsTicker.C: |
||||
s.updateTotalStats(ctx) |
||||
case <-ctx.Done(): |
||||
return ctx.Err() |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (s *Service) collect(ctx context.Context) (map[string]interface{}, error) { |
||||
m := map[string]interface{}{} |
||||
|
||||
statsQuery := models.GetSystemStatsQuery{} |
||||
if err := s.sqlstore.GetSystemStats(ctx, &statsQuery); err != nil { |
||||
s.log.Error("Failed to get system stats", "error", err) |
||||
return nil, err |
||||
} |
||||
|
||||
m["stats.dashboards.count"] = statsQuery.Result.Dashboards |
||||
m["stats.users.count"] = statsQuery.Result.Users |
||||
m["stats.admins.count"] = statsQuery.Result.Admins |
||||
m["stats.editors.count"] = statsQuery.Result.Editors |
||||
m["stats.viewers.count"] = statsQuery.Result.Viewers |
||||
m["stats.orgs.count"] = statsQuery.Result.Orgs |
||||
m["stats.playlist.count"] = statsQuery.Result.Playlists |
||||
m["stats.plugins.apps.count"] = s.appCount(ctx) |
||||
m["stats.plugins.panels.count"] = s.panelCount(ctx) |
||||
m["stats.plugins.datasources.count"] = s.dataSourceCount(ctx) |
||||
m["stats.alerts.count"] = statsQuery.Result.Alerts |
||||
m["stats.active_users.count"] = statsQuery.Result.ActiveUsers |
||||
m["stats.active_admins.count"] = statsQuery.Result.ActiveAdmins |
||||
m["stats.active_editors.count"] = statsQuery.Result.ActiveEditors |
||||
m["stats.active_viewers.count"] = statsQuery.Result.ActiveViewers |
||||
m["stats.active_sessions.count"] = statsQuery.Result.ActiveSessions |
||||
m["stats.monthly_active_users.count"] = statsQuery.Result.MonthlyActiveUsers |
||||
m["stats.daily_active_users.count"] = statsQuery.Result.DailyActiveUsers |
||||
m["stats.daily_active_admins.count"] = statsQuery.Result.DailyActiveAdmins |
||||
m["stats.daily_active_editors.count"] = statsQuery.Result.DailyActiveEditors |
||||
m["stats.daily_active_viewers.count"] = statsQuery.Result.DailyActiveViewers |
||||
m["stats.daily_active_sessions.count"] = statsQuery.Result.DailyActiveSessions |
||||
m["stats.datasources.count"] = statsQuery.Result.Datasources |
||||
m["stats.stars.count"] = statsQuery.Result.Stars |
||||
m["stats.folders.count"] = statsQuery.Result.Folders |
||||
m["stats.dashboard_permissions.count"] = statsQuery.Result.DashboardPermissions |
||||
m["stats.folder_permissions.count"] = statsQuery.Result.FolderPermissions |
||||
m["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards |
||||
m["stats.snapshots.count"] = statsQuery.Result.Snapshots |
||||
m["stats.teams.count"] = statsQuery.Result.Teams |
||||
m["stats.total_auth_token.count"] = statsQuery.Result.AuthTokens |
||||
m["stats.dashboard_versions.count"] = statsQuery.Result.DashboardVersions |
||||
m["stats.annotations.count"] = statsQuery.Result.Annotations |
||||
m["stats.alert_rules.count"] = statsQuery.Result.AlertRules |
||||
m["stats.library_panels.count"] = statsQuery.Result.LibraryPanels |
||||
m["stats.library_variables.count"] = statsQuery.Result.LibraryVariables |
||||
m["stats.dashboards_viewers_can_edit.count"] = statsQuery.Result.DashboardsViewersCanEdit |
||||
m["stats.dashboards_viewers_can_admin.count"] = statsQuery.Result.DashboardsViewersCanAdmin |
||||
m["stats.folders_viewers_can_edit.count"] = statsQuery.Result.FoldersViewersCanEdit |
||||
m["stats.folders_viewers_can_admin.count"] = statsQuery.Result.FoldersViewersCanAdmin |
||||
m["stats.api_keys.count"] = statsQuery.Result.APIKeys |
||||
|
||||
ossEditionCount := 1 |
||||
enterpriseEditionCount := 0 |
||||
if s.cfg.IsEnterprise { |
||||
enterpriseEditionCount = 1 |
||||
ossEditionCount = 0 |
||||
} |
||||
m["stats.edition.oss.count"] = ossEditionCount |
||||
m["stats.edition.enterprise.count"] = enterpriseEditionCount |
||||
|
||||
userCount := statsQuery.Result.Users |
||||
avgAuthTokensPerUser := statsQuery.Result.AuthTokens |
||||
if userCount != 0 { |
||||
avgAuthTokensPerUser /= userCount |
||||
} |
||||
|
||||
m["stats.avg_auth_token_per_user.count"] = avgAuthTokensPerUser |
||||
|
||||
dsStats := models.GetDataSourceStatsQuery{} |
||||
if err := s.sqlstore.GetDataSourceStats(ctx, &dsStats); err != nil { |
||||
s.log.Error("Failed to get datasource stats", "error", err) |
||||
return nil, err |
||||
} |
||||
|
||||
// send counters for each data source
|
||||
// but ignore any custom data sources
|
||||
// as sending that name could be sensitive information
|
||||
dsOtherCount := 0 |
||||
for _, dsStat := range dsStats.Result { |
||||
if s.usageStats.ShouldBeReported(ctx, dsStat.Type) { |
||||
m["stats.ds."+dsStat.Type+".count"] = dsStat.Count |
||||
} else { |
||||
dsOtherCount += dsStat.Count |
||||
} |
||||
} |
||||
m["stats.ds.other.count"] = dsOtherCount |
||||
|
||||
esDataSourcesQuery := models.GetDataSourcesByTypeQuery{Type: models.DS_ES} |
||||
if err := s.sqlstore.GetDataSourcesByType(ctx, &esDataSourcesQuery); err != nil { |
||||
s.log.Error("Failed to get elasticsearch json data", "error", err) |
||||
return nil, err |
||||
} |
||||
|
||||
for _, data := range esDataSourcesQuery.Result { |
||||
esVersion, err := data.JsonData.Get("esVersion").Int() |
||||
if err != nil { |
||||
continue |
||||
} |
||||
|
||||
statName := fmt.Sprintf("stats.ds.elasticsearch.v%d.count", esVersion) |
||||
|
||||
count, _ := m[statName].(int64) |
||||
|
||||
m[statName] = count + 1 |
||||
} |
||||
|
||||
m["stats.packaging."+s.cfg.Packaging+".count"] = 1 |
||||
m["stats.distributor."+s.cfg.ReportingDistributor+".count"] = 1 |
||||
|
||||
// fetch datasource access stats
|
||||
dsAccessStats := models.GetDataSourceAccessStatsQuery{} |
||||
if err := s.sqlstore.GetDataSourceAccessStats(ctx, &dsAccessStats); err != nil { |
||||
s.log.Error("Failed to get datasource access stats", "error", err) |
||||
return nil, err |
||||
} |
||||
|
||||
// send access counters for each data source
|
||||
// but ignore any custom data sources
|
||||
// as sending that name could be sensitive information
|
||||
dsAccessOtherCount := make(map[string]int64) |
||||
for _, dsAccessStat := range dsAccessStats.Result { |
||||
if dsAccessStat.Access == "" { |
||||
continue |
||||
} |
||||
|
||||
access := strings.ToLower(dsAccessStat.Access) |
||||
|
||||
if s.usageStats.ShouldBeReported(ctx, dsAccessStat.Type) { |
||||
m["stats.ds_access."+dsAccessStat.Type+"."+access+".count"] = dsAccessStat.Count |
||||
} else { |
||||
old := dsAccessOtherCount[access] |
||||
dsAccessOtherCount[access] = old + dsAccessStat.Count |
||||
} |
||||
} |
||||
|
||||
for access, count := range dsAccessOtherCount { |
||||
m["stats.ds_access.other."+access+".count"] = count |
||||
} |
||||
|
||||
// get stats about alert notifier usage
|
||||
anStats := models.GetAlertNotifierUsageStatsQuery{} |
||||
if err := s.sqlstore.GetAlertNotifiersUsageStats(ctx, &anStats); err != nil { |
||||
s.log.Error("Failed to get alert notification stats", "error", err) |
||||
return nil, err |
||||
} |
||||
|
||||
for _, stats := range anStats.Result { |
||||
m["stats.alert_notifiers."+stats.Type+".count"] = stats.Count |
||||
} |
||||
|
||||
// Add stats about auth configuration
|
||||
authTypes := map[string]bool{} |
||||
authTypes["anonymous"] = s.cfg.AnonymousEnabled |
||||
authTypes["basic_auth"] = s.cfg.BasicAuthEnabled |
||||
authTypes["ldap"] = s.cfg.LDAPEnabled |
||||
authTypes["auth_proxy"] = s.cfg.AuthProxyEnabled |
||||
|
||||
for provider, enabled := range s.social.GetOAuthProviders() { |
||||
authTypes["oauth_"+provider] = enabled |
||||
} |
||||
|
||||
for authType, enabled := range authTypes { |
||||
enabledValue := 0 |
||||
if enabled { |
||||
enabledValue = 1 |
||||
} |
||||
m["stats.auth_enabled."+authType+".count"] = enabledValue |
||||
} |
||||
|
||||
// Get concurrent users stats as histogram
|
||||
concurrentUsersStats, err := s.concurrentUsers(ctx) |
||||
if err != nil { |
||||
s.log.Error("Failed to get concurrent users stats", "error", err) |
||||
return nil, err |
||||
} |
||||
|
||||
// Histogram is cumulative and metric name has a postfix of le_"<upper inclusive bound>"
|
||||
m["stats.auth_token_per_user_le_3"] = concurrentUsersStats.BucketLE3 |
||||
m["stats.auth_token_per_user_le_6"] = concurrentUsersStats.BucketLE6 |
||||
m["stats.auth_token_per_user_le_9"] = concurrentUsersStats.BucketLE9 |
||||
m["stats.auth_token_per_user_le_12"] = concurrentUsersStats.BucketLE12 |
||||
m["stats.auth_token_per_user_le_15"] = concurrentUsersStats.BucketLE15 |
||||
m["stats.auth_token_per_user_le_inf"] = concurrentUsersStats.BucketLEInf |
||||
|
||||
m["stats.uptime"] = int64(time.Since(s.startTime).Seconds()) |
||||
|
||||
featureUsageStats := s.features.GetUsageStats(ctx) |
||||
for k, v := range featureUsageStats { |
||||
m[k] = v |
||||
} |
||||
|
||||
return m, nil |
||||
} |
||||
|
||||
func (s *Service) updateTotalStats(ctx context.Context) bool { |
||||
if !s.cfg.MetricsEndpointEnabled || s.cfg.MetricsEndpointDisableTotalStats { |
||||
return false |
||||
} |
||||
|
||||
statsQuery := models.GetSystemStatsQuery{} |
||||
if err := s.sqlstore.GetSystemStats(ctx, &statsQuery); err != nil { |
||||
s.log.Error("Failed to get system stats", "error", err) |
||||
return false |
||||
} |
||||
|
||||
metrics.MStatTotalDashboards.Set(float64(statsQuery.Result.Dashboards)) |
||||
metrics.MStatTotalFolders.Set(float64(statsQuery.Result.Folders)) |
||||
metrics.MStatTotalUsers.Set(float64(statsQuery.Result.Users)) |
||||
metrics.MStatActiveUsers.Set(float64(statsQuery.Result.ActiveUsers)) |
||||
metrics.MStatTotalPlaylists.Set(float64(statsQuery.Result.Playlists)) |
||||
metrics.MStatTotalOrgs.Set(float64(statsQuery.Result.Orgs)) |
||||
metrics.StatsTotalViewers.Set(float64(statsQuery.Result.Viewers)) |
||||
metrics.StatsTotalActiveViewers.Set(float64(statsQuery.Result.ActiveViewers)) |
||||
metrics.StatsTotalEditors.Set(float64(statsQuery.Result.Editors)) |
||||
metrics.StatsTotalActiveEditors.Set(float64(statsQuery.Result.ActiveEditors)) |
||||
metrics.StatsTotalAdmins.Set(float64(statsQuery.Result.Admins)) |
||||
metrics.StatsTotalActiveAdmins.Set(float64(statsQuery.Result.ActiveAdmins)) |
||||
metrics.StatsTotalDashboardVersions.Set(float64(statsQuery.Result.DashboardVersions)) |
||||
metrics.StatsTotalAnnotations.Set(float64(statsQuery.Result.Annotations)) |
||||
metrics.StatsTotalAlertRules.Set(float64(statsQuery.Result.AlertRules)) |
||||
metrics.StatsTotalLibraryPanels.Set(float64(statsQuery.Result.LibraryPanels)) |
||||
metrics.StatsTotalLibraryVariables.Set(float64(statsQuery.Result.LibraryVariables)) |
||||
|
||||
dsStats := models.GetDataSourceStatsQuery{} |
||||
if err := s.sqlstore.GetDataSourceStats(ctx, &dsStats); err != nil { |
||||
s.log.Error("Failed to get datasource stats", "error", err) |
||||
return true |
||||
} |
||||
|
||||
for _, dsStat := range dsStats.Result { |
||||
metrics.StatsTotalDataSources.WithLabelValues(dsStat.Type).Set(float64(dsStat.Count)) |
||||
} |
||||
return true |
||||
} |
||||
|
||||
func (s *Service) appCount(ctx context.Context) int { |
||||
return len(s.plugins.Plugins(ctx, plugins.App)) |
||||
} |
||||
|
||||
func (s *Service) panelCount(ctx context.Context) int { |
||||
return len(s.plugins.Plugins(ctx, plugins.Panel)) |
||||
} |
||||
|
||||
func (s *Service) dataSourceCount(ctx context.Context) int { |
||||
return len(s.plugins.Plugins(ctx, plugins.DataSource)) |
||||
} |
||||
@ -0,0 +1,362 @@ |
||||
package statscollector |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson" |
||||
"github.com/grafana/grafana/pkg/infra/usagestats" |
||||
"github.com/grafana/grafana/pkg/login/social" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/plugins" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
func TestTotalStatsUpdate(t *testing.T) { |
||||
sqlStore := mockstore.NewSQLStoreMock() |
||||
s := createService(t, setting.NewCfg(), sqlStore) |
||||
s.cfg.MetricsEndpointEnabled = true |
||||
s.cfg.MetricsEndpointDisableTotalStats = false |
||||
|
||||
sqlStore.ExpectedSystemStats = &models.SystemStats{} |
||||
|
||||
tests := []struct { |
||||
MetricsEndpointEnabled bool |
||||
MetricsEndpointDisableTotalStats bool |
||||
ExpectedUpdate bool |
||||
}{ |
||||
{ |
||||
MetricsEndpointEnabled: false, |
||||
MetricsEndpointDisableTotalStats: false, |
||||
ExpectedUpdate: false, |
||||
}, |
||||
{ |
||||
MetricsEndpointEnabled: false, |
||||
MetricsEndpointDisableTotalStats: true, |
||||
ExpectedUpdate: false, |
||||
}, |
||||
{ |
||||
MetricsEndpointEnabled: true, |
||||
MetricsEndpointDisableTotalStats: true, |
||||
ExpectedUpdate: false, |
||||
}, |
||||
{ |
||||
MetricsEndpointEnabled: true, |
||||
MetricsEndpointDisableTotalStats: false, |
||||
ExpectedUpdate: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tc := range tests { |
||||
tc := tc |
||||
|
||||
t.Run(fmt.Sprintf( |
||||
"metricsEnabled(%v) * totalStatsDisabled(%v) = %v", |
||||
tc.MetricsEndpointEnabled, |
||||
tc.MetricsEndpointDisableTotalStats, |
||||
tc.ExpectedUpdate, |
||||
), func(t *testing.T) { |
||||
s.cfg.MetricsEndpointEnabled = tc.MetricsEndpointEnabled |
||||
s.cfg.MetricsEndpointDisableTotalStats = tc.MetricsEndpointDisableTotalStats |
||||
|
||||
assert.Equal(t, tc.ExpectedUpdate, s.updateTotalStats(context.Background())) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestFeatureUsageStats(t *testing.T) { |
||||
store := mockstore.NewSQLStoreMock() |
||||
mockSystemStats(store) |
||||
s := createService(t, setting.NewCfg(), store) |
||||
|
||||
m, err := s.collect(context.Background()) |
||||
require.NoError(t, err, "Expected no error") |
||||
|
||||
assert.Equal(t, 1, m["stats.features.feature_1.count"]) |
||||
assert.Equal(t, 1, m["stats.features.feature_2.count"]) |
||||
} |
||||
|
||||
func TestCollectingUsageStats(t *testing.T) { |
||||
sqlStore := mockstore.NewSQLStoreMock() |
||||
s := createService(t, &setting.Cfg{ |
||||
ReportingEnabled: true, |
||||
BuildVersion: "5.0.0", |
||||
AnonymousEnabled: true, |
||||
BasicAuthEnabled: true, |
||||
LDAPEnabled: true, |
||||
AuthProxyEnabled: true, |
||||
Packaging: "deb", |
||||
ReportingDistributor: "hosted-grafana", |
||||
}, sqlStore) |
||||
|
||||
s.startTime = time.Now().Add(-1 * time.Minute) |
||||
|
||||
mockSystemStats(sqlStore) |
||||
setupSomeDataSourcePlugins(t, s) |
||||
|
||||
sqlStore.ExpectedDataSourceStats = []*models.DataSourceStats{ |
||||
{ |
||||
Type: models.DS_ES, |
||||
Count: 9, |
||||
}, |
||||
{ |
||||
Type: models.DS_PROMETHEUS, |
||||
Count: 10, |
||||
}, |
||||
{ |
||||
Type: "unknown_ds", |
||||
Count: 11, |
||||
}, |
||||
{ |
||||
Type: "unknown_ds2", |
||||
Count: 12, |
||||
}, |
||||
} |
||||
|
||||
sqlStore.ExpectedDataSources = []*models.DataSource{ |
||||
{ |
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{ |
||||
"esVersion": 2, |
||||
}), |
||||
}, |
||||
{ |
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{ |
||||
"esVersion": 2, |
||||
}), |
||||
}, |
||||
{ |
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{ |
||||
"esVersion": 70, |
||||
}), |
||||
}, |
||||
} |
||||
|
||||
sqlStore.ExpectedDataSourcesAccessStats = []*models.DataSourceAccessStats{ |
||||
{ |
||||
Type: models.DS_ES, |
||||
Access: "direct", |
||||
Count: 1, |
||||
}, |
||||
{ |
||||
Type: models.DS_ES, |
||||
Access: "proxy", |
||||
Count: 2, |
||||
}, |
||||
{ |
||||
Type: models.DS_PROMETHEUS, |
||||
Access: "proxy", |
||||
Count: 3, |
||||
}, |
||||
{ |
||||
Type: "unknown_ds", |
||||
Access: "proxy", |
||||
Count: 4, |
||||
}, |
||||
{ |
||||
Type: "unknown_ds2", |
||||
Access: "", |
||||
Count: 5, |
||||
}, |
||||
{ |
||||
Type: "unknown_ds3", |
||||
Access: "direct", |
||||
Count: 6, |
||||
}, |
||||
{ |
||||
Type: "unknown_ds4", |
||||
Access: "direct", |
||||
Count: 7, |
||||
}, |
||||
{ |
||||
Type: "unknown_ds5", |
||||
Access: "proxy", |
||||
Count: 8, |
||||
}, |
||||
} |
||||
|
||||
sqlStore.ExpectedNotifierUsageStats = []*models.NotifierUsageStats{ |
||||
{ |
||||
Type: "slack", |
||||
Count: 1, |
||||
}, |
||||
{ |
||||
Type: "webhook", |
||||
Count: 2, |
||||
}, |
||||
} |
||||
|
||||
createConcurrentTokens(t, sqlStore) |
||||
|
||||
s.social = &mockSocial{ |
||||
OAuthProviders: map[string]bool{ |
||||
"github": true, |
||||
"gitlab": true, |
||||
"azuread": true, |
||||
"google": true, |
||||
"generic_oauth": true, |
||||
"grafana_com": true, |
||||
}, |
||||
} |
||||
|
||||
metrics, err := s.collect(context.Background()) |
||||
require.NoError(t, err) |
||||
|
||||
assert.EqualValues(t, 15, metrics["stats.total_auth_token.count"]) |
||||
assert.EqualValues(t, 2, metrics["stats.api_keys.count"]) |
||||
assert.EqualValues(t, 5, metrics["stats.avg_auth_token_per_user.count"]) |
||||
assert.EqualValues(t, 16, metrics["stats.dashboard_versions.count"]) |
||||
assert.EqualValues(t, 17, metrics["stats.annotations.count"]) |
||||
assert.EqualValues(t, 18, metrics["stats.alert_rules.count"]) |
||||
assert.EqualValues(t, 19, metrics["stats.library_panels.count"]) |
||||
assert.EqualValues(t, 20, metrics["stats.library_variables.count"]) |
||||
|
||||
assert.EqualValues(t, 9, metrics["stats.ds."+models.DS_ES+".count"]) |
||||
assert.EqualValues(t, 10, metrics["stats.ds."+models.DS_PROMETHEUS+".count"]) |
||||
|
||||
assert.EqualValues(t, 11+12, metrics["stats.ds.other.count"]) |
||||
|
||||
assert.EqualValues(t, 1, metrics["stats.ds_access."+models.DS_ES+".direct.count"]) |
||||
assert.EqualValues(t, 2, metrics["stats.ds_access."+models.DS_ES+".proxy.count"]) |
||||
assert.EqualValues(t, 3, metrics["stats.ds_access."+models.DS_PROMETHEUS+".proxy.count"]) |
||||
assert.EqualValues(t, 6+7, metrics["stats.ds_access.other.direct.count"]) |
||||
assert.EqualValues(t, 4+8, metrics["stats.ds_access.other.proxy.count"]) |
||||
|
||||
assert.EqualValues(t, 1, metrics["stats.alert_notifiers.slack.count"]) |
||||
assert.EqualValues(t, 2, metrics["stats.alert_notifiers.webhook.count"]) |
||||
|
||||
assert.EqualValues(t, 1, metrics["stats.auth_enabled.anonymous.count"]) |
||||
assert.EqualValues(t, 1, metrics["stats.auth_enabled.basic_auth.count"]) |
||||
assert.EqualValues(t, 1, metrics["stats.auth_enabled.ldap.count"]) |
||||
assert.EqualValues(t, 1, metrics["stats.auth_enabled.auth_proxy.count"]) |
||||
assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_github.count"]) |
||||
assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_gitlab.count"]) |
||||
assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_google.count"]) |
||||
assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_azuread.count"]) |
||||
assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_generic_oauth.count"]) |
||||
assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_grafana_com.count"]) |
||||
|
||||
assert.EqualValues(t, 1, metrics["stats.packaging.deb.count"]) |
||||
assert.EqualValues(t, 1, metrics["stats.distributor.hosted-grafana.count"]) |
||||
|
||||
assert.InDelta(t, int64(65), metrics["stats.uptime"], 6) |
||||
} |
||||
|
||||
func mockSystemStats(sqlStore *mockstore.SQLStoreMock) { |
||||
sqlStore.ExpectedSystemStats = &models.SystemStats{ |
||||
Dashboards: 1, |
||||
Datasources: 2, |
||||
Users: 3, |
||||
Admins: 31, |
||||
Editors: 32, |
||||
Viewers: 33, |
||||
ActiveUsers: 4, |
||||
ActiveAdmins: 21, |
||||
ActiveEditors: 22, |
||||
ActiveViewers: 23, |
||||
ActiveSessions: 24, |
||||
DailyActiveUsers: 25, |
||||
DailyActiveAdmins: 26, |
||||
DailyActiveEditors: 27, |
||||
DailyActiveViewers: 28, |
||||
DailyActiveSessions: 29, |
||||
Orgs: 5, |
||||
Playlists: 6, |
||||
Alerts: 7, |
||||
Stars: 8, |
||||
Folders: 9, |
||||
DashboardPermissions: 10, |
||||
FolderPermissions: 11, |
||||
ProvisionedDashboards: 12, |
||||
Snapshots: 13, |
||||
Teams: 14, |
||||
AuthTokens: 15, |
||||
DashboardVersions: 16, |
||||
Annotations: 17, |
||||
AlertRules: 18, |
||||
LibraryPanels: 19, |
||||
LibraryVariables: 20, |
||||
DashboardsViewersCanAdmin: 3, |
||||
DashboardsViewersCanEdit: 2, |
||||
FoldersViewersCanAdmin: 1, |
||||
FoldersViewersCanEdit: 5, |
||||
APIKeys: 2, |
||||
} |
||||
} |
||||
|
||||
type mockSocial struct { |
||||
social.Service |
||||
|
||||
OAuthProviders map[string]bool |
||||
} |
||||
|
||||
func (m *mockSocial) GetOAuthProviders() map[string]bool { |
||||
return m.OAuthProviders |
||||
} |
||||
|
||||
type fakePluginStore struct { |
||||
plugins.Store |
||||
|
||||
plugins map[string]plugins.PluginDTO |
||||
} |
||||
|
||||
func (pr fakePluginStore) Plugin(_ context.Context, pluginID string) (plugins.PluginDTO, bool) { |
||||
p, exists := pr.plugins[pluginID] |
||||
|
||||
return p, exists |
||||
} |
||||
|
||||
func setupSomeDataSourcePlugins(t *testing.T, s *Service) { |
||||
t.Helper() |
||||
|
||||
s.plugins = &fakePluginStore{ |
||||
plugins: map[string]plugins.PluginDTO{ |
||||
models.DS_ES: { |
||||
Signature: "internal", |
||||
}, |
||||
models.DS_PROMETHEUS: { |
||||
Signature: "internal", |
||||
}, |
||||
models.DS_GRAPHITE: { |
||||
Signature: "internal", |
||||
}, |
||||
models.DS_MYSQL: { |
||||
Signature: "internal", |
||||
}, |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func (pr fakePluginStore) Plugins(_ context.Context, pluginTypes ...plugins.Type) []plugins.PluginDTO { |
||||
var result []plugins.PluginDTO |
||||
for _, v := range pr.plugins { |
||||
for _, t := range pluginTypes { |
||||
if v.Type == t { |
||||
result = append(result, v) |
||||
} |
||||
} |
||||
} |
||||
|
||||
return result |
||||
} |
||||
|
||||
func createService(t testing.TB, cfg *setting.Cfg, store sqlstore.Store) *Service { |
||||
t.Helper() |
||||
|
||||
return ProvideService( |
||||
&usagestats.UsageStatsMock{}, |
||||
cfg, |
||||
store, |
||||
&mockSocial{}, |
||||
&fakePluginStore{}, |
||||
featuremgmt.WithFeatures("feature1", "feature2"), |
||||
) |
||||
} |
||||
Loading…
Reference in new issue