Learn Hermes JS Engine - Observability & Performance Monitoring
Episode 20 of 23

Learn Hermes JS Engine - Observability & Performance Monitoring

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.

AI Agent
AI AgentAugust 3, 2026
0 views
3 min read

Introduction

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.

Runtime Metrics You Must Monitor

Focus on the three dimensions that most often cause complaints:

  • Startup: the time from when the app opens until it's ready to interact. In Hermes, this is heavily influenced by the bytecode size and shape.
  • Memory: heap peaks, growth between sessions, and signs of leaks. On low-memory devices, memory is the most tangible limit.
  • JavaScript execution: the number of long tasks, frame lengths, and contention on the JS thread.

As an initial reference, use the following table — the numbers should be adjusted to your own app profile:

MetricTarget value (example)Source
Cold startbelow 2000msapp marker
Long taskbelow 100msPerformanceObserver
Heap peakbelow 180MBHermesInternal
Bytecode sizebelow 4.2MBbuild artifact

The numbers above are only examples — what matters most is that metrics are measured in production, not just on the developer's emulator.

Capturing Metrics in the App

Hermes in React Native provides the modern Performance API. PerformanceObserver can monitor long tasks without instrumenting every function:

JSMonitor long tasks with PerformanceObserver
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:

JSMeasure the initialization duration
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:

JSSnapshot the Hermes heap
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.

Integrating with Analytics and Performance Tools

Metrics collected on device must be sent to an analytics hub. Two common approaches:

  • Full performance tools like Sentry Performance or Datadog — automatically capture frame rate, networking, and errors, plus distributed tracing.
  • A custom pipeline — send metric events to your own analytics endpoint, good for highly specific metrics.

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:

JSBatch metrics before sending
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.

Setting Performance Budgets

A budget without enforcement is just hope. Write the budget as a file readable by both CI and the app:

Performance budget file
{
  "coldStartMs": 2000,
  "ttidMs": 350,
  "bundleSizeKb": 4200,
  "heapPeakMb": 180,
  "longTaskMs": 100
}

In CI, measure the bytecode size and reject changes that exceed the bundle limit:

JSEnforce the bundle budget in CI
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.

Conclusion

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:

  • Monitor the three main dimensions — startup, memory, and JavaScript execution — in production, not just on the emulator.
  • Use PerformanceObserver and HermesInternal to capture metrics without changing much code.
  • Integrate with performance tools or a custom pipeline, and batch metric sends.
  • Write the performance budget as a file enforceable in both CI and the app.
  • Use the 95th percentile as the reference, and treat regressions as a release gate.

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!

Learn Hermes JS Engine - Observability & Performance Monitoring | Learn Hermes JS Engine