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

performance-probe

v1.4.0

Published

Lightweight, zero-dependency Express middleware for server latency profiling — p50/p95/p99, slow-request capture, event-loop lag, GC tracking.

Readme

performance-probe

Lightweight, zero-dependency Express middleware for server latency profiling.

What it gives you

| Feature | Description | |---|---| | Per-route percentiles | p50 / p95 / p99 / max / avg, sorted by p95 desc | | HTML dashboard | Visual report with color-coded health, progress bars, search/filter controls, customizable columns, and a diagnosis guide | | Search & Filtering | Instant client-side search by route paths and filtering by HTTP method or traffic status (Active, Inactive, Slow Only) | | Custom Column Views | Keeps the dashboard simple and clean by hiding percentiles (p50, p95, p99) by default. Toggle them with checkboxes anytime! | | 🧹 Reset Stats | Clear accumulated metrics directly from the dashboard UI with one click without restarting the server | | Built-in auth | API key required to access dashboard — auto-generated or via env var | | Slow-request capture | Full context: concurrent reqs, event-loop lag, memory, correlation ID | | Queue-time detection | Measures nginx/LB → app delay from X-Request-Start header | | Server diagnostics | CPU load, memory pressure, heap %, uptime — with auto-verdict | | Event-loop lag | monitorEventLoopDelay histogram, reset per slow event | | GC pause tracking | PerformanceObserver on gc entries, correlated to slow requests | | Downstream isolation | Wrap DB/API calls to see if lag is yours or a dependency | | Route pre-scanning | probe.scan(app) discovers all routes before they're hit | | Correlation ID | Auto-generates/propagates x-request-id | | Zero dependencies | Single file, no node_modules needed |

Install

npm i performance-probe

Usage

CommonJS (require)

const createProbe = require('performance-probe');

ES Modules (import)

import createProbe from 'performance-probe';
// or
import { createProbe } from 'performance-probe';

Both formats work out of the box — no config needed.

Quick Start

const express = require('express');
const createProbe = require('performance-probe');

const probe = createProbe({ slowThresholdMs: 300 });
const app = express();

// Mount FIRST — before body parsers, auth, etc.
app.use(probe.middleware);

// Dashboard endpoint (protected by API key automatically)
app.get('/latency-report', probe.reportRoute);

// ... your existing routes ...

// After all routes are defined:
probe.scan(app);

app.listen(3000);

On startup you'll see:

[probe] Dashboard key: a1b2c3d4e5f6a1b2c3d4e5f6
[probe] Access: /latency-report?key=a1b2c3d4e5f6a1b2c3d4e5f6

Authentication

The dashboard is protected by default. No one can access it without the key.

How to access

# Browser
https://yourapi.com/latency-report?key=YOUR_KEY

# API / curl
curl -H "x-probe-key: YOUR_KEY" https://yourapi.com/latency-report?format=json

Set a fixed key (recommended for production)

Set the PROBE_KEY environment variable:

PROBE_KEY=my-secret-probe-key-123 node server.js

Or pass it directly:

const probe = createProbe({
  slowThresholdMs: 300,
  reportKey: 'my-secret-probe-key-123'
});

If you don't set one, a random key is auto-generated on each restart and printed to console.

Performance Overhead

The probe adds < 0.05ms per request. Here's why:

| What it does per request | Cost | |---|---| | process.hrtime.bigint() — start timer | ~1 nanosecond | | ++inFlight — increment a counter | ~1 nanosecond | | res.on('finish', fn) — register callback | negligible | | next()your route runs normally | 0 | | Subtraction + division on finish | ~1 nanosecond | | ring.push() — write to pre-allocated typed array | O(1), no allocation |

What it does NOT do in the hot path:

  • ❌ No JSON.stringify
  • ❌ No file I/O
  • ❌ No network calls
  • ❌ No array growth or reallocation
  • ❌ No sorting (only on dashboard load)
  • ❌ No logging (only for slow requests, via setImmediate)

The probe's overhead is 6,000x smaller than a typical 300ms API response.

Nginx Setup (for queue-time detection)

Add this to your nginx location block:

proxy_set_header X-Request-Start "t=${msec}";

The probe auto-detects this header and tracks how long requests sat in nginx before reaching your app. Optional — everything else works without it.

API

createProbe(options?)

| Option | Default | Description | |---|---|---| | slowThresholdMs | 500 | Requests above this are flagged slow | | ringSize | 500 | Max samples per route (ring buffer) | | reportKey | auto-generated | API key for dashboard access (or set PROBE_KEY env var) | | reportAuth | null | Legacy: fn(req) → bool gate for report endpoint | | enableGC | true | Track GC pause durations | | enableCorrelationId | true | Auto x-request-id | | enableQueueTime | true | Detect nginx/LB queue time | | enableServerInfo | true | Include server diagnostics in full report | | instanceId | $HOSTNAME | Tag for multi-instance deploys | | onSlow | console.warn | Custom handler fn(slowCtx) |

Instance methods

| Method | Description | |---|---| | probe.middleware | Express middleware — mount first | | probe.report() | Route stats array (sorted by p95 desc) | | probe.summary() | Quick health-check object | | probe.serverInfo() | Full server diagnostics + verdict | | probe.queueReport() | Nginx/LB queue time percentiles | | probe.reportRoute | Express route handler for the dashboard | | probe.scan(app) | Pre-register all Express routes (call after routes are defined) | | probe.wrapCall(label, fn) | Returns wrapped async fn with latency tracking | | probe.downstreamReport() | Stats for wrapped downstream calls | | probe.gcReport() | Recent GC pause entries | | probe.reset() | Clear all stats |

Glossary

| Term | Meaning | |---|---| | p50 | Median — half your requests are faster than this | | p95 | 95% are faster — what your frustrated users experience | | p99 | 99% are faster — worst-case outliers | | Avg | Simple average — hides spikes, use p95 instead | | Max | Single slowest request ever recorded | | Slow Count | Requests above your threshold — each logged with full context | | In-Flight | Requests currently being processed | | Event Loop Lag | How long callbacks wait to run. High = blocking code | | Queue Time | Nginx/LB → app delay. High = nginx is the bottleneck | | RSS | Total memory your process uses. Climbing = leak | | Heap % | V8 heap fullness. >85% = GC thrashing = lag spikes | | GC Pause | Garbage collection freeze. Long pauses = memory pressure | | Load/Core | CPU load ÷ cores. >1.0 = saturated | | Verdict | Auto-diagnosis flag: HEALTHY, CPU_HIGH, CPU_SATURATED, etc. |

Diagnosis Guide

| Symptom | Check | Likely Cause | |---|---|---| | High queue time, low duration | Queue Time section | Nginx/LB overloaded — not your app | | High duration, high downstream p95 | Downstream report | DB or external API is slow | | High duration, low downstream p95 | Event loop lag | Your code is blocking | | Slow requests cluster after GC | GC report timestamps | Memory pressure / leak | | verdict: CPU_SATURATED | Server info | Server overloaded — scale up/out | | verdict: MEMORY_PRESSURE | Server info | Swapping — reduce usage or scale | | verdict: HEAP_PRESSURE | Server info | GC thrashing — memory leak likely |

Downstream Call Wrapping

const findOrder = probe.wrapCall('db:findOrder', async (id) => {
  return db.orders.findById(id);
});

// Use normally — latency tracked automatically
const order = await findOrder('order-123');

Caveats

  • In-memory only — resets on restart. Persist probe.report() on an interval for history.
  • Per-instance — in multi-pod setups, use instanceId and aggregate externally.
  • Queue time requires header — add proxy_set_header X-Request-Start "t=${msec}"; to nginx.