PlaywrightとChrome DevTools Protocol用いてWebページのメモリ使用量を計測する

:::note info
viviONグループでは、DLsiteやcomipoなど、二次元コンテンツを世の中に届けるためのサービスを運営しています。
ともに働く仲間を募集していますので、興味のある方は[こちら](#一緒に二次元業界を盛り上げていきませんか)まで。
:::


## はじめに

Webページのメモリ使用量をモニタリングする必要があったので、PlaywrightとCDPを用いて計測してみました。
Playwrightの解説は割愛します。

## 使用環境

- node 20+
- playwright@1.47.0

## Chrome DevTools Protocol（CDP）とは

Chrome DevTools Protocol（CDP）は、Chromium、Chrome、その他のBlinkベースのブラウザを外部から操作するためのプロトコルです。CDPを利用することで、パフォーマンスの計測など、Devtoolで提供されている機能をプログラム経由で制御することが可能になります。

https://chromedevtools.github.io/devtools-protocol/

## PlaywrightとCDPを用いてWebページのメモリ使用量を計測する

Playwrightを用いてWebページにアクセスし、CDP経由でメモリ使用量を計測してみます。

### サンプルスクリプト

以下が今回解説するスクリプトです。

```typescript
import { chromium } from "playwright";

async function main() {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  const client = await page.context().newCDPSession(page);

  await client.send("Performance.enable");
  await page.goto("https://example.com");

  // 必要であれば計測前にGCを実行する
  // await client.send('HeapProfiler.collectGarbage')
  const performanceMetrics = await client.send("Performance.getMetrics");

  const memoryMetrics = performanceMetrics.metrics.filter(
    (metric) =>
      metric.name === "JSHeapUsedSize" || metric.name === "JSHeapTotalSize"
  );

  console.log("Memory Metrics:", memoryMetrics);

  await browser.close();
}

main();
```

スクリプトを実行すると、以下のメトリクスを取得できます。

```typescript
[
  {
    name: "JSHeapUsedSize",
    value: 874812,
  }, {
    name: "JSHeapTotalSize",
    value: 2260992,
  }
]
```

### 解説

1. `chromium.launch()`でChromiumブラウザを起動し、新規ページを作成します

    ```typescript
    const browser = await chromium.launch();
    const page = await browser.newPage();
    ```

2. `context.newCDPSession`を使用して、ページに対するCDPセッションを開始します

    ```typescript
    const client = await page.context().newCDPSession(page);
    ```

https://playwright.dev/docs/api/class-cdpsession

3. メトリクスの計測を有効にする

    CDPの`Performance.enable`を呼び出してメトリクスの計測を有効にします。
    
    ```typescript
    await client.send('Performance.enable');
    ```

4. 対象のページに移動する

    `page.goto`を使用して対象のWebページに移動します。
    
    ```typescript
    await page.goto('https://example.com')
    ```

5. パフォーマンスメトリクスを取得する

    CDPの`Performance.getMetrics`を呼び出してメトリクスを取得します。
    必要であれば事前に `HeapProfiler.collectGarbage`を呼び出して、不要になったメモリを解放してください。


    ```typescript
    // 必要であれば計測前にGCを実行する
    // await client.send('HeapProfiler.collectGarbage')
    const performanceMetrics = await client.send('Performance.getMetrics');
    
    const memoryMetrics = performanceMetrics.metrics.filter(
    (metric) => ['JSHeapUsedSize', 'JSHeapTotalSize'].includes(metric.name)
    );
    
    console.log('Memory Metrics:', memoryMetrics);
    ```

今回はメモリ関連以外のメトリクスは除外していますが、以下が計測可能な全てのメトリクスです。

<details><summary>全てのメトリクス</summary>

```typescript
[
  {
    name: "Timestamp",
    value: 896268.163451,
  }, {
    name: "AudioHandlers",
    value: 0,
  }, {
    name: "AudioWorkletProcessors",
    value: 0,
  }, {
    name: "Documents",
    value: 3,
  }, {
    name: "Frames",
    value: 1,
  }, {
    name: "JSEventListeners",
    value: 0,
  }, {
    name: "LayoutObjects",
    value: 15,
  }, {
    name: "MediaKeySessions",
    value: 0,
  }, {
    name: "MediaKeys",
    value: 0,
  }, {
    name: "Nodes",
    value: 43,
  }, {
    name: "Resources",
    value: 0,
  }, {
    name: "ContextLifecycleStateObservers",
    value: 3,
  }, {
    name: "V8PerContextDatas",
    value: 4,
  }, {
    name: "WorkerGlobalScopes",
    value: 0,
  }, {
    name: "UACSSResources",
    value: 0,
  }, {
    name: "RTCPeerConnections",
    value: 0,
  }, {
    name: "ResourceFetchers",
    value: 3,
  }, {
    name: "AdSubframes",
    value: 0,
  }, {
    name: "DetachedScriptStates",
    value: 2,
  }, {
    name: "ArrayBufferContents",
    value: 0,
  }, {
    name: "LayoutCount",
    value: 0,
  }, {
    name: "RecalcStyleCount",
    value: 0,
  }, {
    name: "LayoutDuration",
    value: 0,
  }, {
    name: "RecalcStyleDuration",
    value: 0,
  }, {
    name: "DevToolsCommandDuration",
    value: 0.000014,
  }, {
    name: "ScriptDuration",
    value: 0,
  }, {
    name: "V8CompileDuration",
    value: 0,
  }, {
    name: "TaskDuration",
    value: 0.000016,
  }, {
    name: "TaskOtherDuration",
    value: 0.000002,
  }, {
    name: "ThreadTime",
    value: 0.000052,
  }, {
    name: "ProcessTime",
    value: 0.076343,
  }, {
    name: "JSHeapUsedSize",
    value: 874812,
  }, {
    name: "JSHeapTotalSize",
    value: 2260992,
  }, {
    name: "FirstMeaningfulPaint",
    value: 0,
  }, {
    name: "DomContentLoaded",
    value: 896268.128472,
  }, {
    name: "NavigationStart",
    value: 896267.363206,
  }
]
```
</details>


## まとめ

今回はPlaywrightとCDPを用いてWebページのメモリ使用量を計測する方法を紹介しました。
Webページのメモリ使用量を継続的にモニタリングすることは多くないですが、ぜひ試してみてください。

## 参考

- https://chromedevtools.github.io/devtools-protocol/
- https://playwright.dev/docs/api/class-cdpsession

# 一緒に二次元業界を盛り上げていきませんか？
株式会社viviONでは、フロントエンドエンジニアを募集しています。

https://vivion.jp/recruit/

また、フロントエンドエンジニアに限らず、バックエンド・SRE・スマホアプリなど様々なエンジニア職を募集していますので、ぜひ採用情報をご覧ください。