/**
* PerformanceMonitor - Advanced performance tracking and analysis
* Includes CPU profiling, memory monitoring, network analysis, and render metrics
*/
import { EventEmitter } from "events";
import { performance, PerformanceObserver } from "perf_hooks";
import { ConfigManager } from "../config/ConfigManager.js";
import { Logger } from "../utils/Logger.js";
import type { PerformanceMetrics } from "../core/DebuggerCore.js";
// Import v8-profiler-next for CPU profiling
let v8Profiler: any = null;
try {
v8Profiler = await import('v8-profiler-next');
} catch (error) {
// v8-profiler-next is optional
}
export interface CPUProfile {
id: string;
title: string;
startTime: number;
endTime: number;
duration: number;
profile: any; // V8 CPU profile data
flameGraph?: FlameGraphNode[];
}
export interface FlameGraphNode {
name: string;
value: number;
children?: FlameGraphNode[];
selfTime: number;
totalTime: number;
hitCount: number;
url?: string;
lineNumber?: number;
columnNumber?: number;
}
export interface MemorySnapshot {
id: string;
timestamp: number;
heapUsed: number;
heapTotal: number;
external: number;
arrayBuffers: number;
rss: number;
gcStats?: {
totalGCTime: number;
totalGCCount: number;
majorGCCount: number;
minorGCCount: number;
};
}
export interface NetworkMetrics {
requestId: string;
url: string;
method: string;
startTime: number;
endTime: number;
duration: number;
responseSize: number;
requestSize: number;
statusCode: number;
timing: {
dnsLookup: number;
tcpConnect: number;
tlsHandshake: number;
requestSent: number;
waiting: number;
contentDownload: number;
};
}
export interface RenderMetrics {
componentName: string;
renderTime: number;
renderCount: number;
propsChanged: boolean;
stateChanged: boolean;
contextChanged: boolean;
timestamp: number;
callStack?: string[];
}
export interface PerformanceAlert {
id: string;
type: 'cpu' | 'memory' | 'network' | 'render';
severity: 'low' | 'medium' | 'high' | 'critical';
message: string;
details: any;
timestamp: number;
resolved: boolean;
}
export class PerformanceMonitor extends EventEmitter {
private logger: Logger;
private configManager: ConfigManager;
private metrics: PerformanceMetrics[] = [];
private cpuProfiles: Map<string, CPUProfile> = new Map();
private memorySnapshots: MemorySnapshot[] = [];
private networkMetrics: NetworkMetrics[] = [];
private renderMetrics: RenderMetrics[] = [];
private alerts: PerformanceAlert[] = [];
// Performance observers
private performanceObserver?: PerformanceObserver;
private gcObserver?: PerformanceObserver;
// Monitoring state
private isMonitoring = false;
private monitoringInterval?: NodeJS.Timeout;
private memoryMonitoringInterval?: NodeJS.Timeout;
// Baseline metrics for comparison
private baselineMetrics?: PerformanceMetrics;
// Chrome debugger reference for heap profiling
private chromeDebugger?: any;
// Component manager reference for render tracking
private componentManager?: any;
constructor(configManager: ConfigManager) {
super();
this.logger = new Logger("PerformanceMonitor");
this.configManager = configManager;
}
/**
* Set Chrome debugger reference for advanced memory profiling
*/
setChromeDebugger(chromeDebugger: any): void {
this.chromeDebugger = chromeDebugger;
this.logger.info("Chrome debugger reference set for advanced memory profiling");
// Set up network monitoring
this.setupNetworkMonitoring();
}
/**
* Set component manager reference for render tracking
*/
setComponentManager(componentManager: any): void {
this.componentManager = componentManager;
this.logger.info("Component manager reference set for render tracking");
// Set up render monitoring
this.setupRenderMonitoring();
}
/**
* Set up render monitoring with component manager
*/
private setupRenderMonitoring(): void {
if (!this.componentManager) return;
try {
// Listen for render metrics from component manager
this.componentManager.on('renderMetric', (metric: RenderMetrics) => {
this.addRenderMetric(metric);
});
// Listen for component renders
this.componentManager.on('componentRender', (renderEvent: any) => {
this.emit('componentRender', renderEvent);
// Check for slow renders
if (renderEvent.duration > 16) { // Slower than 60fps
this.createAlert('render', 'medium', 'Slow component render detected', {
componentName: renderEvent.componentName,
duration: renderEvent.duration,
reason: renderEvent.reason
});
}
});
this.logger.info("Render monitoring set up successfully");
} catch (error) {
this.logger.error("Failed to set up render monitoring:", error);
}
}
/**
* Set up network monitoring with Chrome debugger
*/
private setupNetworkMonitoring(): void {
if (!this.chromeDebugger) return;
try {
// Listen for network metrics from Chrome debugger
this.chromeDebugger.on('networkMetric', (metric: NetworkMetrics) => {
this.addNetworkMetric(metric);
});
// Listen for network errors
this.chromeDebugger.on('networkError', (error: any) => {
this.createAlert('network', 'medium', 'Network request failed', {
url: error.url,
method: error.method,
errorText: error.errorText
});
});
this.logger.info("Network monitoring set up successfully");
} catch (error) {
this.logger.error("Failed to set up network monitoring:", error);
}
}
/**
* Add network metric
*/
private addNetworkMetric(metric: NetworkMetrics): void {
this.networkMetrics.push(metric);
// Keep only recent metrics
if (this.networkMetrics.length > 1000) {
this.networkMetrics = this.networkMetrics.slice(-1000);
}
this.emit('networkMetric', metric);
this.checkNetworkPerformance(metric);
}
async initialize(): Promise<void> {
this.logger.info("Initializing enhanced PerformanceMonitor");
try {
// Set up performance observers
await this.setupPerformanceObservers();
// Start baseline monitoring
await this.startBaselineMonitoring();
// Set up memory monitoring
await this.startMemoryMonitoring();
this.isMonitoring = true;
this.logger.info("Enhanced PerformanceMonitor initialized successfully");
} catch (error) {
this.logger.error("Failed to initialize PerformanceMonitor:", error);
throw error;
}
}
/**
* Set up performance observers for various metrics
*/
private async setupPerformanceObservers(): Promise<void> {
try {
// Observe navigation and resource timing
this.performanceObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
for (const entry of entries) {
this.processPerformanceEntry(entry);
}
});
this.performanceObserver.observe({
entryTypes: ['resource', 'measure', 'mark'] as any
});
// Observe garbage collection if available
try {
this.gcObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
for (const entry of entries) {
this.processGCEntry(entry);
}
});
this.gcObserver.observe({ entryTypes: ['gc'] });
} catch (error) {
this.logger.warn("GC performance observation not available:", error);
}
} catch (error) {
this.logger.error("Failed to setup performance observers:", error);
throw error;
}
}
/**
* Process performance entries from observers
*/
private processPerformanceEntry(entry: PerformanceEntry): void {
try {
if (entry.entryType === 'navigation') {
this.processNavigationEntry(entry as PerformanceNavigationTiming);
} else if (entry.entryType === 'resource') {
this.processResourceEntry(entry as PerformanceResourceTiming);
} else if (entry.entryType === 'measure') {
this.processMeasureEntry(entry);
}
} catch (error) {
this.logger.error("Failed to process performance entry:", error);
}
}
/**
* Process navigation timing entries
*/
private processNavigationEntry(entry: PerformanceNavigationTiming): void {
const metrics: PerformanceMetrics = {
timestamp: new Date(),
browser: {
memoryUsage: 0, // Will be updated by memory monitoring
cpuUsage: 0, // Will be updated by CPU monitoring
networkRequests: 1,
renderTime: entry.loadEventEnd - entry.loadEventStart
},
build: {
bundleSize: 0,
buildTime: 0,
chunkCount: 0
},
runtime: {
componentRenders: 0,
hookUpdates: 0,
stateChanges: 0
}
};
this.addMetrics(metrics);
this.emit('navigationTiming', entry);
}
/**
* Process resource timing entries
*/
private processResourceEntry(entry: PerformanceResourceTiming): void {
const networkMetric: NetworkMetrics = {
requestId: `${entry.name}_${entry.startTime}`,
url: entry.name,
method: 'GET', // Default, would need CDP for actual method
startTime: entry.startTime,
endTime: entry.responseEnd,
duration: entry.duration,
responseSize: entry.transferSize || 0,
requestSize: 0, // Would need CDP for request size
statusCode: 200, // Default, would need CDP for actual status
timing: {
dnsLookup: entry.domainLookupEnd - entry.domainLookupStart,
tcpConnect: entry.connectEnd - entry.connectStart,
tlsHandshake: entry.secureConnectionStart > 0 ?
entry.connectEnd - entry.secureConnectionStart : 0,
requestSent: entry.requestStart - entry.connectEnd,
waiting: entry.responseStart - entry.requestStart,
contentDownload: entry.responseEnd - entry.responseStart
}
};
this.networkMetrics.push(networkMetric);
this.emit('networkMetric', networkMetric);
// Check for slow network requests
this.checkNetworkPerformance(networkMetric);
}
/**
* Process measure entries (custom performance marks)
*/
private processMeasureEntry(entry: PerformanceEntry): void {
this.emit('performanceMeasure', {
name: entry.name,
duration: entry.duration,
startTime: entry.startTime,
timestamp: new Date()
});
}
/**
* Process garbage collection entries
*/
private processGCEntry(entry: PerformanceEntry): void {
this.emit('garbageCollection', {
kind: (entry as any).kind,
duration: entry.duration,
timestamp: new Date()
});
}
/**
* Start baseline performance monitoring
*/
private async startBaselineMonitoring(): Promise<void> {
// Collect initial baseline metrics
this.baselineMetrics = await this.collectCurrentMetrics();
// Set up periodic monitoring
this.monitoringInterval = setInterval(async () => {
try {
const currentMetrics = await this.collectCurrentMetrics();
this.addMetrics(currentMetrics);
this.checkPerformanceAlerts(currentMetrics);
} catch (error) {
this.logger.error("Error during baseline monitoring:", error);
}
}, 5000); // Every 5 seconds
}
/**
* Start memory monitoring
*/
private async startMemoryMonitoring(): Promise<void> {
this.memoryMonitoringInterval = setInterval(async () => {
try {
const snapshot = await this.takeMemorySnapshot();
this.memorySnapshots.push(snapshot);
// Keep only last 100 snapshots
if (this.memorySnapshots.length > 100) {
this.memorySnapshots = this.memorySnapshots.slice(-100);
}
this.emit('memorySnapshot', snapshot);
this.checkMemoryLeaks(snapshot);
} catch (error) {
this.logger.error("Error during memory monitoring:", error);
}
}, 10000); // Every 10 seconds
}
/**
* Collect current performance metrics
*/
private async collectCurrentMetrics(): Promise<PerformanceMetrics> {
const memoryUsage = process.memoryUsage();
const cpuUsage = process.cpuUsage();
return {
timestamp: new Date(),
browser: {
memoryUsage: memoryUsage.heapUsed,
cpuUsage: (cpuUsage.user + cpuUsage.system) / 1000000, // Convert to ms
networkRequests: this.networkMetrics.length,
renderTime: this.getAverageRenderTime()
},
build: {
bundleSize: 0, // Would need build system integration
buildTime: 0,
chunkCount: 0
},
runtime: {
componentRenders: this.renderMetrics.length,
hookUpdates: 0, // Would need React DevTools integration
stateChanges: 0
}
};
}
/**
* Take a memory snapshot
*/
private async takeMemorySnapshot(): Promise<MemorySnapshot> {
const memoryUsage = process.memoryUsage();
let heapUsage = null;
// Try to get browser heap usage if Chrome debugger is available
if (this.chromeDebugger) {
try {
heapUsage = await this.chromeDebugger.getHeapUsage();
} catch (error) {
this.logger.debug("Could not get browser heap usage:", error);
}
}
return {
id: `snapshot_${Date.now()}`,
timestamp: Date.now(),
heapUsed: heapUsage?.usedSize || memoryUsage.heapUsed,
heapTotal: heapUsage?.totalSize || memoryUsage.heapTotal,
external: memoryUsage.external,
arrayBuffers: memoryUsage.arrayBuffers,
rss: memoryUsage.rss,
gcStats: await this.getGCStats()
};
}
/**
* Get garbage collection statistics
*/
private async getGCStats(): Promise<MemorySnapshot['gcStats']> {
try {
// This would require more sophisticated GC monitoring
// For now, return basic stats
return {
totalGCTime: 0,
totalGCCount: 0,
majorGCCount: 0,
minorGCCount: 0
};
} catch (error) {
this.logger.debug("Could not get GC stats:", error);
return undefined;
}
}
/**
* Take detailed heap snapshot using Chrome DevTools
*/
async takeDetailedHeapSnapshot(): Promise<any> {
if (!this.chromeDebugger) {
throw new Error("Chrome debugger not available for detailed heap snapshots");
}
try {
const snapshot = await this.chromeDebugger.takeHeapSnapshot();
this.logger.info("Detailed heap snapshot taken");
return snapshot;
} catch (error) {
this.logger.error("Failed to take detailed heap snapshot:", error);
throw error;
}
}
/**
* Start heap sampling for memory profiling
*/
async startHeapSampling(samplingInterval: number = 32768): Promise<void> {
if (!this.chromeDebugger) {
throw new Error("Chrome debugger not available for heap sampling");
}
try {
await this.chromeDebugger.startHeapSampling(samplingInterval);
this.logger.info(`Heap sampling started with interval: ${samplingInterval}`);
} catch (error) {
this.logger.error("Failed to start heap sampling:", error);
throw error;
}
}
/**
* Stop heap sampling and get profile
*/
async stopHeapSampling(): Promise<any> {
if (!this.chromeDebugger) {
throw new Error("Chrome debugger not available for heap sampling");
}
try {
const profile = await this.chromeDebugger.stopHeapSampling();
this.logger.info("Heap sampling stopped and profile collected");
return profile;
} catch (error) {
this.logger.error("Failed to stop heap sampling:", error);
throw error;
}
}
/**
* Force garbage collection
*/
async forceGarbageCollection(): Promise<void> {
if (!this.chromeDebugger) {
this.logger.warn("Chrome debugger not available for garbage collection");
return;
}
try {
await this.chromeDebugger.collectGarbage();
this.logger.info("Garbage collection forced");
} catch (error) {
this.logger.error("Failed to force garbage collection:", error);
throw error;
}
}
/**
* Analyze network performance patterns
*/
analyzeNetworkPerformance(timeframe: number = 300000): {
totalRequests: number;
averageResponseTime: number;
slowRequests: NetworkMetrics[];
failedRequests: number;
largestRequests: NetworkMetrics[];
domainBreakdown: Record<string, number>;
recommendations: string[];
} {
const cutoff = Date.now() - timeframe;
const recentMetrics = this.networkMetrics.filter(m => m.startTime >= cutoff);
if (recentMetrics.length === 0) {
return {
totalRequests: 0,
averageResponseTime: 0,
slowRequests: [],
failedRequests: 0,
largestRequests: [],
domainBreakdown: {},
recommendations: []
};
}
const totalRequests = recentMetrics.length;
const averageResponseTime = recentMetrics.reduce((sum, m) => sum + m.duration, 0) / totalRequests;
const slowRequests = recentMetrics
.filter(m => m.duration > 2000) // Requests slower than 2 seconds
.sort((a, b) => b.duration - a.duration)
.slice(0, 10);
const failedRequests = recentMetrics.filter(m => m.statusCode >= 400).length;
const largestRequests = recentMetrics
.sort((a, b) => b.responseSize - a.responseSize)
.slice(0, 10);
// Domain breakdown
const domainBreakdown: Record<string, number> = {};
recentMetrics.forEach(metric => {
try {
const domain = new URL(metric.url).hostname;
domainBreakdown[domain] = (domainBreakdown[domain] || 0) + 1;
} catch (error) {
// Invalid URL, skip
}
});
// Generate recommendations
const recommendations: string[] = [];
if (slowRequests.length > 0) {
recommendations.push(`${slowRequests.length} slow requests detected. Consider optimizing API endpoints.`);
}
if (failedRequests > totalRequests * 0.05) {
recommendations.push(`High failure rate: ${((failedRequests / totalRequests) * 100).toFixed(1)}%. Check API reliability.`);
}
if (largestRequests.length > 0 && largestRequests[0] && largestRequests[0].responseSize > 1000000) {
recommendations.push("Large response sizes detected. Consider implementing pagination or data compression.");
}
return {
totalRequests,
averageResponseTime,
slowRequests,
failedRequests,
largestRequests,
domainBreakdown,
recommendations
};
}
/**
* Clear browser cache
*/
async clearBrowserCache(): Promise<void> {
if (!this.chromeDebugger) {
throw new Error("Chrome debugger not available for cache operations");
}
try {
await this.chromeDebugger.clearBrowserCache();
this.logger.info("Browser cache cleared");
} catch (error) {
this.logger.error("Failed to clear browser cache:", error);
throw error;
}
}
/**
* Clear browser cookies
*/
async clearBrowserCookies(): Promise<void> {
if (!this.chromeDebugger) {
throw new Error("Chrome debugger not available for cookie operations");
}
try {
await this.chromeDebugger.clearBrowserCookies();
this.logger.info("Browser cookies cleared");
} catch (error) {
this.logger.error("Failed to clear browser cookies:", error);
throw error;
}
}
/**
* Set network throttling for testing
*/
async setNetworkThrottling(options: {
offline?: boolean;
downloadThroughput?: number;
uploadThroughput?: number;
latency?: number;
}): Promise<void> {
if (!this.chromeDebugger) {
throw new Error("Chrome debugger not available for network throttling");
}
try {
await this.chromeDebugger.setNetworkThrottling(options);
this.logger.info("Network throttling configured", options);
} catch (error) {
this.logger.error("Failed to set network throttling:", error);
throw error;
}
}
/**
* Analyze render performance patterns
*/
analyzeRenderPerformance(timeframe: number = 300000): {
totalRenders: number;
averageRenderTime: number;
slowRenders: RenderMetrics[];
rendersByComponent: Record<string, number>;
renderReasons: Record<string, number>;
recommendations: string[];
performanceScore: number;
} {
const cutoff = Date.now() - timeframe;
const recentMetrics = this.renderMetrics.filter(m => m.timestamp >= cutoff);
if (recentMetrics.length === 0) {
return {
totalRenders: 0,
averageRenderTime: 0,
slowRenders: [],
rendersByComponent: {},
renderReasons: {},
recommendations: [],
performanceScore: 100
};
}
const totalRenders = recentMetrics.length;
const averageRenderTime = recentMetrics.reduce((sum, m) => sum + m.renderTime, 0) / totalRenders;
const slowRenders = recentMetrics
.filter(m => m.renderTime > 16) // Slower than 60fps
.sort((a, b) => b.renderTime - a.renderTime)
.slice(0, 10);
// Group by component
const rendersByComponent: Record<string, number> = {};
recentMetrics.forEach(metric => {
rendersByComponent[metric.componentName] = (rendersByComponent[metric.componentName] || 0) + 1;
});
// Group by reason (props, state, context changes)
const renderReasons: Record<string, number> = {};
recentMetrics.forEach(metric => {
const reason = metric.propsChanged ? 'props' :
metric.stateChanged ? 'state' :
metric.contextChanged ? 'context' : 'unknown';
renderReasons[reason] = (renderReasons[reason] || 0) + 1;
});
// Generate recommendations
const recommendations: string[] = [];
if (slowRenders.length > 0) {
recommendations.push(`${slowRenders.length} slow renders detected. Consider optimizing heavy components.`);
}
if (averageRenderTime > 8) {
recommendations.push(`Average render time is ${averageRenderTime.toFixed(2)}ms. Target < 8ms for smooth performance.`);
}
const unnecessaryRerenders = recentMetrics.filter(m => !m.propsChanged && !m.stateChanged && !m.contextChanged);
if (unnecessaryRerenders.length > totalRenders * 0.2) {
recommendations.push(`${unnecessaryRerenders.length} unnecessary re-renders detected. Consider React.memo or useMemo.`);
}
// Calculate performance score (0-100)
let score = 100;
score -= Math.min(slowRenders.length * 5, 30); // -5 per slow render, max -30
score -= Math.min((averageRenderTime - 4) * 2, 20); // -2 per ms over 4ms, max -20
score -= Math.min(unnecessaryRerenders.length / totalRenders * 50, 30); // Up to -30 for unnecessary renders
const performanceScore = Math.max(score, 0);
return {
totalRenders,
averageRenderTime,
slowRenders,
rendersByComponent,
renderReasons,
recommendations,
performanceScore
};
}
/**
* Get detailed render metrics from component manager
*/
getDetailedRenderMetrics(): any {
if (!this.componentManager) {
return {
totalRenders: 0,
averageRenderTime: 0,
heavyComponents: [],
recentRenders: [],
slowRenders: [],
renderTrends: []
};
}
try {
return this.componentManager.getRenderMetrics();
} catch (error) {
this.logger.error("Failed to get detailed render metrics:", error);
return {
totalRenders: 0,
averageRenderTime: 0,
heavyComponents: [],
recentRenders: [],
slowRenders: [],
renderTrends: []
};
}
}
/**
* Start CPU profiling
*/
async startCPUProfiling(title: string = `profile_${Date.now()}`): Promise<string> {
if (!v8Profiler) {
throw new Error("v8-profiler-next not available. CPU profiling disabled.");
}
try {
const profileId = `cpu_${Date.now()}`;
v8Profiler.startProfiling(profileId, true);
this.logger.info(`Started CPU profiling: ${profileId}`);
return profileId;
} catch (error) {
this.logger.error("Failed to start CPU profiling:", error);
throw error;
}
}
/**
* Stop CPU profiling and generate flame graph
*/
async stopCPUProfiling(profileId: string): Promise<CPUProfile> {
if (!v8Profiler) {
throw new Error("v8-profiler-next not available. CPU profiling disabled.");
}
try {
const profile = v8Profiler.stopProfiling(profileId);
const endTime = Date.now();
const cpuProfile: CPUProfile = {
id: profileId,
title: profileId,
startTime: endTime - (profile.getDuration() / 1000),
endTime,
duration: profile.getDuration() / 1000, // Convert to ms
profile: profile.export(),
flameGraph: this.generateFlameGraph(profile.export())
};
this.cpuProfiles.set(profileId, cpuProfile);
this.emit('cpuProfile', cpuProfile);
// Clean up
profile.delete();
this.logger.info(`Stopped CPU profiling: ${profileId}, duration: ${cpuProfile.duration}ms`);
return cpuProfile;
} catch (error) {
this.logger.error("Failed to stop CPU profiling:", error);
throw error;
}
}
/**
* Generate flame graph data from V8 CPU profile
*/
private generateFlameGraph(profile: any): FlameGraphNode[] {
try {
const nodes: FlameGraphNode[] = [];
if (profile.head) {
nodes.push(this.convertProfileNodeToFlameGraph(profile.head));
}
return nodes;
} catch (error) {
this.logger.error("Failed to generate flame graph:", error);
return [];
}
}
/**
* Convert V8 profile node to flame graph node
*/
private convertProfileNodeToFlameGraph(node: any): FlameGraphNode {
const flameNode: FlameGraphNode = {
name: node.functionName || '(anonymous)',
value: node.hitCount || 0,
selfTime: node.selfTime || 0,
totalTime: node.totalTime || 0,
hitCount: node.hitCount || 0,
children: []
};
// Add location information if available
if (node.callUID && node.url) {
flameNode.url = node.url;
flameNode.lineNumber = node.lineNumber;
flameNode.columnNumber = node.columnNumber;
}
// Process children recursively
if (node.children && node.children.length > 0) {
flameNode.children = node.children.map((child: any) =>
this.convertProfileNodeToFlameGraph(child)
);
}
return flameNode;
}
/**
* Get average render time from recent metrics
*/
private getAverageRenderTime(): number {
if (this.renderMetrics.length === 0) return 0;
const recentMetrics = this.renderMetrics.slice(-20); // Last 20 renders
const totalTime = recentMetrics.reduce((sum, metric) => sum + metric.renderTime, 0);
return totalTime / recentMetrics.length;
}
/**
* Check for performance alerts
*/
private checkPerformanceAlerts(metrics: PerformanceMetrics): void {
const config = this.configManager.getConfig().performance;
// Check CPU usage
if (metrics.browser?.cpuUsage && metrics.browser.cpuUsage > 80) {
this.createAlert('cpu', 'high', 'High CPU usage detected', {
currentUsage: metrics.browser.cpuUsage,
threshold: 80
});
}
// Check memory usage
if (metrics.browser?.memoryUsage && metrics.browser.memoryUsage > config.memoryLeakThreshold) {
this.createAlert('memory', 'high', 'High memory usage detected', {
currentUsage: metrics.browser.memoryUsage,
threshold: config.memoryLeakThreshold
});
}
// Check render time
if (metrics.browser?.renderTime && metrics.browser.renderTime > config.renderTimeThreshold) {
this.createAlert('render', 'medium', 'Slow render time detected', {
currentTime: metrics.browser.renderTime,
threshold: config.renderTimeThreshold
});
}
}
/**
* Check network performance
*/
private checkNetworkPerformance(metric: NetworkMetrics): void {
const config = this.configManager.getConfig().performance;
if (metric.duration > config.networkTimeoutThreshold) {
this.createAlert('network', 'medium', 'Slow network request detected', {
url: metric.url,
duration: metric.duration,
threshold: config.networkTimeoutThreshold
});
}
}
/**
* Check for memory leaks
*/
private checkMemoryLeaks(snapshot: MemorySnapshot): void {
if (this.memorySnapshots.length < 10) return; // Need enough data
const recent = this.memorySnapshots.slice(-10);
const trend = this.calculateMemoryTrend(recent);
if (trend.slope > 1000000) { // 1MB per snapshot
this.createAlert('memory', 'high', 'Potential memory leak detected', {
trend: trend.slope,
recentGrowth: recent.length > 0 ?
(recent[recent.length - 1]?.heapUsed || 0) - (recent[0]?.heapUsed || 0) : 0
});
}
}
/**
* Calculate memory usage trend
*/
private calculateMemoryTrend(snapshots: MemorySnapshot[]): { slope: number; correlation: number } {
if (snapshots.length < 2) return { slope: 0, correlation: 0 };
const n = snapshots.length;
const x = snapshots.map((_, i) => i);
const y = snapshots.map(s => s.heapUsed);
const sumX = x.reduce((a, b) => a + b, 0);
const sumY = y.reduce((a, b) => a + b, 0);
const sumXY = x.reduce((sum, xi, i) => sum + xi * (y[i] || 0), 0);
const sumXX = x.reduce((sum, xi) => sum + xi * xi, 0);
const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
return { slope, correlation: 0 }; // Simplified for now
}
/**
* Create performance alert
*/
private createAlert(
type: PerformanceAlert['type'],
severity: PerformanceAlert['severity'],
message: string,
details: any
): void {
const alert: PerformanceAlert = {
id: `alert_${Date.now()}`,
type,
severity,
message,
details,
timestamp: Date.now(),
resolved: false
};
this.alerts.push(alert);
this.emit('performanceAlert', alert);
this.logger.warn(`Performance alert: ${message}`, details);
}
/**
* Add render metrics
*/
addRenderMetric(metric: RenderMetrics): void {
this.renderMetrics.push(metric);
// Keep only recent metrics
if (this.renderMetrics.length > 1000) {
this.renderMetrics = this.renderMetrics.slice(-1000);
}
this.emit('renderMetric', metric);
}
/**
* Add performance metrics
*/
private addMetrics(metrics: PerformanceMetrics): void {
this.metrics.push(metrics);
// Keep only recent metrics
if (this.metrics.length > 1000) {
this.metrics = this.metrics.slice(-1000);
}
this.emit('metrics', metrics);
}
/**
* Get performance metrics with filtering options
*/
async getMetrics(options: {
timeframe?: string;
type?: 'cpu' | 'memory' | 'network' | 'render';
limit?: number;
} = {}): Promise<PerformanceMetrics[]> {
let filteredMetrics = [...this.metrics];
// Apply time filtering
if (options.timeframe) {
const cutoff = this.getTimeframeCutoff(options.timeframe);
filteredMetrics = filteredMetrics.filter(m => m.timestamp >= cutoff);
}
// Apply limit
if (options.limit) {
filteredMetrics = filteredMetrics.slice(-options.limit);
}
return filteredMetrics;
}
/**
* Get CPU profiles
*/
getCPUProfiles(): CPUProfile[] {
return Array.from(this.cpuProfiles.values());
}
/**
* Get memory snapshots
*/
getMemorySnapshots(limit?: number): MemorySnapshot[] {
return limit ? this.memorySnapshots.slice(-limit) : [...this.memorySnapshots];
}
/**
* Get network metrics
*/
getNetworkMetrics(limit?: number): NetworkMetrics[] {
return limit ? this.networkMetrics.slice(-limit) : [...this.networkMetrics];
}
/**
* Get render metrics
*/
getRenderMetrics(limit?: number): RenderMetrics[] {
return limit ? this.renderMetrics.slice(-limit) : [...this.renderMetrics];
}
/**
* Get performance alerts
*/
getAlerts(resolved?: boolean): PerformanceAlert[] {
if (resolved !== undefined) {
return this.alerts.filter(a => a.resolved === resolved);
}
return [...this.alerts];
}
/**
* Resolve performance alert
*/
resolveAlert(alertId: string): boolean {
const alert = this.alerts.find(a => a.id === alertId);
if (alert) {
alert.resolved = true;
this.emit('alertResolved', alert);
return true;
}
return false;
}
/**
* Get timeframe cutoff date
*/
private getTimeframeCutoff(timeframe: string): Date {
const now = new Date();
switch (timeframe) {
case '1h': return new Date(now.getTime() - 60 * 60 * 1000);
case '24h': return new Date(now.getTime() - 24 * 60 * 60 * 1000);
case '7d': return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
default: return new Date(0);
}
}
/**
* Shutdown performance monitoring
*/
async shutdown(): Promise<void> {
this.isMonitoring = false;
// Clear intervals
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
}
if (this.memoryMonitoringInterval) {
clearInterval(this.memoryMonitoringInterval);
}
// Disconnect observers
if (this.performanceObserver) {
this.performanceObserver.disconnect();
}
if (this.gcObserver) {
this.gcObserver.disconnect();
}
this.logger.info("Enhanced PerformanceMonitor shutdown complete");
}
}