This episode covers observability for Hermes apps: monitoring runtime metrics like startup, memory, and JavaScript execution, integrating them with analytics and performance tools, then setting performance budgets that can be enforced in CI.

Episode 19 closed with a CI pipeline that produces validated artifacts. But a good build isn't proof the app runs well in the field — you need observability: the ability to see what's actually happening on users' devices. Without it, every optimization is just a guess.
Episode 20's roadmap: the runtime metrics you must monitor, how to capture them in the app, integration with analytics and performance tools, then setting performance budgets that can be enforced.
Focus on the three dimensions that most often cause complaints:
As an initial reference, use the following table — the numbers should be adjusted to your own app profile:
| Metric | Target value (example) | Source |
|---|---|---|
| Cold start | below 2000ms | app marker |
| Long task | below 100ms | PerformanceObserver |
| Heap peak | below 180MB | HermesInternal |
| Bytecode size | below 4.2MB | build artifact |
The numbers above are only examples — what matters most is that metrics are measured in production, not just on the developer's emulator.
Hermes in React Native provides the modern Performance API. PerformanceObserver can monitor long tasks without instrumenting every function:
import { PerformanceObserver } from "react-native-performance";
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === "longtask" && entry.duration >= 100) {
reportMetric("long_task", entry.duration);
}
}
});
observer.observe({ type: "longtask", buffered: true });For startup, mark the important moments with performance.mark then compute the difference with performance.measure at the end:
import { performance } from "react-native-performance";
performance.mark("app:initStart");
// module initialization, state restore, first render
performance.mark("app:initEnd");
performance.measure("app:init", "app:initStart", "app:initEnd");For memory, global.HermesInternal exposes heap info that can be snapshotted periodically:
function sampleHeap() {
const info = global.HermesInternal.getHeapInfo();
reportMetric("heap_used", info.hermes_allocatedBytes ?? 0);
reportMetric("heap_size", info.hermes_heapSize ?? 0);
}
setInterval(sampleHeap, 60_000);Info
Wrap all HermesInternal access with a global.HermesInternal check so the code stays safe on platforms that don't provide it, for example when the app runs with a different engine during development.
Metrics collected on device must be sent to an analytics hub. Two common approaches:
The principle that applies to both: batch and send periodically, don't send per event. Sending hundreds of small requests disrupts the very performance you're measuring:
const queue = [];
export function reportMetric(name, value) {
queue.push({ name, value, ts: Date.now() });
}
setInterval(() => {
if (queue.length === 0) return;
const batch = queue.splice(0, queue.length);
fetch("https://analytics.kalian.app/ingest", {
method: "POST",
body: JSON.stringify({ app: "belajar-hermes", events: batch }),
}).catch(() => {});
}, 30_000);Don't forget data redaction: never include personal information in metric events — see episode 13 on runtime privacy.
A budget without enforcement is just hope. Write the budget as a file readable by both CI and the app:
{
"coldStartMs": 2000,
"ttidMs": 350,
"bundleSizeKb": 4200,
"heapPeakMb": 180,
"longTaskMs": 100
}In CI, measure the bytecode size and reject changes that exceed the bundle limit:
import fs from "node:fs";
const budget = JSON.parse(fs.readFileSync("budget.json", "utf8"));
const sizeKb = fs.statSync("index.android.hbc").size / 1024;
if (sizeKb > budget.bundleSizeKb) {
throw new Error(
`bundle ${Math.round(sizeKb)}kB melebihi budget ${budget.bundleSizeKb}kB`,
);
}
console.log("budget bundle terpenuhi");Runtime budgets (cold start, long tasks, heap) can only be enforced through aggregating production data. Set the 95th percentile as the standard, and make cross-version regressions a reason to delay a release.
Observability turns optimization from a habit into a process: metrics are measured in production, sent in aggregate, and compared against explicit budgets. If the numbers decline between releases, you know exactly when to investigate — instead of waiting for user reports.
The essentials to take home:
PerformanceObserver and HermesInternal to capture metrics without changing much code.In episode 21, we cover Scaling Teams & Release Automation: sharing build conventions, onboarding new developers to the Hermes runtime, and documenting performance and build standards. See you there!