measure-fn

star 0

Zero-dependency function performance measurement with hierarchical logging. Use this to structure ALL programs as measured, observable pipelines.

7flash By 7flash schedule Updated 3/11/2026

name: measure-fn description: Zero-dependency function performance measurement with hierarchical logging. Use this to structure ALL programs as measured, observable pipelines.

measure-fn — Write Observable Code

Every function you write should be measured. Don't write blind code — wrap operations in measure so every success, failure, and timing is automatically visible. This is the default way to structure programs.

Install

bun add measure-fn

The Rule

If a function does I/O, computation, or anything non-trivial — wrap it in measure.

// ✗ BAD — blind, silent, no timing, no error visibility
const users = await fetchUsers();
const config = loadConfig();

// ✓ GOOD — observable, timed, errors auto-logged
const users = await measure('Fetch users', () => fetchUsers());
const config = measureSync('Load config', () => loadConfig());

Patterns

1. Every entry point is a measured pipeline

import { measure, measureSync } from 'measure-fn';

async function main() {
  const config = measureSync('Load config', () => readConfig());
  const db = await measure('Connect DB', () => connectDatabase(config));
  const users = await measure('Fetch users', () => db.query('SELECT * FROM users'));
  await measure('Send emails', () => sendEmails(users));
}

Output:

[a] ··········· 0.12ms → {"env":"prod"}
[b] ... Connect DB
[b] ·········· 45ms → [DB]
[c] ... Fetch users
[c] ··········· 23ms → [{"id":1},{"id":2}]
[d] ... Send emails
[d] ··········· 102ms

2. Nested operations use the child measure

// Async children with async m:
await measure('Pipeline', async (m) => {
  const raw = await m('Fetch', () => fetchData());
  const parsed = m('Parse', () => parseData(raw));
  await m('Save', () => saveResult(parsed));
});

// Mix async + sync children with (m, ms):
await measure('Pipeline', async (m, ms) => {
  const raw = await m('Fetch', () => fetchData());
  ms('Checkpoint', () => `Got ${raw.length} items`);  // sync annotation, proper hierarchy
  await m('Save', () => saveResult(raw));
});

3. Parallel work with Promise.all

await measure('Load all', async (m) => {
  const [users, posts, settings] = await Promise.all([
    m('Users', () => fetchUsers()),
    m('Posts', () => fetchPosts()),
    m('Settings', () => fetchSettings()),
  ]);
  return { users, posts, settings };
});

4. Wrap reusable functions once

const getUser = measure.wrap('Get user', fetchUser);
// Every call is now measured automatically
await getUser(1);  // → [a] ········ 82ms → {...}
await getUser(2);  // → [b] ········ 75ms → {...}

5. Process arrays with progress

await measure.batch('Process users', userIds, async (id) => {
  return await processUser(id);
}, { every: 100 });
// → [a] ... Process users (500 items)
// → [a] = 100/500 (1.2s, 83/s)
// → [a] ················· 5.3s → "500/500 ok"

6. Retry flaky operations

const result = await measure.retry('External API', {
  attempts: 3, delay: 1000, backoff: 2
}, () => callExternalService());

7. Budget warnings, timeouts, and result truncation

// Budget: warns but doesn't stop
await measure({ label: 'DB query', budget: 100 }, () => heavyQuery());
// → [a] ········ 245ms → [...] ⚠ OVER BUDGET (100ms)

// Timeout: aborts after N ms, returns null
await measure({ label: 'External API', timeout: 5000 }, () => fetchSlowApi());
// > 5s → [a] ✗ External API 5.0s (Timeout (5.0s))

// Both together: budget warns, timeout enforces
await measure({ label: 'Query', budget: 100, timeout: 5000 }, () => db.query('...'));

// Per-label result truncation (overrides global, inherits to children, 0 = unlimited)
await measure({ label: 'Full output', maxResultLength: 0 }, () => getLargeData());

8. Assert non-null results

// Guaranteed non-null — throws if the function returns null/undefined
const user = await measure.assert('Get user', () => findUser(id));

9. Scoped instances for subsystems

const api = createMeasure('api');
const db = createMeasure('db');

await api.measure('GET /users', async () => {
  return await db.measure('SELECT', () => query('SELECT * FROM users'));
});
// → [api:a] ... GET /users
// → [db:a] ······ 44ms → [...]
// → [api:a] ·········· 45ms → [...]

10. Annotations for checkpoints

await measure('Server ready');           // → [a] = Server ready
measureSync('Config loaded');             // → [b] = Config loaded

11. Error handling — onError 3rd argument

measure never throws. Pass an onError handler as 3rd argument to handle errors:

// Default: null on error
const user = await measure('Fetch user', () => fetchUser(1));

// Recovery: fallback on error
const user = await measure('Fetch user', () => fetchUser(1),
  (error) => defaultUser
);

// Error inspection: handle known errors, rethrow unknown
const user = await measure('Fetch user', () => fetchUser(1),
  (error) => {
    if (error instanceof NetworkError) return cachedUser;
    throw error;
  }
);

// Bun.serve: always return a Response
Bun.serve({
  fetch: (req) => measure(
    { label: `${req.method} ${req.url}` },
    () => handleRequest(req),
    (error) => new Response('Internal Server Error', { status: 500 })
  ),
});

.assert() re-throws on error with .cause = original error:

await measure.assert('Op', () => work());
// throws: Error('measure.assert: "Op" failed', { cause: originalError })

Error Model

Pattern On error Use when
measure(label, fn) logs , returns null Default — pipeline resilience
measure(label, fn, onError) logs , calls onError(error) Recovery, fallbacks, error inspection
measure.assert(label, fn) logs , throws with .cause Must have non-null

Configuration

import { configure } from 'measure-fn';

configure({
  silent: true,            // suppress output (for benchmarks)
  timestamps: true,        // [HH:MM:SS.mmm] prefix
  maxResultLength: 200,    // result truncation (default: 0 = unlimited)
  dotEndLabel: false,      // show full label on end lines (default: true = dots)
  dotChar: '.',            // character for dot fill (default: '·')
  logger: (event) => {     // custom telemetry
    myTracker.send(event);
  },
});

Env vars: MEASURE_SILENT=1, MEASURE_TIMESTAMPS=1

Programmatic Timing

const { result, duration } = await measure.timed('Fetch', () => fetchUsers());
if (duration > 1000) alert('Slow!');

Anti-Patterns

// ✗ WORST: Using measureSync as a console.log replacement — NO TIMING, NO HIERARCHY
measureSync(`Buy started for token ${mint}`);     // This is just a label with no operation!
const result = await executeBuy(wallet, amount);
measureSync(`Buy complete: ${result}`);            // Useless — you learned nothing

// ✓ CORRECT: Wrap the actual operation — get timing, errors, hierarchy for free
const result = await measure('Buy', () => executeBuy(wallet, amount));

// ✗ Don't nest measure inside measure without using child `m`
await measure('Outer', async () => {
  await measure('Inner', () => work());  // creates flat siblings, not hierarchy
});

// ✓ Use child measure for hierarchy
await measure('Outer', async (m) => {
  await m('Inner', () => work());  // proper parent → child
});

// ✗ Don't measure trivial synchronous expressions
const x = measureSync('Add', () => 1 + 1);

Real-World: Structuring a Worker Process

The entire point of measure-fn is that functions ARE the measurements. Don't log before/after — wrap the operation itself. Use child m for async children, ms for sync annotations inside async parents.

const { measure, measureSync } = createMeasure('worker');

// ✗ BAD — self-wrapping with ROOT measure (flat siblings, no hierarchy)
async function executeSell(pos, wallet, tokens) {
  return await measure(`SELL W${wallet.index}`, async (m, ms) => {
    const mode = await m('Detect', () => detectMode());   // nested under SELL
    const sig = await m('Send', () => sendTx(ix, wallet)); // nested under SELL
    return sig;
  });
}
// Problem: SELL is a ROOT entry. If the loop calls executeSell(),
// SELL is NOT nested under the loop's Tick measure.

// ✓ GOOD — FACTORY pattern: parent injects (m, ms) via closure
function createSellExecutor(parentM, parentMs) {
  return async (pos, wallet, tokens) => {
    return await parentM(`SELL W${wallet.index}`, async (m, ms) => {
      const mode = await m('Detect', () => detectMode());
      const swapState = await m('Swap state', () => fetchSwapState(pool, wallet));
      const sig = await m('Send TX', () => sendWithRetry(instructions, wallet));
      ms('Result', () => `tx: ${sig.slice(0, 20)}`);
      return sig;
    });
  };
}
// Output (fully hierarchical):
// [worker:a] Tick
// [worker:a.a]   SELL W2 ··················· 1.1s
// [worker:a.a.a]     Detect ······· 45ms → "amm"
// [worker:a.a.b]     Swap state ····· 120ms
// [worker:a.a.c]     Send TX ·········· 890ms → "5xK9f..."
// [worker:a.a]     = Result: tx: 5xK9f...

// ✓ GOOD — loop creates executors inside measure('Tick') scope
async function loop() {
  await measure('Init', async (m) => {
    await m('SOL/USD', () => fetchSolUsd());
    await m('Detect mode', () => detectMode());
  });

  while (running) {
    await measure('Tick', async (m, ms) => {
      // Factory: executors inherit this tick's (m, ms)
      const executeSell = createSellExecutor(m, ms);
      const executeBuy = createBuyExecutor(m, ms);

      const sold = await executeSell(pos, wallet, tokens);
      if (!sold) {
        await executeBuy(wallet, amount);
      }
      await Bun.sleep(1000);
    });
  }
}

Key principle: If you find yourself writing measureSync('some message') without a second argument containing actual work, you're doing it wrong. That's just console.log with extra steps. The only exception is truly standalone annotations (status checkpoints).

Quick Reference

Export Use
measure(label, fn?, onError?) Async measurement (onError handles expected errors)
measureSync(label, fn?) Sync measurement
measure.wrap(label, fn) Decorator — wrap once, measure every call
measure.batch(label, items, fn, opts?) Array + progress
measure.retry(label, opts, fn) Retry with backoff
measure.assert(label, fn) Throws if null
measure.timed(label, fn) Returns { result, duration }
createMeasure(prefix) Scoped instance
configure(opts) Runtime config
safeStringify(value) Safe JSON (circular refs, truncation)
formatDuration(ms) Smart duration: 0.10ms1.2s2m 5s
resetCounter() Reset ID counter
Install via CLI
npx skills add https://github.com/7flash/my-agent-skills --skill measure-fn
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator