Performance API Reference

Actively under development

The Performance API is under active development and its surface may evolve. If you're interested in using it or have feedback, we'd love to hear from you — reach out at support@meticulous.ai.

Meticulous can feed frontend performance data into your existing monitoring systems — such as Datadog, Grafana, New Relic, or any custom analytics pipeline — so you can track how rendering speed, JavaScript execution time, and memory usage change across commits.

When Meticulous replays a recorded session, it stubs browser time and performance APIs to ensure deterministic behavior. The Performance API provides access to real, non-stubbed browser performance primitives that you can report directly to your monitoring backend.


Overview

During a Meticulous replay:

  • performance.now(), Date.now(), and other timing APIs return virtual (deterministic) values.
  • performance.memory returns fixed values.
  • performance.measureUserAgentSpecificMemory() returns fixed values.
  • PerformanceObserver is stubbed.
  • PressureObserver is stubbed.
  • setTimeout, setInterval, clearTimeout, and clearInterval schedule callbacks on virtual (deterministic) time.

The Performance API, available on window.Meticulous.replay.native, bypasses this stubbing and returns real values. This lets you measure actual frontend performance and feed it into dashboards like Datadog, Grafana, or your own monitoring system.

Frontend performance only

This API measures frontend performance only: rendering time, JavaScript execution, and memory usage.

During replays all network traffic is mocked using previously recorded responses — network requests return instantly with cached data. Do not use these APIs to measure network latency, API response times, or backend performance. Those measurements are not meaningful during a replay.


When to Use

Only collect and report metrics when isBenchmarkableReplay is true. This flag indicates the replay was executed under conditions where performance data is meaningful.

if (window.Meticulous?.replay?.isBenchmarkableReplay) {
  // Safe to collect and report performance metrics
}

Available APIs

All APIs live on window.Meticulous.replay.native.

native.performance.now()

Returns the real elapsed time in milliseconds, bypassing Meticulous's virtual time. Wraps the native Performance.now().

Returns: number

const realElapsed = window.Meticulous.replay.native.performance.now();

Use this wherever you would normally use performance.now() to measure durations:

const perf = window.Meticulous?.replay?.isBenchmarkableReplay
  ? window.Meticulous.replay.native.performance
  : window.performance;

const start = perf.now();
doExpensiveWork();
const duration = perf.now() - start;

native.performance.memory

Type: { jsHeapSizeLimit: number; totalJSHeapSize: number; usedJSHeapSize: number } | undefined

Returns actual browser memory usage, bypassing the fixed values Meticulous stubs in. Wraps the native Performance.memory.

PropertyTypeDescription
jsHeapSizeLimitnumberMaximum heap size in bytes
totalJSHeapSizenumberTotal allocated heap in bytes
usedJSHeapSizenumberCurrently used heap in bytes
const mem = window.Meticulous.replay.native.performance.memory;
if (mem) {
  console.log("Heap used:", mem.usedJSHeapSize);
}

native.performance.measureUserAgentSpecificMemory()

Type: (() => Promise<MemoryMeasurement>) | undefined

Returns a promise that resolves to a detailed, attributed breakdown of the memory used by all JavaScript realms (the page, its iframes, and its workers), bypassing Meticulous's deterministic stub. Wraps the native Performance.measureUserAgentSpecificMemory().

The resolved value has the following shape:

PropertyTypeDescription
bytesnumberTotal memory used, in bytes
breakdownArray<{ bytes: number; attribution: unknown[]; types: string[] }>Per-realm/per-type breakdown of bytes

breakdown[].types is an open-ended set that varies by browser, build, and the page itself (e.g. "JavaScript", "DOM", "Shared", "WebAssembly", "Detached", ...). A single breakdown entry can carry multiple types, so per-type buckets generally do not sum to bytes — use bytes for the canonical total.

const measure = window.Meticulous.replay.native.performance
  .measureUserAgentSpecificMemory;

if (measure) {
  const measurement = await measure();
  console.log("Total bytes:", measurement.bytes);
  for (const entry of measurement.breakdown) {
    console.log(entry.types, entry.bytes);
  }
}

Cross-origin isolation and availability

The native measureUserAgentSpecificMemory() is normally only callable from a cross-origin isolated context. Meticulous replays do not run in a cross-origin isolated context, so this API may be undefined (or throw a SecurityError when called) and you should always check for its presence and try/catch around the call.

However, depending on how Meticulous proxies and serves your application during replay, the underlying browser context may end up satisfying the security checks even when window.crossOriginIsolated is false. As a result, on some projects this function is callable and returns real measurements. Contact the Meticulous engineering team for more information about whether this API is available for your project.


native.PerformanceObserver

Type: typeof PerformanceObserver

The native PerformanceObserver constructor for observing real performance entries (e.g., navigation, resource, measure), unaffected by virtual time.

const observer = new window.Meticulous.replay.native.PerformanceObserver(
  (list) => {
    for (const entry of list.getEntries()) {
      reportMetric(entry.name, entry.duration);
    }
  }
);
observer.observe({ entryTypes: ["measure", "navigation"] });

native.PressureObserver

Type: typeof PressureObserver | undefined

The native PressureObserver constructor from the Compute Pressure API, exposed under native for symmetry with the other performance primitives.

Use it to observe the pressure state of system resources such as the CPU ("nominal", "fair", "serious", "critical") and correlate it with your frontend performance metrics.

const replay = window.Meticulous?.replay;
const PressureObserver = replay?.native.PressureObserver;

if (replay?.isBenchmarkableReplay && PressureObserver) {
  const observer = new PressureObserver((records) => {
    for (const record of records) {
      reportPressure({
        source: record.source,
        state: record.state,
        time: record.time,
      });
    }
  });

  observer.observe("cpu", { sampleInterval: 1000 }).catch((err) => {
    console.warn("Failed to observe CPU pressure", err);
  });
}

The callback receives MeticulousPressureRecord[] entries, each with a source (MeticulousPressureSource, currently "cpu"), a state (MeticulousPressureState), and a time (DOMHighResTimeStamp). Types are exported from @alwaysmeticulous/replay-browser-scripts-api as MeticulousPressureObserver, MeticulousPressureObserverConstructor, MeticulousPressureRecord, MeticulousPressureSource, and MeticulousPressureState.


native.setTimeout(), native.setInterval(), native.clearTimeout(), and native.clearInterval()

Types: Same signatures as the native setTimeout, setInterval, clearTimeout, and clearInterval functions.

During a replay, the global setTimeout and setInterval are intercepted and schedule callbacks against Meticulous's virtual time. The versions on window.Meticulous.replay.native use the real browser timer functions instead, so callbacks fire after actual wall-clock delays.

Use these when you need real-time scheduling — for example, to defer performance metric collection until after the main thread has settled:

const replay = window.Meticulous?.replay;
if (replay?.isBenchmarkableReplay) {
  replay.native.setTimeout(() => {
    reportPerformanceMetrics();
  }, 0);
}

Use with extreme care

These functions run against real wall-clock time and their callbacks are not synchronized with Meticulous's virtual event loop or screenshot timing.

Avoid any callback behaviour that has a visual impact — including DOM mutations, CSS changes, animations, scrolls, focus changes, toasts, modals, or any other UI updates. Such side effects can desynchronize the replay from its recorded state and cause visual-test failures or flaky results.

Restrict callbacks to non-visual work such as collecting metrics, sending analytics payloads, or other read-only instrumentation. Never use these timers to drive UI updates, lazy-load visible content, or trigger layout during a replay.


Metadata

When reporting metrics you'll typically want to attach context about what is being tested. Two properties on window.Meticulous.replay provide this:

commitUnderTest

Type: { sha: string; baseCommitSha: string | null; branchName: string | null; date: string | null } | undefined

PropertyTypeDescription
shastringFull commit SHA being tested
baseCommitShastring | nullBase commit SHA this test run is compared against (typically the commit on the main branch that the PR branch forked from). null when the test run is not associated with a PR, or when no base commit was specified or could be determined
branchNamestring | nullGit branch name
datestring | nullCommit date in ISO 8601 format

sessionBeingReplayed

Type: { id: string }

The ID of the recorded session being replayed.

browser

Type: { version: string }

Information about the Chrome/Chromium build that is driving the replay. Useful for tagging reported metrics so that performance dashboards can be sliced by browser version — rendering speed, JavaScript execution time, and memory usage can shift meaningfully between Chrome releases, and aggregating across versions can mask regressions.

PropertyTypeDescription
versionstringThe Chrome/Chromium version, e.g. "139.0.7258.5". The leading product label ("Chrome/", "HeadlessChrome/", ...) is stripped, so the value can be parsed directly as a dotted version number. Falls back to "unknown" only in the very rare case Meticulous could not read the version from the underlying browser.
const replay = window.Meticulous?.replay;
if (replay) {
  console.log("Replay running on Chrome", replay.browser.version);
}

Sending Data to Analytics

During replays, Meticulous intercepts and mocks network requests. To let your analytics requests pass through, add the meticulous-passthrough header set to "true":

fetch("https://analytics.example.com/metrics", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "meticulous-passthrough": "true",
  },
  body: JSON.stringify(payload),
});

Without this header, the request will be intercepted by the network stubbing layer and will not reach your analytics endpoint.


Full Example

const reportPerformanceMetrics = () => {
  const replay = window.Meticulous?.replay;
  if (!replay?.isBenchmarkableReplay) {
    return;
  }

  const elapsed = replay.native.performance.now();
  const memory = replay.native.performance.memory;
  const commit = replay.commitUnderTest;

  fetch("https://analytics.example.com/metrics", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "meticulous-passthrough": "true",
    },
    body: JSON.stringify({
      elapsedMs: elapsed,
      heapUsedBytes: memory?.usedJSHeapSize,
      heapTotalBytes: memory?.totalJSHeapSize,
      commitSha: commit?.sha,
      baseCommitSha: commit?.baseCommitSha,
      branch: commit?.branchName,
      sessionId: replay.sessionBeingReplayed.id,
      chromeVersion: replay.browser.version,
    }),
  });
};

Data Noise

Treat performance data collected from Meticulous replays as noisy. Two factors introduce variance:

  • Hardware differences: Replays may execute on different machines with different CPU, memory, and disk characteristics. Absolute numbers will vary across runs.
  • Network mocking overhead: Meticulous intercepts and mocks all network requests during replay. The mocking mechanism itself introduces some overhead that does not exist in production.

Because of this, focus on trends over time rather than individual data points. Aggregate across multiple replays and commits to identify meaningful regressions or improvements.


Requirements

  • Gate on isBenchmarkableReplay: Always check this before collecting metrics. When false, the replay conditions do not guarantee meaningful numbers and results should be discarded.
  • Do not render metrics in the UI: Displaying real performance values in the DOM will cause visual differences between runs. Report them to external systems only.
  • Native timers are for non-visual work only: native.setTimeout and native.setInterval bypass virtual time. Avoid any callback behaviour with a visual impact — DOM updates, animations, scrolls, focus changes, and similar side effects can break visual tests.
  • Use the passthrough header: Without meticulous-passthrough: "true", analytics requests will be intercepted and mocked, and your data will never reach your analytics endpoint.
  • Frontend metrics only: Rendering, JavaScript execution, and memory are meaningful. Network and backend latency are not, because all requests return mocked responses.

See Also