claude-code-templates 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -0
- package/package.json +26 -12
- package/src/analytics/core/ConversationAnalyzer.js +754 -0
- package/src/analytics/core/FileWatcher.js +285 -0
- package/src/analytics/core/ProcessDetector.js +242 -0
- package/src/analytics/core/SessionAnalyzer.js +597 -0
- package/src/analytics/core/StateCalculator.js +190 -0
- package/src/analytics/data/DataCache.js +550 -0
- package/src/analytics/notifications/NotificationManager.js +448 -0
- package/src/analytics/notifications/WebSocketServer.js +526 -0
- package/src/analytics/utils/PerformanceMonitor.js +455 -0
- package/src/analytics-web/assets/js/main.js +312 -0
- package/src/analytics-web/components/Charts.js +114 -0
- package/src/analytics-web/components/ConversationTable.js +437 -0
- package/src/analytics-web/components/Dashboard.js +573 -0
- package/src/analytics-web/components/SessionTimer.js +596 -0
- package/src/analytics-web/index.html +882 -49
- package/src/analytics-web/index.html.original +1939 -0
- package/src/analytics-web/services/DataService.js +357 -0
- package/src/analytics-web/services/StateService.js +276 -0
- package/src/analytics-web/services/WebSocketService.js +523 -0
- package/src/analytics.js +626 -2317
- package/src/analytics.log +0 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DataCache - Multi-level caching system for analytics performance optimization
|
|
6
|
+
* Provides file content, parsed data, and computation result caching with smart invalidation
|
|
7
|
+
*/
|
|
8
|
+
class DataCache {
|
|
9
|
+
constructor() {
|
|
10
|
+
// Multi-level caching strategy
|
|
11
|
+
this.caches = {
|
|
12
|
+
// File content cache (highest impact)
|
|
13
|
+
fileContent: new Map(), // filepath -> { content, timestamp, stats }
|
|
14
|
+
|
|
15
|
+
// Parsed data cache
|
|
16
|
+
parsedConversations: new Map(), // filepath -> { messages, timestamp }
|
|
17
|
+
|
|
18
|
+
// Computed results cache
|
|
19
|
+
tokenUsage: new Map(), // filepath -> { usage, timestamp }
|
|
20
|
+
modelInfo: new Map(), // filepath -> { info, timestamp }
|
|
21
|
+
statusSquares: new Map(), // filepath -> { squares, timestamp }
|
|
22
|
+
|
|
23
|
+
// Expensive computations cache
|
|
24
|
+
sessions: { data: null, timestamp: 0, dependencies: new Set() },
|
|
25
|
+
summary: { data: null, timestamp: 0, dependencies: new Set() },
|
|
26
|
+
|
|
27
|
+
// Process cache enhancement
|
|
28
|
+
processes: { data: null, timestamp: 0, ttl: 500 },
|
|
29
|
+
|
|
30
|
+
// Metadata cache
|
|
31
|
+
fileStats: new Map(), // filepath -> { stats, timestamp }
|
|
32
|
+
projectStats: new Map(), // projectPath -> { data, timestamp }
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Cache configuration
|
|
36
|
+
this.config = {
|
|
37
|
+
fileContentTTL: 60000, // 1 minute for file content
|
|
38
|
+
parsedDataTTL: 30000, // 30 seconds for parsed data
|
|
39
|
+
computationTTL: 20000, // 20 seconds for expensive computations
|
|
40
|
+
metadataTTL: 10000, // 10 seconds for metadata
|
|
41
|
+
processTTL: 500, // 500ms for process data
|
|
42
|
+
maxCacheSize: 100, // Reduced from 1000 to 100 to prevent memory buildup
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Dependency tracking for smart invalidation
|
|
46
|
+
this.dependencies = new Map(); // computation -> Set(filepaths)
|
|
47
|
+
|
|
48
|
+
// Performance metrics
|
|
49
|
+
this.metrics = {
|
|
50
|
+
hits: 0,
|
|
51
|
+
misses: 0,
|
|
52
|
+
invalidations: 0,
|
|
53
|
+
filesInvalidated: 0,
|
|
54
|
+
computationsInvalidated: 0,
|
|
55
|
+
evictions: 0
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Start automatic cleanup interval
|
|
59
|
+
this.cleanupInterval = setInterval(() => {
|
|
60
|
+
this.evictOldEntries();
|
|
61
|
+
this.enforceSizeLimits();
|
|
62
|
+
}, 30000); // Every 30 seconds
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get file content with caching and modification time checking
|
|
67
|
+
* @param {string} filepath - Path to file
|
|
68
|
+
* @returns {Promise<string>} File content
|
|
69
|
+
*/
|
|
70
|
+
async getFileContent(filepath) {
|
|
71
|
+
const cached = this.caches.fileContent.get(filepath);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const stats = await fs.stat(filepath);
|
|
75
|
+
|
|
76
|
+
// Check if cached content is still valid
|
|
77
|
+
if (cached && cached.timestamp >= stats.mtime.getTime()) {
|
|
78
|
+
this.metrics.hits++;
|
|
79
|
+
return cached.content;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Cache miss - read file
|
|
83
|
+
this.metrics.misses++;
|
|
84
|
+
const content = await fs.readFile(filepath, 'utf8');
|
|
85
|
+
|
|
86
|
+
this.caches.fileContent.set(filepath, {
|
|
87
|
+
content,
|
|
88
|
+
timestamp: stats.mtime.getTime(),
|
|
89
|
+
stats: stats
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Also cache the file stats
|
|
93
|
+
this.caches.fileStats.set(filepath, {
|
|
94
|
+
stats: stats,
|
|
95
|
+
timestamp: Date.now()
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return content;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// File doesn't exist or can't be read
|
|
101
|
+
this.invalidateFile(filepath);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get parsed conversation with caching
|
|
108
|
+
* @param {string} filepath - Path to conversation file
|
|
109
|
+
* @returns {Promise<Array>} Parsed conversation messages
|
|
110
|
+
*/
|
|
111
|
+
async getParsedConversation(filepath) {
|
|
112
|
+
const cached = this.caches.parsedConversations.get(filepath);
|
|
113
|
+
const fileStats = await this.getFileStats(filepath);
|
|
114
|
+
|
|
115
|
+
// Check if cached parsed data is still valid
|
|
116
|
+
if (cached && cached.timestamp >= fileStats.mtime.getTime()) {
|
|
117
|
+
this.metrics.hits++;
|
|
118
|
+
return cached.messages;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Cache miss - parse conversation
|
|
122
|
+
this.metrics.misses++;
|
|
123
|
+
const content = await this.getFileContent(filepath);
|
|
124
|
+
|
|
125
|
+
const messages = content.trim().split('\n')
|
|
126
|
+
.filter(line => line.trim())
|
|
127
|
+
.map(line => {
|
|
128
|
+
try {
|
|
129
|
+
const item = JSON.parse(line);
|
|
130
|
+
if (item.message && item.message.role) {
|
|
131
|
+
return {
|
|
132
|
+
role: item.message.role,
|
|
133
|
+
timestamp: new Date(item.timestamp),
|
|
134
|
+
content: item.message.content,
|
|
135
|
+
model: item.message.model || null,
|
|
136
|
+
usage: item.message.usage || null,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
} catch {}
|
|
140
|
+
return null;
|
|
141
|
+
})
|
|
142
|
+
.filter(Boolean);
|
|
143
|
+
|
|
144
|
+
this.caches.parsedConversations.set(filepath, {
|
|
145
|
+
messages,
|
|
146
|
+
timestamp: fileStats.mtime.getTime()
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return messages;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get file stats with caching
|
|
154
|
+
* @param {string} filepath - Path to file
|
|
155
|
+
* @returns {Promise<Object>} File stats
|
|
156
|
+
*/
|
|
157
|
+
async getFileStats(filepath) {
|
|
158
|
+
const cached = this.caches.fileStats.get(filepath);
|
|
159
|
+
|
|
160
|
+
// Check if metadata cache is still valid
|
|
161
|
+
if (cached && (Date.now() - cached.timestamp) < this.config.metadataTTL) {
|
|
162
|
+
this.metrics.hits++;
|
|
163
|
+
return cached.stats;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Cache miss - get fresh stats
|
|
167
|
+
this.metrics.misses++;
|
|
168
|
+
const stats = await fs.stat(filepath);
|
|
169
|
+
|
|
170
|
+
this.caches.fileStats.set(filepath, {
|
|
171
|
+
stats: stats,
|
|
172
|
+
timestamp: Date.now()
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return stats;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Cache expensive computation results with dependency tracking
|
|
180
|
+
* @param {string} key - Cache key
|
|
181
|
+
* @param {Function} computeFn - Function to compute result
|
|
182
|
+
* @param {Array<string>} dependencies - File dependencies
|
|
183
|
+
* @param {number} ttl - Time to live override
|
|
184
|
+
* @returns {Promise<any>} Computation result
|
|
185
|
+
*/
|
|
186
|
+
async getCachedComputation(key, computeFn, dependencies = [], ttl = null) {
|
|
187
|
+
const cached = this.caches[key];
|
|
188
|
+
const effectiveTTL = ttl || this.config.computationTTL;
|
|
189
|
+
|
|
190
|
+
// Check if any dependencies have changed
|
|
191
|
+
let dependenciesChanged = false;
|
|
192
|
+
if (dependencies.length > 0) {
|
|
193
|
+
for (const dep of dependencies) {
|
|
194
|
+
try {
|
|
195
|
+
const depStats = await this.getFileStats(dep);
|
|
196
|
+
const cachedDep = cached?.dependencies?.has(dep);
|
|
197
|
+
const depTimestamp = cached?.dependencyTimestamps?.get(dep);
|
|
198
|
+
|
|
199
|
+
if (!cachedDep || !depTimestamp || depStats.mtime.getTime() > depTimestamp) {
|
|
200
|
+
dependenciesChanged = true;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
// Dependency file doesn't exist anymore
|
|
205
|
+
dependenciesChanged = true;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check cache validity
|
|
212
|
+
const isCacheValid = cached?.data &&
|
|
213
|
+
!dependenciesChanged &&
|
|
214
|
+
(Date.now() - cached.timestamp) < effectiveTTL;
|
|
215
|
+
|
|
216
|
+
if (isCacheValid) {
|
|
217
|
+
this.metrics.hits++;
|
|
218
|
+
return cached.data;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Cache miss - compute result
|
|
222
|
+
this.metrics.misses++;
|
|
223
|
+
const result = await computeFn();
|
|
224
|
+
|
|
225
|
+
// Store dependency timestamps
|
|
226
|
+
const dependencyTimestamps = new Map();
|
|
227
|
+
for (const dep of dependencies) {
|
|
228
|
+
try {
|
|
229
|
+
const stats = await this.getFileStats(dep);
|
|
230
|
+
dependencyTimestamps.set(dep, stats.mtime.getTime());
|
|
231
|
+
} catch {}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
this.caches[key] = {
|
|
235
|
+
data: result,
|
|
236
|
+
timestamp: Date.now(),
|
|
237
|
+
dependencies: new Set(dependencies),
|
|
238
|
+
dependencyTimestamps: dependencyTimestamps
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Cache token usage calculation
|
|
246
|
+
* @param {string} filepath - File path
|
|
247
|
+
* @param {Function} calculateFn - Token calculation function
|
|
248
|
+
* @returns {Promise<Object>} Token usage data
|
|
249
|
+
*/
|
|
250
|
+
async getCachedTokenUsage(filepath, calculateFn) {
|
|
251
|
+
const cached = this.caches.tokenUsage.get(filepath);
|
|
252
|
+
const fileStats = await this.getFileStats(filepath);
|
|
253
|
+
|
|
254
|
+
if (cached && cached.timestamp >= fileStats.mtime.getTime()) {
|
|
255
|
+
this.metrics.hits++;
|
|
256
|
+
return cached.usage;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.metrics.misses++;
|
|
260
|
+
const usage = await calculateFn();
|
|
261
|
+
|
|
262
|
+
this.caches.tokenUsage.set(filepath, {
|
|
263
|
+
usage,
|
|
264
|
+
timestamp: fileStats.mtime.getTime()
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return usage;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Cache model info extraction
|
|
272
|
+
* @param {string} filepath - File path
|
|
273
|
+
* @param {Function} extractFn - Model extraction function
|
|
274
|
+
* @returns {Promise<Object>} Model info data
|
|
275
|
+
*/
|
|
276
|
+
async getCachedModelInfo(filepath, extractFn) {
|
|
277
|
+
const cached = this.caches.modelInfo.get(filepath);
|
|
278
|
+
const fileStats = await this.getFileStats(filepath);
|
|
279
|
+
|
|
280
|
+
if (cached && cached.timestamp >= fileStats.mtime.getTime()) {
|
|
281
|
+
this.metrics.hits++;
|
|
282
|
+
return cached.info;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this.metrics.misses++;
|
|
286
|
+
const info = await extractFn();
|
|
287
|
+
|
|
288
|
+
this.caches.modelInfo.set(filepath, {
|
|
289
|
+
info,
|
|
290
|
+
timestamp: fileStats.mtime.getTime()
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return info;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Cache status squares generation
|
|
298
|
+
* @param {string} filepath - File path
|
|
299
|
+
* @param {Function} generateFn - Status squares generation function
|
|
300
|
+
* @returns {Promise<Array>} Status squares data
|
|
301
|
+
*/
|
|
302
|
+
async getCachedStatusSquares(filepath, generateFn) {
|
|
303
|
+
const cached = this.caches.statusSquares.get(filepath);
|
|
304
|
+
const fileStats = await this.getFileStats(filepath);
|
|
305
|
+
|
|
306
|
+
if (cached && cached.timestamp >= fileStats.mtime.getTime()) {
|
|
307
|
+
this.metrics.hits++;
|
|
308
|
+
return cached.squares;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
this.metrics.misses++;
|
|
312
|
+
const squares = await generateFn();
|
|
313
|
+
|
|
314
|
+
this.caches.statusSquares.set(filepath, {
|
|
315
|
+
squares,
|
|
316
|
+
timestamp: fileStats.mtime.getTime()
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return squares;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Smart invalidation based on file changes
|
|
324
|
+
* @param {string} filepath - Path of changed file
|
|
325
|
+
*/
|
|
326
|
+
invalidateFile(filepath) {
|
|
327
|
+
this.metrics.filesInvalidated++;
|
|
328
|
+
|
|
329
|
+
// Remove direct file caches
|
|
330
|
+
this.caches.fileContent.delete(filepath);
|
|
331
|
+
this.caches.parsedConversations.delete(filepath);
|
|
332
|
+
this.caches.tokenUsage.delete(filepath);
|
|
333
|
+
this.caches.modelInfo.delete(filepath);
|
|
334
|
+
this.caches.statusSquares.delete(filepath);
|
|
335
|
+
this.caches.fileStats.delete(filepath);
|
|
336
|
+
|
|
337
|
+
// Invalidate computations that depend on this file
|
|
338
|
+
['sessions', 'summary'].forEach(key => {
|
|
339
|
+
const cached = this.caches[key];
|
|
340
|
+
if (cached?.dependencies?.has(filepath)) {
|
|
341
|
+
this.caches[key] = {
|
|
342
|
+
data: null,
|
|
343
|
+
timestamp: 0,
|
|
344
|
+
dependencies: new Set(),
|
|
345
|
+
dependencyTimestamps: new Map()
|
|
346
|
+
};
|
|
347
|
+
this.metrics.computationsInvalidated++;
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
this.metrics.invalidations++;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Invalidate multiple files (for batch operations)
|
|
356
|
+
* @param {Array<string>} filepaths - Array of file paths
|
|
357
|
+
*/
|
|
358
|
+
invalidateFiles(filepaths) {
|
|
359
|
+
filepaths.forEach(filepath => this.invalidateFile(filepath));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Invalidate all computations (force recalculation)
|
|
364
|
+
*/
|
|
365
|
+
invalidateComputations() {
|
|
366
|
+
['sessions', 'summary'].forEach(key => {
|
|
367
|
+
this.caches[key] = {
|
|
368
|
+
data: null,
|
|
369
|
+
timestamp: 0,
|
|
370
|
+
dependencies: new Set(),
|
|
371
|
+
dependencyTimestamps: new Map()
|
|
372
|
+
};
|
|
373
|
+
});
|
|
374
|
+
this.metrics.computationsInvalidated += 2;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Clear all caches
|
|
379
|
+
*/
|
|
380
|
+
clearAll() {
|
|
381
|
+
Object.values(this.caches).forEach(cache => {
|
|
382
|
+
if (cache instanceof Map) {
|
|
383
|
+
cache.clear();
|
|
384
|
+
} else {
|
|
385
|
+
cache.data = null;
|
|
386
|
+
cache.timestamp = 0;
|
|
387
|
+
cache.dependencies = new Set();
|
|
388
|
+
cache.dependencyTimestamps = new Map();
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Reset metrics
|
|
393
|
+
this.metrics = {
|
|
394
|
+
hits: 0,
|
|
395
|
+
misses: 0,
|
|
396
|
+
invalidations: 0,
|
|
397
|
+
filesInvalidated: 0,
|
|
398
|
+
computationsInvalidated: 0
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Evict old entries to manage memory
|
|
404
|
+
*/
|
|
405
|
+
evictOldEntries() {
|
|
406
|
+
const now = Date.now();
|
|
407
|
+
let evicted = 0;
|
|
408
|
+
|
|
409
|
+
// Evict old file content
|
|
410
|
+
for (const [filepath, data] of this.caches.fileContent.entries()) {
|
|
411
|
+
if (now - data.timestamp > this.config.fileContentTTL) {
|
|
412
|
+
this.caches.fileContent.delete(filepath);
|
|
413
|
+
evicted++;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Evict old parsed data
|
|
418
|
+
for (const [filepath, data] of this.caches.parsedConversations.entries()) {
|
|
419
|
+
if (now - data.timestamp > this.config.parsedDataTTL) {
|
|
420
|
+
this.caches.parsedConversations.delete(filepath);
|
|
421
|
+
evicted++;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Evict old metadata
|
|
426
|
+
for (const [filepath, data] of this.caches.fileStats.entries()) {
|
|
427
|
+
if (now - data.timestamp > this.config.metadataTTL) {
|
|
428
|
+
this.caches.fileStats.delete(filepath);
|
|
429
|
+
evicted++;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (evicted > 0) {
|
|
434
|
+
this.metrics.evictions += evicted;
|
|
435
|
+
console.log(chalk.gray(`🗑️ Evicted ${evicted} old cache entries`));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Enforce cache size limits to prevent memory buildup
|
|
441
|
+
*/
|
|
442
|
+
enforceSizeLimits() {
|
|
443
|
+
const maxSize = this.config.maxCacheSize;
|
|
444
|
+
let totalEvicted = 0;
|
|
445
|
+
|
|
446
|
+
// Enforce size limits on each cache
|
|
447
|
+
const caches = [
|
|
448
|
+
['fileContent', this.caches.fileContent],
|
|
449
|
+
['parsedConversations', this.caches.parsedConversations],
|
|
450
|
+
['tokenUsage', this.caches.tokenUsage],
|
|
451
|
+
['modelInfo', this.caches.modelInfo],
|
|
452
|
+
['statusSquares', this.caches.statusSquares],
|
|
453
|
+
['fileStats', this.caches.fileStats],
|
|
454
|
+
['projectStats', this.caches.projectStats]
|
|
455
|
+
];
|
|
456
|
+
|
|
457
|
+
for (const [, cache] of caches) {
|
|
458
|
+
if (cache.size > maxSize) {
|
|
459
|
+
// Convert to array and sort by timestamp (oldest first)
|
|
460
|
+
const entries = Array.from(cache.entries()).sort((a, b) => {
|
|
461
|
+
const timestampA = a[1].timestamp || 0;
|
|
462
|
+
const timestampB = b[1].timestamp || 0;
|
|
463
|
+
return timestampA - timestampB;
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Remove oldest entries until we're under the limit
|
|
467
|
+
const toRemove = cache.size - maxSize;
|
|
468
|
+
for (let i = 0; i < toRemove && i < entries.length; i++) {
|
|
469
|
+
cache.delete(entries[i][0]);
|
|
470
|
+
totalEvicted++;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (totalEvicted > 0) {
|
|
476
|
+
this.metrics.evictions += totalEvicted;
|
|
477
|
+
console.log(chalk.gray(`🗑️ Enforced size limits, evicted ${totalEvicted} entries`));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Clean up resources and stop timers
|
|
483
|
+
*/
|
|
484
|
+
cleanup() {
|
|
485
|
+
if (this.cleanupInterval) {
|
|
486
|
+
clearInterval(this.cleanupInterval);
|
|
487
|
+
this.cleanupInterval = null;
|
|
488
|
+
}
|
|
489
|
+
this.clearAll();
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Get cache statistics
|
|
494
|
+
* @returns {Object} Cache performance metrics
|
|
495
|
+
*/
|
|
496
|
+
getStats() {
|
|
497
|
+
const hitRate = this.metrics.hits + this.metrics.misses > 0
|
|
498
|
+
? (this.metrics.hits / (this.metrics.hits + this.metrics.misses) * 100).toFixed(2)
|
|
499
|
+
: 0;
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
...this.metrics,
|
|
503
|
+
hitRate: `${hitRate}%`,
|
|
504
|
+
cacheSize: {
|
|
505
|
+
fileContent: this.caches.fileContent.size,
|
|
506
|
+
parsedConversations: this.caches.parsedConversations.size,
|
|
507
|
+
tokenUsage: this.caches.tokenUsage.size,
|
|
508
|
+
modelInfo: this.caches.modelInfo.size,
|
|
509
|
+
statusSquares: this.caches.statusSquares.size,
|
|
510
|
+
fileStats: this.caches.fileStats.size,
|
|
511
|
+
projectStats: this.caches.projectStats.size,
|
|
512
|
+
},
|
|
513
|
+
memoryUsage: process.memoryUsage()
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Log cache performance
|
|
519
|
+
*/
|
|
520
|
+
logStats() {
|
|
521
|
+
const stats = this.getStats();
|
|
522
|
+
console.log(chalk.cyan('📊 Cache Statistics:'));
|
|
523
|
+
console.log(chalk.gray(` Hit Rate: ${stats.hitRate}`));
|
|
524
|
+
console.log(chalk.gray(` Hits: ${stats.hits}, Misses: ${stats.misses}`));
|
|
525
|
+
console.log(chalk.gray(` Invalidations: ${stats.invalidations}`));
|
|
526
|
+
console.log(chalk.gray(` Cache Sizes: ${JSON.stringify(stats.cacheSize)}`));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Check if cache is warming up (high miss rate)
|
|
531
|
+
* @returns {boolean} True if cache needs warming
|
|
532
|
+
*/
|
|
533
|
+
needsWarming() {
|
|
534
|
+
const total = this.metrics.hits + this.metrics.misses;
|
|
535
|
+
if (total < 10) return true; // Not enough data
|
|
536
|
+
|
|
537
|
+
const hitRate = this.metrics.hits / total;
|
|
538
|
+
return hitRate < 0.5; // Less than 50% hit rate
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Set cache configuration
|
|
543
|
+
* @param {Object} config - New configuration
|
|
544
|
+
*/
|
|
545
|
+
configure(config) {
|
|
546
|
+
this.config = { ...this.config, ...config };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
module.exports = DataCache;
|