Skip to content

Commit 0b2d56b

Browse files
committed
Get to middleware
1 parent 76912df commit 0b2d56b

File tree

4 files changed

+93
-5
lines changed

4 files changed

+93
-5
lines changed

cli/root.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ type RootCmd struct {
431431
func telemetryInvocation(i *clibase.Invocation) telemetry.CLIInvocation {
432432
var topts []telemetry.CLIOption
433433
for _, opt := range i.Command.FullOptions() {
434-
if opt.Value.String() == opt.Default {
434+
if opt.ValueSource == clibase.ValueSourceNone || opt.ValueSource == clibase.ValueSourceDefault {
435435
continue
436436
}
437437
topts = append(topts, telemetry.CLIOption{
@@ -493,7 +493,7 @@ func (r *RootCmd) InitClient(client *codersdk.Client) clibase.MiddlewareFunc {
493493
}
494494
err = r.setClient(
495495
client, r.clientURL,
496-
append(r.header, "Coder-CLI-Invokation="+
496+
append(r.header, codersdk.CLITelemetryHeader+"="+
497497
base64.StdEncoding.EncodeToString(byt),
498498
),
499499
)

coderd/httpmw/telemetry.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package httpmw
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"net/http"
7+
"sync"
8+
"time"
9+
10+
"tailscale.com/tstime/rate"
11+
12+
"cdr.dev/slog"
13+
"github.com/coder/coder/coderd/telemetry"
14+
"github.com/coder/coder/codersdk"
15+
)
16+
17+
func ReportCLITelemetry(log slog.Logger, rep telemetry.Reporter) func(http.Handler) http.Handler {
18+
var mu sync.Mutex
19+
20+
var (
21+
// We send telemetry at most once per minute.
22+
limiter = rate.NewLimiter(rate.Every(time.Minute), 1)
23+
queue []telemetry.CLIInvocation
24+
)
25+
26+
return func(next http.Handler) http.Handler {
27+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
28+
defer next.ServeHTTP(rw, r)
29+
payload := r.Header.Get(codersdk.CLITelemetryHeader)
30+
31+
// We do simple checks and processing outside of the goroutine
32+
// to avoid the overhead of an additional goroutine on every
33+
// request.
34+
if payload == "" {
35+
return
36+
}
37+
38+
byt, err := base64.StdEncoding.DecodeString(payload)
39+
if err != nil {
40+
log.Error(
41+
r.Context(),
42+
"base64 decode CLI telemetry header",
43+
slog.F("error", err),
44+
)
45+
return
46+
}
47+
48+
var inv telemetry.CLIInvocation
49+
err = json.Unmarshal(byt, &inv)
50+
if err != nil {
51+
log.Error(
52+
r.Context(),
53+
"unmarshal CLI telemetry header",
54+
slog.Error(err),
55+
)
56+
return
57+
}
58+
59+
go func() {
60+
mu.Lock()
61+
defer mu.Unlock()
62+
63+
queue = append(queue, inv)
64+
if !limiter.Allow() && len(queue) < 1024 {
65+
return
66+
}
67+
rep.Report(&telemetry.Snapshot{
68+
CLIInvocations: queue,
69+
})
70+
log.Debug(
71+
r.Context(),
72+
"reported CLI telemetry", slog.F("count", len(queue)),
73+
)
74+
queue = queue[:0]
75+
}()
76+
})
77+
}
78+
}

coderd/telemetry/telemetry.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ type Snapshot struct {
700700
WorkspaceBuilds []WorkspaceBuild `json:"workspace_build"`
701701
WorkspaceResources []WorkspaceResource `json:"workspace_resources"`
702702
WorkspaceResourceMetadata []WorkspaceResourceMetadata `json:"workspace_resource_metadata"`
703-
CLIInvokations []CLIInvocation `json:"cli_invocations"`
703+
CLIInvocations []CLIInvocation `json:"cli_invocations"`
704704
}
705705

706706
// Deployment contains information about the host running Coder.
@@ -881,8 +881,8 @@ type CLIOption struct {
881881
}
882882

883883
type CLIInvocation struct {
884-
Command string `json:"command"`
885-
Options []CLIOption
884+
Command string `json:"command"`
885+
Options []CLIOption `json:"options"`
886886
}
887887

888888
type noopReporter struct{}

codersdk/client.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ const (
6161
// Only owners can bypass rate limits. This is typically used for scale testing.
6262
// nolint: gosec
6363
BypassRatelimitHeader = "X-Coder-Bypass-Ratelimit"
64+
65+
// Note: the use of X- prefix is deprecated, and we should eventually remove
66+
// it from BypassRatelimitHeader.
67+
//
68+
// See: https://datatracker.ietf.org/doc/html/rfc6648.
69+
70+
// CLIInvokableHeader contains a base64-encoded representation of the CLI
71+
// command that was invoked to produce the request. It is for internal use
72+
// only and should not be relied upon.
73+
CLITelemetryHeader = "Coder-CLI-Telemetry"
6474
)
6575

6676
// loggableMimeTypes is a list of MIME types that are safe to log

0 commit comments

Comments
 (0)