metrics-collector.ts•7.08 kB
/**
* Performance metrics collection system
*/
import { logger } from '../logging/logger.js';
import { requestLogger } from '../logging/request-logger.js';
export interface SystemMetrics {
timestamp: string;
memory: {
used: number;
total: number;
percentage: number;
heapUsed: number;
heapTotal: number;
};
cpu: {
usage: number;
loadAverage: number[];
};
requests: {
active: number;
total: number;
averageResponseTime: number;
errorRate: number;
};
cache: {
hitRate: number;
size: number;
entries: number;
};
uptime: number;
}
export interface OperationMetrics {
operation: string;
count: number;
totalDuration: number;
averageDuration: number;
minDuration: number;
maxDuration: number;
errorCount: number;
successRate: number;
lastExecuted: string;
}
export class MetricsCollector {
private operationMetrics = new Map<string, {
count: number;
totalDuration: number;
minDuration: number;
maxDuration: number;
errorCount: number;
lastExecuted: Date;
}>();
private requestMetrics = {
total: 0,
totalResponseTime: 0,
errors: 0
};
private cacheMetrics = {
hits: 0,
misses: 0,
size: 0,
entries: 0
};
private startTime = Date.now();
private lastCpuUsage = process.cpuUsage();
private lastCpuTime = Date.now();
/**
* Record operation metrics
*/
public recordOperation(
operation: string,
duration: number,
success: boolean
): void {
const existing = this.operationMetrics.get(operation) || {
count: 0,
totalDuration: 0,
minDuration: Infinity,
maxDuration: 0,
errorCount: 0,
lastExecuted: new Date()
};
existing.count++;
existing.totalDuration += duration;
existing.minDuration = Math.min(existing.minDuration, duration);
existing.maxDuration = Math.max(existing.maxDuration, duration);
existing.lastExecuted = new Date();
if (!success) {
existing.errorCount++;
}
this.operationMetrics.set(operation, existing);
// Log performance metrics in debug mode
logger.logPerformance(operation, {
duration,
success,
averageDuration: existing.totalDuration / existing.count,
successRate: ((existing.count - existing.errorCount) / existing.count) * 100
});
}
/**
* Record request metrics
*/
public recordRequest(duration: number, success: boolean): void {
this.requestMetrics.total++;
this.requestMetrics.totalResponseTime += duration;
if (!success) {
this.requestMetrics.errors++;
}
}
/**
* Record cache metrics
*/
public recordCacheHit(): void {
this.cacheMetrics.hits++;
}
public recordCacheMiss(): void {
this.cacheMetrics.misses++;
}
public updateCacheSize(size: number, entries: number): void {
this.cacheMetrics.size = size;
this.cacheMetrics.entries = entries;
}
/**
* Get current system metrics
*/
public getSystemMetrics(): SystemMetrics {
const memUsage = process.memoryUsage();
const cpuUsage = this.getCpuUsage();
const activeRequests = requestLogger.getActiveRequestCount();
return {
timestamp: new Date().toISOString(),
memory: {
used: memUsage.rss,
total: memUsage.rss + memUsage.heapTotal,
percentage: (memUsage.rss / (memUsage.rss + memUsage.heapTotal)) * 100,
heapUsed: memUsage.heapUsed,
heapTotal: memUsage.heapTotal
},
cpu: {
usage: cpuUsage,
loadAverage: process.platform !== 'win32' ? require('os').loadavg() : [0, 0, 0]
},
requests: {
active: activeRequests,
total: this.requestMetrics.total,
averageResponseTime: this.requestMetrics.total > 0
? this.requestMetrics.totalResponseTime / this.requestMetrics.total
: 0,
errorRate: this.requestMetrics.total > 0
? (this.requestMetrics.errors / this.requestMetrics.total) * 100
: 0
},
cache: {
hitRate: (this.cacheMetrics.hits + this.cacheMetrics.misses) > 0
? (this.cacheMetrics.hits / (this.cacheMetrics.hits + this.cacheMetrics.misses)) * 100
: 0,
size: this.cacheMetrics.size,
entries: this.cacheMetrics.entries
},
uptime: Date.now() - this.startTime
};
}
/**
* Get operation metrics
*/
public getOperationMetrics(): OperationMetrics[] {
return Array.from(this.operationMetrics.entries()).map(([operation, metrics]) => ({
operation,
count: metrics.count,
totalDuration: metrics.totalDuration,
averageDuration: metrics.totalDuration / metrics.count,
minDuration: metrics.minDuration === Infinity ? 0 : metrics.minDuration,
maxDuration: metrics.maxDuration,
errorCount: metrics.errorCount,
successRate: ((metrics.count - metrics.errorCount) / metrics.count) * 100,
lastExecuted: metrics.lastExecuted.toISOString()
}));
}
/**
* Get CPU usage percentage
*/
private getCpuUsage(): number {
const currentUsage = process.cpuUsage();
const currentTime = Date.now();
const userDiff = currentUsage.user - this.lastCpuUsage.user;
const systemDiff = currentUsage.system - this.lastCpuUsage.system;
const timeDiff = (currentTime - this.lastCpuTime) * 1000; // Convert to microseconds
const cpuPercent = ((userDiff + systemDiff) / timeDiff) * 100;
this.lastCpuUsage = currentUsage;
this.lastCpuTime = currentTime;
return Math.min(100, Math.max(0, cpuPercent));
}
/**
* Reset metrics
*/
public resetMetrics(): void {
this.operationMetrics.clear();
this.requestMetrics = {
total: 0,
totalResponseTime: 0,
errors: 0
};
this.cacheMetrics = {
hits: 0,
misses: 0,
size: 0,
entries: 0
};
}
/**
* Get metrics summary for logging
*/
public getMetricsSummary(): Record<string, any> {
const systemMetrics = this.getSystemMetrics();
const operationMetrics = this.getOperationMetrics();
return {
system: {
memoryUsage: `${(systemMetrics.memory.used / 1024 / 1024).toFixed(2)}MB`,
memoryPercentage: `${systemMetrics.memory.percentage.toFixed(2)}%`,
cpuUsage: `${systemMetrics.cpu.usage.toFixed(2)}%`,
uptime: `${(systemMetrics.uptime / 1000 / 60).toFixed(2)}min`
},
requests: {
active: systemMetrics.requests.active,
total: systemMetrics.requests.total,
averageResponseTime: `${systemMetrics.requests.averageResponseTime.toFixed(2)}ms`,
errorRate: `${systemMetrics.requests.errorRate.toFixed(2)}%`
},
cache: {
hitRate: `${systemMetrics.cache.hitRate.toFixed(2)}%`,
entries: systemMetrics.cache.entries,
size: `${(systemMetrics.cache.size / 1024 / 1024).toFixed(2)}MB`
},
operations: operationMetrics.length
};
}
}
// Export singleton instance
export const metricsCollector = new MetricsCollector();