UNPKG

@ai-capabilities-suite/mcp-debugger-core

Version:

Core debugging engine for Node.js and TypeScript applications. Provides Inspector Protocol integration, breakpoint management, variable inspection, execution control, profiling, hang detection, and source map support.

258 lines 9.35 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PerformanceTimeline = exports.PerformanceEventType = void 0; /** * Performance event types */ var PerformanceEventType; (function (PerformanceEventType) { PerformanceEventType["FUNCTION_CALL"] = "FunctionCall"; PerformanceEventType["SCRIPT_EVALUATION"] = "ScriptEvaluation"; PerformanceEventType["GARBAGE_COLLECTION"] = "GarbageCollection"; PerformanceEventType["COMPILE_SCRIPT"] = "CompileScript"; PerformanceEventType["PARSE_SCRIPT"] = "ParseScript"; PerformanceEventType["OTHER"] = "Other"; })(PerformanceEventType || (exports.PerformanceEventType = PerformanceEventType = {})); /** * Performance Timeline for recording and analyzing performance events * Tracks function execution times and identifies slow operations */ class PerformanceTimeline { constructor(inspector) { this.recording = false; this.events = []; this.startTime = 0; this.functionTimings = new Map(); this.inspector = inspector; } /** * Start recording performance events */ async startRecording() { if (this.recording) { throw new Error("Performance recording is already active"); } // Clear previous data this.events = []; this.functionTimings.clear(); this.startTime = Date.now(); // Enable Runtime domain for console timing await this.inspector.send("Runtime.enable"); // Enable Profiler domain for precise timing await this.inspector.send("Profiler.enable"); // Set up event listeners this.inspector.on("Runtime.consoleAPICalled", this.handleConsoleAPI.bind(this)); this.recording = true; } /** * Stop recording performance events * @returns Performance report */ async stopRecording() { if (!this.recording) { throw new Error("Performance recording is not active"); } this.recording = false; // Remove event listeners this.inspector.off("Runtime.consoleAPICalled", this.handleConsoleAPI.bind(this)); // Disable profiler await this.inspector.send("Profiler.disable"); // Generate report return this.generateReport(); } /** * Handle console API calls (for console.time/timeEnd) */ handleConsoleAPI(params) { if (!this.recording) return; // Track console.time and console.timeEnd calls if (params.type === "timeEnd" && params.args && params.args.length > 0) { const label = params.args[0].value; // This would need more sophisticated tracking to match time/timeEnd pairs // For now, we just record the event this.recordEvent({ type: PerformanceEventType.OTHER, name: label, startTime: Date.now(), endTime: Date.now(), duration: 0, }); } } /** * Record a performance event * @param event The event to record */ recordEvent(event) { if (!this.recording) return; this.events.push(event); // Track function timings if (event.type === PerformanceEventType.FUNCTION_CALL && event.details) { const key = `${event.name}:${event.details["file"]}:${event.details["line"]}`; const existing = this.functionTimings.get(key); if (existing) { existing.callCount++; existing.totalTime += event.duration; existing.minTime = Math.min(existing.minTime, event.duration); existing.maxTime = Math.max(existing.maxTime, event.duration); } else { this.functionTimings.set(key, { callCount: 1, totalTime: event.duration, minTime: event.duration, maxTime: event.duration, file: event.details["file"] || "", line: event.details["line"] || 0, }); } } } /** * Record a function call timing * @param functionName Name of the function * @param file File path * @param line Line number * @param duration Duration in microseconds */ recordFunctionCall(functionName, file, line, duration) { const now = Date.now(); this.recordEvent({ type: PerformanceEventType.FUNCTION_CALL, name: functionName, startTime: now - duration / 1000, endTime: now, duration: duration / 1000, // Convert to milliseconds details: { file, line }, }); } /** * Record a garbage collection event * @param duration Duration in microseconds */ recordGarbageCollection(duration) { const now = Date.now(); this.recordEvent({ type: PerformanceEventType.GARBAGE_COLLECTION, name: "GC", startTime: now - duration / 1000, endTime: now, duration: duration / 1000, // Convert to milliseconds }); } /** * Generate a performance report from recorded events * @returns Performance report */ generateReport() { const totalDuration = Date.now() - this.startTime; // Identify slow operations (>100ms) const slowOperations = this.events .filter((event) => event.duration > 100) .sort((a, b) => b.duration - a.duration); // Calculate GC statistics const gcEvents = this.events.filter((event) => event.type === PerformanceEventType.GARBAGE_COLLECTION); const gcTime = gcEvents.reduce((sum, event) => sum + event.duration, 0); const gcCount = gcEvents.length; // Convert function timings to array const functionTimings = Array.from(this.functionTimings.entries()).map(([key, data]) => { const [functionName] = key.split(":"); return { functionName, file: data.file, line: data.line, callCount: data.callCount, totalTime: data.totalTime, averageTime: data.totalTime / data.callCount, minTime: data.minTime, maxTime: data.maxTime, }; }); // Sort by total time functionTimings.sort((a, b) => b.totalTime - a.totalTime); return { totalDuration, eventCount: this.events.length, events: this.events, slowOperations, functionTimings: functionTimings.slice(0, 20), // Top 20 gcTime, gcCount, }; } /** * Format performance report as a human-readable string * @param report The performance report to format * @returns Formatted string */ formatReport(report) { const lines = []; lines.push("Performance Report"); lines.push("=================="); lines.push(`Total Duration: ${report.totalDuration.toFixed(2)}ms`); lines.push(`Total Events: ${report.eventCount}`); lines.push(`GC Time: ${report.gcTime.toFixed(2)}ms (${report.gcCount} collections)`); lines.push(""); if (report.slowOperations.length > 0) { lines.push("Slow Operations (>100ms):"); for (const op of report.slowOperations.slice(0, 10)) { lines.push(` ${op.duration.toFixed(2)}ms - ${op.name} (${op.type})`); if (op.details?.["file"]) { lines.push(` at ${op.details["file"]}:${op.details["line"]}`); } } lines.push(""); } if (report.functionTimings.length > 0) { lines.push("Top Functions by Total Time:"); for (const fn of report.functionTimings.slice(0, 10)) { lines.push(` ${fn.totalTime.toFixed(2)}ms - ${fn.functionName} (${fn.callCount} calls, avg: ${fn.averageTime.toFixed(2)}ms)`); if (fn.file) { lines.push(` at ${fn.file}:${fn.line}`); } } } return lines.join("\n"); } /** * Check if recording is active */ isRecording() { return this.recording; } /** * Get all recorded events */ getEvents() { return this.events; } /** * Clear all recorded events */ clearEvents() { this.events = []; this.functionTimings.clear(); } /** * Get function timings */ getFunctionTimings() { return Array.from(this.functionTimings.entries()).map(([key, data]) => { const [functionName] = key.split(":"); return { functionName, file: data.file, line: data.line, callCount: data.callCount, totalTime: data.totalTime, averageTime: data.totalTime / data.callCount, minTime: data.minTime, maxTime: data.maxTime, }; }); } } exports.PerformanceTimeline = PerformanceTimeline; //# sourceMappingURL=performance-timeline.js.map