The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/infra/usagestats/statscollector/service.go

358 lines
13 KiB

package statscollector
import (
"context"
"fmt"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/stats"
"github.com/grafana/grafana/pkg/setting"
)
type Service struct {
cfg *setting.Cfg
sqlstore db.DB
plugins plugins.Store
usageStats usagestats.Service
statsService stats.Service
features *featuremgmt.FeatureManager
datasources datasources.DataSourceService
httpClientProvider httpclient.Provider
log log.Logger
startTime time.Time
concurrentUserStatsCache memoConcurrentUserStats
promFlavorCache memoPrometheusFlavor
usageStatProviders []registry.ProvidesUsageStats
}
func ProvideService(
us usagestats.Service,
statsService stats.Service,
cfg *setting.Cfg,
store db.DB,
social social.Service,
plugins plugins.Store,
features *featuremgmt.FeatureManager,
datasourceService datasources.DataSourceService,
httpClientProvider httpclient.Provider,
) *Service {
s := &Service{
cfg: cfg,
sqlstore: store,
plugins: plugins,
usageStats: us,
statsService: statsService,
features: features,
datasources: datasourceService,
httpClientProvider: httpClientProvider,
startTime: time.Now(),
log: log.New("infra.usagestats.collector"),
}
collectors := []usagestats.MetricsFunc{
s.collectSystemStats,
s.collectConcurrentUsers,
s.collectDatasourceStats,
s.collectDatasourceAccess,
s.collectElasticStats,
s.collectAlertNotifierStats,
s.collectPrometheusFlavors,
s.collectAdditionalMetrics,
}
for _, c := range collectors {
us.RegisterMetricsFunc(c)
}
return s
}
// RegisterProviders is called only once - during Grafana start up
func (s *Service) RegisterProviders(usageStatProviders []registry.ProvidesUsageStats) {
s.log.Info("registering usage stat providers", "usageStatsProvidersLen", len(usageStatProviders))
s.usageStatProviders = usageStatProviders
}
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) collectSystemStats(ctx context.Context) (map[string]interface{}, error) {
m := map[string]interface{}{}
statsQuery := stats.GetSystemStatsQuery{}
if err := s.statsService.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
m["stats.data_keys.count"] = statsQuery.Result.DataKeys
m["stats.active_data_keys.count"] = statsQuery.Result.ActiveDataKeys
m["stats.public_dashboards.count"] = statsQuery.Result.PublicDashboards
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
m["stats.packaging."+s.cfg.Packaging+".count"] = 1
m["stats.distributor."+s.cfg.ReportingDistributor+".count"] = 1
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) collectAdditionalMetrics(ctx context.Context) (map[string]interface{}, error) {
m := map[string]interface{}{}
for _, usageStatProvider := range s.usageStatProviders {
stats := usageStatProvider.GetUsageStats(ctx)
for k, v := range stats {
m[k] = v
}
}
return m, nil
}
func (s *Service) collectAlertNotifierStats(ctx context.Context) (map[string]interface{}, error) {
m := map[string]interface{}{}
// get stats about alert notifier usage
anStats := stats.GetAlertNotifierUsageStatsQuery{}
if err := s.statsService.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
}
return m, nil
}
func (s *Service) collectDatasourceStats(ctx context.Context) (map[string]interface{}, error) {
m := map[string]interface{}{}
dsStats := stats.GetDataSourceStatsQuery{}
if err := s.statsService.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
return m, nil
}
func (s *Service) collectElasticStats(ctx context.Context) (map[string]interface{}, error) {
m := map[string]interface{}{}
esDataSourcesQuery := datasources.GetDataSourcesByTypeQuery{Type: datasources.DS_ES}
dataSources, err := s.datasources.GetDataSourcesByType(ctx, &esDataSourcesQuery)
if err != nil {
s.log.Error("Failed to get elasticsearch json data", "error", err)
return nil, err
}
for _, data := range dataSources {
esVersion, err := data.JsonData.Get("esVersion").String()
if err != nil {
continue
}
statName := fmt.Sprintf("stats.ds.elasticsearch.v%s.count", strings.ReplaceAll(esVersion, ".", "_"))
count, _ := m[statName].(int64)
m[statName] = count + 1
}
return m, nil
}
func (s *Service) collectDatasourceAccess(ctx context.Context) (map[string]interface{}, error) {
m := map[string]interface{}{}
// fetch datasource access stats
dsAccessStats := stats.GetDataSourceAccessStatsQuery{}
if err := s.statsService.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
}
return m, nil
}
func (s *Service) updateTotalStats(ctx context.Context) bool {
if !s.cfg.MetricsEndpointEnabled || s.cfg.MetricsEndpointDisableTotalStats {
return false
}
statsQuery := stats.GetSystemStatsQuery{}
if err := s.statsService.GetSystemStats(ctx, &statsQuery); err != nil {
s.log.Error("Failed to get system stats", "error", err)
return false
}
if statsQuery.Result == nil {
s.log.Error("Cannot retrieve system stats")
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))
metrics.StatsTotalDataKeys.With(prometheus.Labels{"active": "true"}).Set(float64(statsQuery.Result.ActiveDataKeys))
inactiveDataKeys := statsQuery.Result.DataKeys - statsQuery.Result.ActiveDataKeys
metrics.StatsTotalDataKeys.With(prometheus.Labels{"active": "false"}).Set(float64(inactiveDataKeys))
metrics.MStatTotalPublicDashboards.Set(float64(statsQuery.Result.PublicDashboards))
dsStats := stats.GetDataSourceStatsQuery{}
if err := s.statsService.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))
}