Performance
App launch, screen load, network timing and frame metrics — the tiered collector model, custom traces, and how to control overhead.
Performance monitoring is tiered by cost, not on/off. Cheap event-driven collectors run by default; expensive continuous ones are sampled.
The two tiers#
| Tier | Collectors | Default | Cost |
|---|---|---|---|
| Tier 1 | App launch, screen load, network, UI-hang watchdog | All on | Event-driven — fires only when something happens |
| Tier 2 | Frame metrics, JS stall | On, sampled at 0.1 | Continuous — measures every frame |
Tier 1 is effectively free: each collector emits once per launch, per navigation, per request, or only when a real hang occurs. Tier 2 runs continuously while active, which is why it is sampled per session rather than per event — a session either participates or it does not, so a sampled trace is internally consistent.
await ScaleBun.init({ projectId: '…', publishableKey: '…', apiBaseUrl: '…', performance: { enabled: true, tier1: { appLaunch: true, screenLoad: true, network: true, uiHang: true }, tier2: { enabled: true, sampleRate: 0.1 }, },});Custom traces#
Measure a span your app cares about — a checkout, a sync, an image pipeline:
const traceId = ScaleBun.performance.startTrace('checkout');
ScaleBun.performance.addTraceSpan(traceId, 'validate_cart', 42);ScaleBun.performance.addTraceMeasurement(traceId, 'cart_items', 3, 'count');
ScaleBun.performance.stopTrace(traceId);startTrace returns null when performance monitoring is inactive — not entitled,
disabled, or before init() resolved. Guard on it rather than assuming a string:
const traceId = ScaleBun.performance.startTrace('checkout');if (traceId) { // … ScaleBun.performance.stopTrace(traceId);}// Throws nothing, but silently attaches spans to a trace that does not exist.const traceId = ScaleBun.performance.startTrace('checkout')!;ScaleBun.performance.addTraceSpan(traceId, 'step', 10);Always stop a trace you start. An unstopped trace never reports, so a missing
stopTrace on an error path shows up as missing data rather than as a slow trace —
put it in a finally.
Screen load#
If you set the navigation ref, screen-load timing is automatic. Mark it manually when a screen's useful content arrives later than its mount — a list that renders a skeleton first, for example:
ScaleBun.performance.markScreenLoadStart('ProductList');await loadProducts();ScaleBun.performance.markScreenLoadEnd('ProductList');This is the difference between measuring "the screen appeared" and "the screen became useful", and the second is what your users experience.
Checking state#
ScaleBun.performance.isActive(); // false when disabled or not entitledNative module requirement#
The UI-hang watchdog and Tier-2 collectors need the native module, so they are unavailable in Expo Go. Use a development build. Tier-1 network and screen-load timing work without it.
Related#
Full performance namespace reference — all 10 members.
Configuration — the
performanceflag block.Session replay — correlating a slow trace with what the user saw.