@@ -59,6 +59,9 @@ type Options struct {
59
59
// New constructs a reporter for telemetry data.
60
60
// Duplicate data will be sent, it's on the server-side to index by UUID.
61
61
// Data is anonymized prior to being sent!
62
+ //
63
+ // The returned Reporter should be started with RunSnapshotter() to begin
64
+ // reporting.
62
65
func New (options Options ) (Reporter ,error ) {
63
66
if options .SnapshotFrequency == 0 {
64
67
// Report once every 30mins by default!
@@ -83,7 +86,6 @@ func New(options Options) (Reporter, error) {
83
86
snapshotURL :snapshotURL ,
84
87
startedAt :dbtime .Now (),
85
88
}
86
- go reporter .runSnapshotter ()
87
89
return reporter ,nil
88
90
}
89
91
@@ -101,6 +103,12 @@ type Reporter interface {
101
103
Report (snapshot * Snapshot )
102
104
Enabled ()bool
103
105
Close ()
106
+ // RunSnapshotter runs reporting in a loop. It should be called in a
107
+ // goroutine to avoid blocking the caller.
108
+ RunSnapshotter ()
109
+ // ReportDisabledIfNeeded reports disabled telemetry if there was at least one report sent
110
+ // before the telemetry was disabled, and we haven't sent a report since the telemetry was disabled.
111
+ ReportDisabledIfNeeded ()error
104
112
}
105
113
106
114
type remoteReporter struct {
@@ -149,6 +157,12 @@ func (r *remoteReporter) reportSync(snapshot *Snapshot) {
149
157
r .options .Logger .Debug (r .ctx ,"bad response from telemetry server" ,slog .F ("status" ,resp .StatusCode ))
150
158
return
151
159
}
160
+ if err := r .options .Database .UpsertTelemetryItem (r .ctx , database.UpsertTelemetryItemParams {
161
+ Key :string (TelemetryItemKeyLastTelemetryUpdate ),
162
+ Value :dbtime .Now ().Format (time .RFC3339 ),
163
+ });err != nil {
164
+ r .options .Logger .Debug (r .ctx ,"upsert last telemetry update" ,slog .Error (err ))
165
+ }
152
166
r .options .Logger .Debug (r .ctx ,"submitted snapshot" )
153
167
}
154
168
@@ -177,7 +191,7 @@ func (r *remoteReporter) isClosed() bool {
177
191
}
178
192
}
179
193
180
- func (r * remoteReporter )runSnapshotter () {
194
+ func (r * remoteReporter )RunSnapshotter () {
181
195
first := true
182
196
ticker := time .NewTicker (r .options .SnapshotFrequency )
183
197
defer ticker .Stop ()
@@ -330,6 +344,45 @@ func checkIDPOrgSync(ctx context.Context, db database.Store, values *codersdk.De
330
344
return syncConfig .Field != "" ,nil
331
345
}
332
346
347
+ func (r * remoteReporter )ReportDisabledIfNeeded ()error {
348
+ db := r .options .Database
349
+ lastTelemetryUpdate ,telemetryUpdateErr := db .GetTelemetryItem (r .ctx ,string (TelemetryItemKeyLastTelemetryUpdate ))
350
+ if telemetryUpdateErr != nil {
351
+ r .options .Logger .Debug (r .ctx ,"get last telemetry update at" ,slog .Error (telemetryUpdateErr ))
352
+ }
353
+ telemetryDisabled ,telemetryDisabledErr := db .GetTelemetryItem (r .ctx ,string (TelemetryItemKeyTelemetryDisabled ))
354
+ if telemetryDisabledErr != nil {
355
+ r .options .Logger .Debug (r .ctx ,"get telemetry disabled" ,slog .Error (telemetryDisabledErr ))
356
+ }
357
+ shouldReportDisabledTelemetry :=
358
+ telemetryUpdateErr == nil &&
359
+ ((telemetryDisabledErr == nil && lastTelemetryUpdate .UpdatedAt .Before (telemetryDisabled .UpdatedAt ))||
360
+ errors .Is (telemetryDisabledErr ,sql .ErrNoRows ))
361
+ if ! shouldReportDisabledTelemetry {
362
+ return nil
363
+ }
364
+
365
+ if err := db .UpsertTelemetryItem (r .ctx , database.UpsertTelemetryItemParams {
366
+ Key :string (TelemetryItemKeyTelemetryDisabled ),
367
+ Value :time .Now ().Format (time .RFC3339 ),
368
+ });err != nil {
369
+ return xerrors .Errorf ("upsert telemetry disabled: %w" ,err )
370
+ }
371
+ item ,err := db .GetTelemetryItem (r .ctx ,string (TelemetryItemKeyTelemetryDisabled ))
372
+ if err != nil {
373
+ return xerrors .Errorf ("get telemetry disabled: %w" ,err )
374
+ }
375
+
376
+ r .reportSync (
377
+ & Snapshot {
378
+ TelemetryItems : []TelemetryItem {
379
+ ConvertTelemetryItem (item ),
380
+ },
381
+ },
382
+ )
383
+ return nil
384
+ }
385
+
333
386
// createSnapshot collects a full snapshot from the database.
334
387
func (r * remoteReporter )createSnapshot () (* Snapshot ,error ) {
335
388
var (
@@ -1561,7 +1614,9 @@ type Organization struct {
1561
1614
type TelemetryItemKey string
1562
1615
1563
1616
const (
1564
- TelemetryItemKeyHTMLFirstServedAt TelemetryItemKey = "html_first_served_at"
1617
+ TelemetryItemKeyHTMLFirstServedAt TelemetryItemKey = "html_first_served_at"
1618
+ TelemetryItemKeyLastTelemetryUpdate TelemetryItemKey = "last_telemetry_update"
1619
+ TelemetryItemKeyTelemetryDisabled TelemetryItemKey = "telemetry_disabled"
1565
1620
)
1566
1621
1567
1622
type TelemetryItem struct {
@@ -1573,6 +1628,8 @@ type TelemetryItem struct {
1573
1628
1574
1629
type noopReporter struct {}
1575
1630
1576
- func (* noopReporter )Report (_ * Snapshot ) {}
1577
- func (* noopReporter )Enabled ()bool {return false }
1578
- func (* noopReporter )Close () {}
1631
+ func (* noopReporter )Report (_ * Snapshot ) {}
1632
+ func (* noopReporter )Enabled ()bool {return false }
1633
+ func (* noopReporter )Close () {}
1634
+ func (* noopReporter )RunSnapshotter () {}
1635
+ func (* noopReporter )ReportDisabledIfNeeded ()error {return nil }