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();
相关推荐
AnyaPapa9 小时前
常用UI自动化测试框架
ui·自动化·框架·测试
mit6.8241 天前
[测试_3] 生命周期 | Bug级别 | 测试流程 | 思考
测试用例·bug·测试
song_ly00110 天前
深入理解软件测试覆盖率:从概念到实践
笔记·学习·测试
试着14 天前
【AI面试准备】掌握常规的性能、自动化等测试技术,并在工作中熟练应用
面试·职场和发展·自动化·测试
waves浪游15 天前
论坛系统测试报告
测试工具·测试用例·bug·测试
灰色人生qwer16 天前
使用JMeter 编写的测试计划的多个线程组如何生成独立的线程组报告
jmeter·测试
.格子衫.16 天前
powershell批处理——io校验
测试·powershell
试着16 天前
【AI面试准备】TensorFlow与PyTorch构建缺陷预测模型
人工智能·pytorch·面试·tensorflow·测试
waves浪游17 天前
博客系统测试报告
测试工具·测试用例·bug·测试
智云软件测评服务18 天前
数字化时代下,软件测试中的渗透测试是如何保障安全的?
渗透·测试·漏洞