@angular/benchpress

中文文档
0.3.0发布于 2022/02/01MITTypeScriptES Module近 30 天下载 4.1万

Benchpress - a framework for e2e performance tests

此软件包已被标记为弃用

@angular/benchpress has reached End of Life (EOL). It is no longer maintained and will not receive any further fixes or security patches.

原始使用文档
文档目录3 节
  1. A simple benchmark
  2. Measuring in the browser
  3. Custom Metrics Without Using console.time

Benchpress

Benchpress is a framework for e2e performance tests. See here for an example project.

The sources for this package are in the main Angular repo. Please file issues and pull requests against that repo.

License: MIT

Why?

There are so called "micro benchmarks" that essentially use a stop watch in the browser to measure time (e.g. via performance.now()). This approach is limited to time, and in some cases memory (Chrome with special flags), as metric. It does not allow to measure:

  • rendering time: e.g. the time the browser spends to layout or paint elements. This can e.g. used to test the performance impact of stylesheet changes.
  • garbage collection: e.g. how long the browser paused script execution, and how much memory was collected. This can be used to stabilize script execution time, as garbage collection times are usually very unpredictable. This data can also be used to measure and improve memory usage of applications, as the garbage collection amount directly affects garbage collection time.
  • distinguish script execution time from waiting: e.g. to measure the client side only time that is spent in a complex user interaction, ignoring backend calls.
  • measure fps to assert the smoothness of scrolling and animations.

This kind of data is already available in the DevTools of modern browsers. However, there is no standard way to use those tools in an automated way to measure web app performance, especially not across platforms.

Benchpress tries to fill this gap, i.e. allow to access all kinds of performance metrics in an automated way.

How it works

Benchpress uses webdriver to read out the so called "performance log" of browsers. This contains all kinds of interesting data, e.g. when a script started/ended executing, gc started/ended, the browser painted something to the screen, ...

As browsers are different, benchpress has plugins to normalizes these events.

Features

  • Provides a loop (so called "Sampler") that executes the benchmark multiple times
  • Automatically waits/detects until the browser is "warm"
  • Reporters provide a normalized way to store results:
    • console reporter
    • file reporter
    • Google Big Query reporter (coming soon)
  • Supports micro benchmarks as well via console.time() / console.timeEnd()
    • console.time() / console.timeEnd() mark the timeline in the DevTools, so it makes sense to use them in micro benchmark to visualize and understand them, with or without benchpress.
    • running micro benchmarks in benchpress leverages the already existing reporters, the sampler and the auto warmup feature of benchpress.

Supported browsers

  • Chrome on all platforms
  • Mobile Safari (iOS)
  • Firefox (work in progress)

How to write a benchmark

A benchmark in benchpress is made by an application under test and a benchmark driver. The application under test is the actual application consisting of html/css/js that should be tests. A benchmark driver is a webdriver test that interacts with the application under test.

A simple benchmark

Let's assume we want to measure the script execution time, as well as the render time that it takes to fill a container element with a complex html string.

The application under test could look like this:

index.html:

<button id="reset" onclick="reset()">Reset</button>
<button id="fill" onclick="fill()">fill innerHTML</button>
<div id="container"></div>
<script>
  var container = document.getElementById('container');
  var complexHtmlString = '...'; // TODO

  function reset() { container.innerHTML = ''; }

  function fill() {
    container.innerHTML = complexHtmlString;
  }
</script>

A benchmark driver could look like this:

// A runner contains the shared configuration
// and can be shared across multiple tests.
var runner = new Runner(...);

driver.get('http://myserver/index.html');

var resetBtn = driver.findElement(By.id('reset'));
var fillBtn = driver.findElement(By.id('fill'));

runner.sample({
  id: 'fillElement',
  // Prepare is optional...
  prepare: () {
    resetBtn.click();
  },
  execute: () {
    fillBtn.click();
    // Note: if fillBtn would use some asynchronous code,
    // we would need to wait here for its end.
  }
});

Measuring in the browser

If the application under test would like to, it can measure on its own. E.g.

index.html:

<button id="measure" onclick="measure()">Measure document.createElement</button>
<script>
  function measure() {
    console.time('createElement*10000');
    for (var i=0; i<10000; i++) {
      document.createElement('div');
    }
    console.timeEnd('createElement*10000');
  }
</script>

When the measure button is clicked, it marks the timeline and creates 10000 elements. It uses the special names createElement*10000 to tell benchpress that the time that was measured is for 10000 calls to createElement and that benchpress should take the average for it.

A test driver for this would look like this:

driver.get('.../index.html');

var measureBtn = driver.findElement(By.id('measure'));
runner.sample({
  id: 'createElement test',
  microMetrics: {
    'createElement': 'time to create an element (ms)'
  },
  execute: () {
    measureBtn.click();
  }
});

When looking into the DevTools Timeline, we see a marker as well: Marked Timeline

Custom Metrics Without Using console.time

It's also possible to measure any "user metric" within the browser by setting a numeric value on the window object. For example:

bootstrap(App)
  .then(() => {
    window.timeToBootstrap = Date.now() - performance.timing.navigationStart;
  });

A test driver for this user metric could be written as follows:


describe('home page load', function() {
  it('should log load time for a 2G connection', done => {
    runner.sample({
      execute: () => {
        browser.get(`http://localhost:8080`);
      },
      userMetrics: {
        timeToBootstrap: 'The time in milliseconds to bootstrap'
      },
      providers: [
        {provide: RegressionSlopeValidator.METRIC, useValue: 'timeToBootstrap'}
      ]
    }).then(done);
  });
});

Using this strategy, benchpress will wait until the specified property name, timeToBootstrap in this case, is defined as a number on the window object inside the application under test.

Smoothness Metrics

Benchpress can also measure the "smoothness" of scrolling and animations. In order to do that, the following set of metrics can be collected by benchpress:

  • frameTime.mean: mean frame time in ms (target: 16.6ms for 60fps)
  • frameTime.worst: worst frame time in ms
  • frameTime.best: best frame time in ms
  • frameTime.smooth: percentage of frames that hit 60fps

To collect these metrics, you need to execute console.time('frameCapture') and console.timeEnd('frameCapture') either in your benchmark application or in you benchmark driver via webdriver. The metrics mentioned above will only be collected between those two calls and it is recommended to wrap the time/timeEnd calls as closely as possible around the action you want to evaluate to get accurate measurements.

In addition to that, one extra provider needs to be passed to benchpress in tests that want to collect these metrics:

benchpress.sample(providers: [{provide: bp.Options.CAPTURE_FRAMES, useValue: true}], ... )

Requests Metrics

Benchpress can also record the number of requests sent and count the received "encoded" bytes since window.performance.timing.navigationStart:

  • receivedData: number of bytes received since the last navigation start
  • requestCount: number of requests sent since the last navigation start

To collect these metrics, you need the following corresponding extra providers:

benchpress.sample(providers: [
  {provide: bp.Options.RECEIVED_DATA, useValue: true},
  {provide: bp.Options.REQUEST_COUNT, useValue: true}
], ... )

Best practices

  • Use normalized environments

    • metrics that are dependent on the performance of the execution environment must be executed on a normalized machine
    • e.g. a real mobile device whose cpu frequency is set to a fixed value.
      • see our build script
      • this requires root access, e.g. via a userdebug build of Android on a Google Nexus device (see here and here)
    • e.g. a calibrated machine that does not run background jobs, has a fixed cpu frequency, ...
  • Use relative comparisons

    • relative comparisons are less likely to change over time and help to interpret the results of benchmarks
    • e.g. compare an example written using a ui framework against a hand coded example and track the ratio
  • Assert post-commit for commit ranges

    • running benchmarks can take some time. Running them before every commit is usually too slow.
    • when a regression is detected for a commit range, use bisection to find the problematic commit
  • Repeat benchmarks multiple times in a fresh window

    • run the same benchmark multiple times in a fresh window and then take the minimal average value of each benchmark run
  • Use force gc with care

    • forcing gc can skew the script execution time and gcTime numbers, but might be needed to get stable gc time / gc amount numbers
  • Open a new window for every test

    • browsers (e.g. chrome) might keep JIT statistics over page reloads and optimize pages differently depending on what has been loaded before

Detailed overview

Overview

Definitions:

  • valid sample: a sample that represents the world that should be measured in a good way.
  • complete sample: sample of all measure values collected so far

Components:

  • Runner

    • contains a default configuration
    • creates a new injector for every sample call, via which all other components are created
  • Sampler

    • gets data from the metrics
    • reports measure values immediately to the reporters
    • loops until the validator is able to extract a valid sample out of the complete sample (see below).
    • reports the valid sample and the complete sample to the reporters
  • Metric

    • gets measure values from the browser
    • e.g. reads out performance logs, DOM values, JavaScript values
  • Validator

    • extracts a valid sample out of the complete sample of all measure values.
    • e.g. wait until there are 10 samples and take them as valid sample (would include warmup time)
    • e.g. wait until the regression slope for the metric scriptTime through the last 10 measure values is >=0, i.e. the values for the scriptTime metric are no more decreasing
  • Reporter

    • reports measure values, the valid sample and the complete sample to backends
    • e.g. a reporter that prints to the console, a reporter that reports values into Google BigQuery, ...
  • WebDriverAdapter

    • abstraction over the used web driver client
    • one implementation for every webdriver client E.g. one for selenium-webdriver Node.js module, dart async webdriver, dart sync webdriver, ...
  • WebDriverExtension

    • implements additional methods that are standardized in the webdriver protocol using the WebDriverAdapter
    • provides functionality like force gc, read out performance logs in a normalized format
    • one implementation per browser, e.g. one for Chrome, one for mobile Safari, one for Firefox

版本与发布记录

@angular/benchpress 共发布 5 个版本,首次发布于 2016/09/14;过去 12 个月发布了 0 个版本。

版本发布日期距今
0.3.0latest2022/02/014 年前
0.2.22022/01/294 年前
0.2.12020/06/266 年前
0.2.02020/04/246 年前
0.1.02016/09/1410 年前

关于 @angular/benchpress 的常见问题

@angular/benchpress 是什么?

Benchpress - a framework for e2e performance tests注意:该包已被作者标记为弃用——@angular/benchpress has reached End of Life (EOL). It is no longer maintained and will not receive any further fixes or security patches.

如何安装 @angular/benchpress?

在项目目录运行 npm install @angular/benchpress,或使用 pnpm add / yarn add / bun add。

@angular/benchpress 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@angular/benchpress 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@angular/benchpress 可以商用吗?

@angular/benchpress 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@angular/benchpress 的下载量有多少?

根据 npm 官方下载统计,@angular/benchpress 在 2026/10/06 – 2026/11/04 这 30 天内被下载 40,926 次,日均约 1,364 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@angular/benchpress 还在维护吗?

最新版本 0.3.0 发布于 2022/02/01(4 年前);至今共发布 5 个版本,其中过去 12 个月发布了 0 个;首次发布于 2016/09/14。作者已将其标记为弃用,新项目建议寻找替代方案。