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
JavaScript
/**
* 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