UNPKG

agentic-qe

Version:

Agentic Quality Engineering Fleet System - AI-driven quality management platform

366 lines 13.3 kB
"use strict"; /** * Profile Command * Profiles test performance with CPU and memory profiling */ 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.profilePerformance = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const v8 = __importStar(require("v8")); const perf_hooks_1 = require("perf_hooks"); /** * Profile test performance */ async function profilePerformance(options) { try { const startTime = perf_hooks_1.performance.now(); const startMemory = process.memoryUsage(); // Start CPU profiling if requested let cpuProfile = { samples: [], totalTime: 0, samplingInterval: 1, }; if (options.profileCPU) { cpuProfile = await captureCPUProfile(options); } // Execute test (simulated) await simulateTestExecution(); // Capture memory profile let memoryProfile = { heapUsed: 0, heapTotal: 0, external: 0, rss: 0, }; if (options.profileMemory) { memoryProfile = await captureMemoryProfile(options); } const endTime = perf_hooks_1.performance.now(); cpuProfile.totalTime = endTime - startTime; // Identify hot functions if requested let hotFunctions; if (options.identifyHotFunctions) { hotFunctions = identifyHotFunctions(cpuProfile); } const profile = { testFile: options.testFile, timestamp: Date.now(), cpu: cpuProfile, memory: memoryProfile, hotFunctions, }; // Compare with baseline if provided let comparison; if (options.compareWith) { comparison = compareProfiles(profile, options.compareWith); } // Export profile if requested let exportPath; let flamegraphPath; if (options.export) { const outputDir = options.outputDir || path.join(process.cwd(), '.swarm', 'reports'); fs.mkdirSync(outputDir, { recursive: true }); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); if (options.export === 'v8') { exportPath = path.join(outputDir, `profile-${timestamp}.cpuprofile`); const v8Profile = convertToV8Format(profile); fs.writeFileSync(exportPath, JSON.stringify(v8Profile, null, 2)); } else if (options.export === 'chrome-devtools') { exportPath = path.join(outputDir, `profile-${timestamp}.json`); const chromeProfile = convertToChromeDevToolsFormat(profile); fs.writeFileSync(exportPath, JSON.stringify(chromeProfile, null, 2)); } else if (options.export === 'json') { exportPath = path.join(outputDir, `profile-${timestamp}.json`); fs.writeFileSync(exportPath, JSON.stringify(profile, null, 2)); } } // Generate flamegraph if requested if (options.flamegraph) { flamegraphPath = await generateFlamegraph(profile, options); } return { success: true, profile, exportPath, flamegraphPath, comparison, }; } catch (error) { return { success: false, profile: { testFile: options.testFile, timestamp: Date.now(), cpu: { samples: [], totalTime: 0, samplingInterval: 1 }, memory: { heapUsed: 0, heapTotal: 0, external: 0, rss: 0 }, }, error: error.message, }; } } exports.profilePerformance = profilePerformance; async function captureCPUProfile(options) { const samples = []; const startTime = perf_hooks_1.performance.now(); // Capture CPU samples during execution const samplingInterval = 1; // 1ms const duration = 100; // Sample for 100ms for (let i = 0; i < duration; i += samplingInterval) { await new Promise(resolve => setTimeout(resolve, samplingInterval)); // Capture stack trace at this point const stack = new Error().stack; if (stack) { const lines = stack.split('\n'); if (lines.length > 2) { const match = lines[2].match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/); if (match) { samples.push({ timestamp: perf_hooks_1.performance.now(), functionName: match[1], scriptId: 0, lineNumber: parseInt(match[3]), columnNumber: parseInt(match[4]), }); } } } } return { samples, totalTime: perf_hooks_1.performance.now() - startTime, samplingInterval, }; } async function captureMemoryProfile(options) { const memUsage = process.memoryUsage(); const profile = { heapUsed: memUsage.heapUsed, heapTotal: memUsage.heapTotal, external: memUsage.external, rss: memUsage.rss, }; // Capture heap snapshot if requested if (options.heapSnapshot) { try { const snapshot = v8.writeHeapSnapshot(); profile.heapSnapshot = { path: snapshot }; } catch (error) { // Heap snapshot failed, continue without it } } // Detect memory leaks if requested if (options.detectLeaks) { profile.leaks = await detectMemoryLeaks(); } // Capture allocation profile if requested if (options.allocationProfile) { profile.allocations = await captureAllocationProfile(); } return profile; } async function detectMemoryLeaks() { const leaks = []; // Simulate leak detection // In real implementation, this would use heap snapshots and compare const memUsage = process.memoryUsage(); if (memUsage.heapUsed > memUsage.heapTotal * 0.8) { leaks.push({ object: 'LargeArray', size: 1024 * 1024, // 1MB retainedSize: 1024 * 1024 * 5, // 5MB location: 'test-file.ts:42', }); } return leaks; } async function captureAllocationProfile() { // Simulate allocation profiling return [ { size: 1024, count: 100, stackTrace: 'at Function.allocate (allocator.ts:10)', }, { size: 2048, count: 50, stackTrace: 'at Function.createBuffer (buffer.ts:25)', }, ]; } function identifyHotFunctions(cpuProfile) { const functionStats = new Map(); for (const sample of cpuProfile.samples) { const key = `${sample.functionName}:${sample.lineNumber}`; if (!functionStats.has(key)) { functionStats.set(key, { name: sample.functionName, file: 'unknown', line: sample.lineNumber, selfTime: 0, totalTime: 0, calls: 0, percentage: 0, }); } const stat = functionStats.get(key); stat.selfTime += cpuProfile.samplingInterval; stat.totalTime += cpuProfile.samplingInterval; stat.calls += 1; } // Calculate percentages const totalTime = cpuProfile.totalTime; for (const stat of functionStats.values()) { stat.percentage = (stat.selfTime / totalTime) * 100; } // Sort by percentage and return top functions return Array.from(functionStats.values()) .sort((a, b) => b.percentage - a.percentage) .slice(0, 10); } async function simulateTestExecution() { // Simulate CPU-intensive work for (let i = 0; i < 1000000; i++) { Math.sqrt(i); } // Simulate memory allocation const arrays = []; for (let i = 0; i < 100; i++) { arrays.push(new Array(1000).fill(i)); } // Simulate async work await new Promise(resolve => setTimeout(resolve, 10)); } function compareProfiles(current, baseline) { const cpuDiff = current.cpu.totalTime - baseline.cpu.totalTime; const memoryDiff = current.memory.heapUsed - baseline.memory.heapUsed; const improvementPercentage = ((baseline.cpu.totalTime - current.cpu.totalTime) / baseline.cpu.totalTime) * 100; const details = []; if (cpuDiff > 0) { details.push(`CPU time increased by ${cpuDiff.toFixed(2)}ms`); } else { details.push(`CPU time decreased by ${Math.abs(cpuDiff).toFixed(2)}ms`); } if (memoryDiff > 0) { details.push(`Memory usage increased by ${(memoryDiff / 1024 / 1024).toFixed(2)}MB`); } else { details.push(`Memory usage decreased by ${Math.abs(memoryDiff / 1024 / 1024).toFixed(2)}MB`); } return { cpuDiff, memoryDiff, improvementPercentage, details, }; } function convertToV8Format(profile) { return { nodes: profile.cpu.samples.map((sample, index) => ({ id: index, callFrame: { functionName: sample.functionName, scriptId: sample.scriptId.toString(), url: profile.testFile, lineNumber: sample.lineNumber, columnNumber: sample.columnNumber, }, hitCount: 1, children: [], })), startTime: profile.timestamp, endTime: profile.timestamp + profile.cpu.totalTime, samples: profile.cpu.samples.map((_, index) => index), timeDeltas: profile.cpu.samples.map(() => profile.cpu.samplingInterval), }; } function convertToChromeDevToolsFormat(profile) { return { head: { functionName: '(root)', scriptId: '0', url: '', lineNumber: 0, columnNumber: 0, hitCount: 0, children: profile.cpu.samples.map((sample, index) => ({ id: index + 1, functionName: sample.functionName, scriptId: sample.scriptId.toString(), url: profile.testFile, lineNumber: sample.lineNumber, columnNumber: sample.columnNumber, hitCount: 1, children: [], })), }, startTime: profile.timestamp, endTime: profile.timestamp + profile.cpu.totalTime, }; } async function generateFlamegraph(profile, options) { const outputDir = options.outputDir || path.join(process.cwd(), '.swarm', 'reports'); fs.mkdirSync(outputDir, { recursive: true }); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const flamegraphPath = path.join(outputDir, `flamegraph-${timestamp}.svg`); // Generate simple SVG flamegraph const svg = generateSVGFlamegraph(profile); fs.writeFileSync(flamegraphPath, svg); return flamegraphPath; } function generateSVGFlamegraph(profile) { const width = 1200; const height = 600; const barHeight = 20; let svg = `<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg version="1.1" width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg"> <rect width="100%" height="100%" fill="#eeeeee"/> <text x="10" y="20" font-family="Verdana" font-size="14">Flamegraph - ${profile.testFile}</text> `; let y = 40; const totalTime = profile.cpu.totalTime; if (profile.hotFunctions) { for (const func of profile.hotFunctions) { const barWidth = (func.percentage / 100) * (width - 40); const color = `hsl(${(func.percentage / 100) * 120}, 70%, 60%)`; svg += `<rect x="20" y="${y}" width="${barWidth}" height="${barHeight}" fill="${color}" stroke="#000" stroke-width="0.5"/> <text x="${25}" y="${y + 15}" font-family="Verdana" font-size="12">${func.name} (${func.percentage.toFixed(2)}%)</text> `; y += barHeight + 2; } } svg += '</svg>'; return svg; } //# sourceMappingURL=profile.js.map