memlab-analysisSkill memlab-analysis

memlab-analysis是一个专门用于JavaScript内存泄漏检测的技能,使用Facebook的MemLab框架来配置测试场景、执行检测运行、分析堆快照、识别DOM元素泄漏、查找事件监听器和闭包泄漏,并集成到CI/CD管道中。

测试 0 次安装 0 次浏览 更新于 2/25/2026

以下是memlab-analysis技能的中文翻译内容:


name: memlab-analysis description: 利用Facebook MemLab进行JavaScript内存泄漏检测的专家技能。配置MemLab场景,执行内存泄漏检测运行,分析堆快照,识别脱离DOM元素,查找事件监听器泄漏,并与CI管道集成。 allowed-tools: Bash(*) 读写编辑Glob Grep WebFetch metadata: author: babysitter-sdk version: “1.0.0” category: memory-profiling backlog-id: SK-017

memlab-analysis

你是 memlab-analysis - 一个专门用于使用Facebook的MemLab框架进行JavaScript内存泄漏检测的专家技能。这项技能提供了检测、分析和修复Web应用程序内存泄漏的专家能力。

概览

这项技能使AI驱动的JavaScript内存分析成为可能,包括:

  • 配置MemLab测试场景
  • 执行自动化内存泄漏检测运行
  • 分析堆快照以查找内存增长
  • 识别脱离DOM元素
  • 查找事件监听器和闭包泄漏
  • 生成可操作的MemLab报告
  • 与CI/CD管道集成

前提条件

  • Node.js 16+(推荐18+)
  • MemLab CLI: npm install -g memlab
  • Chrome/Chromium浏览器
  • 可选:Puppeteer用于自定义场景

能力

1. MemLab场景开发

编写全面的MemLab测试场景:

// scenario.js - 基本内存泄漏检测场景
module.exports = {
  // 场景元数据
  name: 'user-dashboard-leak-test',

  // 设置 - 导航到起始页面
  async setup(page) {
    await page.goto('https://app.example.com/');
    await page.waitForSelector('.login-form');
  },

  // 动作 - 执行可能泄漏的操作
  async action(page) {
    // 登录
    await page.type('#email', 'test@example.com');
    await page.type('#password', 'password123');
    await page.click('#login-button');
    await page.waitForSelector('.dashboard');

    // 导航到仪表板
    await page.click('[data-testid="analytics-tab"]');
    await page.waitForSelector('.analytics-charts');

    // 与图表交互(潜在泄漏源)
    await page.click('[data-testid="chart-filter"]');
    await page.waitForSelector('.chart-updated');
  },

  // 后退 - 返回到干净状态
  async back(page) {
    // 从可能泄漏的页面导航离开
    await page.click('[data-testid="home-tab"]');
    await page.waitForSelector('.dashboard-home');
  },

  // 可选:自定义泄漏过滤器
  leakFilter(node, snapshot, leakedNodeIds) {
    // 忽略已知非泄漏
    if (node.name === 'InternalCache') return false;
    if (node.retainedSize < 1024) return false; // 忽略小泄漏
    return true;
  }
};

2. 高级场景模式

复杂场景配置:

// modal-leak-scenario.js - 测试模态对话框内存泄漏
module.exports = {
  name: 'modal-dialog-leak',

  // 初始页面状态
  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) {
    // 打开模态
    await page.click('[data-testid="add-product-btn"]');
    await page.waitForSelector('.modal-overlay');

    // 填写表单
    await page.type('[name="productName"]', 'Test Product');
    await page.type('[name="description"]', 'Test Description');

    // 上传图片(潜在泄漏)
    const input = await page.$('[type="file"]');
    await input.uploadFile('./test-image.png');
    await page.waitForSelector('.image-preview');

    // 关闭模态(应清理)
    await page.click('.modal-close');
    await page.waitForSelector('.modal-overlay', { hidden: true });
  },

  async back(page) {
    // 强制垃圾收集机会
    await page.evaluate(() => {
      window.dispatchEvent(new Event('beforeunload'));
    });
    await page.goto('https://app.example.com/');
  },

  // 重复动作多次以放大泄漏
  repeat: () => 3,

  // 自定义泄漏检测
  leakFilter(node, snapshot, leakedNodeIds) {
    // 专注于特定泄漏模式
    const suspectTypes = [
      'HTMLDivElement',
      'HTMLImageElement',
      'EventListener',
      'Closure'
    ];
    return suspectTypes.includes(node.type);
  }
};

3. SPA路由导航测试

在路由更改期间测试内存泄漏:

// 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) {
    // 通过多个路由导航
    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-scenario.js
module.exports = {
  name: 'event-listener-leak',

  async setup(page) {
    await page.goto('https://app.example.com/');

    // 注入事件监听器计数器
    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) {
    // 执行添加事件监听器的动作
    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) {
    // 检查监听器计数
    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. 运行MemLab分析

执行MemLab命令:

# 基本泄漏检测
memlab run --scenario scenario.js

# 增加迭代次数
memlab run --scenario scenario.js --work-dir ./memlab-results

# 运行特定阶段
memlab snapshot --scenario scenario.js
memlab find-leaks --work-dir ./memlab-results

# 分析现有的堆快照
memlab analyze ./memlab-results

# 生成详细报告
memlab report --work-dir ./memlab-results --output-dir ./reports

# 无头模式运行
memlab run --scenario scenario.js --headless

# 自定义Chromium路径
memlab run --scenario scenario.js --chromium-binary /path/to/chrome

6. 堆快照分析

分析堆快照以查找内存问题:

// heap-analysis.js - 自定义堆分析
const { takeNodeMinimalHeap, findLeaks } = require('@memlab/api');

async function analyzeHeap() {
  // 拍摄堆快照
  const heap = await takeNodeMinimalHeap();

  // 按类型查找对象
  const detachedDOMNodes = heap.nodes.filter(node =>
    node.name.startsWith('Detached ') &&
    node.type === 'native'
  );

  // 查找大保留对象
  const largeObjects = heap.nodes
    .filter(node => node.retainedSize > 1024 * 1024) // > 1MB
    .sort((a, b) => b.retainedSize - a.retainedSize)
    .slice(0, 10);

  // 查找特定模式
  const closureLeaks = heap.nodes.filter(node =>
    node.type === 'closure' &&
    node.retainedSize > 10240
  );

  console.log('Analysis Results:');
  console.log('Detached DOM nodes:', detachedDOMNodes.length);
  console.log('Large objects:', largeObjects.map(n => ({
    name: n.name,
    type: n.type,
    size: `${(n.retainedSize / 1024 / 1024).toFixed(2)} MB`
  })));
  console.log('Potential closure leaks:', closureLeaks.length);
}

7. CI/CD集成

将MemLab集成到CI管道中:

# .github/workflows/memory-check.yml
name: 内存泄漏检查

on:
  pull_request:
    branches: [main]

jobs:
  memlab:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 设置Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: 安装依赖
        run: npm ci

      - name: 构建应用程序
        run: npm run build

      - name: 启动应用程序
        run: npm run start &
        env:
          PORT: 3000

      - name: 等待应用程序
        run: npx wait-on http://localhost:3000

      - name: 安装MemLab
        run: npm install -g memlab

      - name: 运行内存泄漏测试
        run: |
          memlab run --scenario ./tests/memlab/dashboard-scenario.js \
            --work-dir ./memlab-results \
            --headless

      - name: 检查泄漏
        run: |
          LEAK_COUNT=$(memlab find-leaks --work-dir ./memlab-results --json | jq '.length')
          if [ "$LEAK_COUNT" -gt 0 ]; then
            echo "::error::Found $LEAK_COUNT memory leaks"
            memlab report --work-dir ./memlab-results
            exit 1
          fi

      - name: 上传工件
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: memlab-results
          path: ./memlab-results

8. 常见泄漏模式检测

识别常见的JavaScript内存泄漏模式:

// leak-patterns.js - 检测常见泄漏模式
module.exports = {
  // 脱离DOM元素
  detectDetachedDOM(node) {
    return node.name.startsWith('Detached ') &&
           ['HTMLDivElement', 'HTMLSpanElement', 'HTMLImageElement']
             .some(type => node.name.includes(type));
  },

  // 事件监听器泄漏
  detectEventListenerLeak(node) {
    return node.type === 'object' &&
           node.name === 'EventListener' &&
           node.retainedSize > 1024;
  },

  // 闭包泄漏(持有引用)
  detectClosureLeak(node) {
    return node.type === 'closure' &&
           node.retainedSize > 10240 &&
           node.edges.some(edge => edge.name === 'context');
  },

  // 定时器泄漏(未清除setInterval)
  detectTimerLeak(node) {
    return node.name === 'Timeout' || node.name === 'Interval';
  },

  // 承诺链泄漏
  detectPromiseLeak(node) {
    return node.name === 'Promise' &&
           node.edges.some(edge =>
             edge.name === 'reactions' && edge.to.retainedSize > 0
           );
  },

  // 组件状态保留
  detectComponentLeak(node) {
    const componentPatterns = [
      'FiberNode', // React
      'ComponentPublicInstance', // Vue
      'ViewRef' // Angular
    ];
    return componentPatterns.some(p => node.name.includes(p));
  }
};

MCP服务器集成

这项技能可以利用以下MCP服务器:

服务器 描述 用例
playwright-mcp 浏览器自动化 自定义场景
clinic.js Node.js分析 替代内存分析

最佳实践

场景设计

  1. 现实用户流程 - 模拟实际用户行为
  2. 多次迭代 - 重复动作以放大泄漏
  3. 清洁回状态 - 确保适当的清理验证
  4. 专注测试 - 每个场景一个潜在泄漏源

泄漏检测

  1. 过滤噪声 - 忽略已知非泄漏
  2. 大小阈值 - 关注重大泄漏
  3. 保留者路径 - 了解为什么对象被保留
  4. DOM焦点 - 脱离DOM是最常见的泄漏

CI集成

  1. 在PR上运行 - 在合并前捕获泄漏
  2. 基线比较 - 与已知良好状态进行比较
  3. 失败阈值 - 设置可接受的泄漏限制
  4. 工件保留 - 保留堆转储以供分析

流程集成

这项技能与以下流程集成:

  • memory-leak-detection.js - 内存泄漏检测工作流
  • memory-profiling-analysis.js - 全面内存分析

输出格式

执行操作时,提供结构化输出:

{
  "operation": "detect-leaks",
  "status": "completed",
  "scenario": "dashboard-leak-test",
  "results": {
    "leaksFound": 3,
    "totalLeakedSize": "2.5 MB",
    "leaks": [
      {
        "type": "Detached HTMLDivElement",
        "count": 15,
        "totalSize": "1.2 MB",
        "retainerPath": ["Window", "EventTarget", "handlers", "closure"],
        "sourceFile": "dashboard.js:245"
      },
      {
        "type": "EventListener",
        "count": 42,
        "totalSize": "850 KB",
        "retainerPath": ["Window", "resize", "listener"],
        "sourceFile": "resize-handler.js:12"
      }
    ]
  },
  "recommendations": [
    {
      "leak": "Detached HTMLDivElement",
      "fix": "Ensure modal DOM is removed in componentWillUnmount",
      "codeLocation": "Modal.tsx:89"
    }
  ],
  "reportPath": "./memlab-results/report/index.html"
}

错误处理

常见问题

错误 原因 解决方案
Chrome not found 缺少浏览器 安装Chrome或指定路径
Timeout exceeded 页面加载缓慢 增加超时,检查网络
OOM in analysis 堆过大 增加Node.js内存限制
No leaks found 场景太短 增加迭代次数,延长动作

约束

  • 需要Chrome/Chromium浏览器
  • 堆分析可能内存密集
  • 可能出现误报 - 推荐手动验证
  • 有源映射时效果最佳