@@ -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 }
0 commit comments