Memlab,一款分析 JavaScript 堆並查詢瀏覽器和 Node.js 中記憶體漏失的開源框架

2022-09-17 06:03:04

Memlab 是一款 E2E 測試和分析框架,用於發現 JavaScript 記憶體漏失和優化機會。

MemlabJavaScript 的記憶體測試框架。它支援定義一個測試場景(使用 Puppeteer API),教 Memlab 如何與您的單頁應用程式(SPA)互動,Memlab 可以自動處理其餘的記憶體漏失檢查:

  • 與瀏覽器互動並獲取 JavaScript 堆快照
  • 分析堆快照並過濾掉記憶體漏失
  • 聚合和分組類似的記憶體漏失
  • 生成用於記憶體偵錯的保留器跟蹤

安裝 Memlab

npm install -g memlab
memlab help

在 Demo App 中檢測洩漏

使用 Memlab 檢測分離的 DOM 元素的教學。Demo 原始碼:

設定範例 Web App

當您單擊 「Create detached DOMs」 按鈕時,Demo app 會洩漏分離的 DOM 元素。每次單擊都會建立 1024 個分離的 DOM 元素,這些元素由 window 物件參照。

// @nolint

import Link from 'next/link';
import React from 'react';

export default function DetachedDom() {

  const addNewItem = () => {
    if (!window.leakedObjects) {
      window.leakedObjects = [];
    }
    for (let i = 0; i < 1024; i++) {
      window.leakedObjects.push(document.createElement('div'));
    }
    console.log('Detached DOMs are created. Please check Memory tab in devtools')
  };

  return (
    <div className="container">
      <div className="row">
        <Link href="/">Go back</Link>
      </div>
      <br />
      <div className="row">
        <button type="button" className="btn" onClick={addNewItem}>
          Create detached DOMs
        </button>
      </div>
    </div>
  );
}

原始檔:packages/e2e/static/example/pages/examples/detached-dom.jsx

1. 克隆倉庫

要在本地機器上執行 demo,請克隆 memlab github 儲存庫

git clone [email protected]:facebookincubator/memlab.git

2. 執行範例 App

從 Memlab 專案的根目錄執行以下命令:

cd packages/e2e/static/example
npm install
npm run dev

這將啟動一個範例 Nextjs app。讓我們通過從瀏覽器存取 http://localhost:3000 來確保它正在執行:

這裡測試的是 Example 1

查詢記憶體漏失

1.建立一個場景檔案

// @nolint
// memlab/packages/e2e/static/example/scenario/detached-dom.js
/**
 * 我們要執行的場景的初始 `url`。
 */
function url() {
  return "http://localhost:3000/examples/detached-dom";
}

/**
 * 指定 memlab 應如何執行您要測試該 action 是否導致記憶體漏失的 action。
 *
 * @param page - Puppeteer's page object:
 * https://pptr.dev/api/puppeteer.page/
 */
async function action(page) {
  const elements = await page.$x(
    "//button[contains(., 'Create detached DOMs')]"
  );
  const [button] = elements;
  if (button) {
    await button.click();
  }
  // 從 memlab 清理外部參照
  await Promise.all(elements.map(e => e.dispose()));
}

/**
 * 指定 memlab 應如何執行將重置您在上面執行的 action 的 action。
 *
 * @param page - Puppeteer's page object:
 * https://pptr.dev/api/puppeteer.page/
 */
async function back(page) {
  await page.click('a[href="/"]');
}

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

這個檔案在 packages/e2e/static/example/scenario/detached-dom.js

2.執行 memlab

這可能需要幾分鐘:

cd packages/e2e/static/example
npm run dev # 注意啟動 Demo
memlab run --scenario scenarios/detached-dom.js

3.偵錯洩漏跟蹤

對於每個洩漏的物件組,memLab 列印一個具有代表性的洩漏跟蹤。

讓我們從上到下分解結果:

第 1 部分:瀏覽器互動麵包屑顯示了按照我們的場景檔案中指定的方式執行的瀏覽器互動(導航)memlab

  • page-load[6.5MB](baseline)[s1] - 初始頁面載入時 JavaScript 堆大小為 6.5MBbaseline 堆快照將在磁碟上儲存為 s1.heapsnapshot
  • action-on-page[6.6MB](baseline)[s2] - 單擊 「Create detached DOMs」 按鈕後,堆大小增加到 6.6MB
  • revert[7MB](final)[s3] - 在離開觸發記憶體漏失的頁面後,該網頁最終達到了 7MB。

第 2 部分:洩漏跟蹤的總體摘要

  • 1024 leaks - 有 1024 個洩漏的物件。example app 的第 12 行在 for 迴圈中建立了 1024 個分離的 DOM 物件。
  • Retained size - 洩漏物件叢集的聚合保留大小為 143.3KB(記憶體漏失根據保留跟蹤的相似性分組在一起)。

第 3 部分:每個洩漏簇的詳細代表洩漏跟蹤

洩漏跟蹤是從 GC 根(垃圾收集器遍歷堆的堆圖中的入口物件)到洩漏物件的物件參照鏈。跟蹤顯示洩漏的物件為何以及如何在記憶體中仍然保持活動狀態。 打破參照鏈意味著洩漏的物件將不再可以從 GC 根存取,因此可以進行垃圾回收。

通過從原生 Window(即 GC 根)向下逐個跟蹤洩漏跟蹤,您將能夠找到應該設定為 null 的參照(但這不是由於bug 引起的)。

  • map - 這是正在存取的物件的 V8 HiddenClass(V8 在內部使用它來儲存有關物件形狀的元資訊和對其原型的參照 - 在此處檢視更多資訊)- 在大多數情況下,這是 V8 實現細節,可以忽略。
  • prototype - 這是 Window 類的範例。
  • leakedObjects - 這表明 leakedObjectsWindow 物件的一個屬性,大小為 148.5KB,指向一個 Array 物件。
  • 0 - 這表明分離的 HTMLDIVElement(即當前未連線到 DOM 樹的 DOM 元素)被儲存為leakedObjects 陣列的第一個元素(由於顯示所有 1024 條洩漏痕跡是壓倒性的,Memlab 只列印一個具有代表性的洩漏痕跡。即屬性 0 而不是屬性 0->1023)

簡而言之,從 Window 物件到洩漏物件的洩漏跟蹤路徑為:

[window](object) -> leakedObjects(property) -> [Array](object)
  -> 0(element) -> [Detached HTMLDIVElement](native)

與範例中的洩漏程式碼匹配:

window.leakedObjects = [];
for (let i = 0; i < 1024; i++) {
    window.leakedObjects.push(document.createElement('div'));
}

更多