Some checks are pending
HVAC Plugin CI/CD Pipeline / Security Analysis (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Unit Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Integration Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Notification (push) Blocked by required conditions
Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Waiting to run
Security Monitoring & Compliance / Secrets & Credential Scan (push) Waiting to run
Security Monitoring & Compliance / WordPress Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Static Code Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Security Compliance Validation (push) Waiting to run
Security Monitoring & Compliance / Security Summary Report (push) Blocked by required conditions
Security Monitoring & Compliance / Security Team Notification (push) Blocked by required conditions
- Add 90+ test files including E2E, unit, and integration tests - Implement Page Object Model (POM) architecture - Add Docker testing environment with comprehensive services - Include modernized test framework with error recovery - Add specialized test suites for master trainer and trainer workflows - Update .gitignore to properly track test infrastructure 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
828 lines
No EOL
30 KiB
JavaScript
828 lines
No EOL
30 KiB
JavaScript
/**
|
|
* Performance Monitor - Phase 3 Integration
|
|
*
|
|
* Comprehensive performance monitoring system for E2E test integration validation.
|
|
* Tracks resource utilization, concurrent load testing, performance benchmarking,
|
|
* and optimization recommendations across all agent implementations.
|
|
*
|
|
* Features:
|
|
* - Real-time resource monitoring (memory, CPU, network)
|
|
* - Concurrent user simulation and load testing
|
|
* - Performance benchmarking against targets
|
|
* - Browser resource utilization tracking
|
|
* - Network request analysis
|
|
* - Page load time optimization
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @version 3.0.0
|
|
* @created 2025-08-27
|
|
*/
|
|
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
const { performance, PerformanceObserver } = require('perf_hooks');
|
|
|
|
// Performance targets and thresholds
|
|
const PERFORMANCE_TARGETS = {
|
|
totalExecutionTime: 30 * 60 * 1000, // 30 minutes
|
|
individualPageLoad: 3000, // 3 seconds
|
|
formSubmission: 5000, // 5 seconds
|
|
authentication: 2000, // 2 seconds
|
|
crossAgentWorkflow: 30000, // 30 seconds
|
|
concurrentUserLimit: 5,
|
|
networkRequestLimit: 100, // per page
|
|
memoryThreshold: 512 * 1024 * 1024, // 512MB
|
|
cpuThreshold: 80, // 80% CPU usage
|
|
errorRate: 0.05 // 5% error rate threshold
|
|
};
|
|
|
|
class PerformanceMonitor {
|
|
constructor() {
|
|
this.isMonitoring = false;
|
|
this.metrics = {
|
|
startTime: null,
|
|
endTime: null,
|
|
memoryBaseline: null,
|
|
cpuBaseline: null,
|
|
networkRequests: [],
|
|
pageLoadTimes: [],
|
|
agentPerformance: {},
|
|
concurrentSessions: [],
|
|
errors: [],
|
|
browserResources: {},
|
|
optimizationRecommendations: []
|
|
};
|
|
this.performanceObserver = null;
|
|
this.browserPage = null;
|
|
}
|
|
|
|
/**
|
|
* Initialize performance monitoring
|
|
*/
|
|
async initialize(browserPage = null) {
|
|
this.browserPage = browserPage;
|
|
this.metrics.startTime = Date.now();
|
|
this.metrics.memoryBaseline = process.memoryUsage();
|
|
|
|
// Set up performance observer for Node.js metrics
|
|
this.setupPerformanceObserver();
|
|
|
|
// Set up browser performance monitoring if page is provided
|
|
if (this.browserPage) {
|
|
await this.setupBrowserMonitoring();
|
|
}
|
|
|
|
this.isMonitoring = true;
|
|
console.log('Performance Monitor initialized');
|
|
console.log(`Memory baseline: ${this.formatBytes(this.metrics.memoryBaseline.heapUsed)}`);
|
|
}
|
|
|
|
/**
|
|
* Start monitoring a specific agent implementation
|
|
*/
|
|
async startAgentMonitoring(agentName) {
|
|
if (!this.isMonitoring) {
|
|
throw new Error('Performance monitor not initialized');
|
|
}
|
|
|
|
const agentStartTime = Date.now();
|
|
|
|
this.metrics.agentPerformance[agentName] = {
|
|
startTime: agentStartTime,
|
|
endTime: null,
|
|
duration: null,
|
|
memoryUsage: process.memoryUsage(),
|
|
pageLoads: [],
|
|
networkRequests: [],
|
|
errors: [],
|
|
resourceUtilization: await this.getBrowserResourceUsage()
|
|
};
|
|
|
|
console.log(`Started monitoring agent: ${agentName}`);
|
|
return agentStartTime;
|
|
}
|
|
|
|
/**
|
|
* Stop monitoring a specific agent implementation
|
|
*/
|
|
async stopAgentMonitoring(agentName) {
|
|
if (!this.metrics.agentPerformance[agentName]) {
|
|
console.warn(`Agent ${agentName} was not being monitored`);
|
|
return null;
|
|
}
|
|
|
|
const agentEndTime = Date.now();
|
|
const agentData = this.metrics.agentPerformance[agentName];
|
|
|
|
agentData.endTime = agentEndTime;
|
|
agentData.duration = agentEndTime - agentData.startTime;
|
|
agentData.finalMemoryUsage = process.memoryUsage();
|
|
agentData.finalResourceUtilization = await this.getBrowserResourceUsage();
|
|
|
|
// Calculate agent-specific performance metrics
|
|
agentData.performanceRating = this.calculateAgentPerformanceRating(agentData);
|
|
|
|
console.log(`Stopped monitoring agent: ${agentName}`);
|
|
console.log(` Duration: ${(agentData.duration / 1000).toFixed(1)}s`);
|
|
console.log(` Performance Rating: ${agentData.performanceRating}`);
|
|
|
|
return agentData;
|
|
}
|
|
|
|
/**
|
|
* Record page load time
|
|
*/
|
|
recordPageLoad(url, loadTime, agentName = null) {
|
|
const pageLoadData = {
|
|
url,
|
|
loadTime,
|
|
timestamp: Date.now(),
|
|
agent: agentName,
|
|
exceededTarget: loadTime > PERFORMANCE_TARGETS.individualPageLoad
|
|
};
|
|
|
|
this.metrics.pageLoadTimes.push(pageLoadData);
|
|
|
|
// Add to agent-specific data if available
|
|
if (agentName && this.metrics.agentPerformance[agentName]) {
|
|
this.metrics.agentPerformance[agentName].pageLoads.push(pageLoadData);
|
|
}
|
|
|
|
if (pageLoadData.exceededTarget) {
|
|
console.warn(`Page load exceeded target: ${url} (${loadTime}ms > ${PERFORMANCE_TARGETS.individualPageLoad}ms)`);
|
|
}
|
|
|
|
return pageLoadData;
|
|
}
|
|
|
|
/**
|
|
* Record network request
|
|
*/
|
|
recordNetworkRequest(requestData, agentName = null) {
|
|
const networkData = {
|
|
...requestData,
|
|
timestamp: Date.now(),
|
|
agent: agentName
|
|
};
|
|
|
|
this.metrics.networkRequests.push(networkData);
|
|
|
|
// Add to agent-specific data if available
|
|
if (agentName && this.metrics.agentPerformance[agentName]) {
|
|
this.metrics.agentPerformance[agentName].networkRequests.push(networkData);
|
|
}
|
|
|
|
return networkData;
|
|
}
|
|
|
|
/**
|
|
* Record error
|
|
*/
|
|
recordError(error, context = '', agentName = null) {
|
|
const errorData = {
|
|
message: error.message || error,
|
|
stack: error.stack || null,
|
|
context,
|
|
timestamp: Date.now(),
|
|
agent: agentName
|
|
};
|
|
|
|
this.metrics.errors.push(errorData);
|
|
|
|
// Add to agent-specific data if available
|
|
if (agentName && this.metrics.agentPerformance[agentName]) {
|
|
this.metrics.agentPerformance[agentName].errors.push(errorData);
|
|
}
|
|
|
|
return errorData;
|
|
}
|
|
|
|
/**
|
|
* Execute concurrent load testing
|
|
*/
|
|
async executeConcurrentLoadTest(sessionConfigs) {
|
|
console.log(`Starting concurrent load test with ${sessionConfigs.length} sessions...`);
|
|
|
|
const concurrentPromises = sessionConfigs.map((config, index) =>
|
|
this.simulateUserSession(`session_${index}`, config)
|
|
);
|
|
|
|
const startTime = Date.now();
|
|
const results = await Promise.allSettled(concurrentPromises);
|
|
const endTime = Date.now();
|
|
|
|
const loadTestResults = {
|
|
totalSessions: sessionConfigs.length,
|
|
successfulSessions: results.filter(r => r.status === 'fulfilled').length,
|
|
failedSessions: results.filter(r => r.status === 'rejected').length,
|
|
totalDuration: endTime - startTime,
|
|
averageSessionDuration: 0,
|
|
concurrentPerformanceRating: 'unknown',
|
|
sessions: []
|
|
};
|
|
|
|
// Process individual session results
|
|
results.forEach((result, index) => {
|
|
const sessionId = `session_${index}`;
|
|
|
|
if (result.status === 'fulfilled') {
|
|
const sessionData = result.value;
|
|
loadTestResults.sessions.push(sessionData);
|
|
loadTestResults.averageSessionDuration += sessionData.duration;
|
|
} else {
|
|
loadTestResults.sessions.push({
|
|
sessionId,
|
|
status: 'failed',
|
|
error: result.reason.message || result.reason,
|
|
duration: 0
|
|
});
|
|
|
|
this.recordError(result.reason, `Concurrent session ${sessionId}`);
|
|
}
|
|
});
|
|
|
|
if (loadTestResults.successfulSessions > 0) {
|
|
loadTestResults.averageSessionDuration /= loadTestResults.successfulSessions;
|
|
}
|
|
|
|
// Calculate concurrent performance rating
|
|
const successRate = loadTestResults.successfulSessions / loadTestResults.totalSessions;
|
|
const avgDurationUnderTarget = loadTestResults.averageSessionDuration <= PERFORMANCE_TARGETS.crossAgentWorkflow;
|
|
|
|
if (successRate >= 0.9 && avgDurationUnderTarget) {
|
|
loadTestResults.concurrentPerformanceRating = 'EXCELLENT';
|
|
} else if (successRate >= 0.8 && avgDurationUnderTarget) {
|
|
loadTestResults.concurrentPerformanceRating = 'GOOD';
|
|
} else if (successRate >= 0.6) {
|
|
loadTestResults.concurrentPerformanceRating = 'ACCEPTABLE';
|
|
} else {
|
|
loadTestResults.concurrentPerformanceRating = 'POOR';
|
|
}
|
|
|
|
this.metrics.concurrentSessions.push(loadTestResults);
|
|
|
|
console.log(`Concurrent load test completed:`);
|
|
console.log(` Success rate: ${(successRate * 100).toFixed(1)}%`);
|
|
console.log(` Average duration: ${(loadTestResults.averageSessionDuration / 1000).toFixed(1)}s`);
|
|
console.log(` Performance rating: ${loadTestResults.concurrentPerformanceRating}`);
|
|
|
|
return loadTestResults;
|
|
}
|
|
|
|
/**
|
|
* Simulate individual user session
|
|
*/
|
|
async simulateUserSession(sessionId, config) {
|
|
const sessionStartTime = Date.now();
|
|
|
|
const sessionData = {
|
|
sessionId,
|
|
startTime: sessionStartTime,
|
|
endTime: null,
|
|
duration: null,
|
|
status: 'running',
|
|
config,
|
|
actions: [],
|
|
errors: []
|
|
};
|
|
|
|
try {
|
|
// Simulate user actions based on config
|
|
for (const action of config.actions) {
|
|
const actionStartTime = Date.now();
|
|
|
|
try {
|
|
await this.simulateUserAction(action, sessionData);
|
|
|
|
sessionData.actions.push({
|
|
action: action.type,
|
|
duration: Date.now() - actionStartTime,
|
|
status: 'completed'
|
|
});
|
|
} catch (actionError) {
|
|
sessionData.actions.push({
|
|
action: action.type,
|
|
duration: Date.now() - actionStartTime,
|
|
status: 'failed',
|
|
error: actionError.message
|
|
});
|
|
|
|
sessionData.errors.push(actionError);
|
|
}
|
|
}
|
|
|
|
sessionData.status = 'completed';
|
|
|
|
} catch (sessionError) {
|
|
sessionData.status = 'failed';
|
|
sessionData.errors.push(sessionError);
|
|
}
|
|
|
|
sessionData.endTime = Date.now();
|
|
sessionData.duration = sessionData.endTime - sessionData.startTime;
|
|
|
|
return sessionData;
|
|
}
|
|
|
|
/**
|
|
* Simulate individual user action
|
|
*/
|
|
async simulateUserAction(action, sessionData) {
|
|
// Mock implementation of user actions
|
|
switch (action.type) {
|
|
case 'login':
|
|
await this.wait(action.duration || 2000);
|
|
this.recordPageLoad('/login', action.duration || 2000, sessionData.sessionId);
|
|
break;
|
|
|
|
case 'navigate':
|
|
await this.wait(action.duration || 1500);
|
|
this.recordPageLoad(action.url || '/dashboard', action.duration || 1500, sessionData.sessionId);
|
|
break;
|
|
|
|
case 'form_submit':
|
|
await this.wait(action.duration || 3000);
|
|
this.recordPageLoad(action.url || '/form', action.duration || 3000, sessionData.sessionId);
|
|
break;
|
|
|
|
case 'create_event':
|
|
await this.wait(action.duration || 5000);
|
|
this.recordPageLoad('/create-event', action.duration || 5000, sessionData.sessionId);
|
|
break;
|
|
|
|
default:
|
|
await this.wait(1000); // Default action duration
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current performance snapshot
|
|
*/
|
|
async getPerformanceSnapshot() {
|
|
const currentTime = Date.now();
|
|
const currentMemory = process.memoryUsage();
|
|
const browserResources = await this.getBrowserResourceUsage();
|
|
|
|
return {
|
|
timestamp: currentTime,
|
|
elapsedTime: currentTime - this.metrics.startTime,
|
|
memory: {
|
|
current: currentMemory,
|
|
baseline: this.metrics.memoryBaseline,
|
|
delta: {
|
|
heapUsed: currentMemory.heapUsed - this.metrics.memoryBaseline.heapUsed,
|
|
heapTotal: currentMemory.heapTotal - this.metrics.memoryBaseline.heapTotal,
|
|
external: currentMemory.external - this.metrics.memoryBaseline.external
|
|
}
|
|
},
|
|
browserResources,
|
|
pageLoads: {
|
|
total: this.metrics.pageLoadTimes.length,
|
|
averageTime: this.calculateAveragePageLoadTime(),
|
|
exceedingTarget: this.metrics.pageLoadTimes.filter(p => p.exceededTarget).length
|
|
},
|
|
networkRequests: {
|
|
total: this.metrics.networkRequests.length,
|
|
averageResponseTime: this.calculateAverageNetworkResponseTime()
|
|
},
|
|
errors: {
|
|
total: this.metrics.errors.length,
|
|
errorRate: this.calculateErrorRate()
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate performance benchmarking report
|
|
*/
|
|
async generatePerformanceBenchmark() {
|
|
const benchmark = {
|
|
overall: {
|
|
totalExecutionTime: Date.now() - this.metrics.startTime,
|
|
targetExecutionTime: PERFORMANCE_TARGETS.totalExecutionTime,
|
|
performanceMet: false,
|
|
rating: 'UNKNOWN'
|
|
},
|
|
agents: {},
|
|
pageLoads: {
|
|
total: this.metrics.pageLoadTimes.length,
|
|
averageTime: this.calculateAveragePageLoadTime(),
|
|
target: PERFORMANCE_TARGETS.individualPageLoad,
|
|
exceedingTarget: this.metrics.pageLoadTimes.filter(p => p.exceededTarget).length,
|
|
rating: 'UNKNOWN'
|
|
},
|
|
memory: {
|
|
current: process.memoryUsage(),
|
|
baseline: this.metrics.memoryBaseline,
|
|
peakUsage: this.calculatePeakMemoryUsage(),
|
|
threshold: PERFORMANCE_TARGETS.memoryThreshold,
|
|
rating: 'UNKNOWN'
|
|
},
|
|
concurrency: {
|
|
maxConcurrentSessions: Math.max(...this.metrics.concurrentSessions.map(c => c.totalSessions), 0),
|
|
averageSuccessRate: this.calculateAverageConcurrentSuccessRate(),
|
|
target: PERFORMANCE_TARGETS.concurrentUserLimit,
|
|
rating: 'UNKNOWN'
|
|
},
|
|
recommendations: []
|
|
};
|
|
|
|
// Calculate overall performance rating
|
|
benchmark.overall.performanceMet = benchmark.overall.totalExecutionTime <= benchmark.overall.targetExecutionTime;
|
|
benchmark.overall.rating = benchmark.overall.performanceMet ? 'PASSED' : 'EXCEEDED';
|
|
|
|
// Calculate page load rating
|
|
const avgPageLoadUnderTarget = benchmark.pageLoads.averageTime <= benchmark.pageLoads.target;
|
|
const exceedingTargetRatio = benchmark.pageLoads.exceedingTarget / benchmark.pageLoads.total;
|
|
|
|
if (avgPageLoadUnderTarget && exceedingTargetRatio <= 0.1) {
|
|
benchmark.pageLoads.rating = 'EXCELLENT';
|
|
} else if (avgPageLoadUnderTarget && exceedingTargetRatio <= 0.2) {
|
|
benchmark.pageLoads.rating = 'GOOD';
|
|
} else if (exceedingTargetRatio <= 0.4) {
|
|
benchmark.pageLoads.rating = 'ACCEPTABLE';
|
|
} else {
|
|
benchmark.pageLoads.rating = 'POOR';
|
|
}
|
|
|
|
// Calculate memory rating
|
|
const currentMemoryUsage = benchmark.memory.current.heapUsed;
|
|
const memoryUnderThreshold = currentMemoryUsage <= benchmark.memory.threshold;
|
|
|
|
benchmark.memory.rating = memoryUnderThreshold ? 'GOOD' : 'EXCEEDED';
|
|
|
|
// Calculate concurrency rating
|
|
if (benchmark.concurrency.averageSuccessRate >= 0.9) {
|
|
benchmark.concurrency.rating = 'EXCELLENT';
|
|
} else if (benchmark.concurrency.averageSuccessRate >= 0.8) {
|
|
benchmark.concurrency.rating = 'GOOD';
|
|
} else if (benchmark.concurrency.averageSuccessRate >= 0.6) {
|
|
benchmark.concurrency.rating = 'ACCEPTABLE';
|
|
} else {
|
|
benchmark.concurrency.rating = 'POOR';
|
|
}
|
|
|
|
// Calculate agent-specific benchmarks
|
|
Object.keys(this.metrics.agentPerformance).forEach(agentName => {
|
|
const agentData = this.metrics.agentPerformance[agentName];
|
|
benchmark.agents[agentName] = {
|
|
duration: agentData.duration,
|
|
performanceRating: agentData.performanceRating,
|
|
pageLoads: agentData.pageLoads.length,
|
|
averagePageLoadTime: this.calculateAveragePageLoadTime(agentData.pageLoads),
|
|
errors: agentData.errors.length,
|
|
memoryDelta: agentData.finalMemoryUsage ?
|
|
agentData.finalMemoryUsage.heapUsed - agentData.memoryUsage.heapUsed : 0
|
|
};
|
|
});
|
|
|
|
// Generate optimization recommendations
|
|
benchmark.recommendations = this.generateOptimizationRecommendations(benchmark);
|
|
|
|
return benchmark;
|
|
}
|
|
|
|
/**
|
|
* Generate optimization recommendations
|
|
*/
|
|
generateOptimizationRecommendations(benchmark) {
|
|
const recommendations = [];
|
|
|
|
// Overall execution time recommendations
|
|
if (!benchmark.overall.performanceMet) {
|
|
recommendations.push({
|
|
category: 'execution_time',
|
|
priority: 'high',
|
|
message: `Total execution time (${(benchmark.overall.totalExecutionTime / 60000).toFixed(1)}m) exceeds target (${(benchmark.overall.targetExecutionTime / 60000).toFixed(1)}m)`,
|
|
suggestions: [
|
|
'Implement more parallel test execution',
|
|
'Optimize page load waiting strategies',
|
|
'Reduce test data setup time',
|
|
'Consider test suite partitioning'
|
|
]
|
|
});
|
|
}
|
|
|
|
// Page load recommendations
|
|
if (benchmark.pageLoads.rating === 'POOR' || benchmark.pageLoads.rating === 'ACCEPTABLE') {
|
|
recommendations.push({
|
|
category: 'page_loads',
|
|
priority: 'medium',
|
|
message: `Average page load time (${benchmark.pageLoads.averageTime.toFixed(0)}ms) needs optimization`,
|
|
suggestions: [
|
|
'Implement page pre-loading strategies',
|
|
'Use more specific element waiting',
|
|
'Optimize CSS and JavaScript loading',
|
|
'Consider headless browser optimization'
|
|
]
|
|
});
|
|
}
|
|
|
|
// Memory recommendations
|
|
if (benchmark.memory.rating === 'EXCEEDED') {
|
|
recommendations.push({
|
|
category: 'memory',
|
|
priority: 'medium',
|
|
message: `Memory usage (${this.formatBytes(benchmark.memory.current.heapUsed)}) exceeds threshold`,
|
|
suggestions: [
|
|
'Implement browser instance cleanup',
|
|
'Reduce test data retention',
|
|
'Optimize page object lifecycle management',
|
|
'Consider garbage collection tuning'
|
|
]
|
|
});
|
|
}
|
|
|
|
// Concurrency recommendations
|
|
if (benchmark.concurrency.rating === 'POOR' || benchmark.concurrency.rating === 'ACCEPTABLE') {
|
|
recommendations.push({
|
|
category: 'concurrency',
|
|
priority: 'medium',
|
|
message: `Concurrent session success rate (${(benchmark.concurrency.averageSuccessRate * 100).toFixed(1)}%) needs improvement`,
|
|
suggestions: [
|
|
'Implement better resource isolation',
|
|
'Add retry mechanisms for failed sessions',
|
|
'Optimize database connection handling',
|
|
'Consider staggered session startup'
|
|
]
|
|
});
|
|
}
|
|
|
|
// Agent-specific recommendations
|
|
Object.keys(benchmark.agents).forEach(agentName => {
|
|
const agentData = benchmark.agents[agentName];
|
|
|
|
if (agentData.performanceRating === 'POOR') {
|
|
recommendations.push({
|
|
category: 'agent_specific',
|
|
priority: 'medium',
|
|
message: `Agent ${agentName} performance needs optimization`,
|
|
suggestions: [
|
|
`Optimize ${agentName} test execution flow`,
|
|
'Review page object efficiency',
|
|
'Consider agent-specific parallelization',
|
|
'Implement smart waiting strategies'
|
|
]
|
|
});
|
|
}
|
|
});
|
|
|
|
return recommendations;
|
|
}
|
|
|
|
/**
|
|
* Export performance report
|
|
*/
|
|
async exportPerformanceReport(outputPath) {
|
|
const benchmark = await this.generatePerformanceBenchmark();
|
|
const snapshot = await this.getPerformanceSnapshot();
|
|
|
|
const report = {
|
|
metadata: {
|
|
generatedAt: new Date().toISOString(),
|
|
testDuration: Date.now() - this.metrics.startTime,
|
|
environment: {
|
|
nodeVersion: process.version,
|
|
platform: process.platform,
|
|
arch: process.arch
|
|
}
|
|
},
|
|
summary: {
|
|
overallRating: benchmark.overall.rating,
|
|
pageLoadRating: benchmark.pageLoads.rating,
|
|
memoryRating: benchmark.memory.rating,
|
|
concurrencyRating: benchmark.concurrency.rating,
|
|
totalErrors: this.metrics.errors.length,
|
|
totalPageLoads: this.metrics.pageLoadTimes.length,
|
|
totalNetworkRequests: this.metrics.networkRequests.length
|
|
},
|
|
benchmark,
|
|
snapshot,
|
|
rawMetrics: {
|
|
agentPerformance: this.metrics.agentPerformance,
|
|
pageLoadTimes: this.metrics.pageLoadTimes,
|
|
networkRequests: this.metrics.networkRequests.length, // Don't include full request data
|
|
errors: this.metrics.errors,
|
|
concurrentSessions: this.metrics.concurrentSessions
|
|
},
|
|
recommendations: benchmark.recommendations
|
|
};
|
|
|
|
await fs.writeFile(outputPath, JSON.stringify(report, null, 2));
|
|
console.log(`Performance report exported to: ${outputPath}`);
|
|
|
|
return report;
|
|
}
|
|
|
|
// =================
|
|
// Helper Methods
|
|
// =================
|
|
|
|
/**
|
|
* Setup performance observer for Node.js metrics
|
|
*/
|
|
setupPerformanceObserver() {
|
|
this.performanceObserver = new PerformanceObserver((list) => {
|
|
const entries = list.getEntries();
|
|
entries.forEach(entry => {
|
|
if (entry.entryType === 'measure') {
|
|
// Record custom performance measurements
|
|
console.log(`Performance measurement: ${entry.name} - ${entry.duration.toFixed(2)}ms`);
|
|
}
|
|
});
|
|
});
|
|
|
|
this.performanceObserver.observe({ entryTypes: ['measure', 'navigation', 'resource'] });
|
|
}
|
|
|
|
/**
|
|
* Setup browser performance monitoring
|
|
*/
|
|
async setupBrowserMonitoring() {
|
|
if (!this.browserPage) return;
|
|
|
|
try {
|
|
// Listen for network requests
|
|
this.browserPage.on('request', (request) => {
|
|
this.recordNetworkRequest({
|
|
url: request.url(),
|
|
method: request.method(),
|
|
resourceType: request.resourceType()
|
|
});
|
|
});
|
|
|
|
// Listen for console errors
|
|
this.browserPage.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
this.recordError(new Error(msg.text()), 'Browser console error');
|
|
}
|
|
});
|
|
|
|
// Listen for page errors
|
|
this.browserPage.on('pageerror', (error) => {
|
|
this.recordError(error, 'Page error');
|
|
});
|
|
|
|
console.log('Browser performance monitoring setup complete');
|
|
} catch (error) {
|
|
console.warn('Failed to setup browser monitoring:', error.message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get browser resource usage
|
|
*/
|
|
async getBrowserResourceUsage() {
|
|
if (!this.browserPage) {
|
|
return { jsHeapSize: 0, totalJSHeapSize: 0, usedJSHeapSize: 0 };
|
|
}
|
|
|
|
try {
|
|
const metrics = await this.browserPage.evaluate(() => {
|
|
if (performance.memory) {
|
|
return {
|
|
jsHeapSize: performance.memory.jsHeapSizeLimit,
|
|
totalJSHeapSize: performance.memory.totalJSHeapSize,
|
|
usedJSHeapSize: performance.memory.usedJSHeapSize
|
|
};
|
|
}
|
|
return { jsHeapSize: 0, totalJSHeapSize: 0, usedJSHeapSize: 0 };
|
|
});
|
|
|
|
return metrics;
|
|
} catch (error) {
|
|
console.warn('Failed to get browser resource usage:', error.message);
|
|
return { jsHeapSize: 0, totalJSHeapSize: 0, usedJSHeapSize: 0 };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate agent performance rating
|
|
*/
|
|
calculateAgentPerformanceRating(agentData) {
|
|
const duration = agentData.duration;
|
|
const errorCount = agentData.errors.length;
|
|
const pageLoadCount = agentData.pageLoads.length;
|
|
const averagePageLoadTime = pageLoadCount > 0 ?
|
|
agentData.pageLoads.reduce((sum, p) => sum + p.loadTime, 0) / pageLoadCount : 0;
|
|
|
|
// Simple rating algorithm
|
|
let score = 100;
|
|
|
|
// Duration penalty (assuming 5 minutes is reasonable for an agent)
|
|
if (duration > 300000) score -= 30; // 5+ minutes
|
|
else if (duration > 180000) score -= 15; // 3-5 minutes
|
|
else if (duration > 60000) score -= 5; // 1-3 minutes
|
|
|
|
// Error penalty
|
|
score -= errorCount * 10;
|
|
|
|
// Page load penalty
|
|
if (averagePageLoadTime > PERFORMANCE_TARGETS.individualPageLoad) {
|
|
score -= 20;
|
|
}
|
|
|
|
// Memory penalty (if memory usage increased significantly)
|
|
if (agentData.finalMemoryUsage && agentData.memoryUsage) {
|
|
const memoryIncrease = agentData.finalMemoryUsage.heapUsed - agentData.memoryUsage.heapUsed;
|
|
if (memoryIncrease > 50 * 1024 * 1024) { // 50MB increase
|
|
score -= 10;
|
|
}
|
|
}
|
|
|
|
// Convert score to rating
|
|
if (score >= 90) return 'EXCELLENT';
|
|
if (score >= 80) return 'GOOD';
|
|
if (score >= 70) return 'ACCEPTABLE';
|
|
if (score >= 60) return 'POOR';
|
|
return 'CRITICAL';
|
|
}
|
|
|
|
/**
|
|
* Calculate average page load time
|
|
*/
|
|
calculateAveragePageLoadTime(pageLoads = null) {
|
|
const loads = pageLoads || this.metrics.pageLoadTimes;
|
|
if (loads.length === 0) return 0;
|
|
|
|
return loads.reduce((sum, p) => sum + p.loadTime, 0) / loads.length;
|
|
}
|
|
|
|
/**
|
|
* Calculate average network response time
|
|
*/
|
|
calculateAverageNetworkResponseTime() {
|
|
const requestsWithDuration = this.metrics.networkRequests.filter(r => r.duration);
|
|
if (requestsWithDuration.length === 0) return 0;
|
|
|
|
return requestsWithDuration.reduce((sum, r) => sum + r.duration, 0) / requestsWithDuration.length;
|
|
}
|
|
|
|
/**
|
|
* Calculate error rate
|
|
*/
|
|
calculateErrorRate() {
|
|
const totalOperations = this.metrics.pageLoadTimes.length + this.metrics.networkRequests.length;
|
|
if (totalOperations === 0) return 0;
|
|
|
|
return this.metrics.errors.length / totalOperations;
|
|
}
|
|
|
|
/**
|
|
* Calculate peak memory usage
|
|
*/
|
|
calculatePeakMemoryUsage() {
|
|
// For now, return current memory as peak
|
|
// In a real implementation, this would track peak usage over time
|
|
return process.memoryUsage().heapUsed;
|
|
}
|
|
|
|
/**
|
|
* Calculate average concurrent success rate
|
|
*/
|
|
calculateAverageConcurrentSuccessRate() {
|
|
if (this.metrics.concurrentSessions.length === 0) return 1.0;
|
|
|
|
const totalSuccessRate = this.metrics.concurrentSessions.reduce((sum, session) => {
|
|
return sum + (session.successfulSessions / session.totalSessions);
|
|
}, 0);
|
|
|
|
return totalSuccessRate / this.metrics.concurrentSessions.length;
|
|
}
|
|
|
|
/**
|
|
* Format bytes for human-readable output
|
|
*/
|
|
formatBytes(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
/**
|
|
* Wait utility
|
|
*/
|
|
async wait(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
/**
|
|
* Cleanup performance monitoring
|
|
*/
|
|
async cleanup() {
|
|
this.isMonitoring = false;
|
|
this.metrics.endTime = Date.now();
|
|
|
|
if (this.performanceObserver) {
|
|
this.performanceObserver.disconnect();
|
|
}
|
|
|
|
console.log('Performance Monitor cleanup complete');
|
|
}
|
|
}
|
|
|
|
module.exports = PerformanceMonitor; |