こんにちは。ものづくり推進部、フロントエンドエンジニアの武田です。 今日は Datadog, Lighthouse を使ったクライアントパフォーマンス計測に取り組んでいる、というお話です。 mediba では webpagetest を使った定期実行と計測を以前から行っています。 DataStudioとGASでWebPagetestの計測結果をグラフ化する uknmr/gas-webpagetest 紹介記事: gas-webpagetestでWebPagetestのパフォーマンス計測を自動化、可視化する 1 での取り組みをベースにし、 clasp で GAS のソースコード管理・デプロイを実現するための仕組みや webpagetest Lighthouse test と連携したメトリクスの取得まで網羅したものが 2 になります。 今回は少し webpagetest とは趣向を変えて Lighthouse による定期実行とパフォーマンス値取得 Datadog API を利用したカスタムメトリクスへの POST Datadog による可視化 についてご紹介します。 実施に必要なもの Datadog (Pro Plan or Enterprise Plan での契約) Node.js v10.13 以上(現時点)、Chrome がインストールされている実行環境 Lighthouse 定期実行する仕組み 超簡単な説明ですが Datadog とは? -> システム監視のための SaaS Lighthouse とは? -> クライアントパフォーマンスのスコアリングツール です。 Lighthouse による定期実行とパフォーマンス値取得 webpagetest ではなくなぜ Lighthouse なのか webpagetest を利用する場合、自前のプライベートインスタンスとして立てることも可能ですが、コストとの兼ね合いでパブリックインスタンスとして存在するリージョンを利用するというシーンがどうしてもあります。今回はプロダクション環境とパブリックに閲覧が不可能であるステージング環境の2環境を施策に合わせて比較をする必要があるため、どうしてもその制限をクリアできません。 ステージング環境を正常に閲覧できる(=リクエストを受け付けられる) Lighthouse 実行のために必要な Node.js v10 以上が動作する Lighthouse 実行のために Chrome が動作する これらを定期的に実行する 以上を実現することが必要そうです。 どこで Lighthouse 定期的に動かすか ランタイムを Node.js v10.x にした lambda もしくは CodeBuild でスクリプトを実行、定期スケジューリングを CloudWatch Events にするということも可能そうです。一度 CodeBuild 想定でコストを計算してみます。CodeBuild の OS image は Chrome も素で入っていますし 、割となんでも入り感があります。 料金 - AWS CodeBuild | AWS 毎月かかりそうな試算の条件 デイリーで24回実行想定 ビルドにかかる時間 5 分 インスタンスタイプに一番高いクラス 1分単位で 0.02 USD 24回/day * 30day * 5 分 * 0.02 USD = 72 USD 現時点レートで 7,827 円、自分が毎月もらっているお小遣いから出せるかと言われると出せない金額なので今回は AWS 環境での実行を諦めます。 いろいろ考えた結果、ほぼゼロコストで仕上げるため選択したのは Travis CI でした。といっても弊社では Travis CI Enterprise を AWS 環境上で動作させており、そこで実行させる想定です(なので詳細を詰められるとゼロコストではないのですが)。 ただ残念ながら定期実行を叶える Travis Cron Jobs はリポジトリのブランチ毎にマニュアルで指定する必要があるのでそこは残念な感じになります、人間がポチポチ GUI から指定して定期実行を実現することにします。 Lighthouse スクリプト フロントエンドエンジニアの方ですと Chrome Devtools -> audit や Lighthouse を CLI から利用することは一度はやってみたことがあると思うのですが、今回はよく見るスコアがほしいわけではなく特定のパフォーマンスメトリクスを取得したいという意図です。個人的にも絶対的なスコアリングが全てだと思っていません、 計測し比較し向上しているかが一番重要であると思っています 。 プログラマブルに Ligththouse を操作していきますが、 実はほとんどドキュメントにあったりするので 、参考に取得してパフォーマンス値をかき集めてみます。 const lighthouse = require('lighthouse'); const log = require('lighthouse-logger'); const chromeLauncher = require('chrome-launcher'); const opts = { chromeFlags: ["--headless", "--disable-gpu"], logLevel: "info" }; log.setLevel(opts.logLevel); launchChromeAndRunLighthouse("https://stg.example.com", opts).then(results => { // 一旦標準出力に表示 console.table(results); }); function launchChromeAndRunLighthouse(url, opts, config = null) { return chromeLauncher.launch({chromeFlags: opts.chromeFlags}).then(chrome => { opts.port = chrome.port; return lighthouse(url, opts, config).then(results => { // results.lhr はよく見るスコアリングの元データ // https://github.com/GoogleChrome/lighthouse/blob/master/types/lhr.d.ts const { "time-to-first-byte": ttfb, "first-contentful-paint": fcp, "first-meaningful-paint": fmp, "speed-index": speedindex, interactive, metrics, } = results.lhr.audits; return chrome.kill().then(() =>({ TTFB: Math.round(ttfb.numericValue), FIRST_PAINT: metrics.details.items[0].observedFirstPaint, FMP: Math.round(fmp.numericValue), FCP: Math.round(fcp.numericValue), SPEED_INDEX: Math.round(speedindex.numericValue), TTI: Math.round(interactive.numericValue), })); }); }); } Lighthouse 実行時の Promise.resolve で返却される results は型定義もあり 、昨今のエディタ補完の恩恵を受けると大変 DX が良いのですが、必要な数値は正直どこに格納されているか探すほかないので、CLI で一度 JSON ファイルを成果物として実行してから探すのが一番手っ取り早いです。切り取られたスクリーンショットの情報が Base64 でエンコードされ格納されている ので違った工夫もできそうではあります。 ここまでで Lighthouse 側の準備は整ったので次に Datadog 側を準備します。 Datadog API を利用したカスタムメトリクスへの POST そもそもなぜ Datadog を選ぶのか 担当するプロダクトのシステムモニタリングとして現在利用しており(他のプロジェクトでの利用実績もあります)、インテグレーションや API 連携について学びたいというのと、値のプロットと可視化を Datadog 一括にしちゃえば便利だし見るところ少なくていいんじゃね? というのが大きなモチベーションです。 私が活動するメインフィールドはフロントエンドなのですが、チームでは属人化を避けるため監視やアラート対応・調査を含め、モブを通して暗黙知をなくそうという取り組みをしている点も Datadog を候補にした理由です。 Datadog API とカスタムメトリクスの利用 次はDatadog API を使ってカスタムメトリクスを作り POST しますが、まずは API key, APP key が必要になってきます。 ナビゲーションから Intergrations -> APIs API key, APP key 取得 実際にリクエストを送る際は node-dogapi というのがあるのでこちらを利用してみます。 初期化 const datadog = require("dogapi"); // 環境変数に Datadog API key, Datadog App key がセットされている想定 datadog.initialize({ api_key: process.env.DATADOG_API_KEY, app_key: process.env.DATADOG_APP_KEY, }); カスタムメトリクスへの送信 任意の文字列をカスタムメトリクス名として、収集した値をプロットしていきます。なお以下のコードは先ほど出力に結果を表示したブロックで利用する想定です。タグ名に関しては特定の要素に紐づけ絞り込みを目的として利用を推奨しているようで、公式にもある通りプロダクション環境やステージング環境の種別としてタギングします。タグのルールとしては <KEY>:<VALUE> というルールになります。 参照 const { TTFB, FIRST_PAINT, FMP, FCP, SPEED_INDEX, TTI, } = results; const tags = "env:staging"; datadog.metric.send_all([ { metric: "webperf.measure.ttfb", points: TTFB, tags, }, { metric: "webperf.measure.first_paint", points: FIRST_PAINT, tags, }, { metric: "webperf.measure.fmp", points: FMP, tags, }, { metric: "webperf.measure.fcp", points: FCP, tags, }, { metric: "webperf.measure.speed_index", points: SPEED_INDEX, tags, }, { metric: "webperf.measure.tti", points: TTI, tags, }, ], (err, results) => { if (err) { console.log(`Error: ${JSON.stringify()}`); process.exit(1); } console.log(`Results: ${JSON.stringify(results, null, 4)}`); }); ここまでのもの実行できれば Datadog のカスタムメトリクスに値がプロットされ可視化するデータは出来たことになります。 Datadog による可視化 ダッシュボードを作成 新しいダッシュボードを作成しグラフを作る準備をします。 Timeborad: 時系列にイベントグラフを閲覧しレイアウトが自動 Screenboard: ステータスやグラフなどデータとして混合されたもの向けでレイアウトがフレキシブル 2つから選べますがお好きな方を選んで調整してください。 ダッシュボードにウィジェットを追加 グラフで可視化する場合は Timeseries というウィジェットを使用します。 ウィジェットでハマるとしたらタグでの絞り込みな気がします。もしタグがサジェストされないなどうまくいかないようでしたら </> アイコンを押下して raw text での変更に切り替えると avg:webperf.measure.ttfb{key:value} のような入力が可能になるので試してみると良いかもしれません。 最近よく聞くようなパフォーマンスバジェットを閾値として定めて、閾値よりオーバーしたら Warning 表示にする、なおかつ閾値を超えた際にモニタリングによって Slack に通知するといったことも可能です。 Google Developers Japan: パフォーマンスバジェットのご紹介 - ウェブパフォーマンスのための予算管理 ダッシュボードは今のところシンプルですがこんな感じになっています。 日々プロットしたグラフを可視化し追いかけたい数値と、重要視したい値・バジェットを定めてデカデカと表示する、見栄えはよくありませんが実務に事足りるものになっています。 まとめ Lighthouse から特定の値を取得、Datadog API を使ったカスタムメトリクスへの POST、Datadog 内での可視化について書かせていただきました。 Datadog のカスタムメトリクス利用は Pro/Enterprise プランのみ 手早く低コストで定期実行したいなら CI のサービス利用を検討 Lighthouse の実行結果からはパフォーマンス値やその他(スクショなど)も取得できる Datadog API の利用は簡単(各種言語のライブラリ等も揃っており充実) ダッシュボードも気軽で簡単に可視化できる 今回は Datadog のプラン次第というところもありますが、まずは低コストでパフォーマンス定期計測やパフォーマンスバジェットの設定などに取り組んでみてはいかがでしょうか。