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

@rnx-kit/tools-performance

v0.1.2

Published

EXPERIMENTAL - USE WITH CAUTION - tools-performance

Readme

@rnx-kit/tools-performance

Build npm version

Lightweight performance tracing and reporting for Node.js tooling. Provides a simple API for measuring the duration of synchronous and asynchronous operations, categorizing them by domain with frequency-based filtering, and printing a summary table on process exit.

Motivation

Build tools like Metro bundlers, dependency resolvers, and transformers benefit from visibility into where time is spent. This package provides a low-overhead way to instrument code, collect timing data across domains, and produce a human-readable report — without adding heavy dependencies.

The API is split into two roles:

  • Instrumenting — library and tool authors add trace points to their code. Instrumentation is inert until tracking is enabled.
  • Enabling and reporting — the application entry point turns on tracking and controls how results are displayed.

Installation

yarn add @rnx-kit/tools-performance --dev

or if you're using npm

npm add --save-dev @rnx-kit/tools-performance

Instrumenting Code

Instrumentation adds trace points to functions you want to measure. When tracking is not enabled, trace calls are zero-cost passthroughs via nullTrace.

Using the module-level API

The simplest approach uses getTrace, which returns a trace function scoped to a domain. If the domain is not enabled, it returns nullTrace — a passthrough that calls the function directly with no recording overhead.

import { getTrace } from "@rnx-kit/tools-performance";

const trace = getTrace("metro");

// Trace a sync function
const config = trace("parse", () => parseConfig(configPath));

// Trace an async function
const bundle = await trace("bundle", async () => {
  return await buildBundle(entryPoint);
});

// Pass arguments directly — types are checked against the function signature
const resolved = trace("resolve", resolveModule, specifier, context);

Using a domain directly

For more control, use getDomain to access the PerfDomain object. This lets you check frequency levels and conditionally set up extra instrumentation.

import { getDomain } from "@rnx-kit/tools-performance";

const domain = getDomain("resolve");

if (domain?.enabled("high")) {
  // set up extra high-frequency instrumentation
}

const trace = domain?.getTrace("high") ?? nullTrace;
trace("lookup", () => resolveModule(specifier));

Frequency levels

Three hierarchical levels control tracing granularity:

  • "low" — Always recorded when tracing is enabled
  • "medium" — Recorded when frequency is "medium" or "high" (default)
  • "high" — Only recorded when frequency is "high"
// This trace only records if the domain's frequency is "high"
const trace = getTrace("resolve", "high");

Null implementations

nullTrace is a no-op that calls the wrapped function directly. It is useful as a default when tracing may not be enabled:

import { getTrace, nullTrace } from "@rnx-kit/tools-performance";

// getTrace returns nullTrace when the domain is not enabled
const trace = getTrace("metro");

// Or use it explicitly as a fallback
const myTrace = someCondition ? customTrace : nullTrace;

Manual event timing

Use startEvent on a domain to manually control the start and end of a timed event. This is useful when start and end points don't wrap a single function call:

import { getDomain } from "@rnx-kit/tools-performance";

const domain = getDomain("metro");
if (domain) {
  const endEvent = domain.startEvent("resolve");
  // ... do work across multiple steps ...
  endEvent(); // records the duration
}

Custom trace functions

Use createTrace with a TraceRecorder to build trace functions backed by custom recording logic. The recorder is called twice per event — once before (returning a handoff value) and once after (receiving it back):

import { createTrace } from "@rnx-kit/tools-performance";
import type { TraceRecorder } from "@rnx-kit/tools-performance";

const recorder: TraceRecorder<number> = (tag, handoff?) => {
  if (handoff !== undefined) {
    console.log(`${tag} took ${performance.now() - handoff}ms`);
  }
  return performance.now();
};

const trace = createTrace(recorder);
trace("work", () => doExpensiveWork());

Enabling and Reporting

The application entry point controls which domains are tracked and how results are reported. Instrumented code is inert until trackPerformance is called.

Quick start

import { trackPerformance, reportPerfData } from "@rnx-kit/tools-performance";

// Enable all domains with in-memory timing
trackPerformance({ strategy: "timing" });

// ... run instrumented code ...

// Print the report (also prints automatically on process exit)
reportPerfData();

Output:

┌──────────────┬───────┬───────┬───────┐
│ operation    │ calls │ total │   avg │
├──────────────┼───────┼───────┼───────┤
│ metro: parse │     1 │    12 │    12 │
│ metro: bundl │     1 │   450 │   450 │
└──────────────┴───────┴───────┴───────┘

Controlling what is tracked

// Enable all domains
trackPerformance({ strategy: "timing" });

// Enable specific domains
trackPerformance({ enable: "metro" });
trackPerformance({ enable: ["resolve", "transform"] });

// Calls are additive — all three domains above are now enabled

Subdomains

Subdomains create a parent–child relationship between domains. When a parent domain is enabled, all its registered subdomains are automatically enabled too. This is useful for organizing related operations under a single toggle.

import { registerSubdomain, getDomain } from "@rnx-kit/tools-performance";

// Register "metro:resolver" as a subdomain of "metro"
registerSubdomain("metro", "resolver");

// Enabling "metro" now also enables "metro:resolver"
trackPerformance({ enable: "metro", strategy: "timing" });

const domain = getDomain("metro:resolver"); // enabled via parent

Subdomains can be registered before or after the parent is enabled — the relationship is resolved in either order.

Checking if tracing is enabled

isTraceEnabled checks domain and frequency without creating a domain as a side effect:

import { isTraceEnabled } from "@rnx-kit/tools-performance";

if (isTraceEnabled("metro")) {
  // domain is enabled
}

if (isTraceEnabled("metro", "high")) {
  // domain is enabled at "high" frequency
}

Using PerfTracker directly

For more control over lifecycle, use PerfTracker directly instead of the module-level API. Each tracker manages its own set of domains and registers a process exit handler automatically.

import { PerfTracker } from "@rnx-kit/tools-performance";

const tracker = new PerfTracker({
  enable: "metro",
  strategy: "timing",
  reportColumns: ["name", "calls", "total", "avg"],
  reportSort: ["total"],
  maxNameWidth: 40,
});

const domain = tracker.domain("metro");
const trace = domain.getTrace();
await trace("bundle", buildBundle, entryPoint);

// Stop tracking and print the report
tracker.finish();

Tracing strategies

| Strategy | Description | | ---------- | --------------------------------------------------------------------------------------------------------------------------- | | "timing" | Records times in memory. Lower overhead, suitable for high-frequency events. Reports to console on process exit by default. | | "node" | Uses performance.mark and performance.measure. Higher overhead, but integrates with Node.js performance tooling. |

Metro Integration

createPerfLoggerFactory returns a factory compatible with Metro's unstable_perfLoggerFactory config option. It bridges Metro's performance logging into the tools-performance domain system under the "metro" parent domain.

import {
  createPerfLoggerFactory,
  trackPerformance,
} from "@rnx-kit/tools-performance";

// Enable the metro domain
trackPerformance({ enable: "metro", strategy: "timing" });

// Pass the factory to Metro config
module.exports = {
  unstable_perfLoggerFactory: createPerfLoggerFactory(),
};

When Metro calls the factory, it creates subdomains like metro:start_up, metro:bundling_request, and metro:hmr. Metro's subSpan calls create deeper subdomains (e.g. metro:start_up:resolver). Metro's point events with _start/_end suffixes are mapped to timed events via startEvent.

When the "metro" domain is not enabled, the factory returns no-op loggers with zero overhead.

API Reference

Module-Level Functions

| Function | Description | | -------------------------------------- | ----------------------------------------------------------------------- | | trackPerformance(config?) | Enable tracking. Config controls domains, strategy, and report options. | | getTrace(domain, frequency?) | Get a trace function for a domain. Returns nullTrace if not enabled. | | getDomain(name) | Get the PerfDomain for a domain, or undefined if not enabled. | | isTraceEnabled(domain, freq?) | Check if tracing is enabled for a domain and optional frequency. | | registerSubdomain(domain, subdomain) | Register a subdomain under a parent. Enabled when the parent is. | | reportPerfData() | Finish tracking and print the performance report. | | createPerfLoggerFactory() | Create a Metro-compatible unstable_perfLoggerFactory. |

PerfTracker

| Member | Description | | -------------------------------- | ---------------------------------------------------------------------- | | new PerfTracker(config?) | Create a new tracker. Auto-registers a process exit handler. | | enable(domain) | Enable tracking for true (all), a string, or string array. | | isEnabled(domain, freq?) | Check if a domain is enabled, optionally at a given frequency. | | domain(name) | Get or create a PerfDomain for an enabled domain. | | registerSubdomain(domain, sub) | Register a subdomain. Enabling the parent enables the subdomain. | | finish(processExit?) | Stop all domains, print the report, and unregister. Only reports once. | | updateConfig(config) | Merge new configuration values. |

PerfDomain

| Member | Description | | ------------------------ | ---------------------------------------------------------------------- | | name | Domain name (readonly). | | strategy | Tracing strategy: "timing" or "node" (readonly). | | frequency | Current frequency level (mutable). | | start() | Begin domain-level timing (called automatically unless waitOnStart). | | stop(processExit?) | End domain-level timing and clean up marks. | | startEvent(tag, freq?) | Start a timed event. Returns a function to call when the event ends. | | enabled(frequency?) | Check if a frequency level is active for this domain. | | getTrace(frequency?) | Get a trace function, or nullTrace if frequency is not active. |

Trace Primitives

| Function | Description | | -------------------------- | ---------------------------------------------------- | | createTrace(recorder) | Create a trace function backed by a TraceRecorder. | | nullTrace(tag, fn, args) | No-op trace — calls fn(...args) directly. |

PerformanceOptions

| Field | Type | Default | Description | | --------------- | ----------------------------- | -------------------------------- | ----------------------------------------------------- | | enable | true \| string \| string[] | true | Domains to enable tracking for. | | strategy | "timing" \| "node" | "node" | Tracing strategy. | | frequency | "low" \| "medium" \| "high" | "medium" | Default event frequency level. | | waitOnStart | boolean | false | Don't auto-start domain timing on creation. | | reportColumns | PerfReportColumn[] | ["name","calls","total","avg"] | Columns to display in the report. | | reportSort | PerfReportColumn[] | insertion order | Columns to sort by, in precedence order. | | showIndex | boolean | false | Show row index in the report. | | maxNameWidth | number | 50 | Max width for operation names (truncated with ...). | | reportHandler | (report: string) => void | console.log | Function that receives the formatted report. |