UNPKG

@digitalnodecom/node-red-contrib-analyzer

Version:

A Node-RED global service that monitors function nodes for debugging artifacts and performance issues. Features real-time quality metrics, Vue.js dashboard, and comprehensive code analysis.

328 lines (248 loc) 13.9 kB
const PerformanceMonitor = require('../../lib/monitoring/performance-monitor'); // Mock os module jest.mock('os', () => ({ totalmem: jest.fn(() => 8 * 1024 * 1024 * 1024) // 8GB })); // Mock the database module jest.mock('../../lib/database/performance-db', () => { return jest.fn().mockImplementation(() => { return { setRetentionDays: jest.fn(), storeMetrics: jest.fn().mockResolvedValue(1), getAverages: jest.fn().mockResolvedValue({ cpu: 0, memory: 0, eventLoop: 0 }), checkSustainedMetric: jest.fn().mockResolvedValue({ sustained: false, ratio: 0, totalReadings: 0, exceedingReadings: 0 }), recordAlert: jest.fn().mockResolvedValue(1), getRecentMetrics: jest.fn().mockResolvedValue([]), clearAll: jest.fn().mockResolvedValue({ message: 'All data cleared' }), getTrend: jest.fn().mockResolvedValue('stable'), getAlertHistory: jest.fn().mockResolvedValue([]), getStats: jest.fn().mockResolvedValue({ totalMetrics: 0, totalAlerts: 0 }), pruneOldData: jest.fn().mockResolvedValue({ message: 'Pruned successfully' }), close: jest.fn() }; }); }); describe('PerformanceMonitor', () => { let performanceMonitor; beforeEach(() => { jest.clearAllMocks(); performanceMonitor = new PerformanceMonitor(); // Fast forward time to avoid CPU calculation issues jest.spyOn(Date, 'now').mockReturnValue(1000000); // Reset all mock implementations performanceMonitor.db.checkSustainedMetric.mockReset(); performanceMonitor.db.recordAlert.mockReset(); performanceMonitor.db.getAverages.mockReset(); // Set default mock values performanceMonitor.db.checkSustainedMetric.mockResolvedValue({ sustained: false, ratio: 0, totalReadings: 0, exceedingReadings: 0 }); performanceMonitor.db.recordAlert.mockResolvedValue(1); performanceMonitor.db.getAverages.mockResolvedValue({ cpu: 0, memory: 0, eventLoop: 0 }); }); afterEach(() => { if (performanceMonitor) { performanceMonitor.stop(); } jest.restoreAllMocks(); }); describe('Constructor', () => { it('should initialize with default configuration', () => { expect(performanceMonitor.config).toEqual({ cpuThreshold: 75, memoryThreshold: 80, eventLoopThreshold: 20, sustainedAlertDuration: 300000, alertCooldown: 1800000, dbRetentionDays: 7, pruneInterval: 86400000 }); }); it('should initialize with monitoring state', () => { expect(performanceMonitor.isMonitoring).toBe(false); expect(performanceMonitor.lastAlertTimes).toHaveProperty('cpu'); expect(performanceMonitor.lastAlertTimes).toHaveProperty('memory'); expect(performanceMonitor.lastAlertTimes).toHaveProperty('eventLoop'); expect(performanceMonitor.db).toBeDefined(); }); }); describe('collectMetrics', () => { it('should collect CPU and memory metrics', () => { // Fast forward time to allow CPU calculation jest.spyOn(Date, 'now').mockReturnValueOnce(1000000).mockReturnValueOnce(1001000); const metrics = performanceMonitor.collectMetrics(); expect(metrics).toHaveProperty('cpu'); expect(metrics).toHaveProperty('memory'); expect(metrics).toHaveProperty('memoryDetails'); expect(metrics).toHaveProperty('timestamp'); expect(metrics.cpu).toBeGreaterThanOrEqual(0); expect(metrics.memory).toBeGreaterThanOrEqual(0); }); it('should store metrics in database', async () => { performanceMonitor.isMonitoring = true; // Ensure monitoring is active performanceMonitor.collectMetrics(); // Wait for async storage with longer timeout await new Promise(resolve => setTimeout(resolve, 100)); expect(performanceMonitor.db.storeMetrics).toHaveBeenCalled(); }); it('should handle database storage errors', async () => { performanceMonitor.db.storeMetrics.mockRejectedValue(new Error('Database error')); expect(() => performanceMonitor.collectMetrics()).not.toThrow(); }); it('should calculate memory percentage correctly', () => { const memoryUsage = { rss: 1024 * 1024 * 1024, // 1GB heapTotal: 500 * 1024 * 1024, heapUsed: 300 * 1024 * 1024, external: 100 * 1024 * 1024 }; const memoryPercent = performanceMonitor.calculateMemoryPercent(memoryUsage); expect(memoryPercent).toBeCloseTo(12.5, 1); // 1GB out of 8GB = 12.5% }); }); describe('checkSustainedThresholds', () => { it('should return no alerts when metrics are below thresholds', async () => { const metrics = { cpu: 50, memory: 60 }; const alerts = await performanceMonitor.checkSustainedThresholds(metrics); expect(alerts).toEqual([]); }); it('should return no alerts when metrics exceed thresholds but are not sustained', async () => { const metrics = { cpu: 85, memory: 60 }; performanceMonitor.db.checkSustainedMetric.mockResolvedValue({ sustained: false }); const alerts = await performanceMonitor.checkSustainedThresholds(metrics); expect(alerts).toEqual([]); }); it('should call database methods for sustained threshold checking', async () => { const metrics = { cpu: 85, memory: 60 }; // Mock calculateAverages to return values above threshold performanceMonitor.calculateAverages = jest.fn().mockResolvedValue({ cpu: 85, memory: 60, eventLoop: 5 }); // Mock the database to return sustained: true performanceMonitor.db.checkSustainedMetric.mockResolvedValueOnce({ sustained: true }); performanceMonitor.db.recordAlert.mockResolvedValueOnce(1); // Reset last alert time to ensure it's past the cooldown period performanceMonitor.lastAlertTimes.cpu = 0; await performanceMonitor.checkSustainedThresholds(metrics); expect(performanceMonitor.db.checkSustainedMetric).toHaveBeenCalledWith('cpu_usage', 75, 300000); }); it('should call database methods for memory threshold checking', async () => { const metrics = { cpu: 50, memory: 90 }; // Mock calculateAverages to return values above threshold performanceMonitor.calculateAverages = jest.fn().mockResolvedValue({ cpu: 50, memory: 90, eventLoop: 5 }); // Mock the database to return sustained: true performanceMonitor.db.checkSustainedMetric.mockResolvedValueOnce({ sustained: true }); performanceMonitor.db.recordAlert.mockResolvedValueOnce(1); // Reset last alert time to ensure it's past the cooldown period performanceMonitor.lastAlertTimes.memory = 0; await performanceMonitor.checkSustainedThresholds(metrics); expect(performanceMonitor.db.checkSustainedMetric).toHaveBeenCalledWith('memory_usage', 80, 300000); }); it('should respect alert cooldown periods', async () => { const metrics = { cpu: 85, memory: 60 }; performanceMonitor.db.checkSustainedMetric.mockResolvedValue({ sustained: true }); // Set last alert time to recent past performanceMonitor.lastAlertTimes.cpu = Date.now() - 1000; // 1 second ago const alerts = await performanceMonitor.checkSustainedThresholds(metrics); expect(alerts).toEqual([]); // Should be suppressed due to cooldown }); }); describe('getPerformanceSummary', () => { it('should return performance summary with current and average metrics', async () => { const summary = await performanceMonitor.getPerformanceSummary(); expect(summary).toHaveProperty('current'); expect(summary).toHaveProperty('averages'); expect(summary).toHaveProperty('alerts'); expect(summary).toHaveProperty('uptime'); expect(summary).toHaveProperty('processInfo'); }); it('should calculate averages from database', async () => { const mockAverages = { cpu: 75, memory: 65, eventLoop: 60 }; performanceMonitor.db.getAverages.mockResolvedValue(mockAverages); const summary = await performanceMonitor.getPerformanceSummary(); expect(summary.averages).toEqual(mockAverages); expect(performanceMonitor.db.getAverages).toHaveBeenCalledWith(10); }); it('should handle database errors gracefully', async () => { performanceMonitor.db.getAverages.mockRejectedValue(new Error('Database error')); const summary = await performanceMonitor.getPerformanceSummary(); expect(summary.averages).toEqual({ cpu: 0, memory: 0, eventLoop: 0 }); }); }); describe('updateConfig', () => { it('should update configuration', () => { const newConfig = { cpuThreshold: 90, memoryThreshold: 95, eventLoopThreshold: 150 }; performanceMonitor.updateConfig(newConfig); expect(performanceMonitor.config.cpuThreshold).toBe(90); expect(performanceMonitor.config.memoryThreshold).toBe(95); expect(performanceMonitor.config.eventLoopThreshold).toBe(150); }); it('should maintain other config values when partially updating', () => { const partialConfig = { cpuThreshold: 90 }; performanceMonitor.updateConfig(partialConfig); expect(performanceMonitor.config.cpuThreshold).toBe(90); expect(performanceMonitor.config.memoryThreshold).toBe(80); expect(performanceMonitor.config.eventLoopThreshold).toBe(20); }); }); describe('start and stop', () => { it('should start monitoring', () => { const spy = jest.spyOn(performanceMonitor, 'collectMetrics'); performanceMonitor.start(); expect(performanceMonitor.isMonitoring).toBe(true); expect(spy).toHaveBeenCalled(); }); it('should stop monitoring', () => { performanceMonitor.start(); performanceMonitor.stop(); expect(performanceMonitor.isMonitoring).toBe(false); }); it('should handle stop without start', () => { expect(() => performanceMonitor.stop()).not.toThrow(); }); }); describe('utility methods', () => { it('should get uptime information', () => { const uptime = performanceMonitor.getUptime(); expect(uptime).toHaveProperty('seconds'); expect(uptime).toHaveProperty('formatted'); expect(typeof uptime.seconds).toBe('number'); expect(typeof uptime.formatted).toBe('string'); }); it('should get process information', () => { const processInfo = performanceMonitor.getProcessInfo(); expect(processInfo).toHaveProperty('pid'); expect(processInfo).toHaveProperty('nodeVersion'); expect(processInfo).toHaveProperty('platform'); expect(processInfo).toHaveProperty('arch'); }); it('should check if monitoring is healthy', async () => { const isHealthy = await performanceMonitor.isHealthy(); expect(typeof isHealthy).toBe('boolean'); }); it('should get recent metrics from database', async () => { const mockMetrics = [{ timestamp: 1000, value: 50 }]; performanceMonitor.db.getRecentMetrics.mockResolvedValue(mockMetrics); const recentMetrics = await performanceMonitor.getRecentMetrics('cpu', 5); expect(recentMetrics).toEqual(mockMetrics); expect(performanceMonitor.db.getRecentMetrics).toHaveBeenCalledWith('cpu', 5); }); it('should clear history via database', async () => { await performanceMonitor.clearHistory(); expect(performanceMonitor.db.clearAll).toHaveBeenCalled(); }); it('should get database statistics', async () => { const mockStats = { totalMetrics: 100, totalAlerts: 5 }; performanceMonitor.db.getStats.mockResolvedValue(mockStats); const stats = await performanceMonitor.getDatabaseStats(); expect(stats).toEqual(mockStats); expect(performanceMonitor.db.getStats).toHaveBeenCalled(); }); it('should prune old data', async () => { const mockResult = { message: 'Pruned successfully' }; performanceMonitor.db.pruneOldData.mockResolvedValue(mockResult); await performanceMonitor.pruneOldData(); expect(performanceMonitor.db.pruneOldData).toHaveBeenCalledWith(7); }); }); });