Skip to content

Commit 5e25405

Browse files
committed
refactor(telemetry): tighten connection telemetry types and flow
Type the connection reason/cause strings (ConnectionStateReason, ConnectionDropCause), have disconnect/scheduleReconnect own drop emission so the dedup flag and triple pre-call drops can go away, add an attempts measurement to the ssh discovery span, and extract the duplicated test telemetry helper.
1 parent 86df598 commit 5e25405

8 files changed

Lines changed: 143 additions & 124 deletions

File tree

src/instrumentation/reconnectingWebSocket.ts

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,30 @@
11
import { NOOP_SPAN, type Span } from "../telemetry/span";
22

33
import type { TelemetryReporter } from "../telemetry/reporter";
4-
import type { CloseEvent } from "../websocket/eventStreamConnection";
54
import type { ConnectionState } from "../websocket/reconnectingWebSocket";
65

6+
export type ConnectionStateReason =
7+
| "initial_connect"
8+
| "manual_reconnect"
9+
| "scheduled_reconnect"
10+
| "open"
11+
| "disconnect"
12+
| "dispose"
13+
| "unrecoverable_close"
14+
| "unrecoverable_http"
15+
| "certificate_error"
16+
| "connection_error"
17+
| "unexpected_close";
18+
19+
export type ConnectionDropCause =
20+
| "manual_disconnect"
21+
| "replaced"
22+
| "unrecoverable_close"
23+
| "normal_close"
24+
| "unexpected_close"
25+
| "disposed"
26+
| "error";
27+
728
interface ReconnectTelemetryCycle {
829
readonly startMs: number;
930
readonly resolve: () => void;
@@ -15,23 +36,21 @@ interface ReconnectTelemetryCycle {
1536

1637
export class ReconnectingWebSocketTelemetry {
1738
private connectionOpenedAtMs: number | undefined;
18-
private connectionDropEmitted = false;
1939
private reconnectCycle: ReconnectTelemetryCycle | undefined;
2040

2141
constructor(private readonly telemetry: TelemetryReporter) {}
2242

2343
stateTransition(
2444
from: ConnectionState,
2545
to: ConnectionState,
26-
reason: string,
46+
reason: ConnectionStateReason,
2747
): void {
2848
this.telemetry.log("connection.state_transition", { from, to, reason });
2949
}
3050

3151
connectionOpened(route: string, connectStartedAtMs: number): void {
3252
const openedAtMs = performance.now();
3353
this.connectionOpenedAtMs = openedAtMs;
34-
this.connectionDropEmitted = false;
3554
this.telemetry.log(
3655
"connection.open",
3756
{ url: route },
@@ -40,8 +59,12 @@ export class ReconnectingWebSocketTelemetry {
4059
this.finishReconnect(true);
4160
}
4261

43-
connectionDropped(cause: string, closeCode?: number, error?: unknown): void {
44-
if (this.connectionOpenedAtMs === undefined || this.connectionDropEmitted) {
62+
connectionDropped(
63+
cause: ConnectionDropCause,
64+
closeCode?: number,
65+
error?: unknown,
66+
): void {
67+
if (this.connectionOpenedAtMs === undefined) {
4568
return;
4669
}
4770

@@ -52,25 +75,19 @@ export class ReconnectingWebSocketTelemetry {
5275
const measurements = {
5376
connectionDurationMs: performance.now() - this.connectionOpenedAtMs,
5477
};
78+
this.connectionOpenedAtMs = undefined;
5579
if (error === undefined) {
5680
this.telemetry.log("connection.drop", properties, measurements);
57-
} else {
58-
this.telemetry.logError(
59-
"connection.drop",
60-
error,
61-
properties,
62-
measurements,
63-
);
81+
return;
6482
}
65-
this.connectionDropEmitted = true;
83+
this.telemetry.logError("connection.drop", error, properties, measurements);
6684
}
6785

6886
clearConnection(): void {
6987
this.connectionOpenedAtMs = undefined;
70-
this.connectionDropEmitted = false;
7188
}
7289

73-
startReconnect(reason: string): void {
90+
startReconnect(reason: ConnectionStateReason): void {
7491
if (this.reconnectCycle) {
7592
return;
7693
}
@@ -137,9 +154,3 @@ export class ReconnectingWebSocketTelemetry {
137154
);
138155
}
139156
}
140-
141-
export function closeEventError(event: CloseEvent): Error {
142-
return new Error(
143-
`WebSocket closed unexpectedly with code ${event.code}: ${event.reason}`,
144-
);
145-
}

src/instrumentation/sshProcess.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,15 @@ export class SshProcessTelemetry {
2525
constructor(private readonly telemetry: TelemetryReporter) {}
2626

2727
async discover(
28-
fn: () => Promise<number | undefined>,
28+
fn: (recordAttempt: () => void) => Promise<number | undefined>,
2929
): Promise<number | undefined> {
30-
return this.telemetry.trace("ssh.process.discovered", fn);
30+
let attempts = 0;
31+
return this.telemetry.trace("ssh.process.discovered", (span) =>
32+
fn(() => {
33+
attempts += 1;
34+
span.setMeasurement("attempts", attempts);
35+
}),
36+
);
3137
}
3238

3339
processStarted(now = performance.now()): void {
@@ -110,7 +116,7 @@ function toNetworkTelemetrySample(
110116
network: NetworkInfo,
111117
): NetworkTelemetrySample {
112118
return {
113-
emittedAtMs: Date.now(),
119+
emittedAtMs: performance.now(),
114120
p2p: network.p2p,
115121
derp: network.preferred_derp,
116122
latencyMs: network.latency,

src/remote/sshProcess.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const CLEANUP_NETWORK_MAX_AGE_MS = 60 * 60 * 1000;
5454
const CLEANUP_LOG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
5555
// Maximum number of proxy log files to keep during cleanup
5656
const CLEANUP_MAX_LOG_FILES = 20;
57+
5758
/**
5859
* Monitors the SSH process for a Coder workspace connection and displays
5960
* network status in the VS Code status bar.
@@ -221,7 +222,9 @@ export class SshProcessMonitor implements vscode.Disposable {
221222
* Starts monitoring when it finds the process through the port.
222223
*/
223224
private async searchForProcess(): Promise<void> {
224-
const pid = await this.telemetry.discover(() => this.discoverSshProcess());
225+
const pid = await this.telemetry.discover((recordAttempt) =>
226+
this.discoverSshProcess(recordAttempt),
227+
);
225228
if (pid === undefined || this.disposed) {
226229
return;
227230
}
@@ -230,7 +233,9 @@ export class SshProcessMonitor implements vscode.Disposable {
230233
this.startMonitoring();
231234
}
232235

233-
private async discoverSshProcess(): Promise<number | undefined> {
236+
private async discoverSshProcess(
237+
recordAttempt: () => void,
238+
): Promise<number | undefined> {
234239
const { discoveryPollIntervalMs, maxDiscoveryBackoffMs, logger, sshHost } =
235240
this.options;
236241
let attempt = 0;
@@ -239,6 +244,7 @@ export class SshProcessMonitor implements vscode.Disposable {
239244

240245
while (!this.disposed) {
241246
attempt++;
247+
recordAttempt();
242248

243249
if (attempt === 1 || attempt % 10 === 0) {
244250
logger.debug(
@@ -334,21 +340,21 @@ export class SshProcessMonitor implements vscode.Disposable {
334340
return;
335341
}
336342

337-
if (previousPid !== pid) {
338-
this.telemetry.processLost("process_changed");
343+
if (previousPid === pid) {
344+
this.telemetry.processRecovered(now);
345+
return;
339346
}
340-
this.telemetry.processRecovered(now);
341347

342-
if (previousPid !== pid) {
343-
this.telemetry.processStarted(now);
344-
this.telemetry.resetNetworkSampling();
345-
this.options.logger.info(
346-
`SSH process changed from ${previousPid} to ${pid}`,
347-
);
348-
this.logFilePath = undefined;
349-
this._onLogFilePathChange.fire(undefined);
350-
this._onPidChange.fire(pid);
351-
}
348+
this.telemetry.processLost("process_changed");
349+
this.telemetry.processRecovered(now);
350+
this.telemetry.processStarted(now);
351+
this.telemetry.resetNetworkSampling();
352+
this.options.logger.info(
353+
`SSH process changed from ${previousPid} to ${pid}`,
354+
);
355+
this.logFilePath = undefined;
356+
this._onLogFilePathChange.fire(undefined);
357+
this._onPidChange.fire(pid);
352358
}
353359

354360
/**

src/telemetry/span.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ export const NOOP_SPAN: Span = {
3535
): Promise<T> {
3636
return fn(NOOP_SPAN);
3737
},
38-
setProperty(): void {
39-
return undefined;
40-
},
41-
setMeasurement(): void {
42-
return undefined;
43-
},
38+
setProperty: () => undefined,
39+
setMeasurement: () => undefined,
4440
};

0 commit comments

Comments
 (0)