claude-code-templates 1.8.0 ā 1.8.2
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 +631 -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 +641 -2311
- package/src/analytics.log +0 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PerformanceMonitor - Monitors and tracks system performance
|
|
3
|
+
* Phase 4: Performance monitoring and optimization
|
|
4
|
+
*/
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
class PerformanceMonitor {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.options = {
|
|
12
|
+
enabled: options.enabled !== false,
|
|
13
|
+
logInterval: options.logInterval || 60000, // 1 minute
|
|
14
|
+
metricsRetention: options.metricsRetention || 3600000, // 1 hour
|
|
15
|
+
memoryThreshold: options.memoryThreshold || 100 * 1024 * 1024, // 100MB
|
|
16
|
+
...options
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
this.metrics = {
|
|
20
|
+
memory: [],
|
|
21
|
+
cpu: [],
|
|
22
|
+
requests: [],
|
|
23
|
+
cache: [],
|
|
24
|
+
errors: [],
|
|
25
|
+
websocket: []
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
this.timers = {};
|
|
29
|
+
this.counters = {};
|
|
30
|
+
this.startTime = Date.now();
|
|
31
|
+
this.logInterval = null;
|
|
32
|
+
|
|
33
|
+
if (this.options.enabled) {
|
|
34
|
+
this.startMonitoring();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Start performance monitoring
|
|
40
|
+
*/
|
|
41
|
+
startMonitoring() {
|
|
42
|
+
console.log(chalk.blue('š Starting performance monitoring...'));
|
|
43
|
+
|
|
44
|
+
// Start periodic logging
|
|
45
|
+
this.logInterval = setInterval(() => {
|
|
46
|
+
this.collectSystemMetrics();
|
|
47
|
+
this.logPerformanceReport();
|
|
48
|
+
this.cleanupOldMetrics();
|
|
49
|
+
}, this.options.logInterval);
|
|
50
|
+
|
|
51
|
+
// Monitor process events
|
|
52
|
+
this.setupProcessMonitoring();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Stop performance monitoring
|
|
57
|
+
*/
|
|
58
|
+
stopMonitoring() {
|
|
59
|
+
if (this.logInterval) {
|
|
60
|
+
clearInterval(this.logInterval);
|
|
61
|
+
this.logInterval = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(chalk.yellow('š Performance monitoring stopped'));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Setup process monitoring
|
|
69
|
+
*/
|
|
70
|
+
setupProcessMonitoring() {
|
|
71
|
+
// Monitor memory usage
|
|
72
|
+
process.on('warning', (warning) => {
|
|
73
|
+
this.recordError('process_warning', warning.message, {
|
|
74
|
+
name: warning.name,
|
|
75
|
+
code: warning.code
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Monitor uncaught exceptions
|
|
80
|
+
process.on('uncaughtException', (error) => {
|
|
81
|
+
this.recordError('uncaught_exception', error.message, {
|
|
82
|
+
stack: error.stack
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Monitor unhandled rejections
|
|
87
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
88
|
+
this.recordError('unhandled_rejection', reason.toString(), {
|
|
89
|
+
promise: promise.toString()
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Start timing an operation
|
|
96
|
+
* @param {string} name - Timer name
|
|
97
|
+
*/
|
|
98
|
+
startTimer(name) {
|
|
99
|
+
this.timers[name] = {
|
|
100
|
+
start: process.hrtime.bigint(),
|
|
101
|
+
startTime: Date.now()
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* End timing an operation
|
|
107
|
+
* @param {string} name - Timer name
|
|
108
|
+
* @param {Object} metadata - Additional metadata
|
|
109
|
+
*/
|
|
110
|
+
endTimer(name, metadata = {}) {
|
|
111
|
+
if (!this.timers[name]) {
|
|
112
|
+
console.warn(`Timer ${name} was not started`);
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const timer = this.timers[name];
|
|
117
|
+
const endTime = process.hrtime.bigint();
|
|
118
|
+
const duration = Number(endTime - timer.start) / 1000000; // Convert to milliseconds
|
|
119
|
+
|
|
120
|
+
this.recordMetric('performance', {
|
|
121
|
+
operation: name,
|
|
122
|
+
duration,
|
|
123
|
+
timestamp: Date.now(),
|
|
124
|
+
...metadata
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
delete this.timers[name];
|
|
128
|
+
return duration;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Increment a counter
|
|
133
|
+
* @param {string} name - Counter name
|
|
134
|
+
* @param {number} value - Increment value
|
|
135
|
+
*/
|
|
136
|
+
incrementCounter(name, value = 1) {
|
|
137
|
+
this.counters[name] = (this.counters[name] || 0) + value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Record a metric
|
|
142
|
+
* @param {string} category - Metric category
|
|
143
|
+
* @param {Object} data - Metric data
|
|
144
|
+
*/
|
|
145
|
+
recordMetric(category, data) {
|
|
146
|
+
if (!this.options.enabled) return;
|
|
147
|
+
|
|
148
|
+
if (!this.metrics[category]) {
|
|
149
|
+
this.metrics[category] = [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.metrics[category].push({
|
|
153
|
+
...data,
|
|
154
|
+
timestamp: data.timestamp || Date.now()
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Keep array size manageable
|
|
158
|
+
if (this.metrics[category].length > 1000) {
|
|
159
|
+
this.metrics[category].shift();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Record an error
|
|
165
|
+
* @param {string} type - Error type
|
|
166
|
+
* @param {string} message - Error message
|
|
167
|
+
* @param {Object} metadata - Additional metadata
|
|
168
|
+
*/
|
|
169
|
+
recordError(type, message, metadata = {}) {
|
|
170
|
+
this.recordMetric('errors', {
|
|
171
|
+
type,
|
|
172
|
+
message,
|
|
173
|
+
...metadata
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
console.error(chalk.red(`ā Error recorded: ${type} - ${message}`));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Record API request metrics
|
|
181
|
+
* @param {string} endpoint - API endpoint
|
|
182
|
+
* @param {number} duration - Request duration
|
|
183
|
+
* @param {number} statusCode - HTTP status code
|
|
184
|
+
* @param {Object} metadata - Additional metadata
|
|
185
|
+
*/
|
|
186
|
+
recordRequest(endpoint, duration, statusCode, metadata = {}) {
|
|
187
|
+
this.recordMetric('requests', {
|
|
188
|
+
endpoint,
|
|
189
|
+
duration,
|
|
190
|
+
statusCode,
|
|
191
|
+
success: statusCode >= 200 && statusCode < 400,
|
|
192
|
+
...metadata
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
this.incrementCounter('total_requests');
|
|
196
|
+
if (statusCode >= 400) {
|
|
197
|
+
this.incrementCounter('error_requests');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Record cache metrics
|
|
203
|
+
* @param {string} operation - Cache operation (hit, miss, set, delete)
|
|
204
|
+
* @param {string} key - Cache key
|
|
205
|
+
* @param {number} duration - Operation duration
|
|
206
|
+
*/
|
|
207
|
+
recordCache(operation, key, duration = 0) {
|
|
208
|
+
this.recordMetric('cache', {
|
|
209
|
+
operation,
|
|
210
|
+
key,
|
|
211
|
+
duration
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
this.incrementCounter(`cache_${operation}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Record WebSocket metrics
|
|
219
|
+
* @param {string} event - WebSocket event
|
|
220
|
+
* @param {Object} data - Event data
|
|
221
|
+
*/
|
|
222
|
+
recordWebSocket(event, data = {}) {
|
|
223
|
+
this.recordMetric('websocket', {
|
|
224
|
+
event,
|
|
225
|
+
...data
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
this.incrementCounter(`websocket_${event}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Collect system metrics
|
|
233
|
+
*/
|
|
234
|
+
collectSystemMetrics() {
|
|
235
|
+
const memUsage = process.memoryUsage();
|
|
236
|
+
const cpuUsage = process.cpuUsage();
|
|
237
|
+
|
|
238
|
+
this.recordMetric('memory', {
|
|
239
|
+
rss: memUsage.rss,
|
|
240
|
+
heapUsed: memUsage.heapUsed,
|
|
241
|
+
heapTotal: memUsage.heapTotal,
|
|
242
|
+
external: memUsage.external,
|
|
243
|
+
arrayBuffers: memUsage.arrayBuffers
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
this.recordMetric('cpu', {
|
|
247
|
+
user: cpuUsage.user,
|
|
248
|
+
system: cpuUsage.system
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Check memory threshold
|
|
252
|
+
if (memUsage.heapUsed > this.options.memoryThreshold) {
|
|
253
|
+
this.recordError('memory_threshold',
|
|
254
|
+
`Memory usage exceeded threshold: ${Math.round(memUsage.heapUsed / 1024 / 1024)}MB`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get performance statistics
|
|
261
|
+
* @param {number} timeframe - Timeframe in milliseconds
|
|
262
|
+
* @returns {Object} Performance statistics
|
|
263
|
+
*/
|
|
264
|
+
getStats(timeframe = 300000) { // Default 5 minutes
|
|
265
|
+
const cutoff = Date.now() - timeframe;
|
|
266
|
+
|
|
267
|
+
const filterRecent = (metrics) =>
|
|
268
|
+
metrics.filter(metric => metric.timestamp > cutoff);
|
|
269
|
+
|
|
270
|
+
const recentRequests = filterRecent(this.metrics.requests || []);
|
|
271
|
+
const recentErrors = filterRecent(this.metrics.errors || []);
|
|
272
|
+
const recentMemory = filterRecent(this.metrics.memory || []);
|
|
273
|
+
const recentCache = filterRecent(this.metrics.cache || []);
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
uptime: Date.now() - this.startTime,
|
|
277
|
+
requests: {
|
|
278
|
+
total: recentRequests.length,
|
|
279
|
+
successful: recentRequests.filter(r => r.success).length,
|
|
280
|
+
errors: recentRequests.filter(r => !r.success).length,
|
|
281
|
+
averageResponseTime: this.calculateAverage(recentRequests, 'duration'),
|
|
282
|
+
endpointStats: this.groupBy(recentRequests, 'endpoint')
|
|
283
|
+
},
|
|
284
|
+
errors: {
|
|
285
|
+
total: recentErrors.length,
|
|
286
|
+
byType: this.groupBy(recentErrors, 'type')
|
|
287
|
+
},
|
|
288
|
+
memory: {
|
|
289
|
+
current: recentMemory.length > 0 ? recentMemory[recentMemory.length - 1] : null,
|
|
290
|
+
average: this.calculateAverageMemory(recentMemory),
|
|
291
|
+
peak: this.calculatePeakMemory(recentMemory)
|
|
292
|
+
},
|
|
293
|
+
cache: {
|
|
294
|
+
operations: recentCache.length,
|
|
295
|
+
hitRate: this.calculateCacheHitRate(recentCache),
|
|
296
|
+
operationStats: this.groupBy(recentCache, 'operation')
|
|
297
|
+
},
|
|
298
|
+
counters: { ...this.counters },
|
|
299
|
+
activeTimers: Object.keys(this.timers).length
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Calculate average value
|
|
305
|
+
* @param {Array} items - Array of items
|
|
306
|
+
* @param {string} field - Field to average
|
|
307
|
+
* @returns {number} Average value
|
|
308
|
+
*/
|
|
309
|
+
calculateAverage(items, field) {
|
|
310
|
+
if (items.length === 0) return 0;
|
|
311
|
+
const sum = items.reduce((acc, item) => acc + (item[field] || 0), 0);
|
|
312
|
+
return Math.round(sum / items.length * 100) / 100;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Calculate average memory usage
|
|
317
|
+
* @param {Array} memoryMetrics - Memory metrics
|
|
318
|
+
* @returns {Object} Average memory usage
|
|
319
|
+
*/
|
|
320
|
+
calculateAverageMemory(memoryMetrics) {
|
|
321
|
+
if (memoryMetrics.length === 0) return null;
|
|
322
|
+
|
|
323
|
+
const avgRss = this.calculateAverage(memoryMetrics, 'rss');
|
|
324
|
+
const avgHeapUsed = this.calculateAverage(memoryMetrics, 'heapUsed');
|
|
325
|
+
const avgHeapTotal = this.calculateAverage(memoryMetrics, 'heapTotal');
|
|
326
|
+
|
|
327
|
+
return { rss: avgRss, heapUsed: avgHeapUsed, heapTotal: avgHeapTotal };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Calculate peak memory usage
|
|
332
|
+
* @param {Array} memoryMetrics - Memory metrics
|
|
333
|
+
* @returns {Object} Peak memory usage
|
|
334
|
+
*/
|
|
335
|
+
calculatePeakMemory(memoryMetrics) {
|
|
336
|
+
if (memoryMetrics.length === 0) return null;
|
|
337
|
+
|
|
338
|
+
const maxRss = Math.max(...memoryMetrics.map(m => m.rss));
|
|
339
|
+
const maxHeapUsed = Math.max(...memoryMetrics.map(m => m.heapUsed));
|
|
340
|
+
|
|
341
|
+
return { rss: maxRss, heapUsed: maxHeapUsed };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Calculate cache hit rate
|
|
346
|
+
* @param {Array} cacheMetrics - Cache metrics
|
|
347
|
+
* @returns {number} Cache hit rate percentage
|
|
348
|
+
*/
|
|
349
|
+
calculateCacheHitRate(cacheMetrics) {
|
|
350
|
+
const hits = cacheMetrics.filter(m => m.operation === 'hit').length;
|
|
351
|
+
const misses = cacheMetrics.filter(m => m.operation === 'miss').length;
|
|
352
|
+
const total = hits + misses;
|
|
353
|
+
|
|
354
|
+
return total > 0 ? Math.round((hits / total) * 100 * 100) / 100 : 0;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Group items by field
|
|
359
|
+
* @param {Array} items - Items to group
|
|
360
|
+
* @param {string} field - Field to group by
|
|
361
|
+
* @returns {Object} Grouped items
|
|
362
|
+
*/
|
|
363
|
+
groupBy(items, field) {
|
|
364
|
+
return items.reduce((acc, item) => {
|
|
365
|
+
const key = item[field] || 'unknown';
|
|
366
|
+
acc[key] = (acc[key] || 0) + 1;
|
|
367
|
+
return acc;
|
|
368
|
+
}, {});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Clean up old metrics
|
|
373
|
+
*/
|
|
374
|
+
cleanupOldMetrics() {
|
|
375
|
+
const cutoff = Date.now() - this.options.metricsRetention;
|
|
376
|
+
|
|
377
|
+
Object.keys(this.metrics).forEach(category => {
|
|
378
|
+
this.metrics[category] = this.metrics[category].filter(
|
|
379
|
+
metric => metric.timestamp > cutoff
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Log performance report
|
|
386
|
+
*/
|
|
387
|
+
logPerformanceReport() {
|
|
388
|
+
const stats = this.getStats();
|
|
389
|
+
|
|
390
|
+
console.log(chalk.cyan('\nš Performance Report:'));
|
|
391
|
+
console.log(chalk.gray(`Uptime: ${Math.round(stats.uptime / 1000)}s`));
|
|
392
|
+
|
|
393
|
+
if (stats.requests.total > 0) {
|
|
394
|
+
console.log(chalk.green(`Requests: ${stats.requests.total} (${stats.requests.successful} success, ${stats.requests.errors} errors)`));
|
|
395
|
+
console.log(chalk.blue(`Avg Response Time: ${stats.requests.averageResponseTime}ms`));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (stats.memory.current) {
|
|
399
|
+
const memMB = Math.round(stats.memory.current.heapUsed / 1024 / 1024);
|
|
400
|
+
console.log(chalk.yellow(`Memory: ${memMB}MB heap used`));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (stats.cache.operations > 0) {
|
|
404
|
+
console.log(chalk.magenta(`Cache: ${stats.cache.hitRate}% hit rate (${stats.cache.operations} ops)`));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (stats.errors.total > 0) {
|
|
408
|
+
console.log(chalk.red(`Errors: ${stats.errors.total} in last 5 minutes`));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Export metrics to file
|
|
414
|
+
* @param {string} filePath - Export file path
|
|
415
|
+
*/
|
|
416
|
+
async exportMetrics(filePath) {
|
|
417
|
+
const stats = this.getStats(3600000); // Last hour
|
|
418
|
+
const exportData = {
|
|
419
|
+
timestamp: new Date().toISOString(),
|
|
420
|
+
stats,
|
|
421
|
+
rawMetrics: this.metrics
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
await fs.writeJson(filePath, exportData, { spaces: 2 });
|
|
425
|
+
console.log(chalk.green(`š Metrics exported to ${filePath}`));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Create performance middleware for Express
|
|
430
|
+
* @returns {Function} Express middleware
|
|
431
|
+
*/
|
|
432
|
+
createExpressMiddleware() {
|
|
433
|
+
return (req, res, next) => {
|
|
434
|
+
const start = Date.now();
|
|
435
|
+
|
|
436
|
+
res.on('finish', () => {
|
|
437
|
+
const duration = Date.now() - start;
|
|
438
|
+
this.recordRequest(
|
|
439
|
+
`${req.method} ${req.route?.path || req.url}`,
|
|
440
|
+
duration,
|
|
441
|
+
res.statusCode,
|
|
442
|
+
{
|
|
443
|
+
method: req.method,
|
|
444
|
+
userAgent: req.get('User-Agent'),
|
|
445
|
+
ip: req.ip
|
|
446
|
+
}
|
|
447
|
+
);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
next();
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
module.exports = PerformanceMonitor;
|