Skip to content

Commit a110d18

Browse files
chore: add DRPC tailnet & cli network telemetry (#13687)
1 parent bfbf634 commit a110d18

22 files changed

+1218
-456
lines changed

cli/ping.go

+3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ func (r *RootCmd) ping() *serpent.Command {
5858
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
5959
opts.BlockEndpoints = true
6060
}
61+
if !r.disableNetworkTelemetry {
62+
opts.EnableTelemetry = true
63+
}
6164
conn, err := workspacesdk.New(client).DialAgent(ctx, workspaceAgent.ID, opts)
6265
if err != nil {
6366
return err

cli/portforward.go

+3
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ func (r *RootCmd) portForward() *serpent.Command {
106106
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
107107
opts.BlockEndpoints = true
108108
}
109+
if !r.disableNetworkTelemetry {
110+
opts.EnableTelemetry = true
111+
}
109112
conn, err := workspacesdk.New(client).DialAgent(ctx, workspaceAgent.ID, opts)
110113
if err != nil {
111114
return err

cli/root.go

+24-15
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,20 @@ var (
5252
)
5353

5454
const (
55-
varURL = "url"
56-
varToken = "token"
57-
varAgentToken = "agent-token"
58-
varAgentTokenFile = "agent-token-file"
59-
varAgentURL = "agent-url"
60-
varHeader = "header"
61-
varHeaderCommand = "header-command"
62-
varNoOpen = "no-open"
63-
varNoVersionCheck = "no-version-warning"
64-
varNoFeatureWarning = "no-feature-warning"
65-
varForceTty = "force-tty"
66-
varVerbose = "verbose"
67-
varDisableDirect = "disable-direct-connections"
55+
varURL = "url"
56+
varToken = "token"
57+
varAgentToken = "agent-token"
58+
varAgentTokenFile = "agent-token-file"
59+
varAgentURL = "agent-url"
60+
varHeader = "header"
61+
varHeaderCommand = "header-command"
62+
varNoOpen = "no-open"
63+
varNoVersionCheck = "no-version-warning"
64+
varNoFeatureWarning = "no-feature-warning"
65+
varForceTty = "force-tty"
66+
varVerbose = "verbose"
67+
varDisableDirect = "disable-direct-connections"
68+
varDisableNetworkTelemetry = "disable-network-telemetry"
6869

6970
notLoggedInMessage = "You are not logged in. Try logging in using 'coder login <url>'."
7071

@@ -435,6 +436,13 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
435436
Value: serpent.BoolOf(&r.disableDirect),
436437
Group: globalGroup,
437438
},
439+
{
440+
Flag: varDisableNetworkTelemetry,
441+
Env: "CODER_DISABLE_NETWORK_TELEMETRY",
442+
Description: "Disable network telemetry. Network telemetry is collected when connecting to workspaces using the CLI, and is forwarded to the server. If telemetry is also enabled on the server, it may be sent to Coder. Network telemetry is used to measure network quality and detect regressions.",
443+
Value: serpent.BoolOf(&r.disableNetworkTelemetry),
444+
Group: globalGroup,
445+
},
438446
{
439447
Flag: "debug-http",
440448
Description: "Debug codersdk HTTP requests.",
@@ -481,8 +489,9 @@ type RootCmd struct {
481489
disableDirect bool
482490
debugHTTP bool
483491

484-
noVersionCheck bool
485-
noFeatureWarning bool
492+
disableNetworkTelemetry bool
493+
noVersionCheck bool
494+
noFeatureWarning bool
486495
}
487496

488497
// InitClient authenticates the client with files from disk

cli/speedtest.go

+4
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ func (r *RootCmd) speedtest() *serpent.Command {
102102
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
103103
opts.BlockEndpoints = true
104104
}
105+
if !r.disableNetworkTelemetry {
106+
opts.EnableTelemetry = true
107+
}
105108
if pcapFile != "" {
106109
s := capture.New()
107110
opts.CaptureHook = s.LogPacket
@@ -183,6 +186,7 @@ func (r *RootCmd) speedtest() *serpent.Command {
183186
outputResult.Intervals[i] = interval
184187
}
185188
}
189+
conn.Conn.SendSpeedtestTelemetry(outputResult.Overall.ThroughputMbits)
186190
out, err := formatter.Format(inv.Context(), outputResult)
187191
if err != nil {
188192
return err

cli/ssh.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,9 @@ func (r *RootCmd) ssh() *serpent.Command {
243243
}
244244
conn, err := workspacesdk.New(client).
245245
DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{
246-
Logger: logger,
247-
BlockEndpoints: r.disableDirect,
246+
Logger: logger,
247+
BlockEndpoints: r.disableDirect,
248+
EnableTelemetry: !r.disableNetworkTelemetry,
248249
})
249250
if err != nil {
250251
return xerrors.Errorf("dial agent: %w", err)
@@ -436,6 +437,7 @@ func (r *RootCmd) ssh() *serpent.Command {
436437
}
437438

438439
err = sshSession.Wait()
440+
conn.SendDisconnectedTelemetry("ssh")
439441
if err != nil {
440442
if exitErr := (&gossh.ExitError{}); errors.As(err, &exitErr) {
441443
// Clear the error since it's not useful beyond

cli/testdata/coder_--help.golden

+7
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ variables or flags.
6666
--disable-direct-connections bool, $CODER_DISABLE_DIRECT_CONNECTIONS
6767
Disable direct (P2P) connections to workspaces.
6868

69+
--disable-network-telemetry bool, $CODER_DISABLE_NETWORK_TELEMETRY
70+
Disable network telemetry. Network telemetry is collected when
71+
connecting to workspaces using the CLI, and is forwarded to the
72+
server. If telemetry is also enabled on the server, it may be sent to
73+
Coder. Network telemetry is used to measure network quality and detect
74+
regressions.
75+
6976
--global-config string, $CODER_CONFIG_DIR (default: ~/.config/coderv2)
7077
Path to the global `coder` config directory.
7178

coderd/telemetry/telemetry.go

+14-26
Original file line numberDiff line numberDiff line change
@@ -1163,9 +1163,9 @@ type Netcheck struct {
11631163
IPv4 bool `json:"ipv4"`
11641164
IPv6CanSend bool `json:"ipv6_can_send"`
11651165
IPv4CanSend bool `json:"ipv4_can_send"`
1166-
OSHasIPv6 bool `json:"os_has_ipv6"`
11671166
ICMPv4 bool `json:"icmpv4"`
11681167

1168+
OSHasIPv6 *bool `json:"os_has_ipv6"`
11691169
MappingVariesByDestIP *bool `json:"mapping_varies_by_dest_ip"`
11701170
HairPinning *bool `json:"hair_pinning"`
11711171
UPnP *bool `json:"upnp"`
@@ -1210,9 +1210,9 @@ func netcheckFromProto(proto *tailnetproto.Netcheck) Netcheck {
12101210
IPv4: proto.IPv4,
12111211
IPv6CanSend: proto.IPv6CanSend,
12121212
IPv4CanSend: proto.IPv4CanSend,
1213-
OSHasIPv6: proto.OSHasIPv6,
12141213
ICMPv4: proto.ICMPv4,
12151214

1215+
OSHasIPv6: protoBool(proto.OSHasIPv6),
12161216
MappingVariesByDestIP: protoBool(proto.MappingVariesByDestIP),
12171217
HairPinning: protoBool(proto.HairPinning),
12181218
UPnP: protoBool(proto.UPnP),
@@ -1221,33 +1221,28 @@ func netcheckFromProto(proto *tailnetproto.Netcheck) Netcheck {
12211221

12221222
PreferredDERP: proto.PreferredDERP,
12231223

1224-
RegionLatency: durationMapFromProto(proto.RegionLatency),
12251224
RegionV4Latency: durationMapFromProto(proto.RegionV4Latency),
12261225
RegionV6Latency: durationMapFromProto(proto.RegionV6Latency),
12271226

12281227
GlobalV4: netcheckIPFromProto(proto.GlobalV4),
12291228
GlobalV6: netcheckIPFromProto(proto.GlobalV6),
1230-
1231-
CaptivePortal: protoBool(proto.CaptivePortal),
12321229
}
12331230
}
12341231

12351232
// NetworkEvent and all related structs come from tailnet.proto.
12361233
type NetworkEvent struct {
1237-
ID uuid.UUID `json:"id"`
1238-
Time time.Time `json:"time"`
1239-
Application string `json:"application"`
1240-
Status string `json:"status"` // connected, disconnected
1241-
DisconnectionReason string `json:"disconnection_reason"`
1242-
ClientType string `json:"client_type"` // cli, agent, coderd, wsproxy
1243-
NodeIDSelf uint64 `json:"node_id_self"`
1244-
NodeIDRemote uint64 `json:"node_id_remote"`
1245-
P2PEndpoint NetworkEventP2PEndpoint `json:"p2p_endpoint"`
1246-
LogIPHashes map[string]NetworkEventIPFields `json:"log_ip_hashes"`
1247-
HomeDERP string `json:"home_derp"`
1248-
Logs []string `json:"logs"`
1249-
DERPMap DERPMap `json:"derp_map"`
1250-
LatestNetcheck Netcheck `json:"latest_netcheck"`
1234+
ID uuid.UUID `json:"id"`
1235+
Time time.Time `json:"time"`
1236+
Application string `json:"application"`
1237+
Status string `json:"status"` // connected, disconnected
1238+
DisconnectionReason string `json:"disconnection_reason"`
1239+
ClientType string `json:"client_type"` // cli, agent, coderd, wsproxy
1240+
NodeIDSelf uint64 `json:"node_id_self"`
1241+
NodeIDRemote uint64 `json:"node_id_remote"`
1242+
P2PEndpoint NetworkEventP2PEndpoint `json:"p2p_endpoint"`
1243+
HomeDERP string `json:"home_derp"`
1244+
DERPMap DERPMap `json:"derp_map"`
1245+
LatestNetcheck Netcheck `json:"latest_netcheck"`
12511246

12521247
ConnectionAge *time.Duration `json:"connection_age"`
12531248
ConnectionSetup *time.Duration `json:"connection_setup"`
@@ -1281,11 +1276,6 @@ func NetworkEventFromProto(proto *tailnetproto.TelemetryEvent) (NetworkEvent, er
12811276
return NetworkEvent{}, xerrors.Errorf("parse id %q: %w", proto.Id, err)
12821277
}
12831278

1284-
logIPHashes := make(map[string]NetworkEventIPFields, len(proto.LogIpHashes))
1285-
for k, v := range proto.LogIpHashes {
1286-
logIPHashes[k] = ipFieldsFromProto(v)
1287-
}
1288-
12891279
return NetworkEvent{
12901280
ID: id,
12911281
Time: proto.Time.AsTime(),
@@ -1296,9 +1286,7 @@ func NetworkEventFromProto(proto *tailnetproto.TelemetryEvent) (NetworkEvent, er
12961286
NodeIDSelf: proto.NodeIdSelf,
12971287
NodeIDRemote: proto.NodeIdRemote,
12981288
P2PEndpoint: p2pEndpointFromProto(proto.P2PEndpoint),
1299-
LogIPHashes: logIPHashes,
13001289
HomeDERP: proto.HomeDerp,
1301-
Logs: proto.Logs,
13021290
DERPMap: derpMapFromProto(proto.DerpMap),
13031291
LatestNetcheck: netcheckFromProto(proto.LatestNetcheck),
13041292

codersdk/workspacesdk/agentconn.go

+6
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ func (c *AgentConn) SSH(ctx context.Context) (*gonet.TCPConn, error) {
149149
return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err())
150150
}
151151

152+
c.Conn.SendConnectedTelemetry(c.agentAddress(), tailnet.TelemetryApplicationSSH)
152153
return c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), AgentSSHPort))
153154
}
154155

@@ -185,6 +186,7 @@ func (c *AgentConn) Speedtest(ctx context.Context, direction speedtest.Direction
185186
return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err())
186187
}
187188

189+
c.Conn.SendConnectedTelemetry(c.agentAddress(), tailnet.TelemetryApplicationSpeedtest)
188190
speedConn, err := c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), AgentSpeedtestPort))
189191
if err != nil {
190192
return nil, xerrors.Errorf("dial speedtest: %w", err)
@@ -378,3 +380,7 @@ func (c *AgentConn) apiClient() *http.Client {
378380
func (c *AgentConn) GetPeerDiagnostics() tailnet.PeerDiagnostics {
379381
return c.Conn.GetPeerDiagnostics(c.opts.AgentID)
380382
}
383+
384+
func (c *AgentConn) SendDisconnectedTelemetry(application string) {
385+
c.Conn.SendDisconnectedTelemetry(c.agentAddress(), application)
386+
}

0 commit comments

Comments
 (0)