Files
Autonomous-Bug-Explorer/dist/plugins/collectors/PerformanceCollector.js

178 lines
7.3 KiB
JavaScript

"use strict";
/**
* PerformanceCollector — captures Navigation Timing and Core Web Vitals after each navigation.
* Detects performance_degradation anomalies based on configurable thresholds.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.PerformanceCollector = exports.DEFAULT_PERF_CONFIG = void 0;
const crypto = __importStar(require("crypto"));
exports.DEFAULT_PERF_CONFIG = {
enabled: true,
lcpThresholdMs: 4000,
clsThreshold: 0.25,
inpThresholdMs: 500,
ttfbThresholdMs: 1800,
};
class PerformanceCollector {
constructor(config = {}) {
this.metricsStore = [];
this.config = { ...exports.DEFAULT_PERF_CONFIG, ...config };
}
async collect(page, stateId, sessionId, actionTrace) {
if (!this.config.enabled) {
const empty = {
id: crypto.randomUUID(), sessionId, stateId, url: page.url(),
ttfb: 0, domContentLoaded: 0, loadComplete: 0,
lcp: null, cls: null, fid: null, inp: null,
totalRequests: 0, failedRequests: 0, capturedAt: Date.now(),
};
return { metrics: empty, anomalies: [] };
}
// Capture Navigation Timing
const timing = await page.evaluate(() => {
const t = performance.timing;
return {
ttfb: t.responseStart - t.requestStart,
domContentLoaded: t.domContentLoadedEventEnd - t.navigationStart,
loadComplete: t.loadEventEnd - t.navigationStart,
};
}).catch(() => ({ ttfb: 0, domContentLoaded: 0, loadComplete: 0 }));
// Capture Core Web Vitals via PerformanceObserver
const vitals = await page.evaluate(() => {
return new Promise((resolve) => {
const result = {
lcp: null, cls: null, inp: null,
};
try {
// Try to observe LCP
if ('PerformanceObserver' in window) {
try {
const lcpObs = new PerformanceObserver((list) => {
const entries = list.getEntries();
if (entries.length > 0) {
result.lcp = entries[entries.length - 1].startTime;
}
});
lcpObs.observe({ type: 'largest-contentful-paint', buffered: true });
}
catch { /* not supported */ }
try {
const clsObs = new PerformanceObserver((list) => {
let clsScore = 0;
for (const entry of list.getEntries()) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
clsScore += entry.value ?? 0;
}
result.cls = clsScore;
});
clsObs.observe({ type: 'layout-shift', buffered: true });
}
catch { /* not supported */ }
}
}
catch { /* ignore */ }
// Resolve after short wait
setTimeout(() => resolve(result), 500);
});
}).catch(() => ({ lcp: null, cls: null, inp: null }));
const metrics = {
id: crypto.randomUUID(),
sessionId,
stateId,
url: page.url(),
ttfb: timing.ttfb,
domContentLoaded: timing.domContentLoaded,
loadComplete: timing.loadComplete,
lcp: vitals.lcp,
cls: vitals.cls,
fid: null,
inp: vitals.inp,
totalRequests: 0,
failedRequests: 0,
capturedAt: Date.now(),
};
this.metricsStore.push(metrics);
const anomalies = this.detectAnomalies(metrics, stateId, actionTrace);
return { metrics, anomalies };
}
getMetrics() {
return this.metricsStore;
}
detectAnomalies(metrics, stateId, actionTrace) {
const anomalies = [];
const issues = [];
let severityRank = 0; // 0=low,1=medium,2=high,3=critical
if (metrics.lcp !== null && metrics.lcp > this.config.lcpThresholdMs) {
issues.push(`LCP: ${metrics.lcp}ms (threshold: ${this.config.lcpThresholdMs}ms)`);
if (severityRank < 2)
severityRank = 2; // high
}
if (metrics.cls !== null && metrics.cls > this.config.clsThreshold) {
issues.push(`CLS: ${metrics.cls.toFixed(3)} (threshold: ${this.config.clsThreshold})`);
if (severityRank < 1)
severityRank = 1; // medium
}
if (metrics.inp !== null && metrics.inp > this.config.inpThresholdMs) {
issues.push(`INP: ${metrics.inp}ms (threshold: ${this.config.inpThresholdMs}ms)`);
if (severityRank < 2)
severityRank = 2; // high
}
if (metrics.ttfb > this.config.ttfbThresholdMs) {
issues.push(`TTFB: ${metrics.ttfb}ms (threshold: ${this.config.ttfbThresholdMs}ms)`);
if (severityRank < 1)
severityRank = 1; // medium
}
const RANK_TO_SEVERITY = ['low', 'medium', 'high', 'critical'];
const maxSeverity = RANK_TO_SEVERITY[severityRank] ?? 'low';
if (issues.length === 0)
return anomalies;
anomalies.push({
id: crypto.randomUUID(),
type: 'performance_degradation',
severity: maxSeverity,
observationId: stateId,
actionTrace,
description: `Performance degradation at ${metrics.url}: ${issues[0]}`,
evidence: {
rawErrors: issues,
},
timestamp: Date.now(),
});
return anomalies;
}
}
exports.PerformanceCollector = PerformanceCollector;