@casoon/auditmysite
Version:
Professional website analysis suite with robust accessibility testing, Core Web Vitals performance monitoring, SEO analysis, and content optimization insights. Features isolated browser contexts, retry mechanisms, and comprehensive API endpoints for profe
449 lines • 16.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResourceMonitor = void 0;
const events_1 = require("events");
const perf_hooks_1 = require("perf_hooks");
/**
* Advanced resource monitoring with trending analysis and alerting
*/
class ResourceMonitor extends events_1.EventEmitter {
constructor(config = {}) {
super();
this.snapshots = [];
this.lastEventLoopTime = perf_hooks_1.performance.now();
this.eventLoopDelays = [];
this.gcEvents = [];
this.isMonitoring = false;
// Check if running in CI environment
const isCI = process.env.CI === 'true' ||
process.env.NODE_ENV === 'test' ||
process.env.JEST_WORKER_ID !== undefined;
this.config = {
enabled: true,
samplingIntervalMs: 2000,
historySize: 100,
memoryWarningThresholdMB: 1536,
memoryCriticalThresholdMB: 2048,
cpuWarningThresholdPercent: 70,
cpuCriticalThresholdPercent: 85,
heapUsageWarningPercent: 75,
heapUsageCriticalPercent: 90,
eventLoopWarningDelayMs: 50,
eventLoopCriticalDelayMs: 100,
enableGCMonitoring: false,
gcWarningFrequency: 60, // 60 GCs per minute is concerning
disableInCI: true,
...config
};
// Disable in CI if configured to do so
if (this.config.disableInCI && isCI) {
this.config.enabled = false;
}
if (this.config.enabled) {
this.setupGCMonitoring();
}
}
/**
* Start resource monitoring
*/
start() {
if (!this.config.enabled || this.isMonitoring)
return;
this.isMonitoring = true;
// Take initial snapshot
this.takeSnapshot();
// Start periodic monitoring
this.monitoringTimer = setInterval(() => {
this.takeSnapshot();
this.analyzeResourceTrends();
}, this.config.samplingIntervalMs);
// Start event loop monitoring
this.startEventLoopMonitoring();
this.emit('monitoringStarted', {
config: this.config,
initialSnapshot: this.getCurrentSnapshot()
});
}
/**
* Stop resource monitoring
*/
stop() {
if (!this.isMonitoring)
return;
this.isMonitoring = false;
if (this.monitoringTimer) {
clearInterval(this.monitoringTimer);
this.monitoringTimer = undefined;
}
if (this.eventLoopTimer) {
clearInterval(this.eventLoopTimer);
this.eventLoopTimer = undefined;
}
this.emit('monitoringStopped', {
totalSnapshots: this.snapshots.length,
monitoringDurationMs: this.snapshots.length * this.config.samplingIntervalMs
});
}
/**
* Get current resource snapshot
*/
getCurrentSnapshot() {
if (!this.config.enabled)
return null;
return this.snapshots[this.snapshots.length - 1] || null;
}
/**
* Get resource history
*/
getHistory(maxSamples) {
const limit = maxSamples || this.snapshots.length;
return this.snapshots.slice(-limit);
}
/**
* Get resource trends analysis
*/
getResourceTrends() {
if (this.snapshots.length < 5)
return [];
const recentSnapshots = this.snapshots.slice(-10);
const trends = [];
const metricsToAnalyze = [
'rssMemoryMB',
'heapUsedMB',
'cpuUsagePercent',
'eventLoopDelayMs'
];
for (const metric of metricsToAnalyze) {
const values = recentSnapshots.map(s => s[metric]).filter(v => v !== undefined);
if (values.length < 3)
continue;
const trend = this.calculateTrend(values);
trends.push({
metric,
trend: trend.direction,
changePercent: trend.changePercent,
samples: values.length
});
}
return trends;
}
/**
* Force garbage collection if available
*/
forceGC() {
if (global.gc) {
try {
global.gc();
return true;
}
catch (error) {
this.emit('error', {
message: 'Failed to trigger garbage collection',
error
});
}
}
return false;
}
/**
* Get memory pressure level
*/
getMemoryPressure() {
const snapshot = this.getCurrentSnapshot();
if (!snapshot)
return 'normal';
if (snapshot.rssMemoryMB >= this.config.memoryCriticalThresholdMB ||
snapshot.heapUsagePercent >= this.config.heapUsageCriticalPercent) {
return 'critical';
}
if (snapshot.rssMemoryMB >= this.config.memoryWarningThresholdMB ||
snapshot.heapUsagePercent >= this.config.heapUsageWarningPercent) {
return 'warning';
}
return 'normal';
}
/**
* Take a resource snapshot
*/
takeSnapshot() {
const memUsage = process.memoryUsage();
const uptime = process.uptime();
// Calculate average event loop delay
const avgEventLoopDelay = this.eventLoopDelays.length > 0
? this.eventLoopDelays.reduce((a, b) => a + b, 0) / this.eventLoopDelays.length
: 0;
this.eventLoopDelays = []; // Reset for next interval
const snapshot = {
timestamp: Date.now(),
rssMemoryMB: memUsage.rss / (1024 * 1024),
heapUsedMB: memUsage.heapUsed / (1024 * 1024),
heapTotalMB: memUsage.heapTotal / (1024 * 1024),
heapUsagePercent: (memUsage.heapUsed / memUsage.heapTotal) * 100,
externalMemoryMB: memUsage.external / (1024 * 1024),
arrayBuffersMB: memUsage.arrayBuffers ? memUsage.arrayBuffers / (1024 * 1024) : 0,
cpuUsagePercent: this.calculateCPUUsage(),
eventLoopDelayMs: avgEventLoopDelay,
uptimeSeconds: uptime,
pid: process.pid,
ppid: process.ppid || 0,
platform: process.platform,
nodeVersion: process.version
};
// Add GC metrics if available
const recentGC = this.getRecentGCActivity();
if (recentGC) {
snapshot.gcCount = recentGC.count;
snapshot.gcDurationMs = recentGC.avgDuration;
snapshot.gcType = recentGC.dominantType;
}
// Add to history
this.snapshots.push(snapshot);
// Maintain history size limit
if (this.snapshots.length > this.config.historySize) {
this.snapshots.shift();
}
// Check for alerts
this.checkForAlerts(snapshot);
this.emit('snapshot', snapshot);
}
/**
* Calculate CPU usage approximation
*/
calculateCPUUsage() {
// This is a rough approximation based on event loop delay
// More sophisticated CPU monitoring would require native modules
const avgDelay = this.eventLoopDelays.length > 0
? this.eventLoopDelays.reduce((a, b) => a + b, 0) / this.eventLoopDelays.length
: 0;
// Convert delay to approximate CPU usage percentage
return Math.min(100, avgDelay * 2);
}
/**
* Start event loop delay monitoring
*/
startEventLoopMonitoring() {
const measureEventLoop = () => {
const start = perf_hooks_1.performance.now();
setImmediate(() => {
const delay = perf_hooks_1.performance.now() - start;
this.eventLoopDelays.push(delay);
// Keep only recent measurements
if (this.eventLoopDelays.length > 50) {
this.eventLoopDelays.shift();
}
});
};
// Measure event loop delay every 100ms
this.eventLoopTimer = setInterval(measureEventLoop, 100);
}
/**
* Setup garbage collection monitoring if available
*/
setupGCMonitoring() {
if (!this.config.enableGCMonitoring)
return;
try {
const v8 = require('v8');
if (v8.getHeapStatistics) {
// Monitor GC events
process.on('exit', () => {
this.logGCSummary();
});
}
}
catch (error) {
// v8 module not available
}
}
/**
* Get recent GC activity summary
*/
getRecentGCActivity() {
const cutoff = Date.now() - 60000; // Last minute
const recentEvents = this.gcEvents.filter(event => event.timestamp > cutoff);
if (recentEvents.length === 0)
return null;
const avgDuration = recentEvents.reduce((sum, event) => sum + event.duration, 0) / recentEvents.length;
const typeCounts = recentEvents.reduce((counts, event) => {
counts[event.type] = (counts[event.type] || 0) + 1;
return counts;
}, {});
const dominantType = Object.entries(typeCounts).reduce((a, b) => a[1] > b[1] ? a : b)[0];
return {
count: recentEvents.length,
avgDuration,
dominantType
};
}
/**
* Log GC summary on exit
*/
logGCSummary() {
if (this.gcEvents.length > 0) {
const totalDuration = this.gcEvents.reduce((sum, event) => sum + event.duration, 0);
this.emit('gcSummary', {
totalEvents: this.gcEvents.length,
totalDurationMs: totalDuration,
avgDurationMs: totalDuration / this.gcEvents.length
});
}
}
/**
* Check for resource alerts
*/
checkForAlerts(snapshot) {
const alerts = [];
// Memory alerts
if (snapshot.rssMemoryMB >= this.config.memoryCriticalThresholdMB) {
alerts.push({
level: 'critical',
metric: 'rssMemory',
current: snapshot.rssMemoryMB,
threshold: this.config.memoryCriticalThresholdMB,
message: `RSS memory usage critical: ${snapshot.rssMemoryMB.toFixed(1)}MB`,
timestamp: snapshot.timestamp,
snapshot
});
}
else if (snapshot.rssMemoryMB >= this.config.memoryWarningThresholdMB) {
alerts.push({
level: 'warning',
metric: 'rssMemory',
current: snapshot.rssMemoryMB,
threshold: this.config.memoryWarningThresholdMB,
message: `RSS memory usage high: ${snapshot.rssMemoryMB.toFixed(1)}MB`,
timestamp: snapshot.timestamp,
snapshot
});
}
// Heap usage alerts
if (snapshot.heapUsagePercent >= this.config.heapUsageCriticalPercent) {
alerts.push({
level: 'critical',
metric: 'heapUsage',
current: snapshot.heapUsagePercent,
threshold: this.config.heapUsageCriticalPercent,
message: `Heap usage critical: ${snapshot.heapUsagePercent.toFixed(1)}%`,
timestamp: snapshot.timestamp,
snapshot
});
}
else if (snapshot.heapUsagePercent >= this.config.heapUsageWarningPercent) {
alerts.push({
level: 'warning',
metric: 'heapUsage',
current: snapshot.heapUsagePercent,
threshold: this.config.heapUsageWarningPercent,
message: `Heap usage high: ${snapshot.heapUsagePercent.toFixed(1)}%`,
timestamp: snapshot.timestamp,
snapshot
});
}
// CPU alerts
if (snapshot.cpuUsagePercent >= this.config.cpuCriticalThresholdPercent) {
alerts.push({
level: 'critical',
metric: 'cpuUsage',
current: snapshot.cpuUsagePercent,
threshold: this.config.cpuCriticalThresholdPercent,
message: `CPU usage critical: ${snapshot.cpuUsagePercent.toFixed(1)}%`,
timestamp: snapshot.timestamp,
snapshot
});
}
else if (snapshot.cpuUsagePercent >= this.config.cpuWarningThresholdPercent) {
alerts.push({
level: 'warning',
metric: 'cpuUsage',
current: snapshot.cpuUsagePercent,
threshold: this.config.cpuWarningThresholdPercent,
message: `CPU usage high: ${snapshot.cpuUsagePercent.toFixed(1)}%`,
timestamp: snapshot.timestamp,
snapshot
});
}
// Event loop delay alerts
if (snapshot.eventLoopDelayMs >= this.config.eventLoopCriticalDelayMs) {
alerts.push({
level: 'critical',
metric: 'eventLoopDelay',
current: snapshot.eventLoopDelayMs,
threshold: this.config.eventLoopCriticalDelayMs,
message: `Event loop delay critical: ${snapshot.eventLoopDelayMs.toFixed(1)}ms`,
timestamp: snapshot.timestamp,
snapshot
});
}
else if (snapshot.eventLoopDelayMs >= this.config.eventLoopWarningDelayMs) {
alerts.push({
level: 'warning',
metric: 'eventLoopDelay',
current: snapshot.eventLoopDelayMs,
threshold: this.config.eventLoopWarningDelayMs,
message: `Event loop delay high: ${snapshot.eventLoopDelayMs.toFixed(1)}ms`,
timestamp: snapshot.timestamp,
snapshot
});
}
// Emit alerts
alerts.forEach(alert => {
this.emit('resourceAlert', alert);
if (alert.level === 'critical') {
this.emit('criticalAlert', alert);
}
});
}
/**
* Calculate trend from a series of values
*/
calculateTrend(values) {
if (values.length < 3) {
return { direction: 'stable', changePercent: 0 };
}
const first = values[0];
const last = values[values.length - 1];
const changePercent = ((last - first) / first) * 100;
// Calculate volatility
const avg = values.reduce((sum, val) => sum + val, 0) / values.length;
const variance = values.reduce((sum, val) => sum + Math.pow(val - avg, 2), 0) / values.length;
const coefficientOfVariation = Math.sqrt(variance) / avg;
if (coefficientOfVariation > 0.2) {
return { direction: 'volatile', changePercent };
}
if (Math.abs(changePercent) < 5) {
return { direction: 'stable', changePercent };
}
return {
direction: changePercent > 0 ? 'increasing' : 'decreasing',
changePercent: Math.abs(changePercent)
};
}
/**
* Analyze resource trends and emit trend updates
*/
analyzeResourceTrends() {
const trends = this.getResourceTrends();
if (trends.length > 0) {
this.emit('trendsUpdate', trends);
// Check for concerning trends
const concerningTrends = trends.filter(trend => trend.trend === 'increasing' &&
['rssMemoryMB', 'heapUsedMB', 'cpuUsagePercent'].includes(trend.metric) &&
trend.changePercent > 20);
if (concerningTrends.length > 0) {
this.emit('concerningTrends', concerningTrends);
}
}
}
/**
* Clean up resources
*/
destroy() {
this.stop();
this.removeAllListeners();
this.snapshots = [];
this.eventLoopDelays = [];
this.gcEvents = [];
}
}
exports.ResourceMonitor = ResourceMonitor;
//# sourceMappingURL=resource-monitor.js.map