Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4617974

Browse files
committed
feat: create memory logger
1 parent 9f918b8 commit 4617974

File tree

2 files changed

+218
-0
lines changed

2 files changed

+218
-0
lines changed

src/extension.ts

+4
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ import { Remote } from "./remote"
1111
import { Storage } from "./storage"
1212
import { toSafeHost } from "./util"
1313
import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider"
14+
import { getMemoryLogger } from "./memoryLogger"
1415

1516
export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
17+
// Initialize the memory logger right when the extension starts.
18+
getMemoryLogger();
19+
1620
// The Remote SSH extension's proposed APIs are used to override the SSH host
1721
// name in VS Code itself. It's visually unappealing having a lengthy name!
1822
//

src/memoryLogger.ts

+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import * as vscode from "vscode"
2+
import * as os from "os"
3+
import * as path from "path"
4+
import * as fs from "fs/promises"
5+
6+
/**
7+
* A class for tracking memory usage and logging resource lifecycles
8+
* to help identify memory leaks in the extension.
9+
*/
10+
export class MemoryLogger {
11+
private outputChannel: vscode.OutputChannel
12+
private logFile: string | undefined
13+
private resourceCounts = new Map<string, number>()
14+
private startTime: number = Date.now()
15+
private logInterval: NodeJS.Timeout | undefined
16+
private disposed: boolean = false
17+
18+
constructor() {
19+
this.outputChannel = vscode.window.createOutputChannel("Coder Memory Logging")
20+
this.outputChannel.show()
21+
22+
// Setup periodic logging of memory usage
23+
this.startPeriodicLogging()
24+
}
25+
26+
/**
27+
* Start logging memory usage periodically
28+
*/
29+
private startPeriodicLogging(intervalMs = 60000) {
30+
if (this.logInterval) {
31+
clearInterval(this.logInterval)
32+
}
33+
34+
this.logInterval = setInterval(() => {
35+
if (this.disposed) return
36+
this.logMemoryUsage("PERIODIC")
37+
this.logResourceCounts()
38+
}, intervalMs)
39+
}
40+
41+
/**
42+
* Initialize the log file for persistent logging
43+
*/
44+
public async initLogFile(globalStoragePath: string): Promise<void> {
45+
try {
46+
const logDir = path.join(globalStoragePath, "logs")
47+
await fs.mkdir(logDir, { recursive: true })
48+
49+
this.logFile = path.join(logDir, `memory-log-${new Date().toISOString().replace(/[:.]/g, "-")}.txt`)
50+
51+
await this.writeToLogFile("Memory logging initialized")
52+
this.info("Memory logging initialized to file: " + this.logFile)
53+
54+
// Log initial memory state
55+
this.logMemoryUsage("INIT")
56+
} catch (err) {
57+
this.error(`Failed to initialize log file: ${err}`)
58+
}
59+
}
60+
61+
/**
62+
* Log a new resource creation
63+
*/
64+
public trackResourceCreated(resourceType: string, id: string = ""): void {
65+
const count = (this.resourceCounts.get(resourceType) || 0) + 1
66+
this.resourceCounts.set(resourceType, count)
67+
this.info(`RESOURCE_CREATED: ${resourceType}${id ? ":" + id : ""} (Total: ${count})`)
68+
}
69+
70+
/**
71+
* Log a resource disposal
72+
*/
73+
public trackResourceDisposed(resourceType: string, id: string = ""): void {
74+
const count = Math.max(0, (this.resourceCounts.get(resourceType) || 1) - 1)
75+
if (count === 0) {
76+
this.resourceCounts.delete(resourceType)
77+
} else {
78+
this.resourceCounts.set(resourceType, count)
79+
}
80+
81+
this.info(`RESOURCE_DISPOSED: ${resourceType}${id ? ":" + id : ""} (Remaining: ${count})`)
82+
}
83+
84+
/**
85+
* Log error with memory usage
86+
*/
87+
public error(message: string, error?: unknown): void {
88+
const errorMsg = error ? `: ${error instanceof Error ? error.stack || error.message : String(error)}` : ""
89+
const fullMessage = `[ERROR] ${message}${errorMsg}`
90+
91+
this.outputChannel.appendLine(fullMessage)
92+
this.writeToLogFile(fullMessage)
93+
this.logMemoryUsage("ERROR")
94+
}
95+
96+
/**
97+
* Log info with timestamp
98+
*/
99+
public info(message: string): void {
100+
const fullMessage = `[INFO] ${message}`
101+
this.outputChannel.appendLine(fullMessage)
102+
this.writeToLogFile(fullMessage)
103+
}
104+
105+
/**
106+
* Log debug info (only to file)
107+
*/
108+
public debug(message: string): void {
109+
const fullMessage = `[DEBUG] ${message}`
110+
this.writeToLogFile(fullMessage)
111+
}
112+
113+
/**
114+
* Log current memory usage
115+
*/
116+
public logMemoryUsage(context: string): void {
117+
try {
118+
const memoryUsage = process.memoryUsage()
119+
const nodeMemoryInfo = {
120+
rss: `${(memoryUsage.rss / 1024 / 1024).toFixed(2)}MB`,
121+
heapTotal: `${(memoryUsage.heapTotal / 1024 / 1024).toFixed(2)}MB`,
122+
heapUsed: `${(memoryUsage.heapUsed / 1024 / 1024).toFixed(2)}MB`,
123+
external: `${(memoryUsage.external / 1024 / 1024).toFixed(2)}MB`,
124+
uptime: formatDuration(process.uptime() * 1000),
125+
totalUptime: formatDuration(Date.now() - this.startTime)
126+
}
127+
128+
const systemMemoryInfo = {
129+
totalMem: `${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)}GB`,
130+
freeMem: `${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)}GB`,
131+
loadAvg: os.loadavg().map(load => load.toFixed(2)).join(", ")
132+
}
133+
134+
const memoryLog = `[MEMORY:${context}] Node: ${JSON.stringify(nodeMemoryInfo)} | System: ${JSON.stringify(systemMemoryInfo)}`
135+
this.outputChannel.appendLine(memoryLog)
136+
this.writeToLogFile(memoryLog)
137+
} catch (err) {
138+
this.outputChannel.appendLine(`[ERROR] Failed to log memory usage: ${err}`)
139+
}
140+
}
141+
142+
/**
143+
* Log the current counts of active resources
144+
*/
145+
private logResourceCounts(): void {
146+
const counts = Array.from(this.resourceCounts.entries())
147+
.map(([type, count]) => `${type}=${count}`)
148+
.join(", ")
149+
150+
const message = `[RESOURCES] Active resources: ${counts || "none"}`
151+
this.outputChannel.appendLine(message)
152+
this.writeToLogFile(message)
153+
}
154+
155+
/**
156+
* Write to log file
157+
*/
158+
private async writeToLogFile(message: string): Promise<void> {
159+
if (!this.logFile) return
160+
161+
try {
162+
const timestamp = new Date().toISOString()
163+
await fs.appendFile(this.logFile, `${timestamp} ${message}\n`)
164+
} catch (err) {
165+
// Don't recursively call this.error to avoid potential loops
166+
this.outputChannel.appendLine(`[ERROR] Failed to write to log file: ${err}`)
167+
}
168+
}
169+
170+
/**
171+
* Show the log in the output channel
172+
*/
173+
public show(): void {
174+
this.outputChannel.show()
175+
}
176+
177+
/**
178+
* Dispose of the logger
179+
*/
180+
public dispose(): void {
181+
this.disposed = true
182+
if (this.logInterval) {
183+
clearInterval(this.logInterval)
184+
this.logInterval = undefined
185+
}
186+
this.logMemoryUsage("DISPOSE")
187+
this.outputChannel.dispose()
188+
}
189+
}
190+
191+
/**
192+
* Format duration in milliseconds to a human-readable string
193+
*/
194+
function formatDuration(ms: number): string {
195+
const seconds = Math.floor((ms / 1000) % 60)
196+
const minutes = Math.floor((ms / (1000 * 60)) % 60)
197+
const hours = Math.floor((ms / (1000 * 60 * 60)) % 24)
198+
const days = Math.floor(ms / (1000 * 60 * 60 * 24))
199+
200+
return `${days}d ${hours}h ${minutes}m ${seconds}s`
201+
}
202+
203+
// Singleton instance
204+
let instance: MemoryLogger | undefined
205+
206+
/**
207+
* Get or initialize the memory logger instance
208+
*/
209+
export function getMemoryLogger(): MemoryLogger {
210+
if (!instance) {
211+
instance = new MemoryLogger()
212+
}
213+
return instance
214+
}

0 commit comments

Comments
 (0)