UNPKG

network-performance-analyzer

Version:

Automated analysis tool for network performance test datasets containing DNS testing results and iperf3 performance measurements

292 lines 11.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PerformanceMonitor = void 0; // Performance Monitor Utility const os_1 = __importDefault(require("os")); const events_1 = require("events"); /** * Performance monitor for tracking system and process resource usage * Emits events when thresholds are exceeded */ class PerformanceMonitor extends events_1.EventEmitter { /** * Creates a new PerformanceMonitor instance * @param options Configuration options */ constructor(options = {}) { super(); this.metricsHistory = []; this.monitoringInterval = null; this.operationTimers = new Map(); this.operationDurations = new Map(); this.options = { monitoringInterval: options.monitoringInterval || 5000, maxHistorySize: options.maxHistorySize || 100, memoryThresholdPercent: options.memoryThresholdPercent || 80, logToConsole: options.logToConsole || false }; } /** * Start monitoring performance metrics */ start() { if (this.monitoringInterval) { return; // Already running } // Collect initial metrics this.collectMetrics(); // Set up interval for collecting metrics this.monitoringInterval = setInterval(() => { this.collectMetrics(); }, this.options.monitoringInterval); console.log(`[PerformanceMonitor] Started monitoring at ${this.options.monitoringInterval}ms intervals`); } /** * Stop monitoring performance metrics */ stop() { if (this.monitoringInterval) { clearInterval(this.monitoringInterval); this.monitoringInterval = null; console.log('[PerformanceMonitor] Stopped monitoring'); } } /** * Start timing an operation * @param operationName Name of the operation to time */ startOperation(operationName) { this.operationTimers.set(operationName, performance.now()); } /** * End timing an operation and record its duration * @param operationName Name of the operation that was timed * @returns Duration of the operation in milliseconds, or undefined if the operation wasn't started */ endOperation(operationName) { const startTime = this.operationTimers.get(operationName); if (startTime === undefined) { return undefined; } const duration = performance.now() - startTime; this.operationTimers.delete(operationName); // Add to durations list if (!this.operationDurations.has(operationName)) { this.operationDurations.set(operationName, []); } const durations = this.operationDurations.get(operationName); durations.push(duration); // Keep only the last 100 durations if (durations.length > 100) { durations.shift(); } return duration; } /** * Get the latest performance metrics * @returns The most recent metrics, or undefined if no metrics have been collected */ getLatestMetrics() { if (this.metricsHistory.length === 0) { return undefined; } return this.metricsHistory[this.metricsHistory.length - 1]; } /** * Get all collected metrics history * @returns Array of all collected metrics */ getMetricsHistory() { return [...this.metricsHistory]; } /** * Get statistics for a specific operation * @param operationName Name of the operation * @returns Statistics for the operation, or undefined if no data is available */ getOperationStats(operationName) { const durations = this.operationDurations.get(operationName); if (!durations || durations.length === 0) { return undefined; } const count = durations.length; const avgDuration = durations.reduce((sum, val) => sum + val, 0) / count; const minDuration = Math.min(...durations); const maxDuration = Math.max(...durations); return { count, avgDuration, minDuration, maxDuration }; } /** * Get statistics for all operations * @returns Map of operation names to their statistics */ getAllOperationStats() { const stats = new Map(); for (const [operationName, durations] of this.operationDurations.entries()) { if (durations.length === 0) { continue; } const count = durations.length; const avgDuration = durations.reduce((sum, val) => sum + val, 0) / count; const minDuration = Math.min(...durations); const maxDuration = Math.max(...durations); stats.set(operationName, { count, avgDuration, minDuration, maxDuration }); } return stats; } /** * Generate a performance report in markdown format * @returns Markdown formatted performance report */ generateReport() { const latest = this.getLatestMetrics(); if (!latest) { return '# Performance Report\n\nNo metrics available.'; } let report = '# Performance Report\n\n'; // System metrics report += '## System Resources\n\n'; report += `- **Memory Usage**: ${latest.memoryUsage.percentUsed.toFixed(1)}% (${this.formatBytes(latest.memoryUsage.used)} / ${this.formatBytes(latest.memoryUsage.total)})\n`; report += `- **CPU Usage**: ${latest.cpuUsage.percentUsed.toFixed(1)}%\n`; report += `- **Load Average**: ${latest.cpuUsage.loadAverage.map(load => load.toFixed(2)).join(', ')}\n\n`; // Process metrics report += '## Process Resources\n\n'; report += `- **RSS**: ${this.formatBytes(latest.processMemory.rss)}\n`; report += `- **Heap Usage**: ${latest.processMemory.percentHeapUsed.toFixed(1)}% (${this.formatBytes(latest.processMemory.heapUsed)} / ${this.formatBytes(latest.processMemory.heapTotal)})\n`; report += `- **External Memory**: ${this.formatBytes(latest.processMemory.external)}\n\n`; // Operation durations report += '## Operation Performance\n\n'; const stats = this.getAllOperationStats(); if (stats.size === 0) { report += 'No operation timing data available.\n'; } else { report += '| Operation | Count | Avg Duration (ms) | Min Duration (ms) | Max Duration (ms) |\n'; report += '|-----------|-------|------------------|------------------|------------------|\n'; for (const [name, opStats] of stats.entries()) { report += `| ${name} | ${opStats.count} | ${opStats.avgDuration.toFixed(2)} | ${opStats.minDuration.toFixed(2)} | ${opStats.maxDuration.toFixed(2)} |\n`; } } return report; } /** * Collect current performance metrics * @private */ collectMetrics() { // System memory const totalMem = os_1.default.totalmem(); const freeMem = os_1.default.freemem(); const usedMem = totalMem - freeMem; const percentUsed = (usedMem / totalMem) * 100; // CPU usage (approximation) const cpus = os_1.default.cpus(); const cpuUsage = this.calculateCpuUsage(cpus); const loadAverage = os_1.default.loadavg(); // Process memory const processMemory = process.memoryUsage(); const percentHeapUsed = (processMemory.heapUsed / processMemory.heapTotal) * 100; // Create metrics object const metrics = { timestamp: Date.now(), memoryUsage: { total: totalMem, used: usedMem, free: freeMem, percentUsed }, cpuUsage: { percentUsed: cpuUsage, loadAverage }, processMemory: { rss: processMemory.rss, heapTotal: processMemory.heapTotal, heapUsed: processMemory.heapUsed, external: processMemory.external, percentHeapUsed }, operationDurations: new Map(this.operationDurations) }; // Add to history this.metricsHistory.push(metrics); // Trim history if needed if (this.metricsHistory.length > this.options.maxHistorySize) { this.metricsHistory.shift(); } // Check thresholds this.checkThresholds(metrics); // Log if enabled if (this.options.logToConsole) { console.log(`[PerformanceMonitor] Memory: ${percentUsed.toFixed(1)}%, Heap: ${percentHeapUsed.toFixed(1)}%, CPU: ${cpuUsage.toFixed(1)}%`); } } /** * Check if any thresholds have been exceeded * @param metrics Current metrics * @private */ checkThresholds(metrics) { // Check memory threshold if (metrics.processMemory.percentHeapUsed > this.options.memoryThresholdPercent) { this.emit('memory-threshold-exceeded', { threshold: this.options.memoryThresholdPercent, current: metrics.processMemory.percentHeapUsed, metrics }); // Try to force garbage collection if available if (global.gc) { console.log(`[PerformanceMonitor] Memory threshold exceeded (${metrics.processMemory.percentHeapUsed.toFixed(1)}%), triggering garbage collection`); global.gc(); } } } /** * Calculate CPU usage percentage * @param cpus CPU information from os.cpus() * @returns CPU usage percentage * @private */ calculateCpuUsage(cpus) { let totalIdle = 0; let totalTick = 0; for (const cpu of cpus) { for (const type in cpu.times) { totalTick += cpu.times[type]; } totalIdle += cpu.times.idle; } const idle = totalIdle / cpus.length; const total = totalTick / cpus.length; const usedCpu = 100 - (idle / total) * 100; return usedCpu; } /** * Format bytes to a human-readable string * @param bytes Number of bytes * @returns Formatted string (e.g., "1.23 MB") * @private */ formatBytes(bytes) { if (bytes === 0) return '0 B'; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`; } } exports.PerformanceMonitor = PerformanceMonitor; // Use the existing Node.js gc type declaration //# sourceMappingURL=PerformanceMonitor.js.map