AutoWebPerf で CoreWebVitals を計測する
はじめに
AutoWebPerf をいろいろ触ってみたので紹介していこうと思います
CoreWebVitals
Core Web Vitals とは優れたユーザーエクスペリエンスを提供するための3つの指標のことです
- Largest Contentful Paint (LCP)
- First Input Delay (FID)
- Cumulative Layout Shift (CLS)
AutoWebPerf
AutoWebPerf とは Google が作った複数のソースからパフォーマンスのデータを自動的に収集できるツールです
Chrome UX Report、PageSpeed Insights、WebPageTest など収集元のツールが増えてきて、それぞれ個別で設定をし計測を行うのは負荷が高まってきたので、1つの統合的なツールを利用して複数のソースからパフォーマンス監視をできるようにしたのが、作成した経緯なのかなと感じます
アーキテクチャ
connector からテストのリストを取得し、gatherer を介してパフォーマンスを取得し、コネクタに結果を出力するようなアーキテクチャになっています
- the engine
- connector modules
- Spreadsheet
- JSON
- CSV
- gatherer modules
- CrUX API
- CrUX BigQuery
- PageSpeed Insights API
- WebPageTest API
- BigQuery(現在開発中とのこと)
クイックスタート
clone
して npm i
すれば ./awp run
でテストを行うことができます
git clone https://github.com/GoogleChromeLabs/AutoWebPerf.git
cd AutoWebPerf
npm i
./awp run examples/tests.json output/results.json
下記テストケースを元にテストを行い、結果を出力します
{
"tests": [
{
"label": "web.dev",
"url": "https://web.dev",
"gatherer": "psi"
}
]
}
結果は下記
{
"results": [
{
"id": "1610180070705-https://web.dev",
"type": "Single",
"gatherer": "psi",
"status": "Retrieved",
"label": "web.dev",
"createdTimestamp": 1610180070705,
"modifiedTimestamp": 1610180070705,
"errors": [],
"url": "https://web.dev",
"psi": {
"status": "Retrieved",
"statusText": "Success",
"settings": {},
"metadata": {
"testId": "https://web.dev/",
"requestedUrl": "https://web.dev/",
"finalUrl": "https://web.dev/",
"lighthouseVersion": "6.3.0",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/85.0.4183.140 Safari/537.36",
"fetchTime": "2021-01-09T08:14:31.866Z"
},
"metrics": {
"RenderBlockingResources": 0,
"crux": {
"LargestContentfulPaint": {
"category": "FAST",
"percentile": 2475,
"good": 0.761798544259217,
"ni": 0.15437896219769925,
"poor": 0.08382249354308509
},
"FirstInputDelay": {
"category": "FAST",
"percentile": 17,
"good": 0.9399224806201529,
"ni": 0.04069767441860455,
"poor": 0.01937984496124033
},
"FirstContentfulPaint": {
"category": "AVERAGE",
"percentile": 2521,
"good": 0.18828643331045844,
"ni": 0.6437886067261603,
"poor": 0.16792495996339965
},
"CumulativeLayoutShift": {
"category": "FAST",
"percentile": 3,
"good": 0.7931119920713579,
"ni": 0.07210109018830525,
"poor": 0.13478691774033683
}
},
"lighthouse": {
"FirstContentfulPaint": 1411,
"FirstMeaningfulPaint": 1682,
"LargestContentfulPaint": 3318.111530962102,
"SpeedIndex": 2162,
"TimeToInteractive": 5448,
"FirstCPUIdle": 4085,
"FirstInputDelay": 13,
"TotalBlockingTime": 109,
"CumulativeLayoutShift": 0,
"TotalSize": 392,
"HTML": 7,
"Javascript": 140,
"CSS": 25,
"Fonts": 74,
"Images": 135,
"Medias": 0,
"ThirdParty": 202,
"UnusedCSS": 21,
"WebPImages": 0,
"OptimizedImages": 0,
"ResponsiveImages": 0,
"OffscreenImages": 0,
"DOMElements": 307,
"Performance": 0.88,
"ProgressiveWebApp": 1,
"Manifest": 1,
"ServiceWorker": 1,
"Offline": 1,
"Accessibility": 1,
"SEO": 0.99,
"BestPractices": 1
}
},
"errors": []
}
}
]
}
CLI で定期的なテスト
AutoWebPerf では CLI で定期的なテストを行えます
準備
まず ./awp recurring
で準備をします
./awp recurring examples/tests-recurring.json output/results.json
{
"tests": [
{
"label": "web.dev",
"url": "https://web.dev",
"recurring": {
"frequency": "Daily",
"nextTriggerTimestamp": 1606955174906
},
"gatherer": "psi",
"errors": []
}
]
}
実行すると tests.recurring.nextTriggerTimestamp
が現在時刻 +1 日に更新されます
これは tests.recurring.frequency
が Daily
に設定されているため、次の実行日時をテスト実行時の +1 日に更新したのだと思います
↓実行後
{
"tests": [
{
"label": "web.dev",
"url": "https://web.dev",
"recurring": {
"frequency": "Daily",
"nextTriggerTimestamp": 1610267063035
},
"gatherer": "psi",
"errors": []
}
]
}
更新後もう一度 ./awp recurring
を実行しても、テストは実行されません
これは現在時刻が tests.recurring.nextTriggerTimestamp
より前の時刻だからだと思われます
準備はつまるところ、tests.recurring.nextTriggerTimestamp
を更新しているので手動で書き換えても良いです!
実行
./awp continue
を実行すると CLI は待機状態になります
./awp continue examples/tests-recurring.json output/results.json
動作確認で tests.recurring.nextTriggerTimestamp
を1分後などにしていたのですが、最初実行されず戸惑いました
これは、どうやら 10 分毎にテストを実行するかチェックをしているっぽいので、動作テストをする場合は10分待つ必要がありました
async continue(options) {
options = options || {};
options.recurring = true;
let self = this;
let isRunning = false;
// Set timer interval as every 10 mins by default.
let timerInterval = options.timerInterval ?
parseInt(options.timerInterval) : 60 * 10;
if (options.verbose) {
this.log(`Timer interval sets as ${timerInterval} seconds.`);
}
await self.recurring(options);
// Run contiuously.
return await new Promise(resolve => {
const interval = setInterval(async () => {
if (isRunning) return;
await self.recurring(options);
await self.retrieve(options);
isRunning = false;
if (options.verbose) {
self.log('Waiting for next timer triggered...');
}
}, timerInterval * 1000);
});
}
無事10分待って定期的な実行を確認 👍
Spreadsheet への書き込み
GitHub にやり方が書いてあったので、これを見ながらやっていきます
手順1 サービスアカウントの作成
すでにサービスアカウントがある場合はスキップ
手順2 スプレッドシートの作成
まず Google Sheets API を有効化しておきます
公式から Spreadsheet のサンプルが提供されているのでこれを参考に Spreadsheet を作ります
Tests
タブと、Results
タブを作成し、A 列は公式の Spreadsheet シートのラベルをコピーします
Tests
にはせっかくなので自分が運営しているサイトと、比較のために web.dev
を指定しています
シートを先程作ったサービスアカウントに共有します
service-account.json
の作成
手順3 サービスアカウントの詳細画面の「鍵を追加」から作ります
AutoWebPerf
では tmp
が .gitignore
に追加されているので、tmp
ディレクトリ配下に置いておきます
手順4 Chrome UX Report API キーの作成
Chrome UX Report API を有効化します
キーはデフォルトで作られるので利用します
手順5 実行
https://docs.google.com/spreadsheets/d/1c3k9eEVg12Atoa72tglVVBg--o0CEMkOF3JCaLlxzeo/edit
MY GOOGLE SHEET ID
は↑の URL であれば 1c3k9eEVg12Atoa72tglVVBg--o0CEMkOF3JCaLlxzeo
になります
SERVICE_ACCOUNT_CREDENTIALS=./tmp/service-account.json CRUX_APIKEY=MY_API_KEY ./awp run sheets:[MY GOOGLE SHEET ID]/Tests sheets:[MY GOOGLE SHEET ID]/Results
成功したが、自分のサイトはデータがなかった(悲しい)
DataStudio での可視化
Spreadsheet への書き込みのドキュメントに DataStudio での可視化が載っているのでやっていきます(このドキュメントに書かれている Spreadsheet の構成が元になっています)
DataStudio のテンプレートが用意されているのでこちらを元に作っていきます
「テンプレートを使用」をクリックし、「新しいデータソース」→「新しいデータソースの作成」
先程作ったスプレッドシートの Results
を選択します
「レポートに追加」
「レポートをコピー」で作成します
良い感じにグラフが表示されました!
Firebase Functions で定期的なテスト
課金は必須ですが、Cloud Scheduler の各ジョブのコストは月額 $0.10(USD)であり、Google アカウントごとに 3 つのジョブを無料で使用できるため、全体的なコストは管理可能であることが期待できます
3つまでは無料とのことなので Firebase Functions で定期的にテストを実行させます
実装
コード上で利用する場合 README とは引数が少し違いました
import * as admin from "firebase-admin";
const AutoWebPerf = require('awp/src/awp-core');
admin.initializeApp();
const bucket = admin.storage().bucket();
const SHEET_ID = 'XXX';
const SERVICE_ACCOUNT_CREDENTIALS_FILE_NAME = 'service-account.json';
const LOCAL_SERVICE_ACCOUNT_CREDENTIALS_PATH = `/tmp/${SERVICE_ACCOUNT_CREDENTIALS_FILE_NAME}`;
const SERVICE_ACCOUNT_CREDENTIALS_PATH = `..${LOCAL_SERVICE_ACCOUNT_CREDENTIALS_PATH}`;
const CRUX_APIKEY = 'XXX';
export async function run() {
await bucket.file(SERVICE_ACCOUNT_CREDENTIALS_FILE_NAME).download({
destination: LOCAL_SERVICE_ACCOUNT_CREDENTIALS_PATH
});
const awp = new AutoWebPerf({
"tests": {
"connector": "sheets",
"path": `${SHEET_ID}/Tests`
},
"results": {
"connector": "sheets",
"path": `${SHEET_ID}/Results`
},
"envVars": {
"SERVICE_ACCOUNT_CREDENTIALS": SERVICE_ACCOUNT_CREDENTIALS_PATH,
"CRUX_APIKEY": CRUX_APIKEY
}
});
awp.run();
}
tests
, results
はコマンドラインからの実行と同じように connector
と path
を定義し、環境変数は envVars
経由で渡します
Firebase Functions でのローカル JSON ファイルの取り扱いがわからなかったので、service-account.json
を Firebase Stroage にアップロードしておいて、毎回ダウンロードして利用しています
import * as functions from 'firebase-functions';
import { run } from './awp';
export const scheduledFunction = functions.pubsub.schedule('every day 03:00').timeZone('Asia/Tokyo').onRun(async () => {
await run();
return null;
});
デプロイ
最初ロケーションを設定していなかったのでビルド時にエラーになりましたが、それ以外は特に詰まったりしなかったです 👍
Error: Cloud resource location is not set for this project but scheduled functions requires it. Please see this documentation for more details: https://firebase.google.com/docs/projects/locations.
終わりに
Firebase Functions でのローカル JSON ファイルの取り扱いに困ったくらいで他はスムーズに構築できました
まだツールをいれてないのであれば、導入を検討してもいいかもしれません!
Discussion