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 🙏

© 2026 – Pkg Stats / Ryan Hefner

perfy

v2.1.0

Published

A tiny, zero-dependency utility for measuring code execution time in high-resolution real time. Works in Node.js, browsers, Deno and Bun.

Readme

perfy

This module is ESM 🔆. Please read this.

A tiny, zero-dependency utility for measuring code execution time in high-resolution real time — named timers, one-shot exec() wrappers, and a rich elapsed-time result. Runs in Node.js, browsers, Deno and Bun.

import { perfy } from 'perfy';

perfy.start('loop');
// ...heavy work...
console.log(perfy.end('loop').time); // -> 1.235 (sec.)

[!NOTE] Perfy picks the most precise clock available: process.hrtime.bigint() on Node.js (exact integer nanoseconds), falling back to performance.now() elsewhere. The elapsed time is computed with integer-nanosecond math, so it never accumulates floating-point drift. Wall-clock startTime / endTime stamps come from Date.now().

Installation

npm install perfy

Usage

Call perfy.start('name') to create a timer and mark its start, then perfy.end('name') to get the elapsed-time result. By default the instance is destroyed once ended.

import { perfy } from 'perfy';

perfy.start('loop-stuff');
// ...some heavy stuff here...
const result = perfy.end('loop-stuff');
console.log(result.time); // -> 1.459 (sec.)

...or wrap the work in exec() and let Perfy time it for you:

perfy.exec('async-stuff', (done) => {
  // ...some heavy async stuff here...
  const result = done();
  console.log(result.time); // -> 1.459 (sec.)
});

The exported perfy is a shared singleton — the simplest way to use the library. When you want an isolated registry (or a custom clock, e.g. in tests), construct your own:

import { Perfy } from 'perfy';

const perfy = new Perfy();

Exports

Everything is a named export (there is no default export):

import {
  perfy,              // shared Perfy singleton — the simplest entry point
  Perfy,              // class — construct an isolated registry: new Perfy(clock?)
  PerfyItem,          // a single timing instance (advanced / typing)
  PerfyError,         // Error subclass thrown on failure, with a `.code`
  createNanoClock,    // build a NanoClock from given host objects
  defaultNanoClock    // the NanoClock selected for this environment
} from 'perfy';

import type {
  IPerfyResult,       // the elapsed-time result object
  IPerfyMeasurement,  // the disposable handle returned by measure()
  NanoClock,          // () => bigint monotonic nanosecond clock
  PerfyErrorCode,     // 'NAME_REQUIRED' | 'NO_INSTANCE' | 'NOT_STARTED' | 'INVALID_CALLBACK' | 'NO_CLOCK'
  DoneFn,             // the `done` callback passed to a callback-style exec task
  SyncTask,
  AsyncTask,
  PromiseTask,
  PerfyTask           // SyncTask | AsyncTask | PromiseTask
} from 'perfy';

API

Every method that takes a name throws a PerfyError with code NAME_REQUIRED when it is empty.

| Method | Returns | Description | | ------ | ------- | ----------- | | start(name, autoDestroy?) | Perfy | Creates a new instance under name and marks its start time. Reusing a name overwrites it. autoDestroy (default true) drops the instance when end() is called. Chainable. | | end(name) | IPerfyResult | Ends the instance and returns the elapsed-time result. If autoDestroy was left on, the instance is removed right after. Calling end() again on a kept instance returns the same cached result. Throws NO_INSTANCE if no such instance exists. | | lap(name) | IPerfyResult | Records a split — the elapsed time since the previous lap (or since start for the first) — then advances the lap marker. The instance keeps running; end() still reports the total from start. Throws NO_INSTANCE if no such instance exists. | | measure(name, onEnd?) | IPerfyMeasurement | Starts a kept instance and returns a disposable whose [Symbol.dispose] ends it — so a using declaration times its enclosing scope. The result stays retrievable via result(name); pass onEnd to receive it immediately. Disposing twice is a no-op. | | exec([name,] fn) | IPerfyResult | Promise<IPerfyResult> | Perfy | Times the execution of fn, picking the mode from the task itself. Synchronous (fn returns a non-thenable) → ended automatically, result returned. Promise-returning (fn returns a promise) → awaited, resolves to the result (a rejection is propagated). Callback-style (fn(done) declares a done argument) → must call done() to end; returns the Perfy instance immediately. Pass a name to keep the instance. Throws INVALID_CALLBACK if fn is not a function. | | result(name) | IPerfyResult | null | The stored result of a kept, ended instance — or null if it does not exist or has not ended yet. | | exists(name) | boolean | Whether an instance currently exists under name. false once an auto-destroyed instance has ended. | | names() | string[] | Names of all existing instances. | | count() | number | Number of existing instances. | | destroy(name) | Perfy | Destroys the instance under name, if any. Chainable. | | destroyAll() | Perfy | Destroys all existing instances. Chainable. |

The Result Object

end() (and exec() / result()) return an IPerfyResult — every field is a plain number/string, so the object is safe to JSON.stringify.

| Property | Type | Description | | -------- | ---- | ----------- | | name | string | Name of the instance ('' for an unnamed exec()). | | time | number | Full elapsed time in seconds (float, 3 decimals). e.g. 1.235 | | milliseconds | number | Full elapsed time in milliseconds (float). e.g. 1235.125 | | nanoseconds | number | Full elapsed time in nanoseconds. e.g. 1235125283 | | summary | string | Human-readable shorthand. e.g. 'loop: 1.235 sec.' | | startTime | number | UTC wall-clock time (ms) at start, via Date.now(). e.g. 1533302465251 | | endTime | number | UTC wall-clock time (ms) at end, via Date.now(). e.g. 1533302466486 |

Examples

Reading the elapsed time in different units:

perfy.start('metric');
// ...
const r = perfy.end('metric');
console.log(`${r.time} sec.`);          // -> 1.234 sec.
console.log(`${r.milliseconds} ms.`);   // -> 1234.567 ms.
console.log(r.summary);                 // -> metric: 1.234 sec.

Auto-destroy (default):

perfy.start('metric').count();  // -> 1
perfy.end('metric');
perfy.count();                  // -> 0 (destroyed on end)

Keep the instance (disable autoDestroy):

perfy.start('metric', false);
perfy.end('metric').time;   // -> 0.123
perfy.exists('metric');     // -> true (kept)
perfy.result('metric');     // -> the same result object

Timing a synchronous functionexec() returns the result directly:

const result = perfy.exec(() => {
  // sync work
});
console.log(result.time);

Timing a promise / async functionexec awaits it and resolves to the result:

const result = await perfy.exec('fetch', async () => {
  await fetch('https://example.com');
});
console.log(result.time);

Timing a callback-style async function — call done() when finished:

perfy.exec((done) => {
  setTimeout(() => {
    const result = done();
    console.log(result.time);
  }, 1000);
});

Laps — record splits within one running timer:

perfy.start('pipeline');
loadData();
console.log('load:', perfy.lap('pipeline').time);
transform();
console.log('transform:', perfy.lap('pipeline').time);
console.log('total:', perfy.end('pipeline').time);

Scope timing with using — the timer ends automatically when the block exits:

{
  using _ = perfy.measure('block', (r) => console.log(r.time));
  // ...work...
} // ended here

Named exec() keeps the instance for later retrieval:

perfy.exec('async-op', (done) => {
  done();
});
perfy.exists('async-op');  // -> true
perfy.result('async-op');  // -> the result object

Destroy everything:

perfy.destroyAll().count(); // -> 0

Errors

Every failure throws a PerfyError — an Error subclass carrying a stable, machine-readable code (NAME_REQUIRED, NO_INSTANCE, NOT_STARTED, INVALID_CALLBACK, NO_CLOCK):

import { perfy, PerfyError } from 'perfy';

try {
  perfy.end('never-started');
} catch (err) {
  if (err instanceof PerfyError && err.code === 'NO_INSTANCE') {
    // handle it
  }
}

Tests & Quality

100% test coverage (statements, branches, functions, lines) and a 100% Stryker mutation score, run across Node.js 22 & 24 in CI.

Changelog

See CHANGELOG.md. v2 is a breaking release (ESM-only, universal clock, streamlined result object) — the migration notes live there.

Related

  • tasktimer — An accurate timer utility for running periodic tasks on the given interval ticks or dates.

License

© 2026, Onur Yıldırım. MIT License.