Claude
Skills
Sign in
Back

memlab-analysis

Included with Lifetime
$97 forever

Expert skill for JavaScript memory leak detection using Facebook MemLab. Configure MemLab scenarios, execute memory leak detection runs, analyze heap snapshots, identify detached DOM elements, find event listener leaks, and integrate with CI pipelines.

Cloud & DevOps

What this skill does


# 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:

```javascript
// 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', '[email protected]');
    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:

```javascript
// 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:

```javascript
// 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:

```javascript
// 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:

```bash
# 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:

```javascript
// 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('An

Related in Cloud & DevOps