Memlab介绍

Memlab是什么

Memlab is a memory testing framework for JavaScript。

Analyzes JavaScript heap and finds memory leaks in browser and node.js。

Memlab是一个JavaScript内存测试框架,可用于在浏览器、Node环境中分析JavaScript堆内存并检测内存泄露。它通过自定义测试场景,与SPA应用交互(使用 Puppeteer API),然后自动完成内存泄漏检查。

它的工作原理如下:

  • 与浏览器交互并获取 JavaScript 堆快照
  • 分析堆快照并识别内存泄漏
  • 对内存泄漏进行聚合、分组
  • 生成可Debug的分析结果

Memlab的特点

  • 面向对象的堆遍历 API:支持自定义内存泄露检测器,支持基于Chromium内核的应用(浏览器、Node环境、Electron、Hermes)
  • Memory CLI 工具箱:内置 CLI 工具箱和 API
  • Node环境下支持内存断言:可以对单元测试或运行中的Node应用保存堆快照,执行内存检查和内存断言

如何安装

ruby 复制代码
$ npm install -g memlab
  • 运行环境要求:Node.js 16+
  • 需要科学上网:Memlab内部依赖种包含 Puppeteer,正常情况下安装会报错。
  • 针对第2点,官方建议设置环境变量 PUPPETEER_SKIP_DOWNLOAD 先忽略浏览器的下载。手动安装好Puppeteer后,再手动下载Chromium文件,解压并放到Puppeteer的默认读取目录下。

如何使用

MemLab是在基于Chromium内核的浏览器中,运行预定义的测试场景并对 JavaScript heap snapshots 进行差异分析,从而发现内存泄漏,步骤如下:

  • 导航到目标页面并返回
  • 查找未释放的对象
  • 显示泄露追踪结果

创建一个测试场景

将该测试场景保存为/memlab/scenario.js

javascript 复制代码
// 测试场景的初始url
function url() {
  return "https://www.baidu.com";
}
// 定义可能出现内存泄露的交互
async function action(page) {
  await page.click('[id="target"]');
}
// 指定回到交互前状态的方式
async function back(page) {
  await page.click('[id="back"]');
}
module.exports = { action, back, url };

运行测试场景

shell 复制代码
$ memlab run --scenario /memlab/scenario.js

运行结果分析

第一部分,MemLab 会实时生成一个面包屑,显示与目标网页交互的进度,对每个步骤的解读如下:

  • page-load[6.5MB](baseline)[s1]

    • 测试试场景起点
    • 初始页面加载时,JavaScript 堆内存大小为 6.5MB。
    • baseline内存快照将作为 s1.heapsnapshot 保存在磁盘上。
  • action-on-page[6.6MB](target)[s2]

    • 执行交互操作
    • 内存大小增加到 6.6MB
    • target内存快照将作为 s2.heapsnapshot 保存在磁盘上。
  • revert[7MB](final)[s3]

    • 执行回退/反向操作
    • 网页内存达到7MB,
    • final内存快照将作为 s3.heapsnapshot 保存在磁盘上。

第二部分,对检测到的内存泄露进行汇总

  • 内存泄露节点数
  • 泄露内存占用大小

第三部分 ,按照内存泄露类型的相似性,对每个种类提取一个代表性的内存泄露节点进行展示,图中是创建1024个分离的DOM节点的内存泄露检测结果分析。

ini 复制代码
window.leakedObjects = [];
for (let i = 0; i < 1024; i++) {
  window.leakedObjects.push(document.createElement('div'));)
}
  • map这是被访问对象的 V8 HiddenClass(V8 在内部使用它来存储对象结构信息和对其原型的引用) - 在大多数情况下,这是 V8 实现的细节,可以忽略。
  • prototypewindows实例
  • leakedObjects表明leakedObjects是Window的属性,大小为148.5KB,指向Array对象
  • 0分离的 HTMLDIVElement元素,被存储为 leakedObjects 数组的第一个元素(Memlab 只打印一个具有代表性的内存泄露)
scss 复制代码
[window](object) -> leakedObjects(property) -> [Array](object)
  -> 0(element) -> [Detached HTMLDIVElement](native)

扩展应用

检测未释放的超大对象

scala 复制代码
// 未清空EventListener,无法释放eventHandler函数,eventHandler函数保存了对bigArray的引用

import { Component, Vue } from 'vue-property-decorator'

@Component
export default class OversizedObject extends Vue {
  public bigArray = Array(1024 * 1024 * 2).fill(0);
  
  eventHandler () {
    console.log('Using hugeObject', bigArray);
  };
  
  mounted () {
    window.addEventListener('custom-click', eventHandler)
  }
}

上述示例在运行测试场景时,无法检测到内存泄露。因为Memlab的泄漏检测器仅将满足以下所有条件的对象视为内存泄漏:

  • 对象在触发action时分配 内存
  • 在触发back后,对象 内存 没有被释放
  • 该对象是一个分离的 DOM 元素或一个未挂载的 React Fiber 节点

这种情况下,可以用LeakFilter来自定义规则过滤内存泄露对象(如内存占用大小),LeakFilter会对每一个由action触发内存分配,但在back后未释放内存的对象进行调用。

java 复制代码
// ...
function leakFilter(node, _snapshot, _leakedNodeIds) {
  return node.retainedSize > 1000 * 1000;
}

module.exports = {action, back, leakFilter, url};

另外也可以创建一个单独的leakFilter.js文件

ini 复制代码
function leakFilter(node, _snapshot, _leakedNodeIds) {
  return node.retainedSize > 1000 * 1000;
}

module.exports = {leakFilter};
css 复制代码
$ memlab find-leaks --leak-filter /memlab/leak-filter.js

检测所有的内存泄露

默认情况下,Memlab只报告准确度高的内存泄漏(由其内置的内存泄漏检测器进行判断)。可能会存在一些内存泄漏,Memlab不会报告。

使用如下命令可以检测所有的 内存 泄露 (确保测试场景不包含LeakFilter):

shell 复制代码
$ memlab run --scenario /memlab/scenarios.js
lua 复制代码
$ memlab find-leaks --trace-all-objects

直接分析内存快照

通常情况下,Memlab的内存分析数据来源于Memlab对Puppeteer API的调用。

通过Memlab内置的Memlab API,可以使用Memlab直接分析基于从Chrome或任何基于Chromium内核的应用中获取的单个JavaScript堆快照,检测内存问题

css 复制代码
$ memlab view-heap --snapshot <PATH TO .heapsnapshot FILE>

自动化内存泄露检测

Memlab支持自动化内存泄露检测,配置步骤如下:

  1. 准备覆盖关键交互的测试场景
  2. 通过Memlab CLIMemlab API触发测试场景运行
  3. 收集结果

在CLI中运行

css 复制代码
$ memlab run --scenario /path/to/test/scenario/file.js \
  --work-dir /path/to/save/memlab/run/results/

在Node.js中运行

ini 复制代码
const {run} = require('@memlab/api');
const scenario = require('/path/to/test/scenario/file.js');
const fs = require('fs-extra');

(async function () {
  const workDir = '/path/to/save/memlab/run/results/';
  fs.mkdirsSync(workDir);
  const result = await run({scenario, workDir});
})();

Memlab 运行完成后,所有结果和数据将保存在指定的工作目录中(workDir ),可以使用内置的结果分析器BrowserInteractionResultReader对结果进行读取并输出。

ini 复制代码
const {BrowserInteractionResultReader} = require('@memlab/api');

const workDir = '/path/to/save/memlab/run/results/';
const result = BrowserInteractionResultReader.from(workDir);
// 获取内存快照文件
const files = result.getSnapshotFiles();
// 对结果进行打印
const metaInfo = result.getRunMetaInfo();
console.log(metaInfo.browserInfo._consoleMessages.join('\n'));
// 清除结果
result.cleanup();
相关推荐
北京_宏哥2 天前
《最新出炉》系列入门篇-Python+Playwright自动化测试-50-滚动条操作
python·前端框架·测试
kida_yuan4 天前
【从零开始】6. RAG 应用性能压测工具(番外篇)
后端·llm·测试
孤蓬&听雨9 天前
Kafka自动生产消息软件(自动化测试Kafka)
分布式·kafka·自动化·测试·生产者
帅得不敢出门12 天前
Python+Appium+Pytest+Allure自动化测试框架-安装篇
python·appium·自动化·pytest·测试·allure
陈明勇14 天前
自动化测试在 Go 开源库中的应用与实践
后端·go·测试
帅得不敢出门14 天前
Python+Appium+Pytest+Allure自动化测试框架-代码篇
python·appium·自动化·pytest·测试·allure
Dylanioucn15 天前
《解锁 TDD 魔法:高效软件开发的利器》
后端·功能测试·测试·测试驱动开发·tdd
北京_宏哥15 天前
《最新出炉》系列入门篇-Python+Playwright自动化测试-41-录制视频
前端·python·测试
努力的小雨16 天前
新手入门Java自动化测试的利器:Selenium WebDriver
后端·测试
画江湖Test21 天前
pytest 单元框架里,前置条件
python·自动化·pytest·测试·1024程序员节