# perfy

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

Latest version **2.1.0** (published 2026-07-23) · MIT license · 0 weekly downloads

## Install

```sh
npm install perfy
pnpm add perfy
yarn add perfy
bun add perfy
```

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 2.1.0 |
| Published | 2026-07-23 |
| First published | 2015-10-12 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Node | >=22 |
| Dependencies | 0 |
| Unpacked size | 49.4 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 55 |
| Author | Onur Yıldırım |
| Maintainers | onury |
| Keywords | perfy, perf, performance, measure, execution, time, timer, high-resolution, hrtime, benchmark, profile, elapsed |

## Links

- npm: https://www.npmjs.com/package/perfy
- Repository: https://github.com/onury/perfy
- Homepage: https://github.com/onury/perfy#readme
- Issues: https://github.com/onury/perfy/issues
- npm.io page: https://npm.io/package/perfy

## Alternatives

- [@js-joda/timezone](https://npm.io/package/@js-joda/timezone.md) — 383.4K weekly downloads
- [chartjs-adapter-moment](https://npm.io/package/chartjs-adapter-moment.md) — 210.8K weekly downloads
- [strftime](https://npm.io/package/strftime.md) — 171.2K weekly downloads
- [vue-flatpickr-component](https://npm.io/package/vue-flatpickr-component.md) — 115.8K weekly downloads
- [timepicker](https://npm.io/package/timepicker.md) — 51.0K weekly downloads

## Recent versions

- 2.1.0 (latest) — 2026-07-23
- 2.0.0 — 2026-07-23
- 1.1.5 — 2018-08-03
- 1.1.2 — 2016-03-22
- 1.1.0 — 2015-10-16
- 1.0.1 — 2015-10-12
- 1.0.0 — 2015-10-12

## README

# perfy

<p align="center">
  <a href="https://github.com/onury/perfy/actions/workflows/ci.yml"><img src="https://github.com/onury/perfy/actions/workflows/ci.yml/badge.svg" alt="build" /></a>
  <a href="#tests--quality"><img src="https://img.shields.io/badge/coverage-100%25-2BB150?logo=vitest&logoColor=%23FDC72B&style=flat" alt="coverage" /></a>
  <a href="https://stryker-mutator.io/docs/"><img src="https://img.shields.io/badge/mutation-100%25-2BB150?style=flat" alt="mutation score" /></a>
  <a href="https://www.npmjs.com/package/perfy"><img src="https://img.shields.io/npm/v/perfy.svg?style=flat&label=&color=%23C6234B&logo=npm" alt="version" /></a>
  <a href="https://www.npmjs.com/package/perfy"><img src="https://img.shields.io/npm/dm/perfy.svg?style=flat&color=2BB150&label=downloads" alt="downloads" /></a>
  <a href="#"><img src="https://img.shields.io/badge/dependencies-0-2BB150?style=flat" alt="zero dependencies" /></a>
  <a href="https://gist.github.com/onury/d3f3d765d7db2e8b2d050d14315f2ac7"><img src="https://img.shields.io/badge/ESM-F7DF1E?style=flat" alt="ESM" /></a>
  <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TS-3260C7?style=flat" alt="TS" /></a>
  <a href="https://github.com/onury/perfy/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/perfy.svg?style=flat&color=blue" alt="license" /></a>
</p>

> This module is **ESM** 🔆. Please [**read this**](https://gist.github.com/onury/d3f3d765d7db2e8b2d050d14315f2ac7).

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**.

```ts
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

```sh
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](#the-result-object). By default the instance is destroyed once ended.

```ts
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:

```ts
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:

```ts
import { Perfy } from 'perfy';

const perfy = new Perfy();
```

## Exports

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

```ts
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`](#errors) 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`](#the-result-object) | 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`](#the-result-object) | 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`](#the-result-object) | 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`](#the-result-object) \| `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`](#the-result-object) \| `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:**

```ts
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):**

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

**Keep the instance (disable `autoDestroy`):**

```ts
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 function** — `exec()` returns the result directly:

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

**Timing a promise / `async` function** — `exec` awaits it and resolves to the result:

```ts
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:

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

**Laps** — record splits within one running timer:

```ts
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:

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

**Named `exec()`** keeps the instance for later retrieval:

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

**Destroy everything:**

```ts
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`):

```ts
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](https://stryker-mutator.io) mutation score**, run across Node.js 22 & 24 in CI.

## Changelog

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

## Related

- [**tasktimer**](https://github.com/onury/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) License.

---
_Source: https://npm.io/package/perfy · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
