ccusage-byobu
Version:
Real-time Claude Code API usage monitoring for byobu/tmux status bars with colored indicators, persistent caching, and automated integration
302 lines (274 loc) • 9.17 kB
JavaScript
/**
* Performance tracking module for ccusage-byobu
* Tracks performance metrics over time including JSON parsing
*/
class PerformanceTracker {
constructor() {
this.metrics = {
jsonParsing: {
operations: [],
totalTime: 0,
totalBytes: 0,
count: 0,
avgTime: 0,
avgThroughput: 0,
minTime: Infinity,
maxTime: 0,
minThroughput: Infinity,
maxThroughput: 0,
},
commandExecution: {
operations: [],
totalTime: 0,
count: 0,
avgTime: 0,
minTime: Infinity,
maxTime: 0,
},
cacheOperations: {
reads: { hits: 0, misses: 0, totalTime: 0 },
writes: { count: 0, totalTime: 0 },
},
memory: {
snapshots: [],
startMemory: null,
currentMemory: null,
peakMemory: { rss: 0, heapUsed: 0 },
},
};
// Track session start time
this.sessionStartTime = Date.now();
this.captureMemorySnapshot('session_start');
}
/**
* Track JSON parsing performance
* @param {number} parseTime - Parse time in milliseconds
* @param {number} jsonSize - Size of JSON in bytes
*/
trackJsonParsing(parseTime, jsonSize) {
const throughput = jsonSize / parseTime / 1024; // KB/ms
const operation = {
timestamp: Date.now(),
parseTime,
jsonSize,
throughput,
};
this.metrics.jsonParsing.operations.push(operation);
this.metrics.jsonParsing.totalTime += parseTime;
this.metrics.jsonParsing.totalBytes += jsonSize;
this.metrics.jsonParsing.count++;
// Update statistics
this.metrics.jsonParsing.avgTime =
this.metrics.jsonParsing.totalTime / this.metrics.jsonParsing.count;
this.metrics.jsonParsing.avgThroughput =
this.metrics.jsonParsing.totalBytes / this.metrics.jsonParsing.totalTime / 1024;
this.metrics.jsonParsing.minTime = Math.min(this.metrics.jsonParsing.minTime, parseTime);
this.metrics.jsonParsing.maxTime = Math.max(this.metrics.jsonParsing.maxTime, parseTime);
this.metrics.jsonParsing.minThroughput = Math.min(
this.metrics.jsonParsing.minThroughput,
throughput
);
this.metrics.jsonParsing.maxThroughput = Math.max(
this.metrics.jsonParsing.maxThroughput,
throughput
);
// Keep only last 100 operations
if (this.metrics.jsonParsing.operations.length > 100) {
this.metrics.jsonParsing.operations.shift();
}
}
/**
* Track command execution performance
* @param {number} executionTime - Execution time in milliseconds
*/
trackCommandExecution(executionTime) {
const operation = {
timestamp: Date.now(),
executionTime,
};
this.metrics.commandExecution.operations.push(operation);
this.metrics.commandExecution.totalTime += executionTime;
this.metrics.commandExecution.count++;
// Update statistics
this.metrics.commandExecution.avgTime =
this.metrics.commandExecution.totalTime / this.metrics.commandExecution.count;
this.metrics.commandExecution.minTime = Math.min(
this.metrics.commandExecution.minTime,
executionTime
);
this.metrics.commandExecution.maxTime = Math.max(
this.metrics.commandExecution.maxTime,
executionTime
);
// Keep only last 100 operations
if (this.metrics.commandExecution.operations.length > 100) {
this.metrics.commandExecution.operations.shift();
}
}
/**
* Track cache read operation
* @param {boolean} hit - Whether it was a cache hit
* @param {number} readTime - Read time in nanoseconds
*/
trackCacheRead(hit, readTime) {
if (hit) {
this.metrics.cacheOperations.reads.hits++;
} else {
this.metrics.cacheOperations.reads.misses++;
}
this.metrics.cacheOperations.reads.totalTime += readTime;
}
/**
* Track cache write operation
* @param {number} writeTime - Write time in nanoseconds
*/
trackCacheWrite(writeTime) {
this.metrics.cacheOperations.writes.count++;
this.metrics.cacheOperations.writes.totalTime += writeTime;
}
/**
* Capture memory snapshot
* @param {string} label - Label for the snapshot
*/
captureMemorySnapshot(label = 'snapshot') {
const usage = process.memoryUsage();
const snapshot = {
timestamp: Date.now(),
label,
rss: usage.rss,
heapTotal: usage.heapTotal,
heapUsed: usage.heapUsed,
external: usage.external,
};
this.metrics.memory.snapshots.push(snapshot);
// Update current memory
this.metrics.memory.currentMemory = snapshot;
// Update peak memory
this.metrics.memory.peakMemory.rss = Math.max(this.metrics.memory.peakMemory.rss, usage.rss);
this.metrics.memory.peakMemory.heapUsed = Math.max(
this.metrics.memory.peakMemory.heapUsed,
usage.heapUsed
);
// Set start memory if not set
if (!this.metrics.memory.startMemory) {
this.metrics.memory.startMemory = snapshot;
}
// Keep only last 50 snapshots
if (this.metrics.memory.snapshots.length > 50) {
this.metrics.memory.snapshots.shift();
}
}
/**
* Get performance summary
* @returns {Object} Performance summary
*/
getSummary() {
const sessionDuration = Date.now() - this.sessionStartTime;
const memoryGrowth =
this.metrics.memory.currentMemory && this.metrics.memory.startMemory
? {
rss: this.metrics.memory.currentMemory.rss - this.metrics.memory.startMemory.rss,
heapUsed:
this.metrics.memory.currentMemory.heapUsed - this.metrics.memory.startMemory.heapUsed,
}
: { rss: 0, heapUsed: 0 };
return {
sessionDuration: sessionDuration / 1000, // seconds
jsonParsing: {
count: this.metrics.jsonParsing.count,
avgTime: this.metrics.jsonParsing.avgTime.toFixed(3),
minTime:
this.metrics.jsonParsing.minTime === Infinity
? 0
: this.metrics.jsonParsing.minTime.toFixed(3),
maxTime: this.metrics.jsonParsing.maxTime.toFixed(3),
avgThroughput: this.metrics.jsonParsing.avgThroughput.toFixed(2),
totalBytes: this.metrics.jsonParsing.totalBytes,
},
commandExecution: {
count: this.metrics.commandExecution.count,
avgTime: this.metrics.commandExecution.avgTime.toFixed(0),
minTime:
this.metrics.commandExecution.minTime === Infinity
? 0
: this.metrics.commandExecution.minTime.toFixed(0),
maxTime: this.metrics.commandExecution.maxTime.toFixed(0),
},
cacheOperations: {
hitRate:
this.metrics.cacheOperations.reads.hits + this.metrics.cacheOperations.reads.misses > 0
? (
(this.metrics.cacheOperations.reads.hits /
(this.metrics.cacheOperations.reads.hits +
this.metrics.cacheOperations.reads.misses)) *
100
).toFixed(2)
: 0,
reads: this.metrics.cacheOperations.reads,
writes: this.metrics.cacheOperations.writes,
},
memory: {
current: {
rss:
this.metrics.memory.currentMemory?.rss != null
? (this.metrics.memory.currentMemory.rss / 1024 / 1024).toFixed(2)
: '0.00',
heapUsed:
this.metrics.memory.currentMemory?.heapUsed != null
? (this.metrics.memory.currentMemory.heapUsed / 1024 / 1024).toFixed(2)
: '0.00',
},
peak: {
rss:
this.metrics.memory.peakMemory?.rss != null
? (this.metrics.memory.peakMemory.rss / 1024 / 1024).toFixed(2)
: '0.00',
heapUsed:
this.metrics.memory.peakMemory?.heapUsed != null
? (this.metrics.memory.peakMemory.heapUsed / 1024 / 1024).toFixed(2)
: '0.00',
},
growth: {
rss: memoryGrowth?.rss != null ? (memoryGrowth.rss / 1024 / 1024).toFixed(2) : '0.00',
heapUsed:
memoryGrowth?.heapUsed != null
? (memoryGrowth.heapUsed / 1024 / 1024).toFixed(2)
: '0.00',
},
},
};
}
/**
* Get detailed report
* @returns {Object} Detailed performance report
*/
getDetailedReport() {
return {
summary: this.getSummary(),
recentOperations: {
jsonParsing: this.metrics.jsonParsing.operations.slice(-10),
commandExecution: this.metrics.commandExecution.operations.slice(-10),
},
memoryTimeline: this.metrics.memory.snapshots.slice(-20),
};
}
}
// Global performance tracker instance
let globalTracker = null;
/**
* Get the global performance tracker instance
* @returns {PerformanceTracker} Performance tracker instance
*/
export function getPerformanceTracker() {
if (!globalTracker) {
globalTracker = new PerformanceTracker();
}
return globalTracker;
}
/**
* Reset the global performance tracker
*/
export function resetPerformanceTracker() {
globalTracker = new PerformanceTracker();
}
export { PerformanceTracker };