Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/usagestats/reporter.go
4094 views
1
package usagestats
2
3
import (
4
"context"
5
"encoding/json"
6
"errors"
7
"math"
8
"os"
9
"path/filepath"
10
"runtime"
11
"time"
12
13
"github.com/go-kit/log"
14
"github.com/go-kit/log/level"
15
"github.com/google/uuid"
16
"github.com/grafana/dskit/backoff"
17
"github.com/grafana/dskit/multierror"
18
"github.com/prometheus/common/version"
19
)
20
21
var (
22
reportCheckInterval = time.Minute
23
reportInterval = 4 * time.Hour
24
)
25
26
// Reporter holds the agent seed information and sends report of usage
27
type Reporter struct {
28
logger log.Logger
29
30
agentSeed *AgentSeed
31
lastReport time.Time
32
}
33
34
// AgentSeed identifies a unique agent
35
type AgentSeed struct {
36
UID string `json:"UID"`
37
CreatedAt time.Time `json:"created_at"`
38
Version string `json:"version"`
39
}
40
41
// NewReporter creates a Reporter that will send periodically reports to grafana.com
42
func NewReporter(logger log.Logger) (*Reporter, error) {
43
r := &Reporter{
44
logger: logger,
45
}
46
return r, nil
47
}
48
49
func (rep *Reporter) init(ctx context.Context) error {
50
path := agentSeedFileName()
51
52
if fileExists(path) {
53
seed, err := rep.readSeedFile(path)
54
rep.agentSeed = seed
55
return err
56
}
57
rep.agentSeed = &AgentSeed{
58
UID: uuid.NewString(),
59
Version: version.Version,
60
CreatedAt: time.Now(),
61
}
62
return rep.writeSeedFile(*rep.agentSeed, path)
63
}
64
65
func fileExists(path string) bool {
66
_, err := os.Stat(path)
67
return !errors.Is(err, os.ErrNotExist)
68
}
69
70
// readSeedFile reads the agent seed file
71
func (rep *Reporter) readSeedFile(path string) (*AgentSeed, error) {
72
data, err := os.ReadFile(path)
73
if err != nil {
74
return nil, err
75
}
76
seed := &AgentSeed{}
77
err = json.Unmarshal(data, seed)
78
if err != nil {
79
return nil, err
80
}
81
return seed, nil
82
}
83
84
// writeSeedFile writes the agent seed file
85
func (rep *Reporter) writeSeedFile(seed AgentSeed, path string) error {
86
data, err := json.Marshal(seed)
87
if err != nil {
88
return err
89
}
90
return os.WriteFile(path, data, 0644)
91
}
92
93
func agentSeedFileName() string {
94
if runtime.GOOS == "windows" {
95
return filepath.Join(os.Getenv("APPDATA"), "agent_seed.json")
96
}
97
// linux/mac
98
return "/tmp/agent_seed.json"
99
}
100
101
// Start inits the reporter seed and start sending report for every interval
102
func (rep *Reporter) Start(ctx context.Context, metricsFunc func() map[string]interface{}) error {
103
level.Info(rep.logger).Log("msg", "running usage stats reporter")
104
err := rep.init(ctx)
105
if err != nil {
106
level.Info(rep.logger).Log("msg", "failed to init seed", "err", err)
107
return err
108
}
109
110
// check every minute if we should report.
111
ticker := time.NewTicker(reportCheckInterval)
112
defer ticker.Stop()
113
114
// find when to send the next report.
115
next := nextReport(reportInterval, rep.agentSeed.CreatedAt, time.Now())
116
if rep.lastReport.IsZero() {
117
// if we never reported assumed it was the last interval.
118
rep.lastReport = next.Add(-reportInterval)
119
}
120
for {
121
select {
122
case <-ticker.C:
123
now := time.Now()
124
if !next.Equal(now) && now.Sub(rep.lastReport) < reportInterval {
125
continue
126
}
127
level.Info(rep.logger).Log("msg", "reporting agent stats", "date", time.Now())
128
if err := rep.reportUsage(ctx, next, metricsFunc()); err != nil {
129
level.Info(rep.logger).Log("msg", "failed to report usage", "err", err)
130
continue
131
}
132
rep.lastReport = next
133
next = next.Add(reportInterval)
134
case <-ctx.Done():
135
return ctx.Err()
136
}
137
}
138
}
139
140
// reportUsage reports the usage to grafana.com.
141
func (rep *Reporter) reportUsage(ctx context.Context, interval time.Time, metrics map[string]interface{}) error {
142
backoff := backoff.New(ctx, backoff.Config{
143
MinBackoff: time.Second,
144
MaxBackoff: 30 * time.Second,
145
MaxRetries: 5,
146
})
147
var errs multierror.MultiError
148
for backoff.Ongoing() {
149
if err := sendReport(ctx, rep.agentSeed, interval, metrics); err != nil {
150
level.Info(rep.logger).Log("msg", "failed to send usage report", "retries", backoff.NumRetries(), "err", err)
151
errs.Add(err)
152
backoff.Wait()
153
continue
154
}
155
level.Info(rep.logger).Log("msg", "usage report sent with success")
156
return nil
157
}
158
return errs.Err()
159
}
160
161
// nextReport compute the next report time based on the interval.
162
// The interval is based off the creation of the agent seed to avoid all agents reporting at the same time.
163
func nextReport(interval time.Duration, createdAt, now time.Time) time.Time {
164
duration := math.Ceil(float64(now.Sub(createdAt)) / float64(interval))
165
return createdAt.Add(time.Duration(duration) * interval)
166
}
167
168