Skip to main content

Introduction

touchstone is a metadata-driven benchmarking framework, built on top of benchmark.js

Declarative Benchmarks#

With touchstone you define your benchmark like this:

import { Suite, Case } from '@pebula/touchstone';
@Suite({ name: 'My First Benchmark Suite' })
class MyFirstBenchmarkSuite {
@Case({ name: 'my-first-benchmark' })
firstBenchmark() {
/* Benchmarking... */
}
@Case()
async secondBenchmark() {
// Will automatically detect that it's async. Name is taken from method name.
/* Benchmarking... */
}
}

Life Cycle Events#

All benchmark.js event are wrapped and delivered in a normalized manner:

@Suite()
class TestSuite {
@OnStart() start(event: SuiteStartEvent) { }
@OnCaseComplete() caseComplete(event: CaseCompleteEvent) { }
@OnComplete() complete(event: SuiteCompleteEvent) { }
// @OnReset, @OnAbort, @OnError
}
info

There are additional, touchstone specific, events...

Composition & Re-use#

touchstone is fully extensible through inheritance or composition (mixins):

import { Suite, Case, Mixin, VegaLiteReporter, SimpleConsoleReporter } from '@pebula/touchstone';
@Suite({ name: 'My First Benchmark Suite' })
class MyFirstBenchmarkSuite extends Mixin(SimpleConsoleReporter, VegaLiteReporter) {
@Case({ name: 'my-first-benchmark' })
firstBenchmark() {
/* Benchmarking... */
}
}

In the example above we Mixin reporting behavior from 2 built-in reporters:

  • SimpleConsoleReporter - Will log progress to the console
  • VegaLiteReporter - Will output HTML, SVG and PNG charts using vega-lite

TouchStone Events#

There are 2 touchstone events:

  • @OnTouchStoneStart() - Fired with the TouchStoneStartEvent event context parameter
  • @OnTouchStoneEnd() - Fired with the TouchStoneEndEvent event context parameter (which contains the SuiteResult[] property)

Both events can be registered on any suite.

Multiple Suites#

You can declare multiple suite's, touchstone will execute them one after the other.

tip

Because multiple suites can be used for a single run it might not make sense to register mixins on the suite. For suite events this might be ok but the touchstone start/end events will trigger multiple times, once for every suite.

For this scenario, and in general, we recommend using a container to manage all mixins, configuration, etc... The container events will invoke once for all of the events in the system. (i.e. A case complete event, from any suite, will fire once on the container)

Execute#

To execute the suite/s and start benchmarking you need to invoke the touchStone() function.

import { Suite, Case, touchStone } from '@pebula/touchstone';
@Suite({ name: 'My First Benchmark Suite' })
class MyFirstBenchmarkSuite {
@Case({ name: 'my-first-benchmark' })
firstBenchmark() {
/* Benchmarking... */
}
@Case()
async secondBenchmark() {
// Will automatically detect that it's async. Name is taken from method name.
/* Benchmarking... */
}
}
await touchStone();
tip

When using a touchstone configuration container you don't need to call touchStone(), the benchmark will automatically execute.

Demo#

The following is the output of the a demo benchmark application: benchmark-chart