From 461797474e14b354f030bf48d7aeb853bca44fc2 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 1 Apr 2025 16:08:36 -0400 Subject: [PATCH 01/21] feat: create memory logger --- src/extension.ts | 4 + src/memoryLogger.ts | 214 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 src/memoryLogger.ts diff --git a/src/extension.ts b/src/extension.ts index e5e2799a..e9e65187 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,8 +11,12 @@ import { Remote } from "./remote" import { Storage } from "./storage" import { toSafeHost } from "./util" import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider" +import { getMemoryLogger } from "./memoryLogger" export async function activate(ctx: vscode.ExtensionContext): Promise { + // Initialize the memory logger right when the extension starts. + getMemoryLogger(); + // The Remote SSH extension's proposed APIs are used to override the SSH host // name in VS Code itself. It's visually unappealing having a lengthy name! // diff --git a/src/memoryLogger.ts b/src/memoryLogger.ts new file mode 100644 index 00000000..df50de3b --- /dev/null +++ b/src/memoryLogger.ts @@ -0,0 +1,214 @@ +import * as vscode from "vscode" +import * as os from "os" +import * as path from "path" +import * as fs from "fs/promises" + +/** + * A class for tracking memory usage and logging resource lifecycles + * to help identify memory leaks in the extension. + */ +export class MemoryLogger { + private outputChannel: vscode.OutputChannel + private logFile: string | undefined + private resourceCounts = new Map() + private startTime: number = Date.now() + private logInterval: NodeJS.Timeout | undefined + private disposed: boolean = false + + constructor() { + this.outputChannel = vscode.window.createOutputChannel("Coder Memory Logging") + this.outputChannel.show() + + // Setup periodic logging of memory usage + this.startPeriodicLogging() + } + + /** + * Start logging memory usage periodically + */ + private startPeriodicLogging(intervalMs = 60000) { + if (this.logInterval) { + clearInterval(this.logInterval) + } + + this.logInterval = setInterval(() => { + if (this.disposed) return + this.logMemoryUsage("PERIODIC") + this.logResourceCounts() + }, intervalMs) + } + + /** + * Initialize the log file for persistent logging + */ + public async initLogFile(globalStoragePath: string): Promise { + try { + const logDir = path.join(globalStoragePath, "logs") + await fs.mkdir(logDir, { recursive: true }) + + this.logFile = path.join(logDir, `memory-log-${new Date().toISOString().replace(/[:.]/g, "-")}.txt`) + + await this.writeToLogFile("Memory logging initialized") + this.info("Memory logging initialized to file: " + this.logFile) + + // Log initial memory state + this.logMemoryUsage("INIT") + } catch (err) { + this.error(`Failed to initialize log file: ${err}`) + } + } + + /** + * Log a new resource creation + */ + public trackResourceCreated(resourceType: string, id: string = ""): void { + const count = (this.resourceCounts.get(resourceType) || 0) + 1 + this.resourceCounts.set(resourceType, count) + this.info(`RESOURCE_CREATED: ${resourceType}${id ? ":" + id : ""} (Total: ${count})`) + } + + /** + * Log a resource disposal + */ + public trackResourceDisposed(resourceType: string, id: string = ""): void { + const count = Math.max(0, (this.resourceCounts.get(resourceType) || 1) - 1) + if (count === 0) { + this.resourceCounts.delete(resourceType) + } else { + this.resourceCounts.set(resourceType, count) + } + + this.info(`RESOURCE_DISPOSED: ${resourceType}${id ? ":" + id : ""} (Remaining: ${count})`) + } + + /** + * Log error with memory usage + */ + public error(message: string, error?: unknown): void { + const errorMsg = error ? `: ${error instanceof Error ? error.stack || error.message : String(error)}` : "" + const fullMessage = `[ERROR] ${message}${errorMsg}` + + this.outputChannel.appendLine(fullMessage) + this.writeToLogFile(fullMessage) + this.logMemoryUsage("ERROR") + } + + /** + * Log info with timestamp + */ + public info(message: string): void { + const fullMessage = `[INFO] ${message}` + this.outputChannel.appendLine(fullMessage) + this.writeToLogFile(fullMessage) + } + + /** + * Log debug info (only to file) + */ + public debug(message: string): void { + const fullMessage = `[DEBUG] ${message}` + this.writeToLogFile(fullMessage) + } + + /** + * Log current memory usage + */ + public logMemoryUsage(context: string): void { + try { + const memoryUsage = process.memoryUsage() + const nodeMemoryInfo = { + rss: `${(memoryUsage.rss / 1024 / 1024).toFixed(2)}MB`, + heapTotal: `${(memoryUsage.heapTotal / 1024 / 1024).toFixed(2)}MB`, + heapUsed: `${(memoryUsage.heapUsed / 1024 / 1024).toFixed(2)}MB`, + external: `${(memoryUsage.external / 1024 / 1024).toFixed(2)}MB`, + uptime: formatDuration(process.uptime() * 1000), + totalUptime: formatDuration(Date.now() - this.startTime) + } + + const systemMemoryInfo = { + totalMem: `${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)}GB`, + freeMem: `${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)}GB`, + loadAvg: os.loadavg().map(load => load.toFixed(2)).join(", ") + } + + const memoryLog = `[MEMORY:${context}] Node: ${JSON.stringify(nodeMemoryInfo)} | System: ${JSON.stringify(systemMemoryInfo)}` + this.outputChannel.appendLine(memoryLog) + this.writeToLogFile(memoryLog) + } catch (err) { + this.outputChannel.appendLine(`[ERROR] Failed to log memory usage: ${err}`) + } + } + + /** + * Log the current counts of active resources + */ + private logResourceCounts(): void { + const counts = Array.from(this.resourceCounts.entries()) + .map(([type, count]) => `${type}=${count}`) + .join(", ") + + const message = `[RESOURCES] Active resources: ${counts || "none"}` + this.outputChannel.appendLine(message) + this.writeToLogFile(message) + } + + /** + * Write to log file + */ + private async writeToLogFile(message: string): Promise { + if (!this.logFile) return + + try { + const timestamp = new Date().toISOString() + await fs.appendFile(this.logFile, `${timestamp} ${message}\n`) + } catch (err) { + // Don't recursively call this.error to avoid potential loops + this.outputChannel.appendLine(`[ERROR] Failed to write to log file: ${err}`) + } + } + + /** + * Show the log in the output channel + */ + public show(): void { + this.outputChannel.show() + } + + /** + * Dispose of the logger + */ + public dispose(): void { + this.disposed = true + if (this.logInterval) { + clearInterval(this.logInterval) + this.logInterval = undefined + } + this.logMemoryUsage("DISPOSE") + this.outputChannel.dispose() + } +} + +/** + * Format duration in milliseconds to a human-readable string + */ +function formatDuration(ms: number): string { + const seconds = Math.floor((ms / 1000) % 60) + const minutes = Math.floor((ms / (1000 * 60)) % 60) + const hours = Math.floor((ms / (1000 * 60 * 60)) % 24) + const days = Math.floor(ms / (1000 * 60 * 60 * 24)) + + return `${days}d ${hours}h ${minutes}m ${seconds}s` +} + +// Singleton instance +let instance: MemoryLogger | undefined + +/** + * Get or initialize the memory logger instance + */ +export function getMemoryLogger(): MemoryLogger { + if (!instance) { + instance = new MemoryLogger() + } + return instance +} \ No newline at end of file From e5ae295c6d40fdc3efdff50cad3d0f40ea90c280 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 1 Apr 2025 16:10:38 -0400 Subject: [PATCH 02/21] fix: format --- src/memoryLogger.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/memoryLogger.ts b/src/memoryLogger.ts index df50de3b..b33274be 100644 --- a/src/memoryLogger.ts +++ b/src/memoryLogger.ts @@ -122,13 +122,16 @@ export class MemoryLogger { heapUsed: `${(memoryUsage.heapUsed / 1024 / 1024).toFixed(2)}MB`, external: `${(memoryUsage.external / 1024 / 1024).toFixed(2)}MB`, uptime: formatDuration(process.uptime() * 1000), - totalUptime: formatDuration(Date.now() - this.startTime) + totalUptime: formatDuration(Date.now() - this.startTime), } const systemMemoryInfo = { totalMem: `${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)}GB`, freeMem: `${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)}GB`, - loadAvg: os.loadavg().map(load => load.toFixed(2)).join(", ") + loadAvg: os + .loadavg() + .map((load) => load.toFixed(2)) + .join(", "), } const memoryLog = `[MEMORY:${context}] Node: ${JSON.stringify(nodeMemoryInfo)} | System: ${JSON.stringify(systemMemoryInfo)}` @@ -211,4 +214,4 @@ export function getMemoryLogger(): MemoryLogger { instance = new MemoryLogger() } return instance -} \ No newline at end of file +} From 1e56a3f0cb814dd2413cc1d035c3f424ad9ba737 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 1 Apr 2025 16:20:36 -0400 Subject: [PATCH 03/21] feat: log information regarding network updates --- src/remote.ts | 145 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 98 insertions(+), 47 deletions(-) diff --git a/src/remote.ts b/src/remote.ts index 5b8a9694..050ebd5e 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -21,6 +21,7 @@ import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport" import { Storage } from "./storage" import { AuthorityPrefix, expandPath, parseRemoteAuthority } from "./util" import { WorkspaceMonitor } from "./workspaceMonitor" +import { getMemoryLogger } from "./memoryLogger" export interface RemoteDetails extends vscode.Disposable { url: string @@ -688,9 +689,20 @@ export class Remote { // showNetworkUpdates finds the SSH process ID that is being used by this // workspace and reads the file being created by the Coder CLI. private showNetworkUpdates(sshPid: number): vscode.Disposable { + const logger = getMemoryLogger() + logger.trackResourceCreated("NetworkStatusBar") + logger.info(`Starting network updates monitor for SSH PID: ${sshPid}`) + const networkStatus = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 1000) + networkStatus.name = "Coder Workspace Update" const networkInfoFile = path.join(this.storage.getNetworkInfoPath(), `${sshPid}.json`) + logger.debug(`Network info file path: ${networkInfoFile}`) + + let refreshCount = 0 + let disposed = false + let lastFileSize = 0 + const updateStatus = (network: { p2p: boolean latency: number @@ -699,78 +711,117 @@ export class Remote { upload_bytes_sec: number download_bytes_sec: number }) => { - let statusText = "$(globe) " - if (network.p2p) { - statusText += "Direct " - networkStatus.tooltip = "You're connected peer-to-peer ✨." - } else { - statusText += network.preferred_derp + " " - networkStatus.tooltip = - "You're connected through a relay 🕵.\nWe'll switch over to peer-to-peer when available." - } - networkStatus.tooltip += - "\n\nDownload ↓ " + - prettyBytes(network.download_bytes_sec, { - bits: true, - }) + - "/s • Upload ↑ " + - prettyBytes(network.upload_bytes_sec, { - bits: true, - }) + - "/s\n" - - if (!network.p2p) { - const derpLatency = network.derp_latency[network.preferred_derp] - - networkStatus.tooltip += `You ↔ ${derpLatency.toFixed(2)}ms ↔ ${network.preferred_derp} ↔ ${(network.latency - derpLatency).toFixed(2)}ms ↔ Workspace` - - let first = true - Object.keys(network.derp_latency).forEach((region) => { - if (region === network.preferred_derp) { - return - } - if (first) { - networkStatus.tooltip += `\n\nOther regions:` - first = false - } - networkStatus.tooltip += `\n${region}: ${Math.round(network.derp_latency[region] * 100) / 100}ms` - }) - } + try { + let statusText = "$(globe) " + if (network.p2p) { + statusText += "Direct " + networkStatus.tooltip = "You're connected peer-to-peer ✨." + } else { + statusText += network.preferred_derp + " " + networkStatus.tooltip = + "You're connected through a relay 🕵.\nWe'll switch over to peer-to-peer when available." + } + networkStatus.tooltip += + "\n\nDownload ↓ " + + prettyBytes(network.download_bytes_sec, { + bits: true, + }) + + "/s • Upload ↑ " + + prettyBytes(network.upload_bytes_sec, { + bits: true, + }) + + "/s\n" + + if (!network.p2p) { + const derpLatency = network.derp_latency[network.preferred_derp] + + networkStatus.tooltip += `You ↔ ${derpLatency.toFixed(2)}ms ↔ ${network.preferred_derp} ↔ ${( + network.latency - derpLatency + ).toFixed(2)}ms ↔ Workspace` + + let first = true + Object.keys(network.derp_latency).forEach((region) => { + if (region === network.preferred_derp) { + return + } + if (first) { + networkStatus.tooltip += `\n\nOther regions:` + first = false + } + networkStatus.tooltip += `\n${region}: ${Math.round(network.derp_latency[region] * 100) / 100}ms` + }) + } - statusText += "(" + network.latency.toFixed(2) + "ms)" - networkStatus.text = statusText - networkStatus.show() + statusText += "(" + network.latency.toFixed(2) + "ms)" + networkStatus.text = statusText + networkStatus.show() + + // Log occasional network stats updates (every 20 refreshes) + if (refreshCount % 20 === 0) { + logger.debug( + `Network stats update #${refreshCount}: p2p=${network.p2p}, latency=${network.latency.toFixed(2)}ms`, + ) + } + } catch (ex) { + // Replace silent error ignoring with proper logging + logger.error("Error updating network status", ex) + } } - let disposed = false + const periodicRefresh = () => { if (disposed) { + logger.debug("Network updates: Skipping refresh as disposed=true") return } + + refreshCount++ + + // Log every 100 refreshes to track long-term operation + if (refreshCount % 100 === 0) { + logger.info(`Network updates: Completed ${refreshCount} refresh cycles for SSH PID: ${sshPid}`) + logger.logMemoryUsage("NETWORK_REFRESH") + } + fs.readFile(networkInfoFile, "utf8") .then((content) => { + const currentSize = content.length + if (lastFileSize !== currentSize) { + logger.debug(`Network info file size changed: ${lastFileSize} -> ${currentSize} bytes`) + lastFileSize = currentSize + } return JSON.parse(content) }) .then((parsed) => { try { updateStatus(parsed) } catch (ex) { - // Ignore + logger.error(`Failed to update status from parsed network info`, ex) } }) - .catch(() => { - // TODO: Log a failure here! + .catch((error) => { + // Replace empty catch with proper error logging + logger.error(`Failed to read or parse network info file: ${networkInfoFile}`, error) }) .finally(() => { // This matches the write interval of `coder vscodessh`. - setTimeout(periodicRefresh, 3000) + if (!disposed) { + setTimeout(periodicRefresh, 3000) + } }) } + + // Log the first refresh + logger.debug(`Starting initial network refresh for SSH PID: ${sshPid}`) periodicRefresh() return { dispose: () => { - disposed = true - networkStatus.dispose() + if (!disposed) { + logger.info(`Disposing network updates monitor for SSH PID: ${sshPid} after ${refreshCount} refreshes`) + disposed = true + networkStatus.dispose() + logger.trackResourceDisposed("NetworkStatusBar") + } }, } } From f201d1ad8982866eed17fa33c9ab60ef1cb68b1b Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 1 Apr 2025 16:56:30 -0400 Subject: [PATCH 04/21] chore: improve logging for findSSHProcessID --- src/remote.ts | 100 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 77 insertions(+), 23 deletions(-) diff --git a/src/remote.ts b/src/remote.ts index 050ebd5e..7754ae1f 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -829,46 +829,100 @@ export class Remote { // findSSHProcessID returns the currently active SSH process ID that is // powering the remote SSH connection. private async findSSHProcessID(timeout = 15000): Promise { + const logger = getMemoryLogger() + logger.info(`Starting SSH process ID search with timeout: ${timeout}ms`) + + let attempts = 0 + let lastFilePath: string | undefined + const search = async (logPath: string): Promise => { - // This searches for the socksPort that Remote SSH is connecting to. We do - // this to find the SSH process that is powering this connection. That SSH - // process will be logging network information periodically to a file. - const text = await fs.readFile(logPath, "utf8") - const matches = text.match(/-> socksPort (\d+) ->/) - if (!matches) { - return - } - if (matches.length < 2) { - return - } - const port = Number.parseInt(matches[1]) - if (!port) { - return - } - const processes = await find("port", port) - if (processes.length < 1) { - return + try { + // This searches for the socksPort that Remote SSH is connecting to. We do + // this to find the SSH process that is powering this connection. That SSH + // process will be logging network information periodically to a file. + const text = await fs.readFile(logPath, "utf8") + + if (attempts % 5 === 0) { + logger.debug(`SSH log file size: ${text.length} bytes`) + } + + const matches = text.match(/-> socksPort (\d+) ->/) + if (!matches) { + return + } + if (matches.length < 2) { + return + } + const port = Number.parseInt(matches[1]) + if (!port) { + return + } + + logger.info(`Found SSH socks port: ${port}, searching for process`) + const processes = await find("port", port) + + if (processes.length < 1) { + logger.debug(`No processes found using port: ${port}`) + return + } + + const process = processes[0] + logger.info(`Found SSH process: PID=${process.pid}, CMD=${process.cmd}`) + return process.pid + } catch (error) { + logger.error(`Error searching for SSH process in log: ${logPath}`, error) + return undefined } - const process = processes[0] - return process.pid } + const start = Date.now() + const loop = async (): Promise => { - if (Date.now() - start > timeout) { + attempts++ + + const elapsed = Date.now() - start + if (elapsed > timeout) { + logger.info(`SSH process ID search timed out after ${attempts} attempts, elapsed: ${elapsed}ms`) return undefined } + + // Log progress periodically + if (attempts % 5 === 0) { + logger.info(`SSH process ID search attempt #${attempts}, elapsed: ${elapsed}ms`) + logger.logMemoryUsage("SSH_PROCESS_SEARCH") + } + // Loop until we find the remote SSH log for this window. const filePath = await this.storage.getRemoteSSHLogPath() + if (!filePath) { - return new Promise((resolve) => setTimeout(() => resolve(loop()), 500)) + if (lastFilePath !== filePath) { + lastFilePath = filePath + logger.debug(`SSH log file not found, will retry`) + } + + return new Promise((resolve) => { + setTimeout(() => resolve(loop()), 500) + }) + } + + if (lastFilePath !== filePath) { + lastFilePath = filePath + logger.info(`Found SSH log file: ${filePath}`) } + // Then we search the remote SSH log until we find the port. const result = await search(filePath) if (!result) { - return new Promise((resolve) => setTimeout(() => resolve(loop()), 500)) + return new Promise((resolve) => { + setTimeout(() => resolve(loop()), 500) + }) } + + logger.info(`SSH process ID search completed successfully after ${attempts} attempts, elapsed: ${elapsed}ms`) return result } + return loop() } From d7c650b02f52c36b40d39dad0bd57e82eb1ad5e3 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 1 Apr 2025 16:57:04 -0400 Subject: [PATCH 05/21] chore: add memory logging to WorkspacesProvider --- src/workspacesProvider.ts | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 0709487e..14fd8c83 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -12,6 +12,7 @@ import { errToStr, } from "./api-helper" import { Storage } from "./storage" +import { getMemoryLogger, MemoryLogger } from "./memoryLogger" export enum WorkspaceQuery { Mine = "owner:me", @@ -227,41 +228,80 @@ export class WorkspaceProvider implements vscode.TreeDataProvider() const watcher: AgentWatcher = { onChange: onChange.event, dispose: () => { if (!disposed) { + logger.info(`Disposing metadata watcher for agent: ${agentId} after ${eventCount} events`) eventSource.close() + onChange.dispose() disposed = true + logger.trackResourceDisposed("AgentMetadataWatcher", agentId) } }, } + eventSource.addEventListener("open", () => { + logger.info(`Metadata EventSource connection opened for agent: ${agentId}`) + }) + eventSource.addEventListener("data", (event) => { + eventCount++ + + // Log periodic updates + if (eventCount % 50 === 0) { + logger.info(`Received ${eventCount} metadata events for agent: ${agentId}`) + logger.logMemoryUsage("AGENT_METADATA") + } + try { const dataEvent = JSON.parse(event.data) const metadata = AgentMetadataEventSchemaArray.parse(dataEvent) // Overwrite metadata if it changed. if (JSON.stringify(watcher.metadata) !== JSON.stringify(metadata)) { + if (eventCount === 1) { + logger.debug(`Initial metadata received for agent: ${agentId}`) + } + watcher.metadata = metadata onChange.fire(null) } } catch (error) { + logger.error(`Error processing metadata for agent: ${agentId}`, error) watcher.error = error onChange.fire(null) } }) + eventSource.addEventListener("error", (error) => { + logger.error(`Metadata EventSource error for agent: ${agentId}`, error) + + // If connection closes permanently, clean up resources + if ((error as any).readyState === EventSource.CLOSED) { + logger.info(`Metadata EventSource connection closed for agent: ${agentId}`) + if (!disposed) { + watcher.dispose() + } + } + }) + return watcher } From f3bcbca61fdb50c2e27ec7fe9f490b8655f7c451 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 1 Apr 2025 18:03:56 -0400 Subject: [PATCH 06/21] chore: add logging to inbox --- src/inbox.ts | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/inbox.ts b/src/inbox.ts index 34a87a5e..f56ceb93 100644 --- a/src/inbox.ts +++ b/src/inbox.ts @@ -5,6 +5,7 @@ import * as vscode from "vscode" import { WebSocket } from "ws" import { errToStr } from "./api-helper" import { type Storage } from "./storage" +import { getMemoryLogger } from "./memoryLogger" // These are the template IDs of our notifications. // Maybe in the future we should avoid hardcoding @@ -16,9 +17,16 @@ export class Inbox implements vscode.Disposable { readonly #storage: Storage #disposed = false #socket: WebSocket + #messageCount = 0 + #workspaceId: string constructor(workspace: Workspace, httpAgent: ProxyAgent, restClient: Api, storage: Storage) { + const logger = getMemoryLogger() this.#storage = storage + this.#workspaceId = workspace.id + + logger.trackResourceCreated("InboxWebSocket", workspace.id) + logger.info(`Creating inbox for workspace: ${workspace.owner_name}/${workspace.name} (${workspace.id})`) const baseUrlRaw = restClient.getAxiosInstance().defaults.baseURL if (!baseUrlRaw) { @@ -37,6 +45,8 @@ export class Inbox implements vscode.Disposable { const socketProto = baseUrl.protocol === "https:" ? "wss:" : "ws:" const socketUrl = `${socketProto}//${baseUrl.host}/api/v2/notifications/inbox/watch?format=plaintext&templates=${watchTemplatesParam}&targets=${watchTargetsParam}` + logger.debug(`Connecting to inbox WebSocket at: ${socketUrl}`) + const coderSessionTokenHeader = "Coder-Session-Token" this.#socket = new WebSocket(new URL(socketUrl), { followRedirects: true, @@ -49,35 +59,72 @@ export class Inbox implements vscode.Disposable { }) this.#socket.on("open", () => { + logger.info(`Inbox WebSocket connection opened for workspace: ${workspace.id}`) this.#storage.writeToCoderOutputChannel("Listening to Coder Inbox") }) this.#socket.on("error", (error) => { + logger.error(`Inbox WebSocket error for workspace: ${workspace.id}`, error) this.notifyError(error) this.dispose() }) + this.#socket.on("close", (code, reason) => { + logger.info(`Inbox WebSocket closed for workspace: ${workspace.id}, code: ${code}, reason: ${reason || "none"}`) + if (!this.#disposed) { + this.dispose() + } + }) + this.#socket.on("message", (data) => { + this.#messageCount++ + + // Log periodic message stats + if (this.#messageCount % 10 === 0) { + logger.info(`Inbox received ${this.#messageCount} messages for workspace: ${workspace.id}`) + logger.logMemoryUsage("INBOX_WEBSOCKET") + } + try { const inboxMessage = JSON.parse(data.toString()) as GetInboxNotificationResponse - + logger.debug(`Inbox notification received: ${inboxMessage.notification.title}`) vscode.window.showInformationMessage(inboxMessage.notification.title) } catch (error) { + logger.error(`Error processing inbox message for workspace: ${workspace.id}`, error) this.notifyError(error) } }) + + // Log memory stats periodically + const memoryInterval = setInterval( + () => { + if (!this.#disposed) { + logger.logMemoryUsage("INBOX_PERIODIC") + } else { + clearInterval(memoryInterval) + } + }, + 5 * 60 * 1000, + ) // Every 5 minutes } dispose() { + const logger = getMemoryLogger() + if (!this.#disposed) { + logger.info(`Disposing inbox for workspace: ${this.#workspaceId} after ${this.#messageCount} messages`) this.#storage.writeToCoderOutputChannel("No longer listening to Coder Inbox") this.#socket.close() this.#disposed = true + logger.trackResourceDisposed("InboxWebSocket", this.#workspaceId) } } private notifyError(error: unknown) { + const logger = getMemoryLogger() const message = errToStr(error, "Got empty error while monitoring Coder Inbox") + + logger.error(`Inbox error for workspace: ${this.#workspaceId}`, error) this.#storage.writeToCoderOutputChannel(message) } } From 5ebc0049f40958d9100ab8acdc6e46dc2b8a9246 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 1 Apr 2025 18:04:18 -0400 Subject: [PATCH 07/21] chore: improve logging around extension starting and stopping --- src/extension.ts | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index e9e65187..c3c8ae87 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,7 +15,18 @@ import { getMemoryLogger } from "./memoryLogger" export async function activate(ctx: vscode.ExtensionContext): Promise { // Initialize the memory logger right when the extension starts. - getMemoryLogger(); + const logger = getMemoryLogger(); + await logger.initLogFile(ctx.globalStorageUri.fsPath) + + // Log initial memory usage and activation + logger.info("CODER extension activating") + logger.logMemoryUsage("EXTENSION_ACTIVATE") + + // Register disposal of the logger when the extension deactivates + ctx.subscriptions.push({ dispose: () => logger.dispose() }) + + // Log extension mode + logger.info(`Extension mode: ${extensionModeToString(ctx.extensionMode)}`); // The Remote SSH extension's proposed APIs are used to override the SSH host // name in VS Code itself. It's visually unappealing having a lengthy name! @@ -29,9 +40,13 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { vscode.extensions.getExtension("codeium.windsurf-remote-openssh") || vscode.extensions.getExtension("ms-vscode-remote.remote-ssh") if (!remoteSSHExtension) { + logger.error("Remote SSH extension not found, cannot activate Coder extension") vscode.window.showErrorMessage("Remote SSH extension not found, cannot activate Coder extension") throw new Error("Remote SSH extension not found") } + + logger.info(`Found Remote SSH extension: ${remoteSSHExtension.id}`); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const vscodeProposed: typeof vscode = (module as any)._load( "vscode", @@ -229,4 +244,26 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { } } } + + logger.info("Coder extension activation complete") +} + +function extensionModeToString(mode: vscode.ExtensionMode): string { + switch (mode) { + case vscode.ExtensionMode.Development: + return "Development"; + case vscode.ExtensionMode.Production: + return "Production"; + case vscode.ExtensionMode.Test: + return "Test"; + default: + return `Unknown (${mode})`; + } } + +// Add deactivation handler to log memory usage on extension shutdown +export function deactivate(): void { + const logger = getMemoryLogger(); + logger.info("Coder extension deactivating"); + logger.logMemoryUsage("EXTENSION_DEACTIVATE"); +} \ No newline at end of file From 854ca01d0d8decbf4340e0a156799ba023fa167a Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 1 Apr 2025 18:04:59 -0400 Subject: [PATCH 08/21] chore: add logging around workspace fetching and refetching logic --- src/workspacesProvider.ts | 62 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 14fd8c83..68f5b706 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -12,7 +12,7 @@ import { errToStr, } from "./api-helper" import { Storage } from "./storage" -import { getMemoryLogger, MemoryLogger } from "./memoryLogger" +import { getMemoryLogger } from "./memoryLogger" export enum WorkspaceQuery { Mine = "owner:me", @@ -57,10 +57,21 @@ export class WorkspaceProvider implements vscode.TreeDataProvider { + logger.debug(`WorkspaceProvider(${this.getWorkspacesQuery}): Executing scheduled refresh`) this.fetchAndRefresh() }, this.timerSeconds * 1000) } } - private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter() readonly onDidChangeTreeData: vscode.Event = @@ -194,9 +224,33 @@ export class WorkspaceProvider implements vscode.TreeDataProvider { + logger.debug(`WorkspaceProvider(${this.getWorkspacesQuery}): Disposing agent watcher ${id}`) + this.agentWatchers[id].dispose() + }) + + this.agentWatchers = {} + this.workspaces = undefined + + logger.logMemoryUsage("WORKSPACE_PROVIDER_DISPOSE") + } + async getTreeItem(element: vscode.TreeItem): Promise { return element } From 8974e8bfc88cabe1742552ca67028d00bcb1b6db Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 1 Apr 2025 18:39:12 -0400 Subject: [PATCH 09/21] fix: resolve lint warnings --- src/extension.ts | 28 ++++++++++++++-------------- src/inbox.ts | 2 +- src/memoryLogger.ts | 12 ++++++++---- src/remote.ts | 2 +- src/workspacesProvider.ts | 6 +++--- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index c3c8ae87..65bab9cf 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -7,15 +7,15 @@ import { makeCoderSdk, needToken } from "./api" import { errToStr } from "./api-helper" import { Commands } from "./commands" import { CertificateError, getErrorDetail } from "./error" +import { getMemoryLogger } from "./memoryLogger" import { Remote } from "./remote" import { Storage } from "./storage" import { toSafeHost } from "./util" import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider" -import { getMemoryLogger } from "./memoryLogger" export async function activate(ctx: vscode.ExtensionContext): Promise { // Initialize the memory logger right when the extension starts. - const logger = getMemoryLogger(); + const logger = getMemoryLogger() await logger.initLogFile(ctx.globalStorageUri.fsPath) // Log initial memory usage and activation @@ -26,7 +26,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push({ dispose: () => logger.dispose() }) // Log extension mode - logger.info(`Extension mode: ${extensionModeToString(ctx.extensionMode)}`); + logger.info(`Extension mode: ${extensionModeToString(ctx.extensionMode)}`) // The Remote SSH extension's proposed APIs are used to override the SSH host // name in VS Code itself. It's visually unappealing having a lengthy name! @@ -44,9 +44,9 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { vscode.window.showErrorMessage("Remote SSH extension not found, cannot activate Coder extension") throw new Error("Remote SSH extension not found") } - - logger.info(`Found Remote SSH extension: ${remoteSSHExtension.id}`); - + + logger.info(`Found Remote SSH extension: ${remoteSSHExtension.id}`) + // eslint-disable-next-line @typescript-eslint/no-explicit-any const vscodeProposed: typeof vscode = (module as any)._load( "vscode", @@ -251,19 +251,19 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { function extensionModeToString(mode: vscode.ExtensionMode): string { switch (mode) { case vscode.ExtensionMode.Development: - return "Development"; + return "Development" case vscode.ExtensionMode.Production: - return "Production"; + return "Production" case vscode.ExtensionMode.Test: - return "Test"; + return "Test" default: - return `Unknown (${mode})`; + return `Unknown (${mode})` } } // Add deactivation handler to log memory usage on extension shutdown export function deactivate(): void { - const logger = getMemoryLogger(); - logger.info("Coder extension deactivating"); - logger.logMemoryUsage("EXTENSION_DEACTIVATE"); -} \ No newline at end of file + const logger = getMemoryLogger() + logger.info("Coder extension deactivating") + logger.logMemoryUsage("EXTENSION_DEACTIVATE") +} diff --git a/src/inbox.ts b/src/inbox.ts index f56ceb93..89b516ef 100644 --- a/src/inbox.ts +++ b/src/inbox.ts @@ -4,8 +4,8 @@ import { ProxyAgent } from "proxy-agent" import * as vscode from "vscode" import { WebSocket } from "ws" import { errToStr } from "./api-helper" -import { type Storage } from "./storage" import { getMemoryLogger } from "./memoryLogger" +import { type Storage } from "./storage" // These are the template IDs of our notifications. // Maybe in the future we should avoid hardcoding diff --git a/src/memoryLogger.ts b/src/memoryLogger.ts index b33274be..bc1668e9 100644 --- a/src/memoryLogger.ts +++ b/src/memoryLogger.ts @@ -1,7 +1,7 @@ -import * as vscode from "vscode" +import * as fs from "fs/promises" import * as os from "os" import * as path from "path" -import * as fs from "fs/promises" +import * as vscode from "vscode" /** * A class for tracking memory usage and logging resource lifecycles @@ -32,7 +32,9 @@ export class MemoryLogger { } this.logInterval = setInterval(() => { - if (this.disposed) return + if (this.disposed) { + return + } this.logMemoryUsage("PERIODIC") this.logResourceCounts() }, intervalMs) @@ -159,7 +161,9 @@ export class MemoryLogger { * Write to log file */ private async writeToLogFile(message: string): Promise { - if (!this.logFile) return + if (!this.logFile) { + return + } try { const timestamp = new Date().toISOString() diff --git a/src/remote.ts b/src/remote.ts index 7754ae1f..03106cae 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -16,12 +16,12 @@ import { Commands } from "./commands" import { featureSetForVersion, FeatureSet } from "./featureSet" import { getHeaderCommand } from "./headers" import { Inbox } from "./inbox" +import { getMemoryLogger } from "./memoryLogger" import { SSHConfig, SSHValues, mergeSSHConfigValues } from "./sshConfig" import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport" import { Storage } from "./storage" import { AuthorityPrefix, expandPath, parseRemoteAuthority } from "./util" import { WorkspaceMonitor } from "./workspaceMonitor" -import { getMemoryLogger } from "./memoryLogger" export interface RemoteDetails extends vscode.Disposable { url: string diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 68f5b706..178cf999 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -11,8 +11,8 @@ import { extractAgents, errToStr, } from "./api-helper" -import { Storage } from "./storage" import { getMemoryLogger } from "./memoryLogger" +import { Storage } from "./storage" export enum WorkspaceQuery { Mine = "owner:me", @@ -71,7 +71,7 @@ export class WorkspaceProvider implements vscode.TreeDataProvider Date: Wed, 2 Apr 2025 16:59:21 -0400 Subject: [PATCH 10/21] chore: remove non-useful avgload information --- src/memoryLogger.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/memoryLogger.ts b/src/memoryLogger.ts index bc1668e9..f6758ccd 100644 --- a/src/memoryLogger.ts +++ b/src/memoryLogger.ts @@ -130,10 +130,6 @@ export class MemoryLogger { const systemMemoryInfo = { totalMem: `${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)}GB`, freeMem: `${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)}GB`, - loadAvg: os - .loadavg() - .map((load) => load.toFixed(2)) - .join(", "), } const memoryLog = `[MEMORY:${context}] Node: ${JSON.stringify(nodeMemoryInfo)} | System: ${JSON.stringify(systemMemoryInfo)}` From 1eed37641b330571679f09291efc41e35abcf593 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 4 Apr 2025 09:28:12 -0400 Subject: [PATCH 11/21] chore: remove unneeded type annotations --- src/memoryLogger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/memoryLogger.ts b/src/memoryLogger.ts index f6758ccd..9af9e43d 100644 --- a/src/memoryLogger.ts +++ b/src/memoryLogger.ts @@ -11,9 +11,9 @@ export class MemoryLogger { private outputChannel: vscode.OutputChannel private logFile: string | undefined private resourceCounts = new Map() - private startTime: number = Date.now() + private startTime = Date.now() private logInterval: NodeJS.Timeout | undefined - private disposed: boolean = false + private disposed = false constructor() { this.outputChannel = vscode.window.createOutputChannel("Coder Memory Logging") From d6fb26ccc7b583af1a39ae9dcec42723150d04f0 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 4 Apr 2025 09:36:03 -0400 Subject: [PATCH 12/21] fix: add radix to number parse --- src/remote.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remote.ts b/src/remote.ts index 03106cae..f0264922 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -853,7 +853,7 @@ export class Remote { if (matches.length < 2) { return } - const port = Number.parseInt(matches[1]) + const port = Number.parseInt(matches[1], 10) if (!port) { return } From 2c09de6ce4b656ef17177949392e7080c34e40e7 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 4 Apr 2025 10:23:16 -0400 Subject: [PATCH 13/21] chore: rename loop attempts variable --- src/remote.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/remote.ts b/src/remote.ts index f0264922..acc3db04 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -832,7 +832,7 @@ export class Remote { const logger = getMemoryLogger() logger.info(`Starting SSH process ID search with timeout: ${timeout}ms`) - let attempts = 0 + let loopAttempts = 0 let lastFilePath: string | undefined const search = async (logPath: string): Promise => { @@ -842,7 +842,7 @@ export class Remote { // process will be logging network information periodically to a file. const text = await fs.readFile(logPath, "utf8") - if (attempts % 5 === 0) { + if (loopAttempts % 5 === 0) { logger.debug(`SSH log file size: ${text.length} bytes`) } @@ -878,17 +878,17 @@ export class Remote { const start = Date.now() const loop = async (): Promise => { - attempts++ + loopAttempts++ const elapsed = Date.now() - start if (elapsed > timeout) { - logger.info(`SSH process ID search timed out after ${attempts} attempts, elapsed: ${elapsed}ms`) + logger.info(`SSH process ID search timed out after ${loopAttempts} attempts, elapsed: ${elapsed}ms`) return undefined } // Log progress periodically - if (attempts % 5 === 0) { - logger.info(`SSH process ID search attempt #${attempts}, elapsed: ${elapsed}ms`) + if (loopAttempts % 5 === 0) { + logger.info(`SSH process ID search attempt #${loopAttempts}, elapsed: ${elapsed}ms`) logger.logMemoryUsage("SSH_PROCESS_SEARCH") } @@ -919,7 +919,7 @@ export class Remote { }) } - logger.info(`SSH process ID search completed successfully after ${attempts} attempts, elapsed: ${elapsed}ms`) + logger.info(`SSH process ID search completed successfully after ${loopAttempts} attempts, elapsed: ${elapsed}ms`) return result } From 3c3f452dc9d37b92e52e5f4d4f7b9c8114971620 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 4 Apr 2025 10:24:35 -0400 Subject: [PATCH 14/21] chore: remove unneeded try-catch --- src/remote.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/remote.ts b/src/remote.ts index acc3db04..13f3f68b 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -792,11 +792,7 @@ export class Remote { return JSON.parse(content) }) .then((parsed) => { - try { - updateStatus(parsed) - } catch (ex) { - logger.error(`Failed to update status from parsed network info`, ex) - } + updateStatus(parsed) }) .catch((error) => { // Replace empty catch with proper error logging From 44106eee70a9b292919e4f25d9b80c449c7e4946 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 4 Apr 2025 10:26:09 -0400 Subject: [PATCH 15/21] chore: remove unneeded comment --- src/remote.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/remote.ts b/src/remote.ts index 13f3f68b..a4a3bcc4 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -795,7 +795,6 @@ export class Remote { updateStatus(parsed) }) .catch((error) => { - // Replace empty catch with proper error logging logger.error(`Failed to read or parse network info file: ${networkInfoFile}`, error) }) .finally(() => { From 9659dd815e2b5e8668a68b60c09f6d1ebfa2557b Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 4 Apr 2025 10:38:24 -0400 Subject: [PATCH 16/21] fix: remove unneeded try catch --- src/remote.ts | 101 ++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/src/remote.ts b/src/remote.ts index a4a3bcc4..0d13fe50 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -711,60 +711,57 @@ export class Remote { upload_bytes_sec: number download_bytes_sec: number }) => { - try { - let statusText = "$(globe) " - if (network.p2p) { - statusText += "Direct " - networkStatus.tooltip = "You're connected peer-to-peer ✨." - } else { - statusText += network.preferred_derp + " " - networkStatus.tooltip = - "You're connected through a relay 🕵.\nWe'll switch over to peer-to-peer when available." - } - networkStatus.tooltip += - "\n\nDownload ↓ " + - prettyBytes(network.download_bytes_sec, { - bits: true, - }) + - "/s • Upload ↑ " + - prettyBytes(network.upload_bytes_sec, { - bits: true, - }) + - "/s\n" - - if (!network.p2p) { - const derpLatency = network.derp_latency[network.preferred_derp] - - networkStatus.tooltip += `You ↔ ${derpLatency.toFixed(2)}ms ↔ ${network.preferred_derp} ↔ ${( - network.latency - derpLatency - ).toFixed(2)}ms ↔ Workspace` - - let first = true - Object.keys(network.derp_latency).forEach((region) => { - if (region === network.preferred_derp) { - return - } - if (first) { - networkStatus.tooltip += `\n\nOther regions:` - first = false - } - networkStatus.tooltip += `\n${region}: ${Math.round(network.derp_latency[region] * 100) / 100}ms` - }) - } + let statusText = "$(globe) " + if (network.p2p) { + statusText += "Direct " + networkStatus.tooltip = "You're connected peer-to-peer ✨." + } else { + statusText += network.preferred_derp + " " + networkStatus.tooltip = + "You're connected through a relay 🕵.\nWe'll switch over to peer-to-peer when available." + } + networkStatus.tooltip += + "\n\nDownload ↓ " + + prettyBytes(network.download_bytes_sec, { + bits: true, + }) + + "/s • Upload ↑ " + + prettyBytes(network.upload_bytes_sec, { + bits: true, + }) + + "/s\n" + + const derpLatency = network.derp_latency[network.preferred_derp] || undefined; + if (!network.p2p && derpLatency !== undefined) { + + networkStatus.tooltip += `You ↔ ${derpLatency.toFixed(2)}ms ↔ ${network.preferred_derp} ↔ ${( + network.latency - derpLatency + ).toFixed(2)}ms ↔ Workspace` + + let first = true + Object.keys(network.derp_latency).forEach((region) => { + if (region === network.preferred_derp) { + return + } + if (first) { + networkStatus.tooltip += `\n\nOther regions:` + first = false + } + networkStatus.tooltip += `\n${region}: ${Math.round(network.derp_latency[region] * 100) / 100}ms` + }) + } else { + logger.error("Network P2P is false and DERP Latency is undefined") + } - statusText += "(" + network.latency.toFixed(2) + "ms)" - networkStatus.text = statusText - networkStatus.show() + statusText += "(" + network.latency.toFixed(2) + "ms)" + networkStatus.text = statusText + networkStatus.show() - // Log occasional network stats updates (every 20 refreshes) - if (refreshCount % 20 === 0) { - logger.debug( - `Network stats update #${refreshCount}: p2p=${network.p2p}, latency=${network.latency.toFixed(2)}ms`, - ) - } - } catch (ex) { - // Replace silent error ignoring with proper logging - logger.error("Error updating network status", ex) + // Log occasional network stats updates (every 20 refreshes) + if (refreshCount % 20 === 0) { + logger.debug( + `Network stats update #${refreshCount}: p2p=${network.p2p}, latency=${network.latency.toFixed(2)}ms`, + ) } } From 86de75dcb7f82ef67ce84f216b58747af7f99a56 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 4 Apr 2025 11:11:23 -0400 Subject: [PATCH 17/21] fix: move interval to class level and clear on dispose --- src/inbox.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/inbox.ts b/src/inbox.ts index 89b516ef..ad51bb8f 100644 --- a/src/inbox.ts +++ b/src/inbox.ts @@ -19,6 +19,7 @@ export class Inbox implements vscode.Disposable { #socket: WebSocket #messageCount = 0 #workspaceId: string + #memoryInterval: NodeJS.Timeout constructor(workspace: Workspace, httpAgent: ProxyAgent, restClient: Api, storage: Storage) { const logger = getMemoryLogger() @@ -96,12 +97,12 @@ export class Inbox implements vscode.Disposable { }) // Log memory stats periodically - const memoryInterval = setInterval( + this.#memoryInterval = setInterval( () => { if (!this.#disposed) { logger.logMemoryUsage("INBOX_PERIODIC") } else { - clearInterval(memoryInterval) + clearInterval(this.#memoryInterval) } }, 5 * 60 * 1000, @@ -118,6 +119,8 @@ export class Inbox implements vscode.Disposable { this.#disposed = true logger.trackResourceDisposed("InboxWebSocket", this.#workspaceId) } + + clearInterval(this.#memoryInterval) } private notifyError(error: unknown) { From 5fbc7a7aa835b20441e49df8ace9b077c06bf96e Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 4 Apr 2025 11:14:08 -0400 Subject: [PATCH 18/21] chore: remove unneeded comment --- src/extension.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 65bab9cf..fb8ecd64 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -261,7 +261,6 @@ function extensionModeToString(mode: vscode.ExtensionMode): string { } } -// Add deactivation handler to log memory usage on extension shutdown export function deactivate(): void { const logger = getMemoryLogger() logger.info("Coder extension deactivating") From c8e45da25115b33e35a1564b52ab9f6f5273b92f Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 4 Apr 2025 14:24:31 -0400 Subject: [PATCH 19/21] fix: fix lint warnings --- src/inbox.ts | 1 - src/remote.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/inbox.ts b/src/inbox.ts index 226ce1e5..8e9f3047 100644 --- a/src/inbox.ts +++ b/src/inbox.ts @@ -49,7 +49,6 @@ export class Inbox implements vscode.Disposable { logger.debug(`Connecting to inbox WebSocket at: ${socketUrl}`) - const coderSessionTokenHeader = "Coder-Session-Token" const token = restClient.getAxiosInstance().defaults.headers.common[coderSessionTokenHeader] as string | undefined this.#socket = new WebSocket(new URL(socketUrl), { agent: httpAgent, diff --git a/src/remote.ts b/src/remote.ts index 0d13fe50..9f40188e 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -731,9 +731,8 @@ export class Remote { }) + "/s\n" - const derpLatency = network.derp_latency[network.preferred_derp] || undefined; + const derpLatency = network.derp_latency[network.preferred_derp] || undefined if (!network.p2p && derpLatency !== undefined) { - networkStatus.tooltip += `You ↔ ${derpLatency.toFixed(2)}ms ↔ ${network.preferred_derp} ↔ ${( network.latency - derpLatency ).toFixed(2)}ms ↔ Workspace` From 90172e6f83d673f0f2e485c66f990ad72d981abc Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 4 Apr 2025 14:27:57 -0400 Subject: [PATCH 20/21] chore: update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a4c4d1a..6ee26106 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "repository": "https://github.com/coder/vscode-coder", "version": "1.7.0", "engines": { - "vscode": "^1.73.0" + "vscode": "^1.73.0-debug.1" }, "license": "MIT", "bugs": { From 0d04fbaca684cdb12d68b864149440c1fce9ec71 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 4 Apr 2025 14:30:36 -0400 Subject: [PATCH 21/21] fix: properly set version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6ee26106..38360ae4 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "displayName": "Coder", "description": "Open any workspace with a single click.", "repository": "https://github.com/coder/vscode-coder", - "version": "1.7.0", + "version": "1.7.0-debug.1", "engines": { - "vscode": "^1.73.0-debug.1" + "vscode": "^1.73.0" }, "license": "MIT", "bugs": {