Skip to content

Commit 7e1cd0b

Browse files
Copilotrajbos
andcommitted
Implement file cache for session files to improve performance
Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com>
1 parent 9be4d68 commit 7e1cd0b

File tree

1 file changed

+110
-4
lines changed

1 file changed

+110
-4
lines changed

src/extension.ts

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,20 @@ interface DetailedStats {
3737
lastUpdated: Date;
3838
}
3939

40+
interface SessionFileCache {
41+
tokens: number;
42+
interactions: number;
43+
modelUsage: ModelUsage;
44+
mtime: number; // file modification time as timestamp
45+
}
46+
4047
class CopilotTokenTracker implements vscode.Disposable {
4148
private statusBarItem: vscode.StatusBarItem;
4249
private updateInterval: NodeJS.Timeout | undefined;
4350
private initialDelayTimeout: NodeJS.Timeout | undefined;
4451
private detailsPanel: vscode.WebviewPanel | undefined;
4552
private outputChannel: vscode.OutputChannel;
53+
private sessionFileCache: Map<string, SessionFileCache> = new Map();
4654
private tokenEstimators: { [key: string]: number } = {
4755
'gpt-4': 0.25,
4856
'gpt-4.1': 0.25,
@@ -81,6 +89,45 @@ class CopilotTokenTracker implements vscode.Disposable {
8189
}
8290
}
8391

92+
// Cache management methods
93+
private isCacheValid(filePath: string, currentMtime: number): boolean {
94+
const cached = this.sessionFileCache.get(filePath);
95+
return cached !== undefined && cached.mtime === currentMtime;
96+
}
97+
98+
private getCachedSessionData(filePath: string): SessionFileCache | undefined {
99+
return this.sessionFileCache.get(filePath);
100+
}
101+
102+
private setCachedSessionData(filePath: string, data: SessionFileCache): void {
103+
this.sessionFileCache.set(filePath, data);
104+
105+
// Limit cache size to prevent memory issues (keep last 1000 files)
106+
if (this.sessionFileCache.size > 1000) {
107+
const entries = Array.from(this.sessionFileCache.entries());
108+
// Remove oldest entries (simple FIFO approach)
109+
const toRemove = entries.slice(0, this.sessionFileCache.size - 1000);
110+
for (const [key] of toRemove) {
111+
this.sessionFileCache.delete(key);
112+
}
113+
}
114+
}
115+
116+
private clearExpiredCache(): void {
117+
// Remove cache entries for files that no longer exist
118+
const filesToCheck = Array.from(this.sessionFileCache.keys());
119+
for (const filePath of filesToCheck) {
120+
try {
121+
if (!fs.existsSync(filePath)) {
122+
this.sessionFileCache.delete(filePath);
123+
}
124+
} catch (error) {
125+
// File access error, remove from cache
126+
this.sessionFileCache.delete(filePath);
127+
}
128+
}
129+
}
130+
84131

85132

86133
constructor() {
@@ -234,7 +281,7 @@ class CopilotTokenTracker implements vscode.Disposable {
234281

235282
// Only process files modified in the current month
236283
if (fileStats.mtime >= monthStart) {
237-
const tokens = await this.estimateTokensFromSession(sessionFile);
284+
const tokens = await this.estimateTokensFromSessionCached(sessionFile, fileStats.mtime.getTime());
238285

239286
monthTokens += tokens;
240287

@@ -266,21 +313,37 @@ class CopilotTokenTracker implements vscode.Disposable {
266313
const monthStats = { tokens: 0, sessions: 0, interactions: 0, modelUsage: {} as ModelUsage };
267314

268315
try {
316+
// Clean expired cache entries
317+
this.clearExpiredCache();
318+
269319
const sessionFiles = await this.getCopilotSessionFiles();
270320
this.log(`Processing ${sessionFiles.length} session files for detailed stats`);
271321

272322
if (sessionFiles.length === 0) {
273323
this.warn('No session files found - this might indicate an issue in GitHub Codespaces or different VS Code configuration');
274324
}
275325

326+
let cacheHits = 0;
327+
let cacheMisses = 0;
328+
276329
for (const sessionFile of sessionFiles) {
277330
try {
278331
const fileStats = fs.statSync(sessionFile);
279332

280333
if (fileStats.mtime >= monthStart) {
281-
const tokens = await this.estimateTokensFromSession(sessionFile);
282-
const interactions = await this.countInteractionsInSession(sessionFile);
283-
const modelUsage = await this.getModelUsageFromSession(sessionFile);
334+
// Check if data is cached before making calls
335+
const wasCached = this.isCacheValid(sessionFile, fileStats.mtime.getTime());
336+
337+
const tokens = await this.estimateTokensFromSessionCached(sessionFile, fileStats.mtime.getTime());
338+
const interactions = await this.countInteractionsInSessionCached(sessionFile, fileStats.mtime.getTime());
339+
const modelUsage = await this.getModelUsageFromSessionCached(sessionFile, fileStats.mtime.getTime());
340+
341+
// Update cache statistics
342+
if (wasCached) {
343+
cacheHits++;
344+
} else {
345+
cacheMisses++;
346+
}
284347

285348
this.log(`Session ${path.basename(sessionFile)}: ${tokens} tokens, ${interactions} interactions`);
286349

@@ -308,6 +371,8 @@ class CopilotTokenTracker implements vscode.Disposable {
308371
this.warn(`Error processing session file ${sessionFile}: ${fileError}`);
309372
}
310373
}
374+
375+
this.log(`Cache performance - Hits: ${cacheHits}, Misses: ${cacheMisses}, Hit Rate: ${sessionFiles.length > 0 ? ((cacheHits / sessionFiles.length) * 100).toFixed(1) : 0}%`);
311376
} catch (error) {
312377
this.error('Error calculating detailed stats:', error);
313378
}
@@ -404,6 +469,45 @@ class CopilotTokenTracker implements vscode.Disposable {
404469
return modelUsage;
405470
}
406471

472+
// Cached versions of session file reading methods
473+
private async getSessionFileDataCached(sessionFilePath: string, mtime: number): Promise<SessionFileCache> {
474+
// Check if we have valid cached data
475+
const cached = this.getCachedSessionData(sessionFilePath);
476+
if (cached && cached.mtime === mtime) {
477+
return cached;
478+
}
479+
480+
// Cache miss - read and process the file once to get all data
481+
const tokens = await this.estimateTokensFromSession(sessionFilePath);
482+
const interactions = await this.countInteractionsInSession(sessionFilePath);
483+
const modelUsage = await this.getModelUsageFromSession(sessionFilePath);
484+
485+
const sessionData: SessionFileCache = {
486+
tokens,
487+
interactions,
488+
modelUsage,
489+
mtime
490+
};
491+
492+
this.setCachedSessionData(sessionFilePath, sessionData);
493+
return sessionData;
494+
}
495+
496+
private async estimateTokensFromSessionCached(sessionFilePath: string, mtime: number): Promise<number> {
497+
const sessionData = await this.getSessionFileDataCached(sessionFilePath, mtime);
498+
return sessionData.tokens;
499+
}
500+
501+
private async countInteractionsInSessionCached(sessionFile: string, mtime: number): Promise<number> {
502+
const sessionData = await this.getSessionFileDataCached(sessionFile, mtime);
503+
return sessionData.interactions;
504+
}
505+
506+
private async getModelUsageFromSessionCached(sessionFile: string, mtime: number): Promise<ModelUsage> {
507+
const sessionData = await this.getSessionFileDataCached(sessionFile, mtime);
508+
return sessionData.modelUsage;
509+
}
510+
407511
private checkCopilotExtension(): void {
408512
this.log('Checking GitHub Copilot extension status');
409513

@@ -1155,6 +1259,8 @@ class CopilotTokenTracker implements vscode.Disposable {
11551259
}
11561260
this.statusBarItem.dispose();
11571261
this.outputChannel.dispose();
1262+
// Clear cache on disposal
1263+
this.sessionFileCache.clear();
11581264
}
11591265
}
11601266

0 commit comments

Comments
 (0)