/**
* PerformanceMonitor Tests
* Tests for enhanced performance monitoring including CPU profiling, memory tracking, and network analysis
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { PerformanceMonitor } from '../src/monitors/PerformanceMonitor.js';
import { ConfigManager } from '../src/config/ConfigManager.js';
describe('PerformanceMonitor', () => {
let performanceMonitor: PerformanceMonitor;
let configManager: ConfigManager;
let mockChromeDebugger: any;
let mockComponentManager: any;
beforeEach(async () => {
configManager = new ConfigManager();
await configManager.initialize();
performanceMonitor = new PerformanceMonitor(configManager);
// Mock Chrome debugger
mockChromeDebugger = {
getHeapUsage: vi.fn().mockResolvedValue({ usedSize: 1000000, totalSize: 2000000 }),
takeHeapSnapshot: vi.fn().mockResolvedValue({ nodes: [], edges: [] }),
startHeapSampling: vi.fn().mockResolvedValue(undefined),
stopHeapSampling: vi.fn().mockResolvedValue({ samples: [] }),
collectGarbage: vi.fn().mockResolvedValue(undefined),
clearBrowserCache: vi.fn().mockResolvedValue(undefined),
clearBrowserCookies: vi.fn().mockResolvedValue(undefined),
setNetworkThrottling: vi.fn().mockResolvedValue(undefined),
on: vi.fn()
};
// Mock Component manager
mockComponentManager = {
getRenderMetrics: vi.fn().mockReturnValue({
totalRenders: 100,
averageRenderTime: 5.2,
heavyComponents: [{ name: 'TestComponent', renderCount: 50, averageRenderTime: 12.5, totalRenderTime: 625 }],
recentRenders: [{ componentName: 'TestComponent', timestamp: Date.now(), duration: 8.5, reason: 'props' }],
slowRenders: [{ componentName: 'SlowComponent', duration: 25, timestamp: Date.now(), reason: 'state' }],
renderTrends: [{ componentName: 'TestComponent', trend: 'improving' }]
}),
on: vi.fn()
};
});
afterEach(async () => {
await performanceMonitor.shutdown();
});
describe('Initialization', () => {
it('should initialize successfully', async () => {
await expect(performanceMonitor.initialize()).resolves.not.toThrow();
});
it('should set Chrome debugger reference', () => {
performanceMonitor.setChromeDebugger(mockChromeDebugger);
expect(mockChromeDebugger.on).toHaveBeenCalledWith('networkMetric', expect.any(Function));
expect(mockChromeDebugger.on).toHaveBeenCalledWith('networkError', expect.any(Function));
});
it('should set component manager reference', () => {
performanceMonitor.setComponentManager(mockComponentManager);
expect(mockComponentManager.on).toHaveBeenCalledWith('renderMetric', expect.any(Function));
expect(mockComponentManager.on).toHaveBeenCalledWith('componentRender', expect.any(Function));
});
});
describe('CPU Profiling', () => {
beforeEach(() => {
performanceMonitor.setChromeDebugger(mockChromeDebugger);
});
it('should start CPU profiling', async () => {
const profileId = await performanceMonitor.startCPUProfiling('test-profile');
expect(profileId).toMatch(/^cpu_\d+$/);
});
it('should stop CPU profiling and return profile', async () => {
const profileId = await performanceMonitor.startCPUProfiling('test-profile');
const profile = await performanceMonitor.stopCPUProfiling(profileId);
expect(profile).toMatchObject({
id: profileId,
title: profileId,
duration: expect.any(Number),
startTime: expect.any(Number),
endTime: expect.any(Number),
profile: expect.any(Object),
flameGraph: expect.any(Array)
});
});
it('should handle CPU profiling errors gracefully', async () => {
// Test error handling by mocking the v8-profiler module to throw an error
const v8Profiler = await import('v8-profiler-next');
const startProfilingSpy = vi.spyOn(v8Profiler, 'startProfiling').mockImplementation(() => {
throw new Error('Profiling failed');
});
const monitor = new PerformanceMonitor(configManager);
monitor.setChromeDebugger(mockChromeDebugger);
await expect(monitor.startCPUProfiling()).rejects.toThrow('Profiling failed');
// Restore the original mock
startProfilingSpy.mockRestore();
});
});
describe('Memory Monitoring', () => {
beforeEach(() => {
performanceMonitor.setChromeDebugger(mockChromeDebugger);
});
it('should take detailed heap snapshot', async () => {
const snapshot = await performanceMonitor.takeDetailedHeapSnapshot();
expect(mockChromeDebugger.takeHeapSnapshot).toHaveBeenCalled();
expect(snapshot).toEqual({ nodes: [], edges: [] });
});
it('should start and stop heap sampling', async () => {
await performanceMonitor.startHeapSampling(16384);
expect(mockChromeDebugger.startHeapSampling).toHaveBeenCalledWith(16384);
const profile = await performanceMonitor.stopHeapSampling();
expect(mockChromeDebugger.stopHeapSampling).toHaveBeenCalled();
expect(profile).toEqual({ samples: [] });
});
it('should force garbage collection', async () => {
await performanceMonitor.forceGarbageCollection();
expect(mockChromeDebugger.collectGarbage).toHaveBeenCalled();
});
it('should get memory snapshots', async () => {
await performanceMonitor.initialize();
// Simulate some memory snapshots
const snapshots = performanceMonitor.getMemorySnapshots(5);
expect(Array.isArray(snapshots)).toBe(true);
});
});
describe('Network Performance Analysis', () => {
beforeEach(() => {
performanceMonitor.setChromeDebugger(mockChromeDebugger);
});
it('should analyze network performance patterns', () => {
// Add some mock network metrics
const networkMetrics = [
{
requestId: 'req1',
url: 'https://api.example.com/data',
method: 'GET',
startTime: Date.now() - 5000,
endTime: Date.now() - 4000,
duration: 1000,
responseSize: 50000,
requestSize: 1000,
statusCode: 200,
timing: { dnsLookup: 10, tcpConnect: 20, tlsHandshake: 30, requestSent: 5, waiting: 900, contentDownload: 35 }
},
{
requestId: 'req2',
url: 'https://api.example.com/slow',
method: 'POST',
startTime: Date.now() - 3000,
endTime: Date.now() - 500,
duration: 2500,
responseSize: 100000,
requestSize: 5000,
statusCode: 500,
timing: { dnsLookup: 15, tcpConnect: 25, tlsHandshake: 35, requestSent: 10, waiting: 2400, contentDownload: 15 }
}
];
// Manually add network metrics for testing
networkMetrics.forEach(metric => {
(performanceMonitor as any).addNetworkMetric(metric);
});
const analysis = performanceMonitor.analyzeNetworkPerformance(10000);
expect(analysis).toMatchObject({
totalRequests: 2,
averageResponseTime: 1750,
slowRequests: expect.arrayContaining([
expect.objectContaining({ duration: 2500 })
]),
failedRequests: 1,
largestRequests: expect.arrayContaining([
expect.objectContaining({ responseSize: 100000 })
]),
domainBreakdown: {
'api.example.com': 2
},
recommendations: expect.arrayContaining([
expect.stringContaining('slow requests detected'),
expect.stringContaining('High failure rate')
])
});
});
it('should clear browser cache and cookies', async () => {
await performanceMonitor.clearBrowserCache();
expect(mockChromeDebugger.clearBrowserCache).toHaveBeenCalled();
await performanceMonitor.clearBrowserCookies();
expect(mockChromeDebugger.clearBrowserCookies).toHaveBeenCalled();
});
it('should set network throttling', async () => {
const options = {
offline: false,
downloadThroughput: 1000000,
uploadThroughput: 500000,
latency: 100
};
await performanceMonitor.setNetworkThrottling(options);
expect(mockChromeDebugger.setNetworkThrottling).toHaveBeenCalledWith(options);
});
});
describe('Render Performance Analysis', () => {
beforeEach(() => {
performanceMonitor.setComponentManager(mockComponentManager);
});
it('should analyze render performance patterns', () => {
// Add some mock render metrics
const renderMetrics = [
{
componentName: 'FastComponent',
renderTime: 5,
renderCount: 10,
propsChanged: true,
stateChanged: false,
contextChanged: false,
timestamp: Date.now() - 1000,
callStack: ['FastComponent.render']
},
{
componentName: 'SlowComponent',
renderTime: 25,
renderCount: 5,
propsChanged: false,
stateChanged: true,
contextChanged: false,
timestamp: Date.now() - 500,
callStack: ['SlowComponent.render']
}
];
// Manually add render metrics for testing
renderMetrics.forEach(metric => {
performanceMonitor.addRenderMetric(metric);
});
const analysis = performanceMonitor.analyzeRenderPerformance(10000);
expect(analysis).toMatchObject({
totalRenders: 2,
averageRenderTime: 15,
slowRenders: expect.arrayContaining([
expect.objectContaining({ renderTime: 25 })
]),
rendersByComponent: {
'FastComponent': 1,
'SlowComponent': 1
},
renderReasons: {
'props': 1,
'state': 1
},
recommendations: expect.arrayContaining([
expect.stringContaining('slow renders detected')
]),
performanceScore: expect.any(Number)
});
});
it('should get detailed render metrics from component manager', () => {
const metrics = performanceMonitor.getDetailedRenderMetrics();
expect(mockComponentManager.getRenderMetrics).toHaveBeenCalled();
expect(metrics).toMatchObject({
totalRenders: 100,
averageRenderTime: 5.2,
heavyComponents: expect.any(Array),
recentRenders: expect.any(Array),
slowRenders: expect.any(Array),
renderTrends: expect.any(Array)
});
});
});
describe('Performance Alerts', () => {
it('should create and manage performance alerts', () => {
// Trigger a performance alert
const mockMetrics = {
timestamp: new Date(),
browser: {
memoryUsage: 100000000, // 100MB - should trigger alert
cpuUsage: 85, // High CPU usage
networkRequests: 10,
renderTime: 20 // Slow render
},
build: { bundleSize: 0, buildTime: 0, chunkCount: 0 },
runtime: { componentRenders: 0, hookUpdates: 0, stateChanges: 0 }
};
// Manually trigger alert checking
(performanceMonitor as any).checkPerformanceAlerts(mockMetrics);
const alerts = performanceMonitor.getAlerts(false); // Unresolved alerts
expect(alerts.length).toBeGreaterThan(0);
// Check alert types
const alertTypes = alerts.map((a: any) => a.type);
expect(alertTypes).toContain('cpu');
expect(alertTypes).toContain('render');
});
it('should resolve performance alerts', () => {
// Create a mock alert
const alertId = 'test-alert-123';
(performanceMonitor as any).alerts.push({
id: alertId,
type: 'cpu',
severity: 'high',
message: 'Test alert',
details: {},
timestamp: Date.now(),
resolved: false
});
const resolved = performanceMonitor.resolveAlert(alertId);
expect(resolved).toBe(true);
const alert = performanceMonitor.getAlerts().find((a: any) => a.id === alertId);
expect(alert?.resolved).toBe(true);
});
});
describe('Performance Metrics Collection', () => {
it('should get performance metrics with filtering', async () => {
await performanceMonitor.initialize();
const metrics = await performanceMonitor.getMetrics({
timeframe: '1h',
type: 'cpu',
limit: 10
});
expect(Array.isArray(metrics)).toBe(true);
});
it('should get CPU profiles', () => {
const profiles = performanceMonitor.getCPUProfiles();
expect(Array.isArray(profiles)).toBe(true);
});
it('should get network and render metrics', () => {
const networkMetrics = performanceMonitor.getNetworkMetrics(10);
const renderMetrics = performanceMonitor.getRenderMetrics(10);
expect(Array.isArray(networkMetrics)).toBe(true);
expect(Array.isArray(renderMetrics)).toBe(true);
});
});
describe('Error Handling', () => {
it('should handle Chrome debugger unavailable gracefully', async () => {
await expect(performanceMonitor.takeDetailedHeapSnapshot()).rejects.toThrow('Chrome debugger not available');
await expect(performanceMonitor.startHeapSampling()).rejects.toThrow('Chrome debugger not available');
await expect(performanceMonitor.clearBrowserCache()).rejects.toThrow('Chrome debugger not available');
});
it('should handle component manager unavailable gracefully', () => {
const metrics = performanceMonitor.getDetailedRenderMetrics();
expect(metrics).toMatchObject({
totalRenders: 0,
averageRenderTime: 0,
heavyComponents: [],
recentRenders: [],
slowRenders: [],
renderTrends: []
});
});
});
});