mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Usage stats: Fix concurrency issue (#84993)
* Usage stats: Fix concurrency issue
This commit is contained in:
parent
93ca49b785
commit
369cb5b6eb
@ -29,16 +29,16 @@ var usageStatsURL = "https://stats.grafana.org/grafana-usage-report"
|
|||||||
|
|
||||||
func (uss *UsageStats) GetUsageReport(ctx context.Context) (usagestats.Report, error) {
|
func (uss *UsageStats) GetUsageReport(ctx context.Context) (usagestats.Report, error) {
|
||||||
version := strings.ReplaceAll(uss.Cfg.BuildVersion, ".", "_")
|
version := strings.ReplaceAll(uss.Cfg.BuildVersion, ".", "_")
|
||||||
metrics := map[string]any{}
|
metrics := sync.Map{}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
edition := "oss"
|
edition := "oss"
|
||||||
if uss.Cfg.IsEnterprise {
|
if uss.Cfg.IsEnterprise {
|
||||||
edition = "enterprise"
|
edition = "enterprise"
|
||||||
}
|
}
|
||||||
|
|
||||||
report := usagestats.Report{
|
report := usagestats.Report{
|
||||||
Version: version,
|
Version: version,
|
||||||
Metrics: metrics,
|
|
||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
Edition: edition,
|
Edition: edition,
|
||||||
@ -46,20 +46,28 @@ func (uss *UsageStats) GetUsageReport(ctx context.Context) (usagestats.Report, e
|
|||||||
UsageStatsId: uss.GetUsageStatsId(ctx),
|
UsageStatsId: uss.GetUsageStatsId(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
uss.gatherMetrics(ctx, metrics)
|
uss.gatherMetrics(ctx, &metrics)
|
||||||
|
|
||||||
// must run after registration of external metrics
|
// must run after registration of external metrics
|
||||||
if v, ok := metrics["stats.valid_license.count"]; ok {
|
if v, ok := metrics.Load("stats.valid_license.count"); ok {
|
||||||
report.HasValidLicense = v == 1
|
report.HasValidLicense = v == 1
|
||||||
} else {
|
} else {
|
||||||
metrics["stats.valid_license.count"] = 0
|
metrics.Store("stats.valid_license.count", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
uss.log.FromContext(ctx).Debug("Collected usage stats", "metricCount", len(metrics), "version", report.Version, "os", report.Os, "arch", report.Arch, "edition", report.Edition, "duration", time.Since(start))
|
report.Metrics = make(map[string]any)
|
||||||
|
metricCount := 0
|
||||||
|
metrics.Range(func(key, value any) bool {
|
||||||
|
report.Metrics[key.(string)] = value
|
||||||
|
metricCount++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
uss.log.FromContext(ctx).Debug("Collected usage stats", "metricCount", metricCount, "version", report.Version, "os", report.Os, "arch", report.Arch, "edition", report.Edition, "duration", time.Since(start))
|
||||||
return report, nil
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uss *UsageStats) gatherMetrics(ctx context.Context, metrics map[string]any) {
|
func (uss *UsageStats) gatherMetrics(ctx context.Context, metrics *sync.Map) {
|
||||||
ctxTracer, span := uss.tracer.Start(ctx, "UsageStats.GatherLoop")
|
ctxTracer, span := uss.tracer.Start(ctx, "UsageStats.GatherLoop")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
totC, errC := 0, 0
|
totC, errC := 0, 0
|
||||||
@ -86,14 +94,14 @@ func (uss *UsageStats) gatherMetrics(ctx context.Context, metrics map[string]any
|
|||||||
}
|
}
|
||||||
|
|
||||||
for name, value := range fnMetrics {
|
for name, value := range fnMetrics {
|
||||||
metrics[name] = value
|
metrics.Store(name, value)
|
||||||
}
|
}
|
||||||
}(fn)
|
}(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
metrics["stats.usagestats.debug.collect.total.count"] = totC
|
metrics.Store("stats.usagestats.debug.collect.total.count", totC)
|
||||||
metrics["stats.usagestats.debug.collect.error.count"] = errC
|
metrics.Store("stats.usagestats.debug.collect.error.count", errC)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uss *UsageStats) runMetricsFunc(ctx context.Context, fn usagestats.MetricsFunc) (map[string]any, error) {
|
func (uss *UsageStats) runMetricsFunc(ctx context.Context, fn usagestats.MetricsFunc) (map[string]any, error) {
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -175,7 +176,9 @@ func TestRegisterMetrics(t *testing.T) {
|
|||||||
|
|
||||||
sqlStore := dbtest.NewFakeDB()
|
sqlStore := dbtest.NewFakeDB()
|
||||||
uss := createService(t, sqlStore, false)
|
uss := createService(t, sqlStore, false)
|
||||||
metrics := map[string]any{"stats.test_metric.count": 1, "stats.test_metric_second.count": 2}
|
metrics := sync.Map{}
|
||||||
|
metrics.Store("stats.test_metric.count", 1)
|
||||||
|
metrics.Store("stats.test_metric_second.count", 2)
|
||||||
|
|
||||||
uss.RegisterMetricsFunc(func(context.Context) (map[string]any, error) {
|
uss.RegisterMetricsFunc(func(context.Context) (map[string]any, error) {
|
||||||
return map[string]any{goodMetricName: 1}, nil
|
return map[string]any{goodMetricName: 1}, nil
|
||||||
@ -187,9 +190,15 @@ func TestRegisterMetrics(t *testing.T) {
|
|||||||
assert.Equal(t, map[string]any{goodMetricName: 1}, extMetrics)
|
assert.Equal(t, map[string]any{goodMetricName: 1}, extMetrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
uss.gatherMetrics(context.Background(), metrics)
|
uss.gatherMetrics(context.Background(), &metrics)
|
||||||
assert.Equal(t, 1, metrics[goodMetricName])
|
v, ok := metrics.Load(goodMetricName)
|
||||||
metricsCount := len(metrics)
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, 1, v)
|
||||||
|
metricsCountBefore := 0
|
||||||
|
metrics.Range(func(_, _ any) bool {
|
||||||
|
metricsCountBefore++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("do not add metrics that return an error when fetched", func(t *testing.T) {
|
t.Run("do not add metrics that return an error when fetched", func(t *testing.T) {
|
||||||
const badMetricName = "stats.test_external_metric_error.count"
|
const badMetricName = "stats.test_external_metric_error.count"
|
||||||
@ -197,15 +206,26 @@ func TestRegisterMetrics(t *testing.T) {
|
|||||||
uss.RegisterMetricsFunc(func(context.Context) (map[string]any, error) {
|
uss.RegisterMetricsFunc(func(context.Context) (map[string]any, error) {
|
||||||
return map[string]any{badMetricName: 1}, errors.New("some error")
|
return map[string]any{badMetricName: 1}, errors.New("some error")
|
||||||
})
|
})
|
||||||
uss.gatherMetrics(context.Background(), metrics)
|
uss.gatherMetrics(context.Background(), &metrics)
|
||||||
|
|
||||||
extErrorMetric := metrics[badMetricName]
|
extErrorMetric, ok := metrics.Load(badMetricName)
|
||||||
extMetric := metrics[goodMetricName]
|
assert.False(t, ok)
|
||||||
|
extMetric, ok := metrics.Load(goodMetricName)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
require.Nil(t, extErrorMetric, "Invalid metric should not be added")
|
require.Nil(t, extErrorMetric, "Invalid metric should not be added")
|
||||||
assert.Equal(t, 1, extMetric)
|
assert.Equal(t, 1, extMetric)
|
||||||
assert.Len(t, metrics, metricsCount, "Expected same number of metrics before and after collecting bad metric")
|
|
||||||
assert.EqualValues(t, 1, metrics["stats.usagestats.debug.collect.error.count"])
|
metricsCountAfter := 0
|
||||||
|
metrics.Range(func(_, _ any) bool {
|
||||||
|
metricsCountAfter++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, metricsCountAfter, metricsCountBefore, "Expected same number of metrics before and after collecting bad metric")
|
||||||
|
errCount, ok := metrics.Load("stats.usagestats.debug.collect.error.count")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.EqualValues(t, 1, errCount)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user