タダです.
フロントエンドのパフォーマンス測定の指標として Web Vitalsを知り,この指標を定期的に測定する方法を調べていたら Lighthouse を使って実現することができそうとわかりました.どうせなら Lighthouse の実行結果を Mackerel に入れて関係者が閲覧できるようにしたいと思い,検証してみた結果をこの記事にまとめていきます.
事前準備
Lighthouse の実行結果を Mackerel に投稿するために,事前準備として Mackerel API キーを発行しておきます.
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), });
サービスメトリクスイメージ
収集したデータは式監視を使って各種メトリクスをまとめて,ダッシュボード化しておくことで関係者が閲覧できるようにしました.
ex. 平均値を出すための式
movingAverage( service('xxx''xxx.lcp' ),1w)
ダッシュボードイメージ
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 に投稿し,閲覧できるようになりました.これでシステムの状況や変更が入ってからパフォーマンス的にどうなったかを確認できる状況になったので,関係者と目線を合わせることに使っていきたいと思います.