継続は力なり

タイトル通り定期的な更新を心掛けるブログです。

Lighthouse の実行結果を Mackerel で可視化する

タダです.

フロントエンドのパフォーマンス測定の指標として Web Vitalsを知り,この指標を定期的に測定する方法を調べていたら Lighthouse を使って実現することができそうとわかりました.どうせなら Lighthouse の実行結果を Mackerel に入れて関係者が閲覧できるようにしたいと思い,検証してみた結果をこの記事にまとめていきます.

事前準備

Lighthouse の実行結果を Mackerel に投稿するために,事前準備として Mackerel API キーを発行しておきます.

mackerel.io

Lighthouse のメトリクスを収集~Mackerel へ投稿する

定期実行するために色々なツールがあると思うんですが, cypress-auditを使ってみました.収集したい情報をTARGET_METRICSに入れて実行するような形にしています.

const TARGET_METRICS = [
    "first-contentful-paint",
    "largest-contentful-paint",
    "cumulative-layout-shift",
    "interactive",
    "speed-index",
    "total-blocking-time",
  ];

--- 中略 ---

cy.lighthouse(
      {},
      {
        onlyAudits: TARGET_METRICS,
        formFactor: "mobile",
        headless: true,
      }
    );

TARGET_METRICS の収集状況を見てみると,audits セクションで下記のような結果が得られてます.

{
  'first-contentful-paint': {
    id: 'first-contentful-paint',
    title: 'First Contentful Paint',
    description: 'First Contentful Paint marks the time at which the first text or image is painted. [Learn more](https://web.dev/first-contentful-paint/).',
    score: 0.62,
    scoreDisplayMode: 'numeric',
    numericValue: 2650.956,
    numericUnit: 'millisecond',
    displayValue: '2.7 s'
  },
  'largest-contentful-paint': {
    id: 'largest-contentful-paint',
    title: 'Largest Contentful Paint',
    description: 'Largest Contentful Paint marks the time at which the largest text or image is painted. [Learn more](https://web.dev/lighthouse-largest-contentful-paint/)',
    score: 0,
    scoreDisplayMode: 'numeric',
    numericValue: 23015.075500000006,
    numericUnit: 'millisecond',
    displayValue: '23.0 s'
  },
  'speed-index': {
    id: 'speed-index',
    title: 'Speed Index',
    description: 'Speed Index shows how quickly the contents of a page are visibly populated. [Learn more](https://web.dev/speed-index/).',
    score: 0.02,
    scoreDisplayMode: 'numeric',
    numericValue: 13843.988954489698,
    numericUnit: 'millisecond',
    displayValue: '13.8 s'
  },
  'total-blocking-time': {
    id: 'total-blocking-time',
    title: 'Total Blocking Time',
    description: 'Sum of all time periods between FCP and Time to Interactive, when task length exceeded 50ms, expressed in milliseconds. [Learn more](https://web.dev/lighthouse-total-blocking-time/).',
    score: 0.11,
    scoreDisplayMode: 'numeric',
    numericValue: 1703.999999999999,
    numericUnit: 'millisecond',
    displayValue: '1,700 ms'
  },
  'max-potential-fid': {
    id: 'max-potential-fid',
    title: 'Max Potential First Input Delay',
    description: 'The maximum potential First Input Delay that your users could experience is the duration of the longest task. [Learn more](https://web.dev/lighthouse-max-potential-fid/).',
    score: 0.09,
    scoreDisplayMode: 'numeric',
    numericValue: 496,
    numericUnit: 'millisecond',
    displayValue: '500 ms'
  },
  interactive: {
    id: 'interactive',
    title: 'Time to Interactive',
    description: 'Time to interactive is the amount of time it takes for the page to become fully interactive. [Learn more](https://web.dev/interactive/).',
    score: 0.01,
    scoreDisplayMode: 'numeric',
    numericValue: 26310.244000000006,
    numericUnit: 'millisecond',
    displayValue: '26.3 s'
  },
  'full-page-screenshot': {
    id: 'full-page-screenshot',
    title: 'Full-page screenshot',
    description: 'A full-height screenshot of the final rendered page',
    score: null,
    scoreDisplayMode: 'informative',
    details: { type: 'full-page-screenshot', screenshot: [Object], nodes: {} }
  }
}

収集したデータから Mackerel のサービスメトリックに投稿するための情報を抽出して投稿が成功するとグラフが描画されます.

const json = JSON.stringify([Lighthouse の結果]);
const parse_json = JSON.parse(json).lhr.audits;
const xxx_fcp = parse_json['first-contentful-paint']['displayValue'];
const xxx_lcp = parse_json['largest-contentful-paint']['displayValue'];
const xxx_cls = parse_json['cumulative-layout-shift']['displayValue']
const xxx_tti = parse_json['interactive']['displayValue'];
const xxx_speed_index = parse_json['speed-index']['displayValue'];
const xxx_ttb = parse_json['total-blocking-time']['displayValue'];
const time = Date.now() / 1000;

const payload = [
    {
      'name' : 'xxx.fcp',
      'time' : time,
      'value' : Number(xxx_fcp.replace(/\s?[ms|s]/g, ''))},
    {
      'name' : 'xxx.lcp',
      'time' : time,
      'value' : Number(xxx_lcp.replace(/\s?[ms|s]/g, ''))},
    {
      'name' : 'xxx.cls',
      'time' : time,
      'value' : Number(xxx_cls.replace(/\s?[ms|s]/g, ''))},
    {
      'name' : 'xxx.tti',
      'time' : time,
      'value' : Number(xxx_tti.replace(/\s?[ms|s]/g, ''))},
    {
      'name' : 'xxx.speed_index',
      'time' : time,
      'value' : Number(xxx_speed_index.replace(/\s?[ms|s]/g, ''))},
    {
      'name' : 'xxx.ttb',
      'time' : time,
      'value' : Number(xxx_ttb.replace(/,/g, '').replace(/\s?[ms|s]/g, ''))},
  ];

--- 中略 ---
await fetch(
        "https://mackerel.io/api/v0/services/prod/tsdb",
        {
          method: "POST",
          headers: {
            "X-Api-Key": process.env.MACKEREL_API_KEY,
            "Content-Type": "application/json",
          },
          body: JSON.stringify(payload),
        });

サービスメトリクスイメージ

f:id:sadayoshi_tada:20211114182514p:plain

収集したデータは式監視を使って各種メトリクスをまとめて,ダッシュボード化しておくことで関係者が閲覧できるようにしました.

ex. 平均値を出すための式

movingAverage(
  service('xxx''xxx.lcp'
    ),1w)

ダッシュボードイメージ

f:id:sadayoshi_tada:20211114183903p:plain

GitHub Actions で定期実行

Lighthouse のメトリクスを収集するコードを定期実行したいので GitHub Actions を使うことにしました.Secretsに Mackerel の API キーを保存しておき,schedule の設定で1時間おきに実行してサービスメトリクスを投稿するようにしました.

name: lighthouse execution

on: 
  schedule:
    - cron: "0 0/1 * * *"
  workflow_dispatch:

jobs:
  test:
    name: Cypress run
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [xx.x]
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - name: Cypress execution
        env:
          MACKEREL_API_KEY: ${{ secrets.MACKEREL_API_KEY }}
        run: |
          npm run xxx[cypress-audit を実行するスクリプトなど]

まとめ

Lighthouse の実行結果を Mackerel に投稿し,閲覧できるようになりました.これでシステムの状況や変更が入ってからパフォーマンス的にどうなったかを確認できる状況になったので,関係者と目線を合わせることに使っていきたいと思います.