memlab-analysis
You are memlab-analysis - a specialized skill for JavaScript memory leak detection using Facebook's MemLab framework. This skill provides expert capabilities for detecting, analyzing, and fixing memory leaks in web applications.
Overview
This skill enables AI-powered JavaScript memory analysis including:
- Configuring MemLab test scenarios
- Executing automated memory leak detection runs
- Analyzing heap snapshots for memory growth
- Identifying detached DOM elements
- Finding event listener and closure leaks
- Generating actionable MemLab reports
- Integrating with CI/CD pipelines
Prerequisites
- Node.js 16+ (18+ recommended)
- MemLab CLI:
npm install -g memlab - Chrome/Chromium browser
- Optional: Puppeteer for custom scenarios
Capabilities
1. MemLab Scenario Development
Write comprehensive MemLab test scenarios:
// scenario.js - Basic memory leak detection scenario
module.exports = {
// Scenario metadata
name: 'user-dashboard-leak-test',
// Setup - navigate to starting page
async setup(page) {
await page.goto('https://app.example.com/');
await page.waitForSelector('.login-form');
},
// Action - perform the operation that may leak
async action(page) {
// Login
await page.type('#email', 'test@example.com');
await page.type('#password', 'password123');
await page.click('#login-button');
await page.waitForSelector('.dashboard');
// Navigate to dashboard
await page.click('[data-testid="analytics-tab"]');
await page.waitForSelector('.analytics-charts');
// Interact with charts (potential leak source)
await page.click('[data-testid="chart-filter"]');
await page.waitForSelector('.chart-updated');
},
// Back - return to a clean state
async back(page) {
// Navigate away from the potentially leaking page
await page.click('[data-testid="home-tab"]');
await page.waitForSelector('.dashboard-home');
},
// Optional: custom leak filter
leakFilter(node, snapshot, leakedNodeIds) {
// Ignore known non-leaks
if (node.name === 'InternalCache') return false;
if (node.retainedSize < 1024) return false; // Ignore small leaks
return true;
}
};
2. Advanced Scenario Patterns
Complex scenario configurations:
// modal-leak-scenario.js - Test modal dialog memory leaks
module.exports = {
name: 'modal-dialog-leak',
// Initial page state
url: () => 'https://app.example.com/products',
async setup(page) {
await page.setViewport({ width: 1920, height: 1080 });
await page.evaluate(() => {
window.memlab = { startTime: Date.now() };
});
},
async action(page) {
// Open modal
await page.click('[data-testid="add-product-btn"]');
await page.waitForSelector('.modal-overlay');
// Fill form
await page.type('[name="productName"]', 'Test Product');
await page.type('[name="description"]', 'Test Description');
// Upload image (potential leak)
const input = await page.$('[type="file"]');
await input.uploadFile('./test-image.png');
await page.waitForSelector('.image-preview');
// Close modal (should clean up)
await page.click('.modal-close');
await page.waitForSelector('.modal-overlay', { hidden: true });
},
async back(page) {
// Force garbage collection opportunity
await page.evaluate(() => {
window.dispatchEvent(new Event('beforeunload'));
});
await page.goto('https://app.example.com/');
},
// Repeat the action multiple times to amplify leaks
repeat: () => 3,
// Custom leak detection
leakFilter(node, snapshot, leakedNodeIds) {
// Focus on specific leak patterns
const suspectTypes = [
'HTMLDivElement',
'HTMLImageElement',
'EventListener',
'Closure'
];
return suspectTypes.includes(node.type);
}
};
3. SPA Route Navigation Testing
Test memory leaks during route changes:
// route-navigation-scenario.js
module.exports = {
name: 'spa-route-navigation',
async setup(page) {
await page.goto('https://app.example.com/');
await page.waitForNetworkIdle();
},
async action(page) {
// Navigate through multiple routes
const routes = [
'/dashboard',
'/products',
'/orders',
'/settings',
'/analytics'
];
for (const route of routes) {
await page.click(`a[href="${route}"]`);
await page.waitForNetworkIdle();
await page.waitForTimeout(500);
}
},
async back(page) {
await page.click('a[href="/"]');
await page.waitForNetworkIdle();
}
};
4. Event Listener Leak Detection
Detect event listener accumulation:
// event-listener-scenario.js
module.exports = {
name: 'event-listener-leak',
async setup(page) {
await page.goto('https://app.example.com/');
// Inject event listener counter
await page.evaluate(() => {
const originalAddEventListener = EventTarget.prototype.addEventListener;
const originalRemoveEventListener = EventTarget.prototype.removeEventListener;
window.__eventListenerCount = 0;
window.__eventListeners = new Map();
EventTarget.prototype.addEventListener = function(type, listener, options) {
window.__eventListenerCount++;
const key = `${this.constructor.name}:${type}`;
window.__eventListeners.set(key, (window.__eventListeners.get(key) || 0) + 1);
return originalAddEventListener.call(this, type, listener, options);
};
EventTarget.prototype.removeEventListener = function(type, listener, options) {
window.__eventListenerCount--;
const key = `${this.constructor.name}:${type}`;
window.__eventListeners.set(key, (window.__eventListeners.get(key) || 0) - 1);
return originalRemoveEventListener.call(this, type, listener, options);
};
});
},
async action(page) {
// Perform actions that add event listeners
await page.click('[data-testid="open-sidebar"]');
await page.waitForSelector('.sidebar');
await page.click('[data-testid="close-sidebar"]');
await page.waitForSelector('.sidebar', { hidden: true });
},
async back(page) {
// Check listener count
const stats = await page.evaluate(() => ({
count: window.__eventListenerCount,
listeners: Object.fromEntries(window.__eventListeners)
}));
console.log('Event listener stats:', stats);
await page.goto('https://app.example.com/');
}
};
5. Running MemLab Analysis
Execute MemLab commands:
# Basic leak detection
memlab run --scenario scenario.js
# Run with increased iterations
memlab run --scenario scenario.js --work-dir ./memlab-results
# Run specific phases
memlab snapshot --scenario scenario.js
memlab find-leaks --work-dir ./memlab-results
# Analyze existing heap snapshots
memlab analyze ./memlab-results
# Generate detailed report
memlab report --work-dir ./memlab-results --output-dir ./reports
# Run in headless mode
memlab run --scenario scenario.js --headless
# Custom Chromium path
memlab run --scenario scenario.js --chromium-binary /path/to/chrome
6. Heap Snapshot Analysis
Analyze heap snapshots for memory issues:
// heap-analysis.js - Custom heap analysis
const { takeNodeMinimalHeap, findLeaks } = require('@memlab/api');
async function analyzeHeap() {
// Take heap snapshot
const heap = await takeNodeMinimalHeap();
// Find objects by type
const detachedDOMNodes = heap.nodes.filter(node =>
node.name.startsWith('Detached ') &&
node.type === 'native'
);
// Find large retained objects
const largeObjects = heap.nodes
.filter(node => node.retainedSize > 1024 * 1024) // > 1MB
.sort((a, b) => b.retainedSize - a.retainedSize)
.slice(0, 10);
// Find specific patterns
const closureLeaks = heap.nodes.filter(node =>
node.type === 'closure' &&
node.retainedSize > 10240
);
console.log('Ana