Ottrelite Core

The core API of Ottrelite provides a set of base classes and common APIs that allow to integrate or extend the Ottrelite tracing framework.

The aim of this project is to provide a unified API for performance tracing in React Native applications, allowing developers to easily instrument their apps with various backends for tracing, profiling, and performance monitoring.

The core package - @ottrelite/core - provides a so-called Development API (which is designed to be consumed by programmers) for instrumenting your application for purely development use cases (profiling your code, tracing performance issues, etc.). The actual implementation of recording / displaying / reporting of the data is provided by individual, pluggable packages (called backends). An arbitrary number of backends (0, 1 or more) can be installed simultaneously, allowing you to use multiple backends at once, or switch between them as needed.

For production use cases, Ottrelite integrates with OpenTelemetry (OTEL) JS API, supporting all OTEL community instrumentations, custom processors, etc. The only limitation is that the setup must follow our guidelines, which are described in this README.

State of the project

Development API

FeatureStatus
C++ API
JS API
Swift API⏳ (in progress)
Java/Kotlin API
RN integration
WARNING

Currently, Android builds are not working yet. This is because we didn't integrate the native C++ code builds into Gradle yet. Please stay tuned for new updates soon. If you want to try out the Android API with RN internals tracing, please switch to the feature/gradle-plugin branch.

OTEL Interoperability

FeatureStatus
OTEL-JS interop
OTEL-CPP interop
OTEL-Java interopTBD
OTEL-Swift interopTBD
RN integration

RN internals integration

PlatformStatus
iOS⚠️ - only Systrace.js enabled
Android

Installation

First, install the package with:

npm install --save @ottrelite/core
# or
yarn add @ottrelite/core
# or
pnpm add @ottrelite/core

Then, follow the instructions from the respective section(s) below, depending on whether your use case involves production-time telemetry, development-time tracing or both.

Available development backends

Backend NameDescriptionPlatform(s)OTEL compatiblityDocumentation page
@ottrelite/backend-platformPlatform-default tracing backend (ATrace for Android, OSSignposter for iOS)Android, iOS✅ Yeshere
@ottrelite/backend-wrapper-tracyTracy profiler backend for Android and iOSAndroid, iOS❌ Incompatible (lack of support for async API)here

Development API

The quickest way to use Ottrelite is via the Development API. This API resembles the RN Systrace API and indeed 'revives' the no-op Systrace export of RN. Calls to this API are reported to Ottrelite backends and can be previewed .

To use it, install Ottrelite Core and register backends you'd like to use, include the following Ottrelite.install() call in your entrypoint file (e.g. index.js):

// install the Ottrelite Core & backend(s)
import { OttreliteBackendPlatform } from '@ottrelite/backend-platform';
import { OttreliteBackendTracy } from '@ottrelite/backend-wrapper-tracy';
import { Ottrelite } from '@ottrelite/core';
import { AppRegistry } from 'react-native';

import { name as appName } from './app.json';
import App from './src/App';

Ottrelite.install(
  // below: list of development backends to install
  [OttreliteBackendPlatform, OttreliteBackendTracy],
  // below: optional configuration options
  {
    reviveSystraceAPI: true, // if set to true, the RN Systrace API will be revived & configured to call Ottrelite's methods
  }
);

AppRegistry.registerComponent(appName, () => App);
TIP

In case of debugging performance problems, tracing usually makes sense only in release builds (used in a development environment, i.e., not in production). Debug builds are unoptimized (both the native and non-native sides) and therefore their performance may not be representative and could greatly differ from the performance of release builds.

TIP

At the same time, depending on your use case, you may want to include a package in production-deployed release builds. Please remember not to install() the development-only backends in production-deployed builds and exclude them from even being packaged with the binary using react-native.config.js file, as per the RN CLI documentation (iOS, Android)

Synchronous traces

To record synchronous traces, you can use the Ottrelite.beginEvent() and Ottrelite.endEvent() methods. The beginEvent method starts a new event (imagine it is put on top of a stack), while the endEvent method ends the most recent event (analogy: popping it off the stack). The event name is a string that identifies the event.

Events maintain a hierarchical structure, so you can nest them. If you begin a new event while another one is already in progress, the new event will be a child of the previous one.

import { Ottrelite } from '@ottrelite/core';

Ottrelite.beginEvent('MyEvent');
// ... do some work ...
Ottrelite.endEvent('MyEvent');

Asynchronous traces

To record asynchronous traces, you can use the Ottrelite.beginAsyncEvent() and Ottrelite.endAsyncEvent() methods. The beginAsyncEvent method starts a new asynchronous event, while the endAsyncEvent method ends the most recent asynchronous event.

import { Ottrelite } from '@ottrelite/core';

// begin an asynchronous event
const token = Ottrelite.beginAsyncEvent('MyAsyncEvent');
// ... do some work ...
Ottrelite.endAsyncEvent('MyAsyncEvent', token);

Counter events

To record counter events, you can use the Ottrelite.counterEvent() method. This method allows you to record a numeric value over time, which can be useful for tracking metrics such as performance counters.

import { Ottrelite } from '@ottrelite/core';

Ottrelite.counterEvent('MyCounterEvent', 42);

setTimeout(() => {
  // update the value
  Ottrelite.counterEvent('MyCounterEvent', 80);
}, 1000);

Integration with RN's Systrace API

React Native exports its own Systrace API, which supports synchronous, asynchronous and counter events. While this API is mostly used for development of RN itself, it is still exposed for production apps, but does nothing (i.e., calls are no-ops).

Ottrelite optionally (when reviveSystraceAPI is set to true) defines global variables that this Systrace API uses, effectively making these calls effective. For instance, if you launch the app, you will see a hierarchy of events related to require() invocations.

NOTE

It is advised to use the Ottrelite API instead of the Systrace API, as the former is more flexible and provides a unified interface for all backends. The Systrace API is provided for compatibility with existing code that uses it.

WARNING

The synchronous and counter APIs of Systrace and Ottrelite are interoperable, i.e., an event began with one API can be ended with the other. However, the asynchronous APIs are not interoperable (the tokens / cookies returned by Systrace are numbered differently than these of Ottrelite and could cause collisions if passed crossing API boundaries), so you must use either Ottrelite or Systrace for given events, but not mix them at start & end of a given event.

// using the Systrace API
import { Systrace } from 'react-native';

Systrace.beginEvent('MyEvent');
// ... do some work ...
Systrace.endEvent('MyEvent');

Systrace.beginAsyncEvent('MyAsyncEvent', 1);
// ... do some work ...
Systrace.endAsyncEvent('MyAsyncEvent', 1);

Systrace.counterEvent('MyCounterEvent', 80);

Recording traces

Each of the backends provides a different way of recording traces, so the exact usage will depend on the backend you choose to use. Please consult their individual README.md files for instructions.

Examples

Example RN app

The examples/rn-app directory contains an example app presenting most of the features. After running pnpm i in the root directory, you can cd to it and run pnpm android or pnpm ios.

Example consumer lib (C++, Kotlin and Swift APIs)

The examples/ottrelite-consumer-lib directory contains an example native module (in this case, a Nitro Module, but this is not required) that consumes the Ottrelite C++ API, the Ottrelite Kotlin/Java API and the Ottrelite Swift/Objective-C API.

Jaeger backend Docker service

The examples/backend-jaeger directory contains a Docker Compose file for running the Jaeger backend service. When run (docker compose up -d), it will start the Jaeger UI to collect OTEL data from the example app. Then, just run the app on the device you've run Jaeger on (otherwise, you will need to adjust endpoint path to match your server's address) and you should start seeing traces in the Jaeger UI.

Roadmap

The roadmap for the nearest future includes the following:

  • integration of more OTEL C++ SDK exporters into Ottrelite's wrappers
  • integration with Swift OTEL SDK
  • integration with Java OTEL SDK
Need to boost your app's performance?
We help React Native teams enhance speed, responsiveness, and efficiency.