mirror of https://github.com/grafana/grafana
Usage Stats: Split domain & service packages (#39488)
parent
b31316753a
commit
a680162792
@ -0,0 +1,163 @@ |
|||||||
|
package service |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/bus" |
||||||
|
"github.com/grafana/grafana/pkg/infra/kvstore" |
||||||
|
"github.com/grafana/grafana/pkg/infra/log" |
||||||
|
"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/services/alerting" |
||||||
|
"github.com/grafana/grafana/pkg/services/live" |
||||||
|
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||||
|
"github.com/grafana/grafana/pkg/setting" |
||||||
|
) |
||||||
|
|
||||||
|
type UsageStats struct { |
||||||
|
Cfg *setting.Cfg |
||||||
|
Bus bus.Bus |
||||||
|
SQLStore *sqlstore.SQLStore |
||||||
|
AlertingUsageStats alerting.UsageStatsQuerier |
||||||
|
PluginManager plugins.Manager |
||||||
|
SocialService social.Service |
||||||
|
grafanaLive *live.GrafanaLive |
||||||
|
kvStore *kvstore.NamespacedKVStore |
||||||
|
|
||||||
|
log log.Logger |
||||||
|
|
||||||
|
oauthProviders map[string]bool |
||||||
|
externalMetrics []usagestats.MetricsFunc |
||||||
|
concurrentUserStatsCache memoConcurrentUserStats |
||||||
|
liveStats liveUsageStats |
||||||
|
startTime time.Time |
||||||
|
} |
||||||
|
|
||||||
|
type liveUsageStats struct { |
||||||
|
numClientsMax int |
||||||
|
numClientsMin int |
||||||
|
numClientsSum int |
||||||
|
numUsersMax int |
||||||
|
numUsersMin int |
||||||
|
numUsersSum int |
||||||
|
sampleCount int |
||||||
|
} |
||||||
|
|
||||||
|
func ProvideService(cfg *setting.Cfg, bus bus.Bus, sqlStore *sqlstore.SQLStore, |
||||||
|
alertingStats alerting.UsageStatsQuerier, pluginManager plugins.Manager, |
||||||
|
socialService social.Service, grafanaLive *live.GrafanaLive, |
||||||
|
kvStore kvstore.KVStore) *UsageStats { |
||||||
|
s := &UsageStats{ |
||||||
|
Cfg: cfg, |
||||||
|
Bus: bus, |
||||||
|
SQLStore: sqlStore, |
||||||
|
AlertingUsageStats: alertingStats, |
||||||
|
oauthProviders: socialService.GetOAuthProviders(), |
||||||
|
PluginManager: pluginManager, |
||||||
|
grafanaLive: grafanaLive, |
||||||
|
kvStore: kvstore.WithNamespace(kvStore, 0, "infra.usagestats"), |
||||||
|
log: log.New("infra.usagestats"), |
||||||
|
startTime: time.Now(), |
||||||
|
} |
||||||
|
|
||||||
|
return s |
||||||
|
} |
||||||
|
|
||||||
|
func (uss *UsageStats) Run(ctx context.Context) error { |
||||||
|
uss.updateTotalStats() |
||||||
|
|
||||||
|
// try to load last sent time from kv store
|
||||||
|
lastSent := time.Now() |
||||||
|
if val, ok, err := uss.kvStore.Get(ctx, "last_sent"); err != nil { |
||||||
|
uss.log.Error("Failed to get last sent time", "error", err) |
||||||
|
} else if ok { |
||||||
|
if parsed, err := time.Parse(time.RFC3339, val); err != nil { |
||||||
|
uss.log.Error("Failed to parse last sent time", "error", err) |
||||||
|
} else { |
||||||
|
lastSent = parsed |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// calculate initial send delay
|
||||||
|
sendInterval := time.Hour * 24 |
||||||
|
nextSendInterval := time.Until(lastSent.Add(sendInterval)) |
||||||
|
if nextSendInterval < time.Minute { |
||||||
|
nextSendInterval = time.Minute |
||||||
|
} |
||||||
|
|
||||||
|
sendReportTicker := time.NewTicker(nextSendInterval) |
||||||
|
updateStatsTicker := time.NewTicker(time.Minute * 30) |
||||||
|
|
||||||
|
defer sendReportTicker.Stop() |
||||||
|
defer updateStatsTicker.Stop() |
||||||
|
|
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-sendReportTicker.C: |
||||||
|
if err := uss.sendUsageStats(ctx); err != nil { |
||||||
|
uss.log.Warn("Failed to send usage stats", "error", err) |
||||||
|
} |
||||||
|
|
||||||
|
lastSent = time.Now() |
||||||
|
if err := uss.kvStore.Set(ctx, "last_sent", lastSent.Format(time.RFC3339)); err != nil { |
||||||
|
uss.log.Warn("Failed to update last sent time", "error", err) |
||||||
|
} |
||||||
|
|
||||||
|
if nextSendInterval != sendInterval { |
||||||
|
nextSendInterval = sendInterval |
||||||
|
sendReportTicker.Reset(nextSendInterval) |
||||||
|
} |
||||||
|
|
||||||
|
// always reset live stats every report tick
|
||||||
|
uss.resetLiveStats() |
||||||
|
case <-updateStatsTicker.C: |
||||||
|
uss.updateTotalStats() |
||||||
|
uss.sampleLiveStats() |
||||||
|
case <-ctx.Done(): |
||||||
|
return ctx.Err() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type memoConcurrentUserStats struct { |
||||||
|
stats *concurrentUsersStats |
||||||
|
|
||||||
|
memoized time.Time |
||||||
|
} |
||||||
|
|
||||||
|
const concurrentUserStatsCacheLifetime = time.Hour |
||||||
|
|
||||||
|
func (uss *UsageStats) GetConcurrentUsersStats(ctx context.Context) (*concurrentUsersStats, error) { |
||||||
|
memoizationPeriod := time.Now().Add(-concurrentUserStatsCacheLifetime) |
||||||
|
if !uss.concurrentUserStatsCache.memoized.Before(memoizationPeriod) { |
||||||
|
return uss.concurrentUserStatsCache.stats, nil |
||||||
|
} |
||||||
|
|
||||||
|
uss.concurrentUserStatsCache.stats = &concurrentUsersStats{} |
||||||
|
err := uss.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(uss.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) |
||||||
|
} |
||||||
|
|
||||||
|
uss.concurrentUserStatsCache.memoized = time.Now() |
||||||
|
return uss.concurrentUserStatsCache.stats, nil |
||||||
|
} |
@ -1,4 +1,4 @@ |
|||||||
package usagestats |
package service |
||||||
|
|
||||||
type concurrentUsersStats struct { |
type concurrentUsersStats struct { |
||||||
BucketLE3 int32 `xorm:"bucket_le_3"` |
BucketLE3 int32 `xorm:"bucket_le_3"` |
Loading…
Reference in new issue