Skip to content

Commit 97f1ee1

Browse files
authored
Merge pull request #23 from rajbos/copilot/fix-837b14b1-ba8e-4021-9f6b-eccbd8253e82
[FEATURE] Cache completed chat logs to improve performance
2 parents 0e705f5 + d069a41 commit 97f1ee1

File tree

3 files changed

+132
-5
lines changed

3 files changed

+132
-5
lines changed

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
66

77
## [Unreleased]
88

9+
### Added
10+
- Intelligent file caching to improve performance when processing session files
11+
- Cache management with automatic size limits and cleanup of non-existent files
12+
- Cache hit/miss rate logging for performance monitoring
13+
14+
### Changed
15+
- Session file processing now uses cached data when files haven't been modified
16+
- Reduced file I/O operations during periodic updates for better performance
17+
18+
- Initial release
919
- Automated VSIX build and release workflow
1020

1121
## [0.0.1] - Initial Release
@@ -15,4 +25,4 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
1525
- Automatic updates every 5 minutes
1626
- Click to refresh functionality
1727
- Smart estimation using character-based analysis
18-
- Detailed view with comprehensive statistics
28+
- Detailed view with comprehensive statistics

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ A VS Code extension that shows your daily and monthly GitHub Copilot estimated t
88
- **Automatic Updates**: Refreshes every 5 minutes to show the latest usage
99
- **Click to Refresh**: Click the status bar item to manually refresh the token count
1010
- **Smart Estimation**: Uses character-based analysis with model-specific ratios for token estimation
11+
- **Intelligent Caching**: Caches processed session files to speed up subsequent updates when files haven't changed
1112

1213
## Status Bar Display
1314

@@ -21,6 +22,16 @@ Hovering on the status bar item shows a detailed breakdown of token usage:
2122
Clicking the status bar item opens a detailed view with comprehensive statistics:
2223
![Detailed View](docs/images/03%20Detail%20panel.png)
2324

25+
## Performance Optimization
26+
27+
The extension uses intelligent caching to improve performance:
28+
29+
- **File Modification Tracking**: Only re-processes session files when they have been modified since the last read
30+
- **Efficient Cache Management**: Stores calculated token counts, interaction counts, and model usage data for each file
31+
- **Memory Management**: Automatically limits cache size to prevent memory issues (maximum 1000 cached files)
32+
- **Cache Statistics**: Logs cache hit/miss rates to help monitor performance improvements
33+
34+
This caching significantly reduces the time needed for periodic updates, especially when you have many chat session files.
2435

2536
## Known Issues
2637

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)