npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

benchmrk

v0.1.3

Published

Benchmarking using the Performance Measurement API

Downloads

19

Readme

test-workflow Coverage Status npm bundle size

:stopwatch: benchmrk

Benchmarking in Node.js, using the User Timing API

Install

npm i benchmrk

Usage

Example:

Run 2 tasks, each one for 3 times, then log a report describing the duration of each run:

import Benchmrk from 'benchmrk'

const runner = new Benchmrk()

await runner.run([
  {
    name: 'Task A',
    cycles: 3,
    fn: function() {
      slowFunctionFoo()
      slowFunctionBar()
    }
  },

  {
    name: 'Task B',
    cycles: 3,
    fn: async function() {
      await slowAsyncFunctionBaz()
    }
  }
])

runner.toTimeline()

outputs a timeline of the task cycles with the duration in milliseconds:

┌─────────┬──────┬───────────┐
│    type │ name │ duration  │
├─────────┼──────┼───────────┤
│ Task: A │      │           │
│         │      │           │
│   cycle │  A 1 │ 321.44 ms │
│   cycle │  A 2 │ 250.95 ms │
│   cycle │  A 3 │ 212.20 ms │
│         │      │           │
│ Task: B │      │           │
│         │      │           │
│   cycle │  B 1 │ 255.61 ms │
│   cycle │  B 2 │ 121.10 ms │
│   cycle │  B 3 │ 193.12 ms │
│         │      │           │
└─────────┴──────┴───────────┘

or output as a histogram:

// ... rest of code

runner.toHistogram()

which outputs:

┌───────────┬───────┬─────────┬─────────┬─────────┬─────────────────────┐
│      name |   min │     max │    mean │    50 % │    99 % │ deviation │
├───────────┼───────┼─────────┼─────────┼─────────┼─────────┼───────────┤
│     tasks │       │         │         │         │         │           │
│           │       │         │         │         │         │           │
│    Task A |  1 ms │  3.2 ms │ 2.13 ms │ 2.01 ms │ 2.10 ms │ 0.29 ms   │
│    Task B │  2 ms │  3.1 ms │ 2.66 ms │ 2.44 ms │ 2.60 ms │ 0.07 ms   │
│           │       │         │         │         │         │           │
│     entry │       │         │         │         │         │           │
│           │       │         │         │         │         │           │
│ mem-usage │       │ 11.2 mb │ 36.3 mb │ 22.1 mb │ 21.2 mb │ 19.2 mb   │
└───────────┴───────┴─────────┴─────────┴─────────┴─────────┴───────────┘

Defining a task

runner.run(tasks) accepts an array of tasks.

Each task is an object with the following properties:

| property | type | description | required | |--------- |------------ |-------------------------------- |---------- | | name | String | the task name | yes | | cycle | Number | how many times to run the task | yes | | fn | Function | the task function | yes |

Example:
// A task

{
  name: 'Task A',
  cycles: 10,
  fn: function() {
    // will run 10 times
  }
}

Capturing measurements

The durations of each task cycle are captured and displayed automatically.

You might also need to capture the durations of specific functions/steps within each task, so you can figure out where most of the time is spent.

For these use-cases, you can use these Performance Measurement methods:

Using performance.timerify

performance.timerify wraps a function within a new function that measures the running time of the wrapped function.

Example

Tracking the duration of fetch and save functions:

assume fetch/save are real functions to get/save a user from/to a database

// normal functions:
const fetch = () => new Promise(res => setTimeout(res, 50))
const save = () => new Promise(res => setTimeout(res, 50))

// timerify both of them:
const fetchTimerified = performance.timerify(fetch)
const saveTimerified = performance.timerify(save)


const runner = new Benchmrk()

await runner.run([
  {
    name: 'A',
    cycles: 3,
    fn: async () => {
      // use timerified function:
      const user = await fetchTimerified('foo')

      user.updateName('bar')

      // use timerified function:
      await saveTimerified(user)
    }
  },

  {
    name: 'B',
    cycles: 2,
    fn: async () => {
      const user = new User('bar')

      user.updateName('baz')

      // use timerified function:
      await saveTimerified(user)
    }
  }
])

runner.toTimeline()

which outputs:

┌──────────┬───────┬───────────┐
│     type │  name │ value     │
├──────────┼───────┼───────────┤
│  Task: A │       │           │
│          │       │           │
│    cycle │   A 1 │  36.36 ms │
│ function │ fetch │  31.12 ms │
│ function │  save │   5.24 ms │
│          │       │           │
│    cycle │   A 2 │ 189.12 ms │
│ function │ fetch │  80.03 ms │
│ function │  save │ 109.09 ms │
│          │       │           │
│  Task: B │       │           │
│          │       │           │
│    cycle │   B 1 │ 111.70 ms │
│ function │  save │  40.43 ms │
│          │       │           │
│    cycle │   B 2 │ 225.74 ms │
│ function │  save │   8.18 ms │
│          │       │           │
│    cycle │   B 3 │  98.79 ms │
│ function │  save │   8.18 ms │
└──────────┴───────┴───────────┘

Using performance.measure

Use performance.measure to capture a time measurement between two marks, set via performance.mark.

Example

Measure the duration between mark a and mark b:

const runner = new Benchmrk()

await runner.run([
  {
    name: 'A',
    cycles: 3,
    fn: async ({ cycle, taskname }) => {
      const user = new User()

      // start mark
      performance.mark('a')

      await user.greet()
      await save(user)

      // end mark
      performance.mark('b')

      // measure duration between `a`-`b`
      performance.measure('a-b', 'a', 'b')
    }
  },

  {
    name: 'B',
    cycles: 2,
    fn: async ({ cycle, taskname }) => {
      const user = new User()

      await save(user)
    }
  }
])

runner.toTimeline()

which outputs:

┌──────────┬───────┬───────────┐
│     type │  name │ duration  │
├──────────┼───────┼───────────┤
│  Task: A │       │           │
│          │       │           │
│    cycle │   A 1 │ 141.71 ms │
|  measure │   a-b │ 120.20 ms │
|          |       |           |
│    cycle │   A 2 │ 125.74 ms │
|  measure │   a-b │  89.18 ms │
│          │       │           │
│    cycle │   A 3 │  98.79 ms │
|  measure │   a-b │  44.35 ms │
│          │       │           │
|  Task: B |       |           |
|          |       |           |
│    cycle │   B 1 │ 100.64 ms │
│    cycle │   B 2 │  90.12 ms │
│    cycle │   B 3 │  76.88 ms │
│          │       │           │
└──────────┴───────┴───────────┘

There's additional ways of using performance.measure which can be found in the Performance:Measure docs.

Measuring arbitrary values

Measure arbitrary, non-duration values by passing a detail object to performance.mark:

performance.mark('user-size', { detail: { value: 10, unit: 'bytes' } })

The passed object should have the following properties:

| property | type | description | required | |--------- |------------ |----------------- |----------- | | value | Number | tracked value | yes | | unit | String | label for value | yes |

Example

Capturing memory usage at the end of each cycle, then displaying the cumulative min/mean/max values:

await runner.run([
  {
    name: 'A',
    cycles: 5,
    fn: async () => {
      const user = new User('foo')

      await save(user)

      performance.mark('mem-usage', {
        detail: {
          value: process.memoryUsage().heapUsed / 1000 / 1000,
          unit: 'mb'
        }
      })
    }
  },

  {
    name: 'B',
    cycles: 10,
    fn: async () => {
      const user = new User('bar')

      await save(user)

      performance.mark('mem-usage', {
        detail: {
          value: process.memoryUsage().heapUsed / 1000 / 1000,
          unit: 'mb'
        }
      })
    }
  }
])

runner.toHistogram()

which outputs:

┌───────────┬───────┬─────────┬─────────┬─────────┬─────────────────────┐
│      name |   min │     max │    mean │    50 % │    99 % │ deviation │
├───────────┼───────┼─────────┼─────────┼─────────┼─────────┼───────────┤
│     tasks │       │         │         │         │         │           │
│           │       │         │         │         │         │           │
│    Task A |  1 ms │  3.2 ms │ 2.13 ms │ 2.01 ms │ 2.10 ms │ 0.29 ms   │
│    Task B │  2 ms │  3.1 ms │ 2.66 ms │ 2.44 ms │ 2.60 ms │ 0.07 ms   │
│           │       │         │         │         │         │           │
│     entry │       │         │         │         │         │           │
│           │       │         │         │         │         │           │
│ mem-usage │ 11 mb | 51.2 mb │ 36.3 mb │ 22.1 mb │ 21.2 mb │ 19.2 mb   │
└───────────┴───────┴─────────┴─────────┴─────────┴─────────┴───────────┘

Displaying results

There are different ways of visualising measurements.

  • A timeline
  • A histogram
  • An ASCII chart of max-durations
Example

Displaying the output as a timeline:

const runner = new Benchmrk()

await runner.run(tasks)

runner.toTimeline()

// or:

// runner.toHistogram()
// runner.toEntries()
// runner.toPlots()

runner.toTimeline()

Displays a detailed breakdown of each cycle, for each task as a timeline:

┌──────────┬───────┬───────────┐
│     type │  name │ value     │
├──────────┼───────┼───────────┤
│  Task: A │       │           │
│          │       │           │
│    cycle │   A 1 │ 36.36 ms  │
│ function │  save │ 36.12 ms  │
│          │       │           │
│    cycle │   A 2 │ 189.33 ms │
│ function │  save │ 189.09 ms │
│          │       │           │
│  Task: B │       │           │
│          │       │           │
│    cycle │   B 1 │ 121.03 ms │
│ function │  save │  40.43 ms │
│ function │ greet │  80.60 ms │
│          │       │           │
│    cycle │   B 2 │ 235.08 ms │
│ function │  save │ 145.08 ms │
│ function │ greet │  90.00 ms │
│          │       │           │
│    cycle │   B 3 │ 165.00 ms │
│ function │  save │ 100.00 ms │
│ function │ greet │  61.00 ms │
│          │       │           │
└──────────┴───────┴───────────┘

runner.toHistogram()

Produces a histogram with min/mean/max and percentiles for each measurement.

┌──────────────┬───────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────┐
│         name │ count │     min │     max │    mean │    50 % │    99 % │ dev │
├──────────────┼───────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────┤
│        tasks │       │         │         │         │         │         │     │
│              │       │         │         │         │         │         │     │
│       Task A │     5 │ 0.04 ms │ 0.29 ms │ 0.17 ms │ 0.04 ms │ 0.29 ms │ 0   |
│       Task B │    10 │ 0.05 ms │ 0.07 ms │ 0.06 ms │ 0.05 ms │ 0.07 ms │ 0   │
│              │       │         │         │         │         │         │     │
│        entry │       │         │         │         │         │         │     │
│              │       │         │         │         │         │         │     │
│ memory-usage │    15 │ 11.2 mb │ 36.3 mb │ 22.1 mb │ 21.2 mb │   19 mb │ 12  │
└──────────────┴───────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────┘

runner.toEntries()

Returns an array of all recorded PerformanceEntry entries of each task, as JSONs.

console.log(runner.toEntries())

[
  {
    name: 'Task A',
    entries: [
      {
        name: 'fetch',
        entryType: 'function',
        startTime: 112.391,
        duration: 0.00533300000000736,
      },

      {
        name: 'save',
        entryType: 'function',
        startTime: 129.125,
        duration: 0.00851780000000122,
      },

      // more "Task A" entries ...
    ]
  },

  {
    name: 'Task B',
    entries: [
      {
        name: 'save',
        entryType: 'function',
        startTime: 160.221,
        duration: 0.00237910000000368,
      },
      {
        name: 'foo',
        entryType: 'mark',
        startTime: 201.539,
        duration: 0
      },

      // more "Task B" entries ...
    ]
  }
]

runner.toPlots()

Draws an ASCII chart of max durations for each task.

The chart displays the duration of each cycle and any timerified functions:

                                Task: "B"

                                      - - - main task   --- fn:save
durations (ms)  

457.00 ┤                                                            
416.80 ┤                                             
376.60 ┼- - - - - - - ─╮                                                      
336.40 ┤               │                                                        
296.20 ┤               ╰- - - - - - - - ╮                                              
256.00 ┤                                |                                               
215.80 ┤                                |                               
175.60 ┤                                |            
135.40 ┤──────────────────────────────────────────────────────────────╮                
95.20  ┤                                |                             │                
55.00  ┼                                |                             |
 ───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬
    0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  17  18     
                                                                   cycles

Current cycle info

The fn function is passed the following parameters:

| name | type | description | |----------- |------------ |------------------ | | cycle | Number | The current cycle | | taskname | String | The task name |

which gives you access to the current cycle:

const runner = new Benchmrk()

await runner.run([
  {
    name: 'A',
    cycles: 3,
    fn: async ({ cycle, taskname }) => {
      console.log(cycle)

      // '1' for 1st cycle
      // '2' for 2nd cycle
      // '3' for 3rd/last cycle

      console.log(taskname)

      // 'A'
    }
  },

  // entries ...
])

Test

Install deps

npm ci

Run unit tests

npm test

Generate test-coverage

npm run lcov

Authors

@nicholaswmin

License

MIT "No attribution" License

Copyright 2024
Nicholas Kyriakides

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.