UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

298 lines • 14.6 kB
/** * Flutter Performance & Monitoring Module * * Handles performance metrics tracking, memory monitoring, and FPS analysis. * This module provides comprehensive performance insights for Flutter applications. */ export class FlutterPerformanceMonitoring { page; performanceMetrics = { fps: { current: 0, average: 0, jankFrames: 0, skippedFrames: 0 }, frameTimings: { rasterTime: 0, uiThreadTime: 0, gpuThreadTime: 0 }, shaderCompilation: { compilationTime: 0, cachedShaders: 0, firstFrameDelay: 0 }, layerTree: { complexity: 0, depth: 0, paintBounds: { width: 0, height: 0 } }, widgetRebuilds: { frequency: 0, hotSpots: [] }, memory: { heapUsage: 0, externalUsage: 0, growthRate: 0 } }; memorySnapshots = []; widgetRebuilds = new Map(); async attachToPage(page) { this.page = page; await this.setupPerformanceTracking(); await this.setupMemoryTracking(); } async getPerformanceMetrics() { if (!this.page) { return this.performanceMetrics; } try { const metrics = await this.page.evaluate(() => { const flutterDebug = window.__FLUTTER_DEBUG__; const performance = window.performance; // FPS calculation const fpsEntries = performance.getEntriesByType('measure').filter((entry) => entry.name.includes('frame') || entry.name.includes('fps')); const currentFps = fpsEntries.length > 0 ? Math.min(60, 1000 / fpsEntries[0].duration) : 0; const averageFps = fpsEntries.length > 0 ? fpsEntries.reduce((sum, entry) => sum + Math.min(60, 1000 / entry.duration), 0) / fpsEntries.length : 0; // Frame timing analysis const frameTimings = flutterDebug?.performance?.frames || []; const rasterTime = frameTimings.length > 0 ? frameTimings[frameTimings.length - 1]?.rasterTime || 0 : 0; const uiThreadTime = frameTimings.length > 0 ? frameTimings[frameTimings.length - 1]?.uiTime || 0 : 0; const gpuThreadTime = frameTimings.length > 0 ? frameTimings[frameTimings.length - 1]?.gpuTime || 0 : 0; // Jank frame detection const jankFrames = frameTimings.filter((frame) => frame.duration > 16.67).length; const skippedFrames = frameTimings.filter((frame) => frame.duration > 33.33).length; // Shader compilation metrics const shaderCompilations = flutterDebug?.performance?.shaderCompilations || []; const compilationTime = shaderCompilations.reduce((sum, compilation) => sum + (compilation.duration || 0), 0); const cachedShaders = shaderCompilations.filter((compilation) => compilation.cached).length; const firstFrameDelay = shaderCompilations.length > 0 ? shaderCompilations[0]?.duration || 0 : 0; // Layer tree complexity const layerElements = document.querySelectorAll('flt-scene-host, flt-clip, flt-opacity, flt-transform'); const complexity = layerElements.length; const depth = Math.max(...Array.from(layerElements).map(el => { let d = 0; let parent = el.parentElement; while (parent && parent !== document.body) { d++; parent = parent.parentElement; } return d; })); // Paint bounds const sceneHost = document.querySelector('flt-scene-host'); const paintBounds = sceneHost ? { width: sceneHost.offsetWidth, height: sceneHost.offsetHeight } : { width: window.innerWidth, height: window.innerHeight }; // Widget rebuilds const widgetRebuilds = flutterDebug?.widgets?.rebuilds || new Map(); const rebuildsArray = Array.from(widgetRebuilds.entries()).map((entry) => { const [widget, count] = entry; return { widget, rebuilds: count }; }); const frequency = rebuildsArray.length > 0 ? rebuildsArray.reduce((sum, item) => sum + item.rebuilds, 0) / rebuildsArray.length : 0; // Memory usage const memoryInfo = performance.memory; const heapUsage = memoryInfo?.usedJSHeapSize || 0; const externalUsage = memoryInfo?.totalJSHeapSize ? memoryInfo.totalJSHeapSize - memoryInfo.usedJSHeapSize : 0; const growthRate = 0; // Would need historical data return { fps: { current: currentFps, average: averageFps, jankFrames, skippedFrames }, frameTimings: { rasterTime, uiThreadTime, gpuThreadTime }, shaderCompilation: { compilationTime, cachedShaders, firstFrameDelay }, layerTree: { complexity, depth: isFinite(depth) ? depth : 0, paintBounds }, widgetRebuilds: { frequency, hotSpots: rebuildsArray.slice(0, 5) }, memory: { heapUsage, externalUsage, growthRate } }; }); this.performanceMetrics = metrics || this.performanceMetrics; return this.performanceMetrics; } catch (error) { return this.performanceMetrics; } } async detectMemoryLeaks() { if (!this.page) { return { detectedLeaks: [], memoryGrowthRate: 0, recommendations: [] }; } try { const leakAnalysis = await this.page.evaluate(() => { const flutterDebug = window.__FLUTTER_DEBUG__; const memoryInfo = performance.memory; const detectedLeaks = []; // Detect widget leaks const widgetRebuilds = flutterDebug?.widgets?.rebuilds || new Map(); for (const [widget, count] of widgetRebuilds.entries()) { if (count > 100) { detectedLeaks.push({ type: 'widget', description: `Widget ${widget} has ${count} rebuilds - possible memory leak`, severity: count > 500 ? 'critical' : count > 200 ? 'warning' : 'info', memoryImpact: count * 100 // Estimated bytes }); } } // Detect timer leaks const timerCount = window.__flutter_timers__?.length || 0; if (timerCount > 50) { detectedLeaks.push({ type: 'timer', description: `${timerCount} active timers detected - possible timer leak`, severity: timerCount > 200 ? 'critical' : timerCount > 100 ? 'warning' : 'info', memoryImpact: timerCount * 50 }); } // Calculate memory growth rate const currentMemory = memoryInfo?.usedJSHeapSize || 0; const memoryGrowthRate = 0; // Would need historical data to calculate // Generate recommendations const recommendations = []; if (detectedLeaks.some(leak => leak.type === 'widget')) { recommendations.push('Optimize widget rebuilds by using const constructors'); recommendations.push('Consider using Provider or Riverpod for state management'); } if (detectedLeaks.some(leak => leak.type === 'timer')) { recommendations.push('Dispose timers in dispose() method'); recommendations.push('Use StreamSubscription.cancel() to clean up streams'); } if (detectedLeaks.some(leak => leak.type === 'controller')) { recommendations.push('Dispose controllers in dispose() method'); } return { detectedLeaks, memoryGrowthRate, recommendations }; }); return leakAnalysis || { detectedLeaks: [], memoryGrowthRate: 0, recommendations: [] }; } catch (error) { return { detectedLeaks: [], memoryGrowthRate: 0, recommendations: [] }; } } async getPerformanceBaseline() { const performance = await this.getPerformanceMetrics(); const baselines = [ { metric: 'FPS', current: performance.fps.average, baseline: 60, status: performance.fps.average >= 55 ? 'good' : performance.fps.average >= 30 ? 'warning' : 'poor', suggestion: performance.fps.average < 55 ? 'Optimize widget rebuilds and animations' : undefined }, { metric: 'Jank Frames', current: performance.fps.jankFrames, baseline: 5, status: performance.fps.jankFrames <= 5 ? 'good' : performance.fps.jankFrames <= 20 ? 'warning' : 'poor', suggestion: performance.fps.jankFrames > 5 ? 'Profile frame rendering and reduce complexity' : undefined }, { metric: 'Memory Growth', current: performance.memory.growthRate / 1024, // KB/s baseline: 100, // 100KB/s status: performance.memory.growthRate < 100 * 1024 ? 'good' : performance.memory.growthRate < 1024 * 1024 ? 'warning' : 'poor', suggestion: performance.memory.growthRate > 100 * 1024 ? 'Check for memory leaks and dispose resources properly' : undefined }, { metric: 'Widget Rebuild Rate', current: performance.widgetRebuilds.frequency, baseline: 10, status: performance.widgetRebuilds.frequency <= 10 ? 'good' : performance.widgetRebuilds.frequency <= 50 ? 'warning' : 'poor', suggestion: performance.widgetRebuilds.frequency > 10 ? 'Use const constructors and optimize setState usage' : undefined }, { metric: 'Layer Tree Complexity', current: performance.layerTree.complexity, baseline: 50, status: performance.layerTree.complexity <= 50 ? 'good' : performance.layerTree.complexity <= 100 ? 'warning' : 'poor', suggestion: performance.layerTree.complexity > 50 ? 'Reduce layer complexity and use RepaintBoundary' : undefined } ]; return baselines; } async setupPerformanceTracking() { if (!this.page) return; await this.page.evaluate(() => { // Set up performance monitoring if ('PerformanceObserver' in window) { const observer = new PerformanceObserver((list) => { const entries = list.getEntries(); const flutterDebug = window.__FLUTTER_DEBUG__; entries.forEach((entry) => { if (entry.entryType === 'measure' && entry.name.includes('frame')) { if (!flutterDebug.performance.frames) { flutterDebug.performance.frames = []; } flutterDebug.performance.frames.push({ duration: entry.duration, startTime: entry.startTime, rasterTime: entry.duration * 0.6, // Estimated uiTime: entry.duration * 0.3, // Estimated gpuTime: entry.duration * 0.1 // Estimated }); // Keep only last 60 frames if (flutterDebug.performance.frames.length > 60) { flutterDebug.performance.frames.shift(); } } }); }); observer.observe({ entryTypes: ['measure', 'navigation'] }); } }); } async setupMemoryTracking() { if (!this.page) return; await this.page.evaluate(() => { // Set up memory monitoring const flutterDebug = window.__FLUTTER_DEBUG__; const trackMemory = () => { const memoryInfo = performance.memory; if (memoryInfo) { if (!flutterDebug.memory.snapshots) { flutterDebug.memory.snapshots = []; } flutterDebug.memory.snapshots.push({ timestamp: Date.now(), usedJSHeapSize: memoryInfo.usedJSHeapSize, totalJSHeapSize: memoryInfo.totalJSHeapSize, jsHeapSizeLimit: memoryInfo.jsHeapSizeLimit }); // Keep only last 100 snapshots if (flutterDebug.memory.snapshots.length > 100) { flutterDebug.memory.snapshots.shift(); } } }; // Track memory every 5 seconds setInterval(trackMemory, 5000); // Track memory on page visibility change document.addEventListener('visibilitychange', trackMemory); }); } } //# sourceMappingURL=flutter-performance-monitoring.js.map