Monitoring Website Performance Using Lighthouse-CI
Tags: webperf, docker, javascript
Published: 2020-04-25 10:06:48


We take performance seriously in platformOS. We want our services, including our documentation site, to just fly to devices, no matter how bad the connection is.
To make sure we do not miss any performance issues/regressions, we set up monitoring after every production deploy to have a high-level overview.

## Prerequisites

Because of how our infrastructure is architected we are using the following tools:

* [Jenkins CI](https://jenkins.io) - runs code on GitHub merge
* [Docker](https://www.docker.com/) - handles isolation and dependencies needed for Lighthouse
* `kanti/lighthouse-ci` - docker image
* Slack - receives notifications

Note: You can use the same technique on any CI/CD system: GitHub Actions, CircleCI, TeamCity, Codeship, Travis CI - as long as it has Docker support. Without Docker you should use [`lighthouse-ci`](https://github.com/GoogleChrome/lighthouse-ci) directly. Read their official [Quick Start guide](https://github.com/GoogleChrome/lighthouse-ci#quick-start) for more information on that.

## Infrastructure

Our documentation site is built, tested, and deployed automatically, every time code is pushed to the master branch.

1. Code is pushed to the `master` branch
2. Jenkins starts its job
3. Assets are built
4. Code is deployed to staging
5. TestCafe tests are run on the staging environment
6a. If they pass, code is deployed to production
6b. If they fail, code is not deployed

Step 7 will test the homepage and one content page and notify us of the results on Slack.

## Code

This the final result of our work:

```shell
stage('Lighthouse') {
  when {
    expression { return params.MP_URL.isEmpty() }
    branch 'master'
  }

  agent { docker { image 'kanti/lighthouse-ci' } }

  steps {
    sh 'curl https://documentation.platformos.com -o warmup.txt'
    sh 'curl https://documentation.platformos.com/developer-guide/glossary -o warmup2.txt'

    sh 'lighthouse-ci https://documentation.platformos.com > $HOME/tmp/lighthouse-home.txt'
    sh 'lighthouse-ci https://documentation.platformos.com/developer-guide/glossary > $HOME/tmp/lighthouse-content.txt'

    script {
      lighthouseHome = sh(returnStdout: true, script: 'grep perf $HOME/tmp/lighthouse-home.txt').trim()
      lighthouseContent = sh(returnStdout: true, script: 'grep perf $HOME/tmp/lighthouse-content.txt').trim()

      slackSend (channel: "#notifications-docs", color: '#304ffe', message: "Lighthouse Home ${lighthouseHome}")
      slackSend (channel: "#notifications-docs", color: '#304ffe', message: "Lighthouse Content ${lighthouseContent}")
    }
  }
}
```

## Explanation

Some of the code you might recognize as standard Linux commands, let's go through the code piece by piece.

```shell
when {
  expression { return params.MP_URL.isEmpty() }
  branch 'master'
}
```

The code will be executed only when code was pushed to the `master` branch and the build URL was not overridden. It is helpful because sometimes we want to build from the `master` branch to a different environment for testing purposes.

```shell
agent { docker { image 'kanti/lighthouse-ci' } }
```

Everything in steps blocks execute within the context of the `kanti/lighthouse-ci` docker container.

```shell
sh 'curl https://documentation.platformos.com -o warmup.txt'
sh 'curl https://documentation.platformos.com/developer-guide/glossary -o warmup2.txt'
```

Run `curl` to those two URLs to warm up the cache after deploy, if there is any cache.

```shell
sh 'lighthouse-ci https://documentation.platformos.com > $HOME/tmp/lighthouse-home.txt'
sh 'lighthouse-ci https://documentation.platformos.com/developer-guide/glossary > $HOME/tmp/lighthouse-content.txt'
```

Run `lighthouse-ci` on given URLs and output the report to their respective text files. In this line we could also add treshold below which lighthouse-ci would use `exit 1` and fail the build. But we didn't see the need for that.

```shell
lighthouseHome = sh(returnStdout: true, script: 'grep perf $HOME/tmp/lighthouse-home.txt').trim()
lighthouseContent = sh(returnStdout: true, script: 'grep perf $HOME/tmp/lighthouse-content.txt').trim()
```

Take lines with `perf` in them and save them to a variable. In practice, this means we are throwing away scores from other audits.

If we didn't do it, we would get also accessibility, SEO and best practices scores:

```shell
performance: 81
accessibility: 100
best-practices: 100
seo: 100
pwa: 56
All checks are passing. 🎉
```

We are interested only in performance, but your requirement might be different. Note that you can specify which audits will be run when running the `lighthouse-ci` CLI.

```shell
slackSend (channel: "#notifications-docs", color: '#304ffe', message: "Lighthouse Home ${lighthouseHome}")
slackSend (channel: "#notifications-docs", color: '#304ffe', message: "Lighthouse Content ${lighthouseContent}")
```

Send the notification to a Slack channel.

## Results

Now every time we do a production deploy (automatically), we also check if we did not introduce a speed regression.

<img src="https://uploads.prod01.oregon.platform-os.com/instances/1/assets/images/best-practices/lighthouse/slack-notification.png?updated=1587666844" alt="Slack notification" loading="lazy">


## Other resources

* If you don't want to write code yourself, take a look at https://speedcurve.com/ (paid).
* If you want more information, timelines, comparisons with competition, take a look at https://www.sitespeed.io/ (open source, self-hosted).
* [Lighthouse-CI Github Action](https://github.com/treosh/lighthouse-ci-action)

You can also see [source code of our documentation](https://github.com/mdyd-dev/nearme-documentation/), including Jenkinsfile on GitHub.