{"id":102058,"date":"2025-02-21T19:50:59","date_gmt":"2025-02-21T19:50:59","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/audit-multiple-sites-with-lighthouse-and-write-results-to-bigquery\/"},"modified":"2025-02-21T19:50:59","modified_gmt":"2025-02-21T19:50:59","slug":"audit-multiple-sites-with-lighthouse-and-write-results-to-bigquery","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=102058","title":{"rendered":"Audit Multiple Sites With Lighthouse And Write Results To BigQuery"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<p><a href=\"https:\/\/cloud.google.com\/\">Google Cloud Platform<\/a> is very, very cool. It\u2019s a fully capable, enterprise-grade, scalable cloud ecosystem which lets even total novices get started with building their first cloud applications. I wrote a long guide for installing <a href=\"https:\/\/www.simoahava.com\/analytics\/install-snowplow-on-the-google-cloud-platform\/\">Snowplow on the GCP<\/a>, and you might want to read that if you want to see how you can build your own analytics tool using some nifty open-source modules.<\/p>\n<p>But this guide will not be about <a href=\"https:\/\/snowplowanalytics.com\/\">Snowplow<\/a>. Rather, it will tackle Google\u2019s own open-source performance audit tool: <a href=\"https:\/\/developers.google.com\/web\/tools\/lighthouse\/\">Lighthouse<\/a>.<\/p>\n<p>With this guide, <strong>you\u2019ll be able to build your own Google Cloud project, using Lighthouse to audit any number of sites you like, and write the results into storage and a BigQuery table<\/strong>!<\/p>\n<div style=\"aspect-ratio: 1413 \/ 462;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/lighthouse.jpg\" title=\"Lighthouse\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"462\" width=\"1413\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/lighthouse.jpg#ZgotmplZ\" alt=\"Lighthouse\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You can try Lighthouse in your Chrome browser right now. Just follow the instructions <a href=\"https:\/\/developers.google.com\/web\/tools\/lighthouse\/#devtools\">here<\/a> to see how you can enable the <strong>Audit<\/strong> tool in DevTools.<\/p>\n<p>One of the limitations of the DevTools feature is that you can only run it on one site at a time, and only through your web browser. I wanted to build something that lets me audit a whole bunch of sites, and automate it as well. For that reason, I wrote an <strong>extremely<\/strong> simple <a href=\"https:\/\/nodejs.org\/en\/\">Node.js<\/a> app, <a href=\"https:\/\/github.com\/sahava\/multisite-lighthouse\">multisite-lighthouse<\/a> which fulfils these requirements. Simply by editing a configuration file and then running the script, you can run an audit against multiple sites, and the results will be written in a local folder.<\/p>\n<p>So that was fun. But then I got a bit more ambitious. Would it be possible to run this app in a <em>serverless<\/em> cloud environment, and have the results be written in a database or storage somewhere, where they can be queried and fetched at leisure?<\/p>\n<div style=\"aspect-ratio: 1349 \/ 394;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/bigquery.jpg\" title=\"BigQuery output\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"394\" width=\"1349\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/bigquery.jpg#ZgotmplZ\" alt=\"BigQuery output\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Well, sure! In this guide, I\u2019ll walk you through how to run a Node.js application in a GCP <a href=\"https:\/\/cloud.google.com\/functions\/\">Cloud Function<\/a>, which writes the results into a <a href=\"https:\/\/cloud.google.com\/bigquery\/\">BigQuery<\/a> table, and stores the reports into a <a href=\"https:\/\/cloud.google.com\/storage\/\">Cloud Storage<\/a> bucket. Oh, and it\u2019s practically free.<\/p>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"how-it-works\">How it works<\/h2>\n<p>First, check out my mad Powerpoint-driven design skills:<\/p>\n<div style=\"aspect-ratio: 1414 \/ 839;\" class=\"figure \">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/process-diagram.jpg\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"839\" width=\"1414\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/process-diagram.jpg#ZgotmplZ\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Let\u2019s step through what goes on here.<\/p>\n<ol>\n<li>\n<p>The <a href=\"https:\/\/cloud.google.com\/functions\/\"><strong>Cloud Function<\/strong><\/a> is a piece of <a href=\"https:\/\/cloud.google.com\/functions\/docs\/concepts\/nodejs-8-runtime\"><strong>Node.js<\/strong><\/a> code running in the Google Cloud. It\u2019s called \u201cserverless\u201d because the setup is fully self-contained. The environment itself takes care of running the code <strong>and<\/strong> the infrastructure necessary to do so.<\/p>\n<\/li>\n<li>\n<p>The <strong>Cloud Function<\/strong> is triggered by a message pushed into a <a href=\"https:\/\/cloud.google.com\/pubsub\/docs\/overview\"><strong>Pub\/Sub<\/strong><\/a> topic. Pub\/Sub is short for Publish\/Subscribe, which means you can <strong>publish<\/strong> messages into a topic, which will then alert any <strong>subscribers<\/strong> of that topic. In other words, it can be used as a simple real-time messaging queue, where published messages can trigger services within the Google Cloud.<\/p>\n<\/li>\n<li>\n<p>The message itself can be published by you <strong>manually<\/strong>, by using the <a href=\"https:\/\/cloud.google.com\/sdk\/\">Google Cloud SDK<\/a> in your local terminal, or by other applications which have been authorized to communicate with the topic. A nice use case would be to have your site\u2019s build pipeline trigger the audit as soon as the new release of the site has been deployed.<\/p>\n<\/li>\n<li>\n<p>The message can also be published by <a href=\"https:\/\/cloud.google.com\/scheduler\/\"><strong>Cloud Scheduler<\/strong><\/a>, which is a Google Cloud service that lets you schedule certain actions such as the publishing of a Pub\/Sub message. You can set the scheduler to trigger your Cloud Function every night, or every week, or every month, for example.<\/p>\n<\/li>\n<li>\n<p>When the Cloud Function is triggered, it fires up an instance of the <strong>Google Chrome<\/strong> browser in headless mode, using a piece of software called <a href=\"https:\/\/github.com\/GoogleChrome\/puppeteer\">Puppeteer<\/a>.<\/p>\n<\/li>\n<li>\n<p>The <a href=\"https:\/\/developers.google.com\/web\/tools\/lighthouse\/\">Lighthouse<\/a> audit tool is run in this browser instance, and it will perform the audit on any URLs you have defined in a special configuration file.<\/p>\n<\/li>\n<li>\n<p>Once the audit is complete, the results are written into <a href=\"https:\/\/cloud.google.com\/storage\/\"><strong>Google Cloud Storage<\/strong><\/a>, which functions as a file archive in the cloud, allowing you to store and fetch items from it at will (and at low cost).<\/p>\n<\/li>\n<li>\n<p>The results are also written into a <a href=\"https:\/\/cloud.google.com\/bigquery\/\"><strong>BigQuery<\/strong><\/a> table. BigQuery is a scalable data warehouse, where records are stored in table format (as opposed to a more traditional relational database). Due to its flat nature, it\u2019s fast, scalable, and extremely responsive. We don\u2019t need its full potential in this project, but having the data stored in BigQuery opens up opportunities in e.g. <a href=\"https:\/\/datastudio.google.com\/\">Google Data Studio<\/a> for visualization of the results.<\/p>\n<\/li>\n<\/ol>\n<p>The <strong>cost<\/strong>? Next to nothing. With just one or two URLs you\u2019re facing practically <strong>zero cost<\/strong>. At most, it will be a handful of cents per month thanks to Google Cloud Storage.<\/p>\n<h2 id=\"preparations\">Preparations<\/h2>\n<p>To be able to follow this guide, you\u2019ll need to install some tools and create a Google Cloud Platform project.<\/p>\n<h3 id=\"install-command-line-tools\">Install command line tools<\/h3>\n<p>First, install the following command line tools:<\/p>\n<ol>\n<li>\n<p><a href=\"https:\/\/nodejs.org\/en\/download\/\">Node.js and npm<\/a>.<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/cloud.google.com\/sdk\/\">Google Cloud SDK<\/a>.<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/git-scm.com\/downloads\">Git<\/a>.<\/p>\n<\/li>\n<\/ol>\n<p>To verify you have these up and running, run the following commands in your terminal (example from OS X):<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-shell\" data-lang=\"shell\">$ node -v\nv11.0.0\n\n$ npm -v\n6.4.1\n\n$ gcloud -v\nGoogle Cloud SDK 228.0.0\n\n$ git --version\ngit version 2.19.2<\/code><\/pre>\n<\/div>\n<p>Naturally, you might see some other version numbers, and that\u2019s OK. Just try to work with recent releases.<\/p>\n<h3 id=\"set-up-a-new-google-cloud-platform-project-with-billing\">Set up a new Google Cloud Platform project with Billing<\/h3>\n<p>The other thing you\u2019ll need to do is <strong>create a GCP project<\/strong>, and you\u2019ll need to <strong>enable billing<\/strong> for this project. The things you build with this guide will most likely <strong>cost you absolutely nothing<\/strong>, or, at worst, just a handful of cents a month. But you still need to provide a credit card to enable some of the services we\u2019ll need.<\/p>\n<p>To set up a new project, you can follow <a href=\"https:\/\/www.simoahava.com\/analytics\/install-snowplow-on-the-google-cloud-platform\/#step-1-set-up-a-new-project\">Step 1<\/a> and <a href=\"https:\/\/www.simoahava.com\/analytics\/install-snowplow-on-the-google-cloud-platform\/#step-2-enable-billing\">Step 2<\/a> in my Snowplow + GCP guide.<\/p>\n<p>Once you\u2019ve created the project, make sure you copy its <strong>Project ID<\/strong> &#8211; you\u2019ll need it when walking through this guide.<\/p>\n<div style=\"aspect-ratio: 771 \/ 298;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/gcp-project-id.jpg\" title=\"GCP Project ID\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"298\" width=\"771\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/gcp-project-id.jpg#ZgotmplZ\" alt=\"GCP Project ID\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once you\u2019ve got the command line tools running and the GCP project created, it\u2019s time to get to work!<\/p>\n<h2 id=\"set-things-up-in-gcp\">Set things up in GCP<\/h2>\n<p>We\u2019ll need to enable some <strong>services<\/strong> in your Google Cloud Platform project. Also, we\u2019ll need <strong>create a BigQuery dataset<\/strong>, a <strong>storage bucket<\/strong> in Cloud Storage, and we\u2019ll need to configure a <strong>Cloud Scheduler<\/strong> job to run the audits periodically.<\/p>\n<h3 id=\"enable-the-services\">Enable the services<\/h3>\n<p>First, make sure you\u2019ve got the correct project selected while in your <a href=\"https:\/\/console.cloud.google.com\/home\/dashboard\">GCP console dashboard<\/a>.<\/p>\n<div style=\"aspect-ratio: 706 \/ 288;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/temp-test-simo.jpg\" title=\"GCP Project selected\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"288\" width=\"706\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/temp-test-simo.jpg#ZgotmplZ\" alt=\"GCP Project selected\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Then, go to <a href=\"https:\/\/console.cloud.google.com\/apis\/library\/cloudfunctions.googleapis.com\">https:\/\/console.cloud.google.com\/apis\/library\/cloudfunctions.googleapis.com<\/a> and click the blue <strong>ENABLE<\/strong> button to enable the <strong>Cloud Functions API<\/strong>.<\/p>\n<div style=\"aspect-ratio: 885 \/ 372;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/enable-cf-api.jpg\" title=\"Enable CF API\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"372\" width=\"885\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/enable-cf-api.jpg#ZgotmplZ\" alt=\"Enable CF API\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once enabled, go to <a href=\"https:\/\/console.cloud.google.com\/apis\/library\/cloudscheduler.googleapis.com\">https:\/\/console.cloud.google.com\/apis\/library\/cloudscheduler.googleapis.com<\/a> and click the blue <strong>ENABLE<\/strong> button to enable the <strong>Cloud Scheduler API<\/strong>.<\/p>\n<p>The other services we\u2019ll need (<strong>BigQuery API<\/strong>, <strong>Cloud Storage API<\/strong>, and <strong>Pub\/Sub API<\/strong>) are already enabled in your project.<\/p>\n<h3 id=\"create-the-bigquery-dataset\">Create the BigQuery dataset<\/h3>\n<p>Now, browse to <a href=\"https:\/\/console.cloud.google.com\/bigquery\">https:\/\/console.cloud.google.com\/bigquery<\/a>, and click your project ID in the list of availabe projects.<\/p>\n<div style=\"aspect-ratio: 964 \/ 281;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/create-new-dataset.jpg\" title=\"Create dataset\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"281\" width=\"964\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/create-new-dataset.jpg#ZgotmplZ\" alt=\"Create dataset\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>We need to create a <strong>dataset<\/strong> which will soon contain a <strong>table<\/strong> where the audit results will be loaded to.<\/p>\n<p>Type <code>lighthouse<\/code> as the dataset ID, like this:<\/p>\n<div style=\"aspect-ratio: 647 \/ 240;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/new-dataset-id.jpg\" title=\"Dataset ID\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"240\" width=\"647\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/new-dataset-id.jpg#ZgotmplZ\" alt=\"Dataset ID\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You can set the location of the dataset to somewhere in your geographical vicinity, if you wish. Once done, click <strong>Create dataset<\/strong>.<\/p>\n<h3 id=\"create-the-gcs-bucket\">Create the GCS bucket<\/h3>\n<p>We\u2019ll need to create a <strong>bucket<\/strong> in Google Cloud Storage. You can think of it like a storage drive, where the bucket represents a specific root directory to which the audit reports will be written.<\/p>\n<p>We\u2019ll also use the bucket to handle <strong>state<\/strong> in the Cloud Function. This is necessary to prevent unintentional retries from writing your data over and over again into BigQuery and storage.<\/p>\n<p>Browse to <a href=\"https:\/\/console.cloud.google.com\/storage\/browser\">https:\/\/console.cloud.google.com\/storage\/browser<\/a> and click <strong>Create bucket<\/strong>.<\/p>\n<p>You\u2019ll need to edit some options.<\/p>\n<ol>\n<li>\n<p><strong>Name<\/strong>: Give the bucket a name that\u2019s <em>globally<\/em> unique. <code><yourname>-lighthouse-reports<\/yourname><\/code> should work nicely. You\u2019ll need this name when configuring the application, so you might as well make note of it now.<\/p>\n<\/li>\n<li>\n<p><strong>Default storage class<\/strong>: Choose an applicable storage class. The storage class determines how much it costs to store and query the data. You\u2019ll be doing a little bit of fetching and a little bit of storing, so the Regional option is typically a good one to go with.<\/p>\n<\/li>\n<li>\n<p><strong>Location<\/strong>, choosing the location will impact costs and latency, so find one that suits you best. Honestly, the costs will be so low you might as well just take the one which is geographically closest to you.<\/p>\n<\/li>\n<\/ol>\n<div style=\"aspect-ratio: 596 \/ 608;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/gcs-bucket-settings.jpg\" title=\"Google Cloud Storage bucket\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"608\" width=\"596\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/gcs-bucket-settings.jpg#ZgotmplZ\" alt=\"Google Cloud Storage bucket\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once happy with the settings, click <strong>Create<\/strong>.<\/p>\n<h3 id=\"set-up-the-scheduled-job\">Set up the scheduled job<\/h3>\n<p>Finally, browse to <a href=\"https:\/\/console.cloud.google.com\/cloudscheduler\">https:\/\/console.cloud.google.com\/cloudscheduler<\/a> and click <strong>Create job<\/strong>. We\u2019ll use the Cloud Scheduler to fire up our Cloud Function periodically.<\/p>\n<p>In the first screen, you\u2019ll need to choose a region. You might as well choose one that\u2019s geographically close to you. Click <strong>Next<\/strong> when ready.<\/p>\n<div style=\"aspect-ratio: 803 \/ 528;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/scheduler-region.jpg\" title=\"Scheduler-region\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"528\" width=\"803\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/scheduler-region.jpg#ZgotmplZ\" alt=\"Scheduler-region\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Next, after some deliberation, you\u2019ll need to give your job a name. Just use something descriptive.<\/p>\n<p>Then, you need to establish the <strong>frequency<\/strong> of the job. Cloud Scheduler uses UNIX\u2019 <code>cron<\/code> format, which can be a bit of a mystery, but it does have a nice logic to it. You can read how it works <a href=\"https:\/\/cloud.google.com\/scheduler\/docs\/configuring\/cron-job-schedules\">here<\/a>, but here are some examples you can input into the field.<\/p>\n<ol>\n<li>\n<p>Every day at 04:00 AM (daily): <code>0 4 * * *<\/code><\/p>\n<\/li>\n<li>\n<p>Every Monday at 04:00 AM (weekly): <code>0 4 * * 1<\/code><\/p>\n<\/li>\n<li>\n<p>First day of every month at 04:00 AM (monthly): <code>0 4 1 * *<\/code><\/p>\n<\/li>\n<\/ol>\n<p>Next, choose a <strong>Timezone<\/strong> with which the \u201c04:00 AM\u201d part is calculated.<\/p>\n<p>Then, choose <strong>Pub\/Sub<\/strong> as the <strong>Target<\/strong> of the job.<\/p>\n<p>Type <code>launch-lighthouse<\/code> into the <strong>Topic<\/strong> field, and <code>all<\/code> into the <strong>Payload<\/strong> field. Don\u2019t worry, these settings will become more apparent as we trod along.<\/p>\n<p>In the end, the setup should look something like this:<\/p>\n<div style=\"aspect-ratio: 498 \/ 632;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/cloud-scheduler-job.jpg\" title=\"Cloud Scheduler Job\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"632\" width=\"498\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/cloud-scheduler-job.jpg#ZgotmplZ\" alt=\"Cloud Scheduler Job\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once ready, click <strong>Create<\/strong>.<\/p>\n<p>Good job! That\u2019s it for the <strong>Google Cloud Platform<\/strong> preparations. Now we\u2019ll need to actually <strong>build the application<\/strong> itself.<\/p>\n<h2 id=\"build-and-configure-the-application\">Build and configure the application<\/h2>\n<p>In this part of the guide, you\u2019ll install application source from my <strong>GitHub repo<\/strong>, update its <strong>configuration<\/strong>, and <strong>deploy it<\/strong> to GCP\u2019s Cloud Function environment.<\/p>\n<h3 id=\"clone-the-repo-and-install-dependencies\">Clone the repo and install dependencies<\/h3>\n<p>Open your terminal (or other command line) application, browse to a directory you\u2019re comfortable to work with, and type the following command:<\/p>\n<p><code>git clone https:\/\/github.com\/sahava\/multisite-lighthouse-gcp.git<\/code><\/p>\n<p>This will download and install the source files from <a href=\"https:\/\/github.com\/sahava\/multisite-lighthouse-gcp\">my GitHub repo<\/a> into the directory <code>multisite-lighthouse-gcp\/<\/code> in the folder where you ran the command.<\/p>\n<div style=\"aspect-ratio: 529 \/ 133;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/git-clone.jpg\" title=\"Git clone\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"133\" width=\"529\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/git-clone.jpg#ZgotmplZ\" alt=\"Git clone\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Next, type <code>cd multisite-lighthouse-gcp<\/code> and press enter. You should now be in the folder with the source files.<\/p>\n<p>Now, type <code>npm install<\/code> to install the <strong>dependencies<\/strong> of the project:<\/p>\n<div style=\"aspect-ratio: 1154 \/ 718;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/npm-install-done.jpg\" title=\"npm install done\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"718\" width=\"1154\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/npm-install-done.jpg#ZgotmplZ\" alt=\"npm install done\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This will take a while, depending on your internet connection. The dependencies include some heavy pieces of software (such as <code>puppeteer<\/code>, which I use to launch a headless Google Chrome instance for the audits). Once it\u2019s done, you should see a note that a bunch of packages were successfully installed.<\/p>\n<h3 id=\"modify-the-configuration-file\">Modify the configuration file<\/h3>\n<p>While in the <code>multisite-lighthouse-gcp<\/code> directory, the next thing you\u2019ll need to do is edit the <code>config.json<\/code> file. So open the file with the text editor of your choice. This is what the default configuration looks like:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-json\" data-lang=\"json\">{\n  <span style=\"color:#1e90ff;font-weight:bold\">\"source\"<\/span>: [\n    {\n      <span style=\"color:#1e90ff;font-weight:bold\">\"url\"<\/span>: <span style=\"color:#a50\">\"https:\/\/www.google.com\/\"<\/span>,\n      <span style=\"color:#1e90ff;font-weight:bold\">\"id\"<\/span>: <span style=\"color:#a50\">\"googlesearch\"<\/span>\n    },{\n      <span style=\"color:#1e90ff;font-weight:bold\">\"url\"<\/span>: <span style=\"color:#a50\">\"https:\/\/www.ebay.com\/\"<\/span>,\n      <span style=\"color:#1e90ff;font-weight:bold\">\"id\"<\/span>: <span style=\"color:#a50\">\"ebay\"<\/span>\n    }\n  ],\n  <span style=\"color:#1e90ff;font-weight:bold\">\"projectId\"<\/span>: <span style=\"color:#a50\">\"multisite-lighthouse-gcp\"<\/span>,\n  <span style=\"color:#1e90ff;font-weight:bold\">\"pubsubTopicId\"<\/span>: <span style=\"color:#a50\">\"launch-lighthouse\"<\/span>,\n  <span style=\"color:#1e90ff;font-weight:bold\">\"datasetId\"<\/span>: <span style=\"color:#a50\">\"lighthouse\"<\/span>,\n  <span style=\"color:#1e90ff;font-weight:bold\">\"minTimeBetweenTriggers\"<\/span>: <span style=\"color:#099\">300000<\/span>,\n  <span style=\"color:#1e90ff;font-weight:bold\">\"gcs\"<\/span>: {\n    <span style=\"color:#1e90ff;font-weight:bold\">\"bucketName\"<\/span>: <span style=\"color:#a50\">\"lighthouse-reports\"<\/span>\n  },\n  <span style=\"color:#1e90ff;font-weight:bold\">\"lighthouseFlags\"<\/span>: {\n    <span style=\"color:#1e90ff;font-weight:bold\">\"output\"<\/span>: [<span style=\"color:#a50\">\"html\"<\/span>, <span style=\"color:#a50\">\"csv\"<\/span>],\n    <span style=\"color:#1e90ff;font-weight:bold\">\"emulatedFormFactor\"<\/span>: <span style=\"color:#a50\">\"desktop\"<\/span>\n  }\n}<\/code><\/pre>\n<\/div>\n<p>Please see the following list of fields and possible values, and edit the configuration file accordingly.<\/p>\n<table>\n<thead>\n<tr>\n<th>Field<\/th>\n<th>Values<\/th>\n<th>Required<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>source<\/code><\/td>\n<td>{url, id}<\/td>\n<td>Yes<\/td>\n<td>An array of objects, each object representing one URL to audit. Type the full URL into the <code>url<\/code> field, and give a unique, descriptive <code>id<\/code> value into the respective field. For each additional URL you want to audit, add another object into the array.<\/td>\n<\/tr>\n<tr>\n<td><code>projectId<\/code><\/td>\n<td><code>temp-test-simo<\/code><\/td>\n<td>Yes<\/td>\n<td>The <a href=\"https:\/\/www.simoahava.com\/google-cloud\/lighthouse-bigquery-google-cloud-platform\/#set-up-a-new-google-cloud-platform-project-with-billing\"><strong>Project ID<\/strong><\/a> of your Google Cloud Platform project.<\/td>\n<\/tr>\n<tr>\n<td><code>pubsubTopicId<\/code><\/td>\n<td><code>launch-lighthouse<\/code><\/td>\n<td>Yes<\/td>\n<td>Set this to <code>launch-lighthouse<\/code>. It\u2019s the name of the Pub\/Sub <strong>topic<\/strong> that will be used to trigger the audits.<\/td>\n<\/tr>\n<tr>\n<td><code>datasetId<\/code><\/td>\n<td><code>lighthouse<\/code><\/td>\n<td>Yes<\/td>\n<td>The <strong>Dataset ID<\/strong> of the dataset you <a href=\"https:\/\/www.simoahava.com\/google-cloud\/lighthouse-bigquery-google-cloud-platform\/#create-the-bigquery-dataset\">just created<\/a> in BigQuery.<\/td>\n<\/tr>\n<tr>\n<td><code>minTimeBetweenTriggers<\/code><\/td>\n<td><code>300000<\/code><\/td>\n<td>Yes<\/td>\n<td>The minimum time in milliseconds to wait before a Pub\/Sub trigger can start the Cloud Function for any given ID. This is to add some <em>idempotency<\/em> to the function (more on this below).<\/td>\n<\/tr>\n<tr>\n<td><code>gcs<\/code><\/td>\n<td>{bucketName}<\/td>\n<td>Yes<\/td>\n<td>The name of the Google Cloud Storage bucket <a href=\"https:\/\/www.simoahava.com\/google-cloud\/lighthouse-bigquery-google-cloud-platform\/#create-the-gcs-bucket\">you created earlier<\/a>.<\/td>\n<\/tr>\n<tr>\n<td><code>lighthouseFlags<\/code><\/td>\n<td>See <a href=\"https:\/\/github.com\/GoogleChrome\/lighthouse#cli-options\">here<\/a>.<\/td>\n<td>No<\/td>\n<td>If you want to store the reports in Google Cloud Storage, set the <code>output<\/code> field to have an array of the formats you want to write the reports in. Available values are <code>\"csv\"<\/code>, <code>\"json\"<\/code>, and <code>\"html\"<\/code>. For each value in this array, a report will be written for every URL audited.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The only optional field is <code>lighthouseFlags<\/code>. If you don\u2019t set <code>lighthouseFlags.output<\/code> to an array of available filetypes, no reports will be written to Google Cloud Storage. The other field provided by default is <code>emulatedFormFactor<\/code>, which specifies that the performance audit should emulate a desktop browser.<\/p>\n<p>The <code>minTimeBetweenTriggers<\/code> flag is used to add a wait time before you can audit any given ID again. The Cloud Functions service doesn\u2019t guarantee that a function is run just once per trigger. This means that any code that\u2019s run should aim to be <em>idempotent<\/em>; that is, multiple executions of the code in a short amount of time should not result in additional state changes (e.g. BigQuery loads).<\/p>\n<p>I ended up using a simple stateful mechanism in Google Cloud Storage. A file named <code>states.json<\/code> is written into the root of the GCS bucket, and this contains timestamps for when each trigger last activated its respective event. If this activation was less than 300 seconds ago (or whatever you specify in the configuration file), the function will abort.<\/p>\n<p>Once done with the configuration, save the changes you made.<\/p>\n<h2 id=\"deploy-the-function-and-test\">Deploy the function and test<\/h2>\n<p>You\u2019re almost done! Now you\u2019ll need to just <strong>deploy<\/strong> the function, and then run a <strong>simple test<\/strong>.<\/p>\n<p>The Cloud Function itself is the <code>launchLighthouse()<\/code> method you can find if you browse the source code of <code>index.js<\/code>. This function is what your Pub\/Sub trigger will launch when your Cloud Scheduler job goes off, or when you manually instruct the trigger to fire.<\/p>\n<p>The Cloud Function will run in Google Cloud\u2019s serverless environment, launching instances of headless Google Chrome to perform the audits on the URLs you provide in the configuration file. The results will then be written into a BigQuery table, as well as stored into the GCS bucket if you so wish.<\/p>\n<h3 id=\"deploy-the-cloud-function\">Deploy the Cloud Function<\/h3>\n<p>While in the <code>multisite-lighthouse-gcp<\/code> folder, type the following command:<\/p>\n<p><code>gcloud auth login<\/code><\/p>\n<p>This should open up a browser window where you need to enter your Google ID credentials. Make sure you <strong>log in with the same Google ID you use to manage your Google Cloud Platform project with<\/strong>! You\u2019ll see that Google Cloud SDK wants to access a bunch of things in your name, so click <strong>Allow<\/strong> to let it do just that.<\/p>\n<p>It\u2019s Google &#8211; what evil could they do?<\/p>\n<p>If successful, you should see this screen:<\/p>\n<div style=\"aspect-ratio: 1147 \/ 487;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/gcloud-auth-login.jpg\" title=\"gcloud auth login\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"487\" width=\"1147\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/gcloud-auth-login.jpg#ZgotmplZ\" alt=\"gcloud auth login\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now, open your terminal window again, and type the following command in the <code>multisite-lighthouse-gcp<\/code> folder:<\/p>\n<p><code>gcloud config set project <projectid\/><\/code><\/p>\n<p>Type your GCP <strong>Project ID<\/strong> in lieu of <code><projectid\/><\/code>, and press enter.<\/p>\n<div style=\"aspect-ratio: 895 \/ 276;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/set-project.jpg\" title=\"Set project\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"276\" width=\"895\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/set-project.jpg#ZgotmplZ\" alt=\"Set project\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now that you\u2019ve authenticated against Google Cloud, and now that you\u2019ve switched the project to your GCP project, it\u2019s time to finally deploy the function with (type it all in one line):<\/p>\n<p><code>gcloud functions deploy launchLighthouse --trigger-topic launch-lighthouse --memory 2048 --timeout 540 --runtime=nodejs8<\/code><\/p>\n<p>If you want to run the function in a specific region, you can add <code>--region=<some gcp=\"\" region=\"\"\/><\/code>, e.g. <code>--region=europe-west1<\/code> to the command.<\/p>\n<p>It will take a minute or two for the function to be deployed to the cloud. Once done, you should see this as a result:<\/p>\n<div style=\"aspect-ratio: 856 \/ 336;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/function-deployed.jpg\" title=\"Function deployed\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"336\" width=\"856\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/function-deployed.jpg#ZgotmplZ\" alt=\"Function deployed\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The deploy should fail in an error if you have mistakes in the configuration file <code>config.json<\/code>, so make sure you follow the <a href=\"https:\/\/www.simoahava.com\/google-cloud\/lighthouse-bigquery-google-cloud-platform\/#modify-the-configuration-file\">instructions above<\/a> for how to configure the function.<\/p>\n<p>The initial deployment creates the <code>launch-lighthouse<\/code> <a href=\"https:\/\/console.cloud.google.com\/cloudpubsub\/topicList\">Pub\/Sub topic and subscriptions<\/a> automatically.<\/p>\n<p>So now that the Pub\/Sub topic has been created, you can actually test the whole thing.<\/p>\n<h3 id=\"test-with-pubsub\">Test with Pub\/Sub<\/h3>\n<p>The Cloud Scheduler will send the message <code>all<\/code> to the Pub\/Sub topic <code>launch-lighthouse<\/code> using the schedule you defined earlier. The topic <code>all<\/code> will, in turn, trigger the Cloud Function you just deployed as many times as it takes to audit all the URLs in the <code>source<\/code> field of the configuration.<\/p>\n<p>You can test this in two ways.<\/p>\n<ol>\n<li>\n<p>You can browse to <a href=\"https:\/\/console.cloud.google.com\/cloudscheduler\">https:\/\/console.cloud.google.com\/cloudscheduler<\/a> and click <strong>Run now<\/strong> next to the schedule job.<\/p>\n<\/li>\n<li>\n<p>You can use the command line to publish the <code>all<\/code> message.<\/p>\n<\/li>\n<\/ol>\n<p>Since the first is so easy, I\u2019ll show you how to do the second.<\/p>\n<p>Open the terminal, and make sure you have your Google Cloud Platform set with:<\/p>\n<p><code>gcloud config set project <projectid\/><\/code><\/p>\n<p>Just remember to replace <code><projectid\/><\/code> with your actual project ID.<\/p>\n<p>Now, run:<\/p>\n<p><code>gcloud pubsub topics publish launch-lighthouse --message all<\/code><\/p>\n<div style=\"aspect-ratio: 600 \/ 63;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/pubsub-message-all.jpg\" title=\"Pub\/Sub message\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"63\" width=\"600\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/pubsub-message-all.jpg#ZgotmplZ\" alt=\"Pub\/Sub message\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You should see a <code>messageIds<\/code> response &#8211; that means the message was published successfully.<\/p>\n<h3 id=\"check-logs\">Check logs<\/h3>\n<p>Now, browse to <a href=\"https:\/\/console.cloud.google.com\/functions\/list\">https:\/\/console.cloud.google.com\/functions\/list<\/a>, where you should see details about your Cloud Function, hopefully with a green checkmark next to its name.<\/p>\n<p>Click the action menu at the end of the row, and choose <strong>View logs<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1181 \/ 315;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/view-logs-cf.jpg\" title=\"View CF logs\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"315\" width=\"1181\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/view-logs-cf.jpg#ZgotmplZ\" alt=\"View CF logs\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You\u2019ll see a bunch of log results here. Here are the entries you should see (in approximately, but not definitely, this order):<\/p>\n<table>\n<thead>\n<tr>\n<th>Message<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Function execution started<\/td>\n<td>The Cloud Function starts. If you sent the message <code>all<\/code>, it now sends a <strong>new<\/strong> Pub\/Sub message for all the IDs in your configuration file, thus starting new Cloud Functions by itself (one for each <code>source<\/code> entry in the config).<\/td>\n<\/tr>\n<tr>\n<td>{id}: Sending init PubSub message<\/td>\n<td>This is the initialization message for source ID {id}.<\/td>\n<\/tr>\n<tr>\n<td>{id}: Init PubSub message sent<\/td>\n<td>The initialization message was sent successfully.<\/td>\n<\/tr>\n<tr>\n<td>Function execution took N ms, finished with status: \u2018ok\u2019<\/td>\n<td>The Cloud Function triggered with <code>all<\/code> has now completed its task.<\/td>\n<\/tr>\n<tr>\n<td>Function execution started<\/td>\n<td>The Cloud Function triggered by the initialization message now starts.<\/td>\n<\/tr>\n<tr>\n<td>{id}: Received message to start with URL {url}<\/td>\n<td>Message to start audit received.<\/td>\n<\/tr>\n<tr>\n<td>{id}: Starting browser for {url}<\/td>\n<td>A headless browser is started for the audit.<\/td>\n<\/tr>\n<tr>\n<td>{id}: Browser started for {url}<\/td>\n<td>The browser startup is successful.<\/td>\n<\/tr>\n<tr>\n<td>{id}: Starting lighthouse for {url}<\/td>\n<td>Beginning the actual audit.<\/td>\n<\/tr>\n<tr>\n<td>{id}: Lighthouse done for {url}<\/td>\n<td>Lighthouse audit complete.<\/td>\n<\/tr>\n<tr>\n<td>{id}: Browser closed for {url}<\/td>\n<td>Browser instance shut down.<\/td>\n<\/tr>\n<tr>\n<td>{id}: Writing {output} report to bucket {bucketName}<\/td>\n<td>If you\u2019ve defined the <code>output<\/code> Lighthouse flag, then a report for each filetype listed in <code>output<\/code> will now be written into your GCS bucket.<\/td>\n<\/tr>\n<tr>\n<td>{id}: Writing log to bucket {bucketName}<\/td>\n<td>The full log of the audit is written to storage, too.<\/td>\n<\/tr>\n<tr>\n<td>{id}: BigQuery job with ID {UUID} starting for {url}<\/td>\n<td>The BigQuery job starts and signals the end of the Cloud Function.<\/td>\n<\/tr>\n<tr>\n<td>Function execution took N ms, finished with status \u2018ok\u2019<\/td>\n<td>Function complete.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div style=\"aspect-ratio: 1329 \/ 659;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/log-sample.jpg\" title=\"Sample of the log\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"659\" width=\"1329\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/log-sample.jpg#ZgotmplZ\" alt=\"Sample of the log\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>If you had multiple URLs defined in the <code>source<\/code> of the configuration file, then you\u2019ll see parallel entries for all the URLs. Each URL is treated in its own Cloud Function, so there will be a lot of overlap in the logs.<\/p>\n<p>If you see permission errors then they are almost certainly because you\u2019ve either forgotten to enable the required services, or because you haven\u2019t filled in the <a href=\"https:\/\/www.simoahava.com\/google-cloud\/lighthouse-bigquery-google-cloud-platform\/#modify-the-configuration-file\">configuration file<\/a> correctly.<\/p>\n<h3 id=\"check-bigquery\">Check BigQuery<\/h3>\n<p>Now, browse to <a href=\"https:\/\/console.cloud.google.com\/bigquery\">https:\/\/console.cloud.google.com\/bigquery<\/a>. Here, click open your project in the left navigation, and choose the dataset you created. You should see a new table under it, named <code>reports<\/code>. Click it.<\/p>\n<p>In the main console, you should now see a bunch of column definitions. Above those definitions is the link titled <strong>Preview<\/strong>, so go ahead and click it. This will \u201cPreview\u201d the BigQuery data stored in the data, and thus not cost you a penny.<\/p>\n<div style=\"aspect-ratio: 1342 \/ 483;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/bigquery-check.jpg\" title=\"BigQuery check\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"483\" width=\"1342\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/bigquery-check.jpg#ZgotmplZ\" alt=\"BigQuery check\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Feel free to scroll around the columns.<\/p>\n<p>Do note that the BigQuery schema <strong>only contains those audits that have a \u201cweight\u201d in determining the total score<\/strong>. I did this purely out of convenience. I might update the schema at some point to simply contain all the audit fields, but this made more sense to start with.<\/p>\n<h3 id=\"check-cloud-storage\">Check Cloud Storage<\/h3>\n<p>Browse to <a href=\"https:\/\/console.cloud.google.com\/storage\/browser\">https:\/\/console.cloud.google.com\/storage\/browser<\/a> and click the bucket name you created earlier. You might see some <code>.appspot.com<\/code> buckets there, too. Don\u2019t worry &#8211; they were created automatically when you deployed your Cloud Function.<\/p>\n<p>You should now see new folders within the bucket &#8211; each named after an <code>id<\/code> value in your configuration file\u2019s <code>source<\/code> array. You\u2019ll also see the <code>states.json<\/code> file, which makes sure your function only runs when it\u2019s supposed to.<\/p>\n<div style=\"aspect-ratio: 967 \/ 393;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/folders-in-bucket.jpg\" title=\"Folders in bucket\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"393\" width=\"967\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/folders-in-bucket.jpg#ZgotmplZ\" alt=\"Folders in bucket\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Click one of the folders to see the contents.<\/p>\n<p>If you defined an <code>output<\/code> array in the Lighthouse flags of the configuration file, you will now see a file prefixed with <code>report_<\/code> for each file type you added to the array. Remember, there were three possible reports Lighthouse can provide you: CSV, HTML, and JSON.<\/p>\n<div style=\"aspect-ratio: 975 \/ 426;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/storage-logs.jpg\" title=\"Storage logs\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"426\" width=\"975\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/storage-logs.jpg#ZgotmplZ\" alt=\"Storage logs\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You\u2019ll also see a JSON file prefixed with <code>log_<\/code>, which will contain the full audit object for you to peruse.<\/p>\n<p>Feel free to download the files and explore them. The HTML report is especially interesting (and visually pleasing) to read.<\/p>\n<h2 id=\"things-to-note\">Things to note<\/h2>\n<h3 id=\"cloud-scheduler\">Cloud Scheduler<\/h3>\n<p>Don\u2019t forget that you have a <strong>Cloud Scheduler<\/strong> running! Periodically, it will push the <code>all<\/code> message into your Pub\/Sub topic, thus auditing each URL in your <code>source<\/code> list, writing the results into BigQuery, and storing the logs in Cloud Storage.<\/p>\n<h3 id=\"manually-trigger-the-audit\">Manually trigger the audit<\/h3>\n<p>You can always trigger the audit manually with<\/p>\n<p><code>gcloud pubsub topics publish launch-lighthouse --message all<\/code><\/p>\n<p>or, if you want to trigger an audit for a specific ID only, the command is<\/p>\n<p><code>gcloud pubsub topics publish launch-lighthouse --message <id\/><\/code><\/p>\n<p>where <code><id\/><\/code> is the ID of the URL you set in the <code>source<\/code> array of the configuration file.<\/p>\n<h3 id=\"cloud-functions-dont-have-the-best-performance\">Cloud Functions don\u2019t have the best performance<\/h3>\n<p>Unfortunately, running the headless browser in the Cloud Function is not the most efficient thing in the world. In fact, the <strong>Performance<\/strong> report (which is, for many, the most interesting one), will most likely under-report the load times quite severely.<\/p>\n<p>There\u2019s not much to do about this currently. Cloud Functions simply do not have the CPU power required to perform complex or expensive tasks like this.<\/p>\n<p>This has been identified by the developers (see <a href=\"https:\/\/github.com\/GoogleChrome\/puppeteer\/issues\/3120\">here<\/a>), and it\u2019s possible that in the near future either Cloud Functions, Puppeteer, or Google Chrome will enable the processes to run smoother and faster.<\/p>\n<h3 id=\"updating-the-configuration-file-is-a-bit-annoying\">Updating the configuration file is a bit annoying<\/h3>\n<p>Due to how the configuration file has been bundled with the Cloud Function itself, any update to the configuration file requires you to <strong>redeploy<\/strong> the Cloud Function itself. This is annoying, since deployment always takes a couple of minutes.<\/p>\n<p>I\u2019m probably going to update the application so that the configuration file is stored in a Google Cloud Storage bucket. That way when you want to update the file, all you need to do is upload it to the bucket using the Google Cloud Console user interface. And that\u2019s really fast.<\/p>\n<h3 id=\"other-ideas\">Other ideas<\/h3>\n<p>You can follow the <a href=\"https:\/\/github.com\/sahava\/multisite-lighthouse-gcp\">GitHub project<\/a> to see where things are heading. Especially the <a href=\"https:\/\/github.com\/sahava\/multisite-lighthouse-gcp\/issues\">Issues<\/a> page should be of interest, since it\u2019s essentially a to-do list for me.<\/p>\n<h2 id=\"final-thoughts\">Final thoughts<\/h2>\n<p>This was a fun exercise, and I hope you managed to complete it following the guide above. If not, please let me know in the comments where you had trouble.<\/p>\n<p>I\u2019m not too happy with the fact that Chrome is just too slow when run through a Cloud Function. It\u2019s possible that the whole thing is quite useless until the browser\u2019s performance in the sandbox is improved. I might have to add the option of using an AppEngine or Kubernetes Engine instance instead. This way you could scale the environment as much as you like to improve the accuracy of the performance audit. Naturally, this will incur some costs not present in the current setup.<\/p>\n<p>On the other hand, I love the fact that the data is being collected in BigQuery, since I can create a nice dashboard with Data Studio that shows the performance progress of the URLs I\u2019ve selected for auditing.<\/p>\n<p>Cloud Functions themselves are simply wonderful. Total game-changers. Running serverless applications with almost no cost at all, and triggering them in different ways (HTTP requests, Pub\/Sub triggers) has huge potential for the future. Thanks to some background service magic, it\u2019s so refreshing not having to worry about authentication and access control levels &#8211; the Cloud Function has all the required access levels for performing a wide variety of tasks.<\/p>\n<p>Check the Cloud Function documentation for inspiration: <a href=\"https:\/\/cloud.google.com\/functions\/docs\/tutorials\/\">https:\/\/cloud.google.com\/functions\/docs\/tutorials\/<\/a>.<\/p>\n<p>As always, please let me know in comments if you have questions about this setup, or if you have suggestions for improvement.<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Google Cloud Platform is very, very cool. It\u2019s a fully capable, enterprise-grade, scalable cloud ecosystem which lets even total novices get started with building their first cloud applications. I wrote a long guide for installing Snowplow on the GCP, and you might want to read that if you want to see how you can build [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":102059,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[16404,28608,8343,1025,4955,11405,4012],"dealstore":[],"offerexpiration":[],"class_list":["post-102058","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-audit","tag-bigquery","tag-lighthouse","tag-multiple","tag-results","tag-sites","tag-write"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Audit Multiple Sites With Lighthouse And Write Results To BigQuery - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=102058\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Audit Multiple Sites With Lighthouse And Write Results To BigQuery - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"Google Cloud Platform is very, very cool. It\u2019s a fully capable, enterprise-grade, scalable cloud ecosystem which lets even total novices get started with building their first cloud applications. I wrote a long guide for installing Snowplow on the GCP, and you might want to read that if you want to see how you can build [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=102058\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-21T19:50:59+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/lighthouse.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1413\" \/>\n\t<meta property=\"og:image:height\" content=\"462\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"21 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=102058#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=102058\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"Audit Multiple Sites With Lighthouse And Write Results To BigQuery\",\"datePublished\":\"2025-02-21T19:50:59+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=102058\"},\"wordCount\":4036,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=102058#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/lighthouse.jpg\",\"keywords\":[\"Audit\",\"BigQuery\",\"Lighthouse\",\"Multiple\",\"Results\",\"Sites\",\"Write\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=102058#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=102058\",\"url\":\"https:\/\/fivemor.com\/?p=102058\",\"name\":\"Audit Multiple Sites With Lighthouse And Write Results To BigQuery - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=102058#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=102058#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/lighthouse.jpg\",\"datePublished\":\"2025-02-21T19:50:59+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=102058#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=102058\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=102058#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/lighthouse.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/lighthouse.jpg\",\"width\":1413,\"height\":462},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=102058#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Audit Multiple Sites With Lighthouse And Write Results To BigQuery\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Audit Multiple Sites With Lighthouse And Write Results To BigQuery - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=102058","og_locale":"en_US","og_type":"article","og_title":"Audit Multiple Sites With Lighthouse And Write Results To BigQuery - Som2ny Network","og_description":"Google Cloud Platform is very, very cool. It\u2019s a fully capable, enterprise-grade, scalable cloud ecosystem which lets even total novices get started with building their first cloud applications. I wrote a long guide for installing Snowplow on the GCP, and you might want to read that if you want to see how you can build [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=102058","og_site_name":"Som2ny Network","article_published_time":"2025-02-21T19:50:59+00:00","og_image":[{"width":1413,"height":462,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/lighthouse.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"21 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=102058#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=102058"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"Audit Multiple Sites With Lighthouse And Write Results To BigQuery","datePublished":"2025-02-21T19:50:59+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=102058"},"wordCount":4036,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=102058#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/lighthouse.jpg","keywords":["Audit","BigQuery","Lighthouse","Multiple","Results","Sites","Write"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=102058#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=102058","url":"https:\/\/fivemor.com\/?p=102058","name":"Audit Multiple Sites With Lighthouse And Write Results To BigQuery - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=102058#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=102058#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/lighthouse.jpg","datePublished":"2025-02-21T19:50:59+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=102058#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=102058"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=102058#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/lighthouse.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/lighthouse.jpg","width":1413,"height":462},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=102058#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"Audit Multiple Sites With Lighthouse And Write Results To BigQuery"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/102058","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=102058"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/102058\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/102059"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=102058"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=102058"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=102058"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=102058"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=102058"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}