diff --git a/docs/docs/docs/_meta.json b/docs/docs/docs/_meta.json index 03e653d..b862217 100644 --- a/docs/docs/docs/_meta.json +++ b/docs/docs/docs/_meta.json @@ -4,6 +4,11 @@ "name": "introduction.mdx", "label": "Introduction" }, + { + "type": "file", + "name": "quick-start.mdx", + "label": "Quick Start" + }, { "type": "dir", "name": "core", diff --git a/docs/docs/docs/backends/_meta.json b/docs/docs/docs/backends/_meta.json index 805e913..670d022 100644 --- a/docs/docs/docs/backends/_meta.json +++ b/docs/docs/docs/backends/_meta.json @@ -1,12 +1,17 @@ [ + { + "type": "file", + "name": "introduction.mdx", + "label": "Introduction" + }, { "type": "file", "name": "backend-platform.mdx", - "label": "Platform Backend" + "label": "Platform" }, { "type": "file", - "name": "backend-wrapper-tracy.mdx", - "label": "Tracy Wrapper Backend" + "name": "backend-tracy.mdx", + "label": "Tracy" } ] diff --git a/docs/docs/docs/backends/backend-platform.mdx b/docs/docs/docs/backends/backend-platform.mdx index 8e31fe2..6ff0aea 100644 --- a/docs/docs/docs/backends/backend-platform.mdx +++ b/docs/docs/docs/backends/backend-platform.mdx @@ -1,5 +1,73 @@ -import Readme from '../../../../packages/backend-platform/README.md'; +# @ottrelite/backend-platform -# Platform Backend +This plugin is the platform-specific backend implementation for RN Ottrelite Core. On Android, it utilizes [ATrace](https://developer.android.com/ndk/reference/group/tracing) (Android tracing API), on iOS it utilizes the [OSSignposter API](https://developer.apple.com/documentation/os/ossignposter). - +## When to use this backend? + +This backend is designed for **system-level analysis using familiar tools** that are already part of your development environment. It leverages the native profiling APIs built into Android and iOS platforms, allowing you to use the debugging tools you're already comfortable with. + +Use Platform Backend when you want: +- **Familiar tooling** - Work with Android Studio Profiler and Xcode Instruments that you already know +- **System-level insights** - Analyze app performance alongside system metrics and other processes +- **No additional setup** - Uses built-in platform APIs without requiring external profiling tools + +:::important +This backend is not designed for production use cases and should not be installed in production builds. See the [backends introduction](/docs/backends/introduction#production-builds) for guidance on excluding backends from production builds. +::: + +## Supported features + +| Feature | Android Support | iOS Support | +| --------------------------------------------- | --------------- | ----------- | +| synchronous traces (`{begin,end}Event`) | API level ≥ 23 (Android M) | iOS 15+ | +| asynchronous traces (`{begin,end}AsyncEvent`) | API level ≥ 29 (Android Q) | iOS 15+ | +| counter events (`counterEvent`) | API level ≥ 29 (Android Q) | iOS 15+ | + +:::note +The support levels above mean that the application must be **compiled** with at least the given `minSdkVersion`. This is because those APIs are not available on older Android versions and therefore it is not possible to compile the native code against older SDKs. +::: + +## Installation + +To use this package, you need to install it in your React Native project: + +```bash +npm install @ottrelite/backend-platform +``` + +And register the backend with Ottrelite Core in your entrypoint file (e.g. `index.js`): + +```typescript +import { OttreliteBackendPlatform } from '@ottrelite/backend-platform'; +import { Ottrelite } from '@ottrelite/core'; + +Ottrelite.install([OttreliteBackendPlatform]); +``` + +## Recording the trace + +### Android + +You can record and view traces in two ways: +- using Android Studio's Profiler's 'Capture System Activities' option, as per the [documentation](https://developer.android.com/studio/profile); this method works both on physical devices & the Android emulator +- using the `perfetto` CLI tool, which is distributed as part of the Android OS onboard most modern devices. More details can be found in the [documentation](https://perfetto.dev/docs/getting-started/system-tracing); the resulting file can be viewed in [Perfetto UI](https://ui.perfetto.dev/); this method works only on a physical device + +:::important +Make sure that the app you are profiling is `profileable` by placing an [appropriate element](https://developer.android.com/guide/topics/manifest/profileable-element) within your `AndroidManifest.xml`'s `` element: `` +::: + +#### Perfetto CLI + +If using the Perfetto CLI via the `record_android_trace` script, make sure to adjust the `--app` argument to match your app's package. We discovered that on some devices it is needed to set it, otherwise some userspace events may be missing. + +Some devices allow to pass an additional tracing category, `app`, which can also be considered in such cases. An example invocation might look like the following: + +```bash +./record_android_trace --app=com.callstack.ottrelite.demo -o trace_file.perfetto-trace -t 40s -b 64mb sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory rs app +``` + +### iOS + +You can view the traces in Xcode Instruments, as per the [documentation](https://developer.apple.com/documentation/os/recording-performance-data#Review-Signposts-in-Instruments). + +The traces will be signpost-ed using the `OSSignposter` API, using the default `OS_LOG_DEFAULT` logger. diff --git a/docs/docs/docs/backends/backend-tracy.mdx b/docs/docs/docs/backends/backend-tracy.mdx new file mode 100644 index 0000000..abf1476 --- /dev/null +++ b/docs/docs/docs/backends/backend-tracy.mdx @@ -0,0 +1,82 @@ +# @ottrelite/backend-wrapper-tracy + +This plugin is a wrapper around the [open-source Tracy profiler](https://github.com/wolfpld/tracy) client, which brings tracing using Tracy to Ottrelite. + +## When to use this backend? + +The Tracy backend is designed for **real-time development profiling** and provides immediate visual feedback as your React Native app runs. Unlike the Platform backend which requires recording sessions and post-analysis, Tracy gives you live insights into your app's performance. + +Use Tracy when you want: +- **Real-time feedback** - See performance data as it happens, perfect for iterative optimization +- **Frame-by-frame analysis** - Detailed timing information for each frame of your app +- **Cross-platform consistency** - Same profiling experience across iOS and Android +- **Memory tracking** - Visual representation of memory usage patterns +- **Interactive exploration** - Zoom, filter, and analyze traces dynamically + +:::important +This backend is not designed for production use cases and should not be installed in production builds. See the [backends introduction](/docs/backends/introduction#production-builds) for guidance on excluding backends from production builds. +::: + +## Supported features + +| Feature | Support level | +| --------------------------------------------- | -------------------------------------------------------------------------- | +| synchronous traces (`{begin,end}Event`) | Supported | +| asynchronous traces (`{begin,end}AsyncEvent`) | Unsupported (see [tracy#149](https://github.com/wolfpld/tracy/issues/149)) | +| counter events (`counterEvent`) | Supported | + +## Installation + +To use this package, you need to install it in your React Native project: + +```bash +npm install @ottrelite/backend-wrapper-tracy +``` + +And register the backend with Ottrelite Core in your entrypoint file (e.g. `index.js`): + +```typescript +import { OttreliteBackendTracy } from '@ottrelite/backend-wrapper-tracy'; +import { Ottrelite } from '@ottrelite/core'; + +Ottrelite.install([OttreliteBackendTracy]); +``` + +:::important +Remember to give attribution to the Tracy Profiler in your application, as in the [last section](#additional-information). +::: + +## Recording the trace + +To record the trace, you must use the Tracy Profiler tool. For Windows, there are releases containg `.7z` archives with prebuilt binaries, for other platforms you must follow the [documentation](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf) to build it yourself. + +In short, the instructions to build the Tracy Profiler tool locally are as follows: + +```bash +git clone -b v0.12.2 https://github.com/wolfpld/tracy +cd tracy +cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release +cmake --build profiler/build --config Release --parallel +cd profiler/build +# on Windows, you can now run tracy-profiler.exe from this directory. +# on macOS & Linux, you can run ./tracy-profiler from this directory. +./tracy-profiler +``` + +In case of Android, simply forward the port used by Tracy (default is `8086`) to your device/emulator, e.g. using `adb` for Android: + +```bash +adb forward tcp:8086 tcp:8086 +``` + +## Limitations + +The `tracy::Profiler::PlotData` function that handles counter events assumes that the counter's name is persisted (it does not copy the string data, just a pointer to it). This is valid for C++ usage where the data is hardcoded in the code (static), yet here (e.g. from JS) it is dynamic. Therefore, this backend keeps the strings passed to the `counterEvent` function in an `std::unordered_set` to ensure that the strings are not deallocated whenever Tracy would try to access them. This means that the counter names will be kept in memory for the lifetime of the backend instance, so be careful with the number of unique counter names you use - using an enormous number of unique counter names (or of enormous lengths) may lead to increased memory usage. + +Since Tracy does not support async events, OTEL APIs data will not be visible here. This is because the closest equivalent of OTEL's `Span`s are async events, which are not supported by this backend. + +## Additional information + +This package utilizes the Tracy Profiler client API under the hood. Please remember to give proper attribution to the Tracy Profiler in your application, as per the [Tracy Profiler license](https://github.com/wolfpld/tracy/blob/master/LICENSE). + +Many thanks to the author & contributors of the [Tracy Profiler](https://github.com/wolfpld/tracy). diff --git a/docs/docs/docs/backends/backend-wrapper-tracy.mdx b/docs/docs/docs/backends/backend-wrapper-tracy.mdx deleted file mode 100644 index ad197d6..0000000 --- a/docs/docs/docs/backends/backend-wrapper-tracy.mdx +++ /dev/null @@ -1,5 +0,0 @@ -import Readme from '../../../../packages/backend-wrapper-tracy/README.md'; - -# Backend Wrapper for Tracy - - diff --git a/docs/docs/docs/backends/introduction.mdx b/docs/docs/docs/backends/introduction.mdx new file mode 100644 index 0000000..78c66fc --- /dev/null +++ b/docs/docs/docs/backends/introduction.mdx @@ -0,0 +1,74 @@ +# Visualization Backends + +Visualization backends are the "display layer" of Ottrelite that transform your trace data into actionable insights. They take raw performance data from your React Native app and present it in different tools and formats to help you quickly identify bottlenecks and optimization opportunities. + +## What are Backends? + +Backends in Ottrelite are plugins that handle how and where your tracing data is visualized. Each backend connects to different profiling tools and provides unique perspectives on your app's performance. You can use multiple backends simultaneously to get comprehensive insights from different angles. + +Think of backends as different "viewers" for the same performance data - some excel at real-time analysis, others integrate with tools you're already familiar with, and some provide specialized visualizations for specific use cases. + +## Available Backends + +| Backend | Package | Best For | What It Does | +|---------|---------|----------|--------------| +| **Platform** | `@ottrelite/backend-platform` | System-level analysis with familiar tools | Integrates with Android Studio Profiler, Xcode Instruments, and Perfetto using native platform APIs | +| **Tracy** | `@ottrelite/backend-wrapper-tracy` | Real-time development profiling | Frame-by-frame analysis with live trace visualization, memory tracking, and cross-platform support | + +### Platform Backend + +The Platform backend leverages native tracing APIs that you're likely already familiar with: +- **Android**: Uses ATrace API, viewable in Android Studio Profiler or Perfetto +- **iOS**: Uses OSSignposter API, viewable in Xcode Instruments + +This backend is ideal when you want to use the profiling tools built into your development environment. + +### Tracy Backend + +The Tracy backend wraps the open-source Tracy profiler, providing real-time profiling capabilities: +- Live trace visualization as your app runs +- Frame-by-frame performance analysis +- Memory tracking and visualization +- Cross-platform consistency + +This backend is perfect for iterative development where you want immediate feedback on performance changes. + +## Production Builds + +Backends are designed for development use only and should not be included in production builds. + +To exclude backends from production builds: + +1. **Conditional installation** - Install backends only when a development feature flag is set: + +```typescript +import { Ottrelite } from '@ottrelite/core'; + +if (__DEV__ || isProfilingEnabled) { + const { OttreliteBackendPlatform } = require('@ottrelite/backend-platform'); + Ottrelite.install([OttreliteBackendPlatform]); +} +``` + +2. **Build-time exclusion** - Use `react-native.config.js` to exclude backend packages from specific build types: + +```javascript +module.exports = { + dependencies: { + '@ottrelite/backend-platform': { + platforms: { + android: { + buildTypes: ['debug'], // Only include in debug builds + }, + ios: { + configurations: ['Debug'], // Only include in debug builds + }, + }, + }, + }, +}; +``` + +:::important +If you've created a separate optimized configuration for performance profiling (as recommended in the [installation guide](/docs/core/introduction#configuring)), make sure to include that configuration in your build-time exclusion list as well to prevent it from being shipped to production. +::: diff --git a/docs/docs/docs/core/_meta.json b/docs/docs/docs/core/_meta.json index 45b517b..30a2f7e 100644 --- a/docs/docs/docs/core/_meta.json +++ b/docs/docs/docs/core/_meta.json @@ -4,11 +4,6 @@ "name": "quick-start.mdx", "label": "Quick Start" }, - { - "type": "file", - "name": "react-api.mdx", - "label": "React API" - }, { "type": "file", "name": "react-native-internals.mdx", diff --git a/docs/docs/docs/core/contributing.mdx b/docs/docs/docs/core/contributing.mdx deleted file mode 100644 index 27db287..0000000 --- a/docs/docs/docs/core/contributing.mdx +++ /dev/null @@ -1,29 +0,0 @@ -# Contributing - -## Creating a backend - -TODO: write this section. - -## Development - -To start developing, first clone the repository: - -```bash -git clone https://github.com/callstackincubator/ottrelite -``` - -Then, install the dependencies & generate the glue code using Nitro Modules: - -```bash -pnpm i -``` - -Any time you change the Nitro spec files (TS), run `pnpm run build` to regenerate the glue code. - -### iOS - additional setup - -For iOS, you need to install the CocoaPods dependencies. Run the following command in the `ios` directory of your project: - -```bash -pnpm pod:install -``` diff --git a/docs/docs/docs/core/introduction.mdx b/docs/docs/docs/core/introduction.mdx new file mode 100644 index 0000000..1701554 --- /dev/null +++ b/docs/docs/docs/core/introduction.mdx @@ -0,0 +1,155 @@ +# Ottrelite Core + +Ottrelite Core is the main tracing API that provides a lightweight, unified interface for performance instrumentation across all React Native platforms. It offers a simple set of methods to trace synchronous operations, asynchronous workflows, and counter metrics in your React Native applications. + +### Available platforms + +Ottrelite Core supports tracing across all major React Native development languages: + +| Feature | Status | +| --------------- | ---------------- | +| C++ API | ✅ | +| JS API | ✅ | +| Swift API | ⏳ (in progress, see [#4](https://github.com/callstackincubator/ottrelite/issues/4) for details) | +| Java/Kotlin API | ✅ | + +## Installation + +First, install the package with: + +```bash +npm install @ottrelite/core +``` + +## Configuring + +The quickest way to use Ottrelite is via the Tracing API. + +Before you use Ottrelite, you need to configure it by including the following `Ottrelite.install()` call in your entrypoint file (e.g. `index.js`): + +```javascript +import { OttreliteBackendPlatform } from '@ottrelite/backend-platform'; +import { Ottrelite } from '@ottrelite/core'; + +Ottrelite.install( + [OttreliteBackendPlatform], +); +``` + +To learn more about available backends, see the [backends](/docs/backends/introduction) section. + +:::tip +In case of debugging performance problems, tracing usually makes sense only in optimized builds, not debug builds. Debug builds are unoptimized (both native and JavaScript sides) and their performance may not be representative of production. + +**Recommended approach:** Create a separate build configuration specifically for performance profiling (e.g., "profiling" or "release-dev") instead of using the production release configuration. This ensures you get optimized performance characteristics while avoiding the risk of accidentally shipping tracing code to production. +::: + +## Features + +### 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 { Tab, Tabs } from 'rspress/theme'; + + + +```typescript title="index.ts" +import { Ottrelite } from '@ottrelite/core'; + +Ottrelite.beginEvent('MyEvent'); +// ... +Ottrelite.endEvent(); +``` + + +```cpp title="index.cpp" +#include + +Ottrelite::beginEvent("MyEvent"); +// ... +Ottrelite::endEvent(); + +``` + + +```java title="index.java" +import com.callstack.ottrelite.OttreliteAndroid + +OttreliteAndroid.beginEvent("MyEvent") +// ... +OttreliteAndroid.endEvent() +``` + + +```kotlin title="index.kt" +// tbd +``` + + + +### 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. + + + +```typescript title="index.ts" +import { Ottrelite } from '@ottrelite/core'; + +const token = Ottrelite.beginAsyncEvent('MyAsyncEvent'); +// ... +Ottrelite.endAsyncEvent('MyAsyncEvent', token); +``` + + +```cpp title="index.cpp" +#include + +const auto token = Ottrelite::beginAsyncEvent("MyAsyncEvent"); +// ... +Ottrelite::endAsyncEvent("MyAsyncEvent", token); +``` + + + +```java title="index.java" +import com.callstack.ottrelite.OttreliteAndroid + +val token = OttreliteAndroid.beginAsyncEvent("MyAsyncEvent") +// ... +OttreliteAndroid.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. + + + +```typescript title="index.ts" +import { Ottrelite } from '@ottrelite/core'; + +Ottrelite.counterEvent('MyCounter', 123.45); +``` + + +```cpp title="index.cpp" +#include + +Ottrelite::counterEvent("MyCounter", 123.45); +``` + + + +```java title="index.java" +import com.callstack.ottrelite.OttreliteAndroid + +OttreliteAndroid.traceCounter("MyCounter", 123.45) +``` + + diff --git a/docs/docs/docs/core/quick-start.mdx b/docs/docs/docs/core/quick-start.mdx deleted file mode 100644 index b605438..0000000 --- a/docs/docs/docs/core/quick-start.mdx +++ /dev/null @@ -1,159 +0,0 @@ -# Ottrelite Core quickstart - -## Available development backends - -| Backend Name | Description | Platform(s) | OTEL-interop layer compatiblity | Documentation | -| ---------------------------------- | --------------------------------------------------------------------------- | ------------ | ----------------------------------------------- | ------------------------------------------------ | -| `@ottrelite/backend-platform` | Platform-default tracing backend (ATrace for Android, OSSignposter for iOS) | Android, iOS | ✅ Yes | [open ➡️](../backends/backend-platform.mdx) | -| `@ottrelite/backend-wrapper-tracy` | Tracy profiler backend for Android and iOS | Android, iOS | ❌ Incompatible (lack of support for async API) | [open ➡️](../backends/backend-wrapper-tracy.mdx) | - -## State of the project - -### Development API - -| Feature | Status | -| --------------- | ---------------- | -| C++ API | ✅ | -| JS API | ✅ | -| Swift API | ⏳ (in progress) | -| Java/Kotlin API | ✅ | -| RN integration | ✅ | - -### RN internals integration - -| Platform | Status | -| -------- | ------------------------------- | -| iOS | ⚠️ - only `Systrace.js` enabled | -| Android | ✅ | - -## Installation - -First, install the package with: - -```bash -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. - -## Development API - -The quickest way to use Ottrelite is via the Development API. This API resembles the [RN Systrace API](https://reactnative.dev/docs/systrace) 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`): - -```javascript -// 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](https://github.com/react-native-community/cli/blob/main/docs/dependencies.md#platformsiosconfigurations), [Android](https://github.com/react-native-community/cli/blob/main/docs/dependencies.md#platformsandroidbuildtypes)) - -### 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. - -```javascript -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. - -```javascript -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. - -```javascript -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. - -```javascript -// using the Systrace API -import { Systrace } from 'react-native'; - -Systrace.beginEvent('MyEvent'); -// ... do some work ... -Systrace.endEvent('MyEvent'); - -const token = Systrace.beginAsyncEvent('MyAsyncEvent'); -// ... do some work ... -Systrace.endAsyncEvent('MyAsyncEvent', token); - -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. diff --git a/docs/docs/docs/core/react-api.mdx b/docs/docs/docs/core/react-api.mdx deleted file mode 100644 index 7249ece..0000000 --- a/docs/docs/docs/core/react-api.mdx +++ /dev/null @@ -1,33 +0,0 @@ -# React API - -Ottrelite offers features simplifying integration with React. You can use the below hooks and other utilities to enhance your React applications. - -## `useComponentRenderTracing` - -This hook traces a component's render lifecycle (i.e., any - first or subsequent - render), however in a different way than you might expect - please read the following explanation. - -The start & end of the span (event) in the trace results is **not** the start & end time of JS render logic execution due to limitations coming from the concurrent root feature of React starting from v18 ([more about that here](https://legacy.reactjs.org/blog/2022/03/29/react-v18.html)). Concurrent rendering makes it possible for React to interrupt a render, effectively making this redundant. Since the Development API **must** maintain integrity within calls to begin/end event methods (e.g. orphaning a started event could cause all sorts of different problems, which could happen if the render was interrupted), it is not possible to simply synchronously start/end events within the render function. - -Therefore, instead we capture the JS logic start and end times in a ref, trace the event from `useEffect` / `useLayoutEffect` hooks to produce a span that starts after its JS render logic finishes running, and ends after the component has been rendered to the tree. The measured JS logic render time is reported as an attribute named `jsLogicDuration` within that span. - -Additional event arguments can be passed as the optional second argument to `useComponentRenderTracing`. - -> [!WARNING] -> The hook **must** be called first in the component to mark the JS logic start time undelayed. Any components you render & return **must** first be stored in a variable, and returned **after** calling the hook's returned `markJSRenderEnd` function. - -```typescript -function MyComponent() { - const { markJSRenderEnd } = useComponentRenderTracing(heading, { - attr1: "...", - ... - }); - - // ... - - const contents = {/* ... */}; - - markJSRenderEnd(); - - return contents; -} -``` diff --git a/docs/docs/docs/core/react-native-internals.mdx b/docs/docs/docs/core/react-native-internals.mdx index 144cfda..87de640 100644 --- a/docs/docs/docs/core/react-native-internals.mdx +++ b/docs/docs/docs/core/react-native-internals.mdx @@ -1,15 +1,31 @@ ## React Native Internals Autoinstrumentation - enable RN Systrace for JS & Java/Kotlin -#### JavaScript +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). -This package has opt-in options to revive the [RN Systrace API](https://reactnative.dev/docs/systrace), which is a simple JS API for tracing synchronous, asynchronous & counter events in your application. By default it's importable, but it's a no-op. Ottrelite can inject proper globals to make it functional and pipe the calls to [Ottrelite's Development API](#development-api). +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. -To activate it, set `reviveSystraceAPI: true` in the optional configuration object passed to `Ottrelite.install`, as presented in the code extract in section [Development API](#development-api). +:::important +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. +::: -On both iOS and Android, the revived Systrace API will cause JS internals that are instrumented in RN source code to be traced, such as the `require()`s for modules required first time in a run. +### Activating the Systrace API + +To enable React Native's internal tracing, pass the `reviveSystraceAPI: true` option when installing Ottrelite. This activates the global variables that React Native's `Systrace` API depends on, making internal framework calls visible in your traces. + +```typescript +Ottrelite.install(yourBackendsOfChoice, { + reviveSystraceAPI: true, +}); +``` -On Android, this feature is much more powerful, enabling tracing of RN Android native source internals in places where RN's instrumentation has already been present. +Once enabled, you'll automatically see traces for React Native's internal operations like module loading and other framework-level activities that are already instrumented in the React Native source code. -#### Java/Kotlin +:::note +The features varies between platforms due to architectural constraints. On Android, you will see traces for both JavaScript and native Java/Kotlin operations. On iOS, you will only see traces for JavaScript operations. +::: + +### Seeing it in practice + +On both iOS and Android, the revived Systrace API will cause JS internals that are instrumented in RN source code to be traced, such as the `require()`s for modules required first time in a run. -TODO: write this! ~ Dominik - Gradle visitor, etc. +[todo - add image] diff --git a/docs/docs/docs/instrumentation-react/__common-directive.mdx b/docs/docs/docs/instrumentation-react/__common-directive.mdx deleted file mode 100644 index cbbdc72..0000000 --- a/docs/docs/docs/instrumentation-react/__common-directive.mdx +++ /dev/null @@ -1,14 +0,0 @@ -The React instrumentation offers a directive allowing you to easily instrument your functional components, class components and hooks. - -The directive's schema is `"use trace [API] [Name]"` such that: - -- `[API]` (default value: `dev`) which must be either: - - `dev` (which would cause the instrumentation to call into the Ottrelite Development API from the core package) - - `otel`, which would cause it to use the OpenTelemetry JS SDK; in such case, you need to set up the OpenTelemetry SDK in your application and then probably you will also want to set up [Ottrelite Interop layer with OTEL](../otel-interop/quick-start) -- `[Name]`, which is optional and is the name of the component being traced; if not passed, Ottrelite will attempt to use your component's name - the details of how it is resolved are described in the [function components](./function-components.mdx), [hooks](./hooks.mdx) and [class components](./class-components.mdx) doc pages - -> [!TIP] -> -> Both parameters are optional, meaning that the shortest you can do is `"use trace"` which is equivalent to `"use trace dev "`. -> -> If you pass **only one** parameter, the hook will first give priority to the `[API]` parameter; if the values does not match the possible options, then this argument will be treated as the `[Name]` parameter. diff --git a/docs/docs/docs/instrumentation-react/class-components.mdx b/docs/docs/docs/instrumentation-react/class-components.mdx deleted file mode 100644 index e6622e7..0000000 --- a/docs/docs/docs/instrumentation-react/class-components.mdx +++ /dev/null @@ -1,86 +0,0 @@ -import CommonDirectiveDoc from './__common-directive.mdx'; - -## The autoinstrumentation directive - - - -## Class components - -> [!NOTE] -> As seen in the examples below, the components include an explicit implementation of `shouldComponentUpdate` to prevent unnecessary re-renders. This is a common practice in React to optimize performance, but it is not compulsory - the instrumentation would still work without it. However, in such case, it would be expected that most likely the instrumentation would produce many entries, for invocations of the component which didn't actually result in an update to the shadow tree. - -```typescript -import _ from 'lodash'; -import { Component } from 'react'; - -export class TracedClassComponent extends Component { - render() { - 'use trace'; - - ... - - return ( - ... - ); - } - - shouldComponentUpdate( - nextProps: Readonly, - nextState: Readonly<{}>, - nextContext: any - ): boolean { - // only rerender if props have changed - return ( - !_.isEqual(this.props, nextProps) || - !_.isEqual(this.state, nextState) || - !_.isEqual(this.context, nextContext) - ); - } -} -``` - -> [!WARNING] -> The name of the tracer is used to create a unique OTEL tracer, so make sure it is unique across your application. - -With an explicit Tracer name: - -```typescript -import _ from 'lodash'; -import { Component } from 'react'; - -export class TracedClassComponent extends Component { - render() { - 'use trace dev TestLibTracedClassComponent'; - - ... - - return ( - ... - ); - } - - shouldComponentUpdate( - nextProps: Readonly, - nextState: Readonly<{}>, - nextContext: any - ): boolean { - // only rerender if props have changed - return ( - !_.isEqual(this.props, nextProps) || - !_.isEqual(this.state, nextState) || - !_.isEqual(this.context, nextContext) - ); - } -} -``` - -### What will be the name of the tracer? - -The following is an in-order (by priority) list of how the name of the tracer is determined: - -| Declaration style | Name | -| --------------------------------------------------- | ---------------------------------------- | -| _Explicit name from the string after `"use trace"`_ | The passed string | -| component with `displayName` or `name` | `displayName` or `name` property | -| named class | Name of the class | -| anonymous class | Name of the assignment target identifier | diff --git a/docs/docs/docs/instrumentation-react/function-components.mdx b/docs/docs/docs/instrumentation-react/function-components.mdx deleted file mode 100644 index 41f4fc7..0000000 --- a/docs/docs/docs/instrumentation-react/function-components.mdx +++ /dev/null @@ -1,76 +0,0 @@ -import CommonDirectiveDoc from './__common-directive.mdx'; - -## The autoinstrumentation directive - - - -## Function components - -> [!NOTE] -> As seen in the examples below, the components are wrapped in `memo` to prevent unnecessary re-renders. This is a common practice in React to optimize performance, but it is not compulsory - the instrumentation would still work without it. However, in such case, it would be expected that most likely the instrumentation would produce many entries, for invocations of the component which didn't actually result in an update to the shadow tree. - -```typescript -export const MyComponent = memo(function MyComponent(){ - "use trace"; - - ... - - return ...; -}); -``` - -Without memoization (be warned as per the notice above): - -```typescript -export const MyComponent = function() { - "use trace"; - - ... - - return ...; -}; -``` - -_Note: arrow function components are supported as well._ - -With an explicit Tracer name: - -> [!NOTE] -> The name of the tracer is used to create a unique OTEL tracer, so make sure it is unique across your application. - -```typescript -export const MyComponent = memo(function MyComponent(){ - "use trace dev TestLibMyComponent"; - - ... - - return ...; -}); -``` - -> [!WARNING] -> Please keep in mind that when using the `memo` helper (which is highly encouraged!), the name of the function will not be propagated from the assigned-to variable. For instance, the following code: -> -> ```typescript -> export const MyComponent = memo(function(){ -> "use trace"; -> -> ... -> -> return ...; -> }); -> ``` -> -> Would result in an **error**, since the Babel plugin would not know the name of the component; for this reason, you need to either make the function a named function, or add an explicit name to the `"use trace API Name"` directive. - -### What will be the name of the tracer? - -The following is an in-order (by priority) list of how the name of the tracer is determined: - -| Declaration style / context | Name | -| --------------------------------------------------- | ---------------------------------------- | -| _Explicit name from the string after `"use trace"`_ | The passed string | -| component with `displayName` or `name` | `displayName` or `name` property | -| named function | Name of the function | -| arrow function | Name of the assignment target identifier | -| anonymous function | Name of the assignment target identifier | diff --git a/docs/docs/docs/instrumentation-react/hooks.mdx b/docs/docs/docs/instrumentation-react/hooks.mdx deleted file mode 100644 index 796c919..0000000 --- a/docs/docs/docs/instrumentation-react/hooks.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# Hooks - -TODO: Write this section diff --git a/docs/docs/docs/instrumentation-react/introduction.mdx b/docs/docs/docs/instrumentation-react/introduction.mdx new file mode 100644 index 0000000..4a78e79 --- /dev/null +++ b/docs/docs/docs/instrumentation-react/introduction.mdx @@ -0,0 +1,174 @@ +# Ottrelite Instrumentation for React + +Ottrelite Instrumentation for React provides two main ways to add performance tracing to your React applications: + +1. **React Hooks API** - Manual instrumentation using React hooks like `useComponentRenderTracing` +2. **Babel Plugin** - Automatic instrumentation using the `"use trace"` directive + +:::note +If you're using a recent version of React Native, consider using [React Performance Tracks](/docs/introduction#react-native-devtools) instead. + +React Performance Tracks offer significantly more features compared to Ottrelite's basic render time tracking, including: + +- **Scheduler tracking** with 4 priority levels (Blocking, Transition, Suspense, Idle) +- **Detailed render phases** (Update, Render, Commit, Effects) with flamegraph visualization +- **Cascading update detection** to identify performance regressions +- **Component-level insights** with render and effect durations +- **Props change inspection** to identify unnecessary renders +- **Integration with browser DevTools** alongside network requests, JavaScript execution, and event loop activity + +Ottrelite serves as an alternative solution for those who haven't upgraded to the latest React Native version yet. +::: + +## Installation + +First, ensure you are already using `@ottrelite/core`. Then, add this package: + +```bash +npm install @ottrelite/instrumentation-react +``` + +## Features + +### React Hooks API + +The React API provides hooks that allow you to manually instrument your components. The main hook is `useComponentRenderTracing`, which traces a component's render lifecycle. + +#### `useComponentRenderTracing` + +This hook traces a component's render lifecycle (i.e., any - first or subsequent - render). + +```typescript +function MyComponent() { + const { markJSRenderEnd } = useComponentRenderTracing("MyComponent"); + + // Your component logic here... + + const contents = {/* ... */}; + + markJSRenderEnd(); + + return contents; +} +``` + +The hook **must** be called first in the component to mark the JS logic start time undelayed. Any components you render & return **must** first be stored in a variable, and returned **after** calling the hook's returned `markJSRenderEnd` function. + +:::important +The traced span starts after the JS render logic finishes and ends after the component is rendered to the tree. Additionally, the JS render execution time is measured and reported as a `jsLogicDuration` attribute within the span. +::: + +### Babel Plugin for Automatic Instrumentation + +The Babel plugin automatically instruments React components and hooks using the `"use trace"` directive. This provides a convenient way to add tracing without manually calling hooks. + +#### Setting up the Babel Plugin + +Add the plugin to your Babel configuration. If you are using a `babel.config.js` file: + +```javascript +module.exports = { + ..., + plugins: ['module:@ottrelite/instrumentation-react'], + ... +}; +``` + +#### Using the "use trace" Directive + +Add the `"use trace"` directive as the first line in functions you want to instrument: + +- **Function components**: First line of the component function body +- **Class components**: First line of the `render` method body +- **Hooks**: First line of the hook function body + +The directive schema is `"use trace [API] [Name]"` where: + +- `[API]` (default: `dev`) - Either `dev` (Ottrelite Development API) or `otel` (OpenTelemetry JS SDK) +- `[Name]` (optional) - Custom name for the tracer; if not provided, the function/component name is used + +#### Functions + +```typescript +export function MyFunction() { + "use trace"; + // Your logic here +}); +``` + +#### Function Components + +```typescript +import { memo } from 'react'; + +export const MyComponent = memo(function MyComponent() { + "use trace"; + + // Your component logic here + + return
Hello World
; +}); +``` + +:::note +As seen in the examples above, the components are wrapped in `memo` to prevent unnecessary re-renders. This is a common practice in React to optimize performance, but it is not compulsory - the instrumentation would still work without it. However, in such case, it would be expected that most likely the instrumentation would produce many entries, for invocations of the component which didn't actually result in an update to the shadow tree. +::: + +When using `memo()`, you must either: +1. Use a named function: `memo(function MyComponent() { ... })` +2. Provide an explicit name: `"use trace dev MyComponent"` + +:::important +Anonymous functions without explicit names will result in an error. +::: + +#### Class Components + +```typescript +import { Component } from 'react'; + +export class MyClassComponent extends Component { + render() { + 'use trace'; + + // Your render logic here + + return
Hello World
; + } + + shouldComponentUpdate(nextProps, nextState, nextContext) { + // only rerender if props, state, or context have changed + return ( + !_.isEqual(this.props, nextProps) || + !_.isEqual(this.state, nextState) || + !_.isEqual(this.context, nextContext) + ); + } +} +``` + +:::note +As seen in the example above, the component includes an explicit implementation of `shouldComponentUpdate` to prevent unnecessary re-renders. This is a common practice in React to optimize performance, but it is not compulsory - the instrumentation would still work without it. However, in such case, it would be expected that most likely the instrumentation would produce many entries, for invocations of the component which didn't actually result in an update to the shadow tree. +::: + +#### Tracer Name Resolution + +The tracer name is determined in the following priority order: + +| Declaration style / context | Name | +| --------------------------------------------------- | ---------------------------------------- | +| Explicit name from the string after `"use trace"` | The passed string | +| component with `displayName` or `name` | `displayName` or `name` property | +| named function/class | Name of the function/class | +| arrow function | Name of the assignment target identifier | +| anonymous function/class | Name of the assignment target identifier | + +Both `[API]` and `[Name]` parameters are optional. The shortest form is `"use trace"` which is equivalent to `"use trace dev "`. + +:::tip +If you pass only one parameter, it will be treated as `[API]` first; if the value doesn't match the API options, it will be treated as the `[Name]` parameter. +::: + +:::tip +Components wrapped in `memo()` or class components with `shouldComponentUpdate` are recommended to prevent unnecessary re-renders and reduce noise in tracing data. +::: diff --git a/docs/docs/docs/instrumentation-react/quick-start.mdx b/docs/docs/docs/instrumentation-react/quick-start.mdx deleted file mode 100644 index 6a32146..0000000 --- a/docs/docs/docs/instrumentation-react/quick-start.mdx +++ /dev/null @@ -1,36 +0,0 @@ -# Ottrelite Instrumentation for React Babel Plugin - -This Babel plugin autoinstruments React components' and hooks' lifecycle in your code, adding tracing to components & hooks using the `"use trace ..."` directive. - -It is meant to be used strictly in conjunction with `@ottrelite/core`, which is required to be added as a runtime dependency & configured as you would normally do. Please consult its docs for first setup. - -## Installation - -First, ensure you are already using `@ottrelite/core`. Then, add this package as a dev-dependency: - -```bash -npm i -D @ottrelite/instrumentation-react -# or -yarn add -D @ottrelite/instrumentation-react -# or -pnpm add -D @ottrelite/instrumentation-react -``` - -## Usage - -First, add the plugin to your Babel configuration. If you are using a `babel.config.js` file, it should look like this: - -```javascript -module.exports = { - ..., - plugins: ['module:@ottrelite/instrumentation-react'], - ... -}; -``` - -Basically, at the very first line of the functions you would like to autoinstrument tracing for, add the `"use trace"` directive. For function components, this is the first line of your component's function body, the same applies to hooks & for class components this is the first line of the class component's `render` function body. - -Optionally, you may specify a name for the tracer manually (especially if the function does not carry a name). - -> [!TIP] -> The implicit name or name specified explicitly after `use trace` is the name of the OTEL tracer that will be created and used for tracing. Make sure it is unique, otherwise you may obtain misleading, mixed traces. diff --git a/docs/docs/docs/interop-otel/_meta.json b/docs/docs/docs/interop-otel/_meta.json index 64f16c2..03a23a0 100644 --- a/docs/docs/docs/interop-otel/_meta.json +++ b/docs/docs/docs/interop-otel/_meta.json @@ -1,7 +1,7 @@ [ { "type": "file", - "name": "quick-start.mdx", - "label": "Quick Start" + "name": "introduction.mdx", + "label": "Introduction" } ] diff --git a/docs/docs/docs/interop-otel/introduction.mdx b/docs/docs/docs/interop-otel/introduction.mdx new file mode 100644 index 0000000..c33276b --- /dev/null +++ b/docs/docs/docs/interop-otel/introduction.mdx @@ -0,0 +1,131 @@ +# OpenTelemetry Integration + +Ottrelite provides seamless integration with OpenTelemetry (OTEL), allowing you to use your existing OpenTelemetry instrumentation while leveraging Ottrelite's visualization backends for development-time analysis. + +The integration works by providing Ottrelite-compatible TracerProvider and MeterProvider implementations that can export to both Ottrelite backends (for development visualization) and traditional OTEL collectors (for production monitoring). + +## Supported platforms + +| Platform | Status | +| -------- | ------ | +| JavaScript | ✅ | +| C++ | ✅ | +| Java/Kotlin | ⏳ | +| Swift | ⏳ | + +## Installation + +```bash +npm install @ottrelite/interop-otel +``` + +## Available classes + +### TracerProvider + +Ottrelite provides [`OttreliteTracerProvider`](https://github.com/callstackincubator/ottrelite/blob/main/packages/interop-otel/src/otel/OttreliteTracerProvider.ts) that extends OpenTelemetry's standard TracerProvider with comprehensive Ottrelite integration: + +**Automatic Configuration:** +- **Resource metadata** - Configures default resource to carry Ottrelite-specific metadata +- **Trace context propagation** - Registers W3C trace context and baggage propagators +- **Context management** - Sets up `StackContextManager` by default for proper span correlation + +### MeterProvider + +The [`OttreliteMeterProvider`](https://github.com/callstackincubator/ottrelite/blob/main/packages/interop-otel/src/otel/OttreliteMeterProvider.ts) provides metrics collection with Ottrelite integration. It uses dynamic instrumentation by hooking into instrument creation methods and their return values, replacing properties on instances to call Ottrelite's logic before the original execution. + +### Development Span Processor + +The [`DevSpanProcessorInterceptor`](https://github.com/callstackincubator/ottrelite/blob/main/packages/interop-otel/src/otel/processor/DevSpanProcessorInterceptor.ts) enables live span visualization during development. It intercepts the OpenTelemetry API to propagate all OTEL spans in real-time to Ottrelite's development backends, allowing you to see traces immediately in Tracy or platform profilers while maintaining your existing OTEL instrumentation. + +## Configuration + +Configure OpenTelemetry to work with Ottrelite's visualization backends: + +```typescript +import { resourceFromAttributes } from '@opentelemetry/resources'; +import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { + ATTR_SERVICE_NAME, + ATTR_SERVICE_VERSION, +} from '@opentelemetry/semantic-conventions'; +import { + DevSpanProcessorInterceptor, + OttreliteExporterOTLP, + OttreliteMeterProvider, + OttreliteTracerProvider, +} from '@ottrelite/interop-otel'; + +// optional step: resource attributes; below: example options, this can be customized as needed +const resource = resourceFromAttributes({ + [ATTR_SERVICE_NAME]: appName, + [ATTR_SERVICE_VERSION]: getVersion(), + 'device.id': getDeviceId(), + 'os.name': Platform.OS, + 'os.version': getSystemVersion(), +}); + +// Configure the OpenTelemetry TracerProvider to use Ottrelite's capabilities +const tracerProvider = new OttreliteTracerProvider({ + spanProcessors: [ + // Register the DevSpanProcessorInterceptor for live tracing in development + new DevSpanProcessorInterceptor(), + ], + resource, +}); + +tracerProvider.register(); + +// Configure the OpenTelemetry MeterProvider to use Ottrelite's capabilities +const meterProvider = new OttreliteMeterProvider({ + resource, +}); + +meterProvider.register(); +``` + +:::important +**Don't forget production exporters!** While `DevSpanProcessorInterceptor` provides live visualization during development, make sure to also add OpenTelemetry exporters to collect trace data for production monitoring and analysis. +::: + +:::note +Make sure to conditionally configure the OttreliteTracerProvider and OttreliteMeterProvider only if you are using Ottrelite's development backends. Production builds should not have any Ottrelite-specific configuration. +::: + +### Community Instrumentations + +Ottrelite integrates with the OpenTelemetry JavaScript API, allowing you to use the same community instrumentations as you would with standard OpenTelemetry JS. This enables automatic instrumentation of various APIs and frameworks. + +The example below shows common instrumentations for React Native apps. You can add more as needed, inspired by the [OpenTelemetry demo React Native app](https://opentelemetry.io/docs/demo/services/react-native-app/). + +```typescript +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; +import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; + +registerInstrumentations({ + instrumentations: [ + new FetchInstrumentation({ + clearTimingResources: false, + propagateTraceHeaderCorsUrls: /.*/, + }), + new XMLHttpRequestInstrumentation({ + ignoreUrls: [/\/fetch-urls\/.*/], + }), + ], +}); +``` + +## C++ Integration + +The C++ integration enables OpenTelemetry spans from C++ code to be visualized in Ottrelite's backends. When you register the JavaScript TracerProvider and MeterProvider, Ottrelite automatically configures the global C++ OpenTelemetry providers to use the same exporters and visualization backends. + +### Options + +The `Ottrelite.install` method accepts C++ OTEL SDK configuration options: + +- `cppBatchLogRecordProcessorOptions` - Configure log record batch processing +- `cppMetricReaderOptions` - Configure metrics export intervals and batching +- `cppTraceBatchSpanProcessorOptions` - Configure span batch processing + +These options only affect C++ data export behavior; other language layers process spans according to their own configurations. diff --git a/docs/docs/docs/interop-otel/quick-start.mdx b/docs/docs/docs/interop-otel/quick-start.mdx deleted file mode 100644 index 3934776..0000000 --- a/docs/docs/docs/interop-otel/quick-start.mdx +++ /dev/null @@ -1,194 +0,0 @@ -# Ottrelite interoperability package with OTEL - -## Status of the project - -| Feature | Status | -| ------------------ | ------ | -| OTEL-JS interop | ✅ | -| OTEL-CPP interop | ✅ | -| OTEL-Java interop | TBD | -| OTEL-Swift interop | TBD | -| RN integration | ✅ | - -## OTEL interoperability - -Ottrelite provides interoperability with OpenTelemetry (OTEL) through a set of adapter facilities. - -### Javascript - -Firstly, Ottrelite provides its own `TracerProvider` ([`OttreliteTracerProvider.ts`](https://github.com/callstackincubator/ottrelite/blob/main/packages/interop-otel/src/otel/OttreliteTracerProvider.ts)) that provides adequate configuration for JS (configures the default resource to carry ottrelite's metadata, registers W3C & trace context baggage propagators, by default - `StackContextManager`). Invoking `register` on it, apart from the aforementioned, also calls invokes configuration of the global C++ API provider, described in the section below. - -The analogous applies to [`OttreliteMeterProvider`](https://github.com/callstackincubator/ottrelite/blob/main/packages/interop-otel/src/otel/OttreliteMeterProvider.ts). An implementation detail difference is that the meter provider hooks into returned instrument creation methods & their return values dynamically by replacing properties on instances, to call its own logic before the original is executed. - -The OTEL API is also hooked into by the [`DevSpanProcessorInterceptor.ts`](https://github.com/callstackincubator/ottrelite/blob/main/packages/interop-otel/src/otel/processor/DevSpanProcessorInterceptor.ts), which can be registered to propagate all OTEL spans live to the [Development API](https://callstackincubator.github.io/ottrelite/docs/core/quick-start.html#development-api-1). - -### C++ - -#### Usage - -The global C++ Tracer and Meter Providers are registered - respectively - from `installGlobalOTELCPPTracerProvider` and `installGlobalOTELCPPMeterProvider` Nitro Module methods. Calling each of those makes sense **only once**. At the first call, the Ottrelite tracer/meter exporters that have been instantiated and registered in JavaScript so far, will also be registered in the C++ API. This is the single-implementation, single-configuration approach that Ottrelite follows: create & configure all tracer/meter exporters in JS, use them from JS, C++, Kotlin/Java & Swift code. - -> [!WARNING] -> Invoking the aforementioned install methods more than once will discard all previously registered C++ tracer/meter exporters. It is important to first register all the tracer/meter exporters in JS, and then call the `register()` methods of `OttreliteTracerProvider` and `OttreliteMeterProvider` in JS. - -```typescript -import { resourceFromAttributes } from '@opentelemetry/resources'; -import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { - ATTR_SERVICE_NAME, - ATTR_SERVICE_VERSION, -} from '@opentelemetry/semantic-conventions'; -import { - DevSpanProcessorInterceptor, - OttreliteExporterOTLP, - OttreliteMeterProvider, - OttreliteTracerProvider, -} from '@ottrelite/interop-otel'; - -// optional step: resource attributes; below: example options, this can be customized as needed -const resource = resourceFromAttributes({ - [ATTR_SERVICE_NAME]: appName, - [ATTR_SERVICE_VERSION]: getVersion(), - 'device.id': getDeviceId(), - 'os.name': Platform.OS, - 'os.version': getSystemVersion(), -}); - -const tracerProvider = new OttreliteTracerProvider({ - spanProcessors: [ - // optional step: configure Ottrelite's span processor (required for live tracing in dev) - new DevSpanProcessorInterceptor(), - - // optional step: configure Ottrelite's actual exporter using a preferred processor of choice - new SimpleSpanProcessor( - new OttreliteExporterOTLP({ - endpoint: 'http://localhost:4318/v1/', - }) - ), - ], - resource, -}); - -// register the React Native tracer provider -tracerProvider.register(); - -// configure the OpenTelemetry MeterProvider to use Ottrelite's capabilities -const meterProvider = new OttreliteMeterProvider({ - resource, -}); - -meterProvider.register(); -``` - -##### OTEL Instrumentations - -Optionally, configure automatic instrumentation of various JS APIs using OpenTelemetry community instrumentations. - -```typescript -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; -import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; - -// optional step: register instrumentations; Ottrelite integrates with the OTEL JS API, therefore allowing to -// use the same instrumentations as you would with OpenTelemetry JS -// the below example shows a few common instrumentations, but you can add more as needed, inspired by https://opentelemetry.io/docs/demo/services/react-native-app/ -registerInstrumentations({ - instrumentations: [ - new FetchInstrumentation({ - clearTimingResources: false, - propagateTraceHeaderCorsUrls: /.*/, - }), - new XMLHttpRequestInstrumentation({ - ignoreUrls: [/\/fetch-urls\/.*/], - }), - ], -}); -``` - -#### Configuration - -The `Ottrelite.install` method accepts 3 C++ OTEL SDK - specific configuration options: - -1. `cppBatchLogRecordProcessorOptions` - options for the `opentelemetry::sdk::logs::BatchLogRecordProcessorFactory` -2. `cppMetricReaderOptions` - options for the `opentelemetry::sdk::metrics::PeriodicExportingMetricReaderFactory` -3. `cppTraceBatchSpanProcessorOptions` - options for the `opentelemetry::sdk::trace::opentelemetry::sdk::trace::BatchSpanProcessorFactory` - -Those can only be passed once and will be used to configure the C++ OTEL SDK, which is the data sink for OTEL traces from all interoperability layers in all languages. However, those options will only affect the C++ data export - other languages will process, report & provide the spans according to their own setup. - -#### Implementing a custom exporter - -To implement a custom exporter, first create a C++ Nitro Module spec: - -```typescript -// in MyOttreliteExporter.nitro.ts -import type { OttreliteExporter } from '@ottrelite/core'; - -export interface MyOttreliteExporter - extends HybridObject<{ ios: 'c++'; android: 'c++' }>, - OttreliteExporter {} -``` - -After codegen-ing the C++ glue code from this spec, implement the exporter in C++. The primary methods that are crucial for the implementation are: - -- `void initExporter(const OTLPExporterConfig &config);` -- `void exportSpans(const std::vector &spans, const std::function &resultCallback);` -- `std::shared_ptr> shutdown();` -- `std::shared_ptr> forceFlush();` - -Those methods are standard for OTEL exporters in according to its specification, yet the methods accept internal Ottrelite object types. - -The `OTLPExporterConfig` is Nitro-codegened from [`OTLPExporterConfig.ts`](https://github.com/callstackincubator/ottrelite/blob/main/packages/interop-otel/src/types/OTLPExporterConfig.ts) and represents the most important JS configuration for exporters in general. If some more configuration levels are needed to be covered, please file a PR or issue. - -The `ExportResult` is trivial and is a 1:1 CPP-JS mapping of the result type. - -The `SerializedReadableSpan` is a representation of a span in JS. Due to some limitations of Nitro, the object currently is not possible to be passed to CPP 1:1, however the overhead of conversion is minimal: some properties are only cast as types, while some need to be remapped: - -- due to Nitro codegen problems with recursive type references, some methods need to be masked & some types require aliases -- all `SpanContext` objects are serialized to a string using built-in JS OTEL functionality, which is then parsed on the C++ side using built-in C++ OTEL functionality - -For conversion of `SerializedReadableSpan`s to C++ OTEL vector of `Recordable`s, the `::ottrelite::interop::otel::SpanConverter::convertSpans(spans, resourcesMemory)` from `SpanConverter.hpp` can be used: - -```cpp -#include "SpanConverter.hpp" - -// ... - -// converted Resource-s must be kept in memory for the time of export -::ottrelite::interop::otel::ResourcesMemory resourcesMemory{}; - -auto recordables = ::ottrelite::interop::otel::SpanConverter::convertSpans(spans, resourcesMemory); - -// create a span view for the exporter -opentelemetry::nostd::span> spanView( - recordables.data(), recordables.size()); - -// example: call the exporter's Export method -auto otelResult = otelExporterPtr->Export(spanView); -``` - -More hollistically, a vector of `SerializedReadableSpan` can be converted & exported on an exporter instance using - -```cpp -#include "SpanConverter.hpp" // required for ResourcesMemory alias -#include "OTELExporterAdapterUtils.hpp" - -// ... - -// converted Resource-s must be kept in memory for the time of export -::ottrelite::interop::otel::ResourcesMemory resourcesMemory{}; - -resultCallback(::ottrelite::interop::otel::OTELExporterAdapterUtils::exportInternalSpansReprsViaOTEL( - resourcesMemory, spans, otlpExporterPtr_)); -``` - -### Android - -TODO: write this section - -### iOS - -TODO: write this section - -## Demo - -The `examples/backend-jaeger` directory contains a Docker Compose file for running the Jaeger backend service. To run it, execute `docker compose up -d`, which will start the Jaeger backend with an OTLP collector, as well as serve Jaeger UI on `http://localhost:16686/` 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. diff --git a/docs/docs/docs/introduction.mdx b/docs/docs/docs/introduction.mdx index 72a57c3..05a096f 100644 --- a/docs/docs/docs/introduction.mdx +++ b/docs/docs/docs/introduction.mdx @@ -1,21 +1,161 @@ -# Ottrelite Core +# What is Ottrelite? -The core API of Ottrelite provides a set of base classes and common APIs that allow to integrate or extend the Ottrelite tracing framework. +Ottrelite is a performance debugging and tracing toolkit for React Native applications. It helps you **visualize and analyze the performance** of any code in your app - from screen renders and animations to business logic and data processing. -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 Problem -The core package - `@ottrelite/core` - provides a so-called [**Development API**](#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. +When building React Native apps, you often run into performance issues that are hard to debug: -For **production use cases**, Ottrelite [integrates with OpenTelemetry (OTEL) JS API](#opentelemetry-otel-api---production--development), 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. +- Your app feels sluggish, but you don't know if it's the UI, business logic, or data processing +- A screen takes 2 seconds to load, but which of the 15 functions in your component is the bottleneck? +- Your complex form validation runs fast in dev but slow on device - which validation rule is the culprit? -## Getting started +React Native's cross-platform nature creates unique debugging opportunities that require specialized tools. -To get started, please follow the [core Quick Start instructions](./core/quick-start.mdx). +Performance issues in React Native apps often span JavaScript, native modules, and platform-specific code. While Chrome DevTools, Xcode Instruments, and Android Studio Profiler are excellent tools for their respective platforms, React Native developers need a solution that bridges these domains. -## Roadmap +Ottrelite provides a unified tracing API that works across all React Native languages and platforms, with flexible visualization backends that give you a complete picture of your app's performance. -The roadmap for the nearest future includes the following: +## The Solution -- integration of more OTEL C++ SDK exporters into Ottrelite's wrappers -- integration with Swift OTEL SDK -- integration with Java OTEL SDK +Ottrelite has a **two-part architecture**: choose your **tracing API** and choose your **visualization backend**. + +### Tracing API + +The Tracing API provides a lightweight interface for React Native development that focuses on fast iteration during development. It supports both synchronous and asynchronous traces, counter events for monitoring metrics, and works across JavaScript, C++, Swift, and Java. + +Below is an example of how to use the Tracing API in different languages to synchronous trace an event named `generateImage`. + +import { Tab, Tabs } from 'rspress/theme'; + + + +```typescript title="index.ts" +import { Ottrelite } from '@ottrelite/core'; + +Ottrelite.beginEvent('generateImage', { + width: size.width.toString(), + height: size.height.toString(), +}); + +// ... + +Ottrelite.endEvent({ + pixels: image.size().toString(), +}); + +``` + + +```cpp title="index.cpp" +#include + +Ottrelite::beginEvent("generateImage", + {{{"width", std::to_string(width)}, {"height", std::to_string(height)}}}); + +// ... + +Ottrelite::endEvent({{{"pixels", std::to_string(image.size())}}}); + +``` + + +```kotlin title="index.kt" +// tbd +``` + + +```java title="index.java" +import com.callstack.ottrelite.OttreliteAndroid + +OttreliteAndroid.beginEvent("generateImage", mapOf( + "width" to width, + "height" to height +)) + +// ... + +OttreliteAndroid.endEvent(mapOf( + "pixels" to image.size() +)) +``` + + + +The API also allows you to trace asynchronous events as well as counter events. For details, see the [Tracing API documentation](/docs/core/introduction). + +:::tip +**Already using OpenTelemetry?** + +Your existing OTEL spans can automatically work with Ottrelite's visualization backends while maintaining your production monitoring setup. You can preview OTEL traces locally during development without needing a dedicated collector service. See the [OpenTelemetry Integration guide](./otel-interop/quick-start.mdx) for setup details. +::: + +:::note +The Tracing API is automatically disabled in production builds, ensuring zero overhead in released apps. +::: + +### Visualization Backend + +Visualization backends transform your trace data into actionable insights. Think of them as the "display layer" that takes your raw performance data and presents it in a way that helps you quickly identify bottlenecks and optimization opportunities. + +**Available Backends:** + +| Backend | Best For | What It Does | +|---------|----------|--------------| +| **Tracy** | Real-time development profiling | Frame-by-frame analysis with live trace visualization, memory tracking, and cross-platform support | +| **Platform** | System-level analysis | Integrates with Android Studio Profiler, Xcode Instruments, and Perfetto | + +Each backend is designed for different development workflows - you can even use multiple backends simultaneously to get different perspectives on your app's performance. + +## When to Use Ottrelite + +Ottrelite shines during the **development and optimization phases** of React Native app development. It's specifically designed for scenarios where you need to understand exactly what's happening inside your code, rather than just monitoring high-level system metrics. + +**Ottrelite is perfect for:** + +**Performance audits and optimization** - When you need to identify which specific functions or components are causing performance issues. Unlike general profiling tools, Ottrelite lets you trace your exact business logic and see precisely where time is being spent. + +**Debugging complex React Native logic** - Whether it's investigating why a screen loads slowly, understanding render performance, or tracking down issues in data processing pipelines. The cross-platform nature means you can trace performance issues that span JavaScript, native modules, and platform-specific code. + +**React Native internals exploration** - Ottrelite can automatically instrument React Native's internal operations, giving you visibility into framework-level performance without modifying React Native source code. + +## When not to use Ottrelite + +Ottrelite is designed for rich, detailed analysis during development, not lightweight production telemetry. For production monitoring, consider using OpenTelemetry instead. + +You can still visualize your production data with Ottrelite's OpenTelemetry integration, which gives you both development-time visualization and production monitoring capabilities. + +## Alternatives + +While Ottrelite provides a unified tracing solution across React Native platforms, there are other tools you might consider depending on your specific needs: + +### React Native DevTools + +Use the standard Performance API (`performance.mark()`, `performance.measure()`) to instrument your React Native code, with results visible in React Native DevTools' Performance panel. + +Use React Performance Tracks to visualize React work with four priority tracks: blocking (synchronous updates from user interactions), transition (background work initiated via `startTransition`), suspense (content revealing and fallback displays), and idle (lowest priority work that executes when no higher priority tasks exist). + +:::note +This feature was recently announced and is currently available as a canary in the upcoming version of React Native. For more details, see the [Global Performance documentation](https://reactnative.dev/docs/next/global-performance). +::: + +For React Native versions that don't yet support performance out of the box, you can use the `react-native-performance` package to preview your traces with Rozenite. This package also provides Objective-C and Java APIs for native-side tracing, though it's limited to a single backend visualization. + +**Best for:** React component performance analysis and standard web performance profiling workflows in React Native using familiar Chrome DevTools. + +### Native Platform Tools + +**Android Studio Profiler** and **Xcode Instruments** are powerful native profiling tools that come with your development environment: + +- **Android Studio Profiler** - CPU, memory, network, and energy profiling for Android apps +- **Xcode Instruments** - Comprehensive performance analysis for iOS apps including Time Profiler, Allocations, and Leaks + +**Best for:** Platform-specific analysis, system-level debugging, and when you need deep native performance insights. + +### When to Choose Ottrelite + +Ottrelite excels when you need: +- **Cross-platform consistency** - Same tracing API across JavaScript, C++, Swift, and Java +- **Custom business logic tracing** - Instrument your specific application code, not just React internals +- **Multi-language performance analysis** - Trace performance issues spanning JavaScript and native code +- **Development-focused workflows** - Real-time feedback and development-optimized visualization diff --git a/docs/docs/docs/otel-interop/_meta.json b/docs/docs/docs/otel-interop/_meta.json deleted file mode 100644 index 64f16c2..0000000 --- a/docs/docs/docs/otel-interop/_meta.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "type": "file", - "name": "quick-start.mdx", - "label": "Quick Start" - } -] diff --git a/docs/docs/docs/otel-interop/quick-start.mdx b/docs/docs/docs/otel-interop/quick-start.mdx deleted file mode 100644 index 140ebf7..0000000 --- a/docs/docs/docs/otel-interop/quick-start.mdx +++ /dev/null @@ -1,38 +0,0 @@ -# OTEL interoperability - -Ottrelite provides interoperability integration points with OpenTelemetry SDKs. - -## `useTracer` - -This hook simply wraps the OTEL `trace.getTracer` API. - -```typescript -const tracer = useTracer('my-trace-unit'); -``` - -## `vanillaTracerFactory` - -This function (not a hook, though) is meant for internal consumption in `useTracer`, or for Class Components. It wraps the OTEL `trace.getTracer` API but also performs a sanity check whether the global `TracerProvider` is actually Ottrelite's implementation, otherwise logging a warning. - -```typescript -class MyComponent { - private tracer = vanillaTracerFactory('MyComponent'); - - ... -} -``` - -# OpenTelemetry (OTEL) API - production and development - -To use the package in production, you can use OpenTelemetry as you would normally do in any project. With Ottrelite, all the spans are processed by JS processor but finally land in exporter(s) only from the `@ottrelite/` packages. Naturally, if you include non-ottrelite's exporters, the JS events will be properly exported via them, yet the events captured in C++, Java/Kotlin, Swift or Objective-C code would not reach these exporters. The main design objective of Ottrelite is to have a common sink (i.e., set of exporters), configured once from JS, implemented in C++ as a native module (current implementation uses Nitro Modules). - -> [!NOTE] -> The above implies a limitation of Ottrelite: to leverage a cross-language single exporter(s) sink, all exporters that are configured must be ottrelite-compatible native exporter packages. What we offer has full parity with the exporters offered by OTEL JS and can be freely extended; contributions are always welcome. - -## Ottrelite cross-language exporters - -TODO: write this! - -## Ottrelite React / Native Instrumentations - -Apart from the [OTEL community instrumentations for JS](https://github.com/open-telemetry/opentelemetry-js-contrib) you can use, Ottrelite provides its own set of instrumentations specifically designed for React and React Native applications. diff --git a/docs/docs/docs/quick-start.mdx b/docs/docs/docs/quick-start.mdx new file mode 100644 index 0000000..78541bf --- /dev/null +++ b/docs/docs/docs/quick-start.mdx @@ -0,0 +1,96 @@ +# Quick Start + +Get up and running with Ottrelite in just a few minutes. Ottrelite needs two components to work: a **tracing API** and a **visualization backend**. + +## Prerequisites + +Ottrelite requires `react-native-nitro-modules` to be installed in your project. If you don't have it yet, install it first: + +```bash +npm install react-native-nitro-modules +``` + +For detailed setup instructions, consult the [react-native-nitro-modules documentation](https://nitro.margelo.com). + +## Step 1: Install the Tracing API + +Install the core Ottrelite package: + +```bash +npm install @ottrelite/core +``` + +Add a sample trace anywhere in your app to test the setup: + +```typescript +import { Ottrelite } from '@ottrelite/core'; + +// Wrap any code you want to measure +Ottrelite.beginEvent('MyEvent'); +// ... do some work ... +Ottrelite.endEvent(); +``` + +## Step 2: Install a Visualization Backend + +Choose a backend to visualize your traces. For this quick start, we'll use the Platform backend, which integrates with Android Studio Profiler and Xcode Instruments. + +```bash +npm install @ottrelite/backend-platform +``` + +Configure Ottrelite to use the backend: + +```typescript +import { Ottrelite } from '@ottrelite/core'; +import { OttreliteBackendPlatform } from '@ottrelite/backend-platform'; + +Ottrelite.install([OttreliteBackendPlatform]); +``` + +:::tip +You can install multiple backends simultaneously to get different perspectives on your app's performance: + +```typescript +Ottrelite.install([ + OttreliteBackendPlatform, + OttreliteBackendTracy +]); +``` +::: + +## Step 3: Record and View Traces + +Each backend provides different ways to record and view traces: + +### Android Studio Profiler +1. Open your project in Android Studio +2. Run your React Native app +3. Open the Profiler tab (View → Tool Windows → Profiler) +4. Start a CPU recording session +5. Trigger the code with your traces +6. Stop recording and view the traces + +### Xcode Instruments +1. Build and run your iOS app +2. Open Instruments (Xcode → Open Developer Tool → Instruments) +3. Choose the "os_signpost" instrument +4. Start recording and trigger your traced code +5. View the hierarchical traces with timing information + +:::note +For detailed backend-specific instructions, consult the individual backend documentation in the [Backends](/docs/backends) section. +::: + +:::tip +For real-time profiling, consider using the Tracy backend. Unlike Android Studio Profiler and Xcode Instruments, Tracy is a real-time profiler that can give you a frame-by-frame analysis of your app's performance without having to wait for the trace to be recorded. +::: + +## Next Steps + +Now that you have basic tracing working: + +- **[Learn about tracing methods](/docs/api/overview)** - Explore synchronous, asynchronous, and counter events +- **[Trace React components](/docs/instrumentation-react/overview)** - Automatically instrument component lifecycle +- **[Explore available backends](/docs/backends/overview)** - Try Tracy for real-time profiling or OpenTelemetry integration +- **[Explore OpenTelemetry integration](/docs/otel-interop/overview)** - Visualize your existing OpenTelemetry setup with Ottrelite's backends diff --git a/docs/docs/index.md b/docs/docs/index.md index 4bc36d2..ec5e5a5 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -2,7 +2,7 @@ pageType: home hero: - name: RN Ottrelite + name: Ottrelite text: Backend-agnostic React Native observability actions: - theme: brand @@ -20,13 +20,17 @@ hero: dark: /img/logo-dark.png alt: Ottrelite logo features: - - title: Unified API - details: All functionalities are accessible through a single API in all languages for developing in React Native. - icon: 📦 - - title: Pluggable - details: The sink for traces can be dynamically loaded. It's possible to implement a custom backend, there is no vendor lock-in. + - title: Cross-Platform Tracing + details: Single API that works across JavaScript, C++, Swift, and Java - trace performance issues spanning React Native's entire stack. + icon: 🌐 + - title: Real-Time Profiling + details: Live trace visualization with Tracy backend for frame-by-frame analysis. + icon: ⚡ + icon: 🛠️ + - title: Multiple Backends + details: Choose your visualization backend - Tracy for real-time profiling, Platform for system-level analysis, or OpenTelemetry integration. icon: 🔌 - - title: Single data sink - details: 'The same API is provided both to JS/TS & native languages: Kotlin, Java, C++, Swift, Objective-C.' - icon: 📑 + - title: OpenTelemetry Integration + details: Preview your existing OTEL spans with Ottrelite's visualization backends while maintaining your production monitoring setup. + icon: 📊 --- diff --git a/examples/rn-app/ios/license_plist.yml b/examples/rn-app/ios/license_plist.yml index fdef9f7..4ab3c7e 100644 --- a/examples/rn-app/ios/license_plist.yml +++ b/examples/rn-app/ios/license_plist.yml @@ -1352,7 +1352,7 @@ manual: - name: argparse@1.0.10 version: '1.0.10' source: nodeca/argparse - file: '../../../node_modules/js-yaml/node_modules/argparse/LICENSE' + file: '../../../node_modules/argparse/LICENSE' - name: sprintf-js@1.0.3 version: '1.0.3' source: https://github.com/alexei/sprintf.js @@ -1404,7 +1404,7 @@ manual: - name: react-is@18.3.1 version: '18.3.1' source: https://github.com/facebook/react - file: '../../../node_modules/pretty-format/node_modules/react-is/LICENSE' + file: '../../../node_modules/react-is/LICENSE' - name: metro-file-map@0.83.1 version: '0.83.1' source: https://github.com/facebook/metro @@ -1940,7 +1940,7 @@ manual: - name: react-is@16.13.1 version: '16.13.1' source: https://github.com/facebook/react - file: '../../../node_modules/react-is/LICENSE' + file: '../../../node_modules/hoist-non-react-statics/node_modules/react-is/LICENSE' - name: color@3.2.1 version: '3.2.1' source: Qix-/color diff --git a/examples/rn-app/ios/ottrelite-example.xcodeproj/project.pbxproj b/examples/rn-app/ios/ottrelite-example.xcodeproj/project.pbxproj index 9f84fe8..c8f6e2f 100644 --- a/examples/rn-app/ios/ottrelite-example.xcodeproj/project.pbxproj +++ b/examples/rn-app/ios/ottrelite-example.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; 9BD1A0AF7F0AA9B1B4239760 /* libPods-ottrelite-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AA5723D352109F51B60E403 /* libPods-ottrelite-example.a */; }; D410E1A8A7878985640D08B5 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; + 3C4851E87A3846E28CE9A682 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 53BC140C272B40FD89C85399 /* Settings.bundle */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -25,9 +26,9 @@ 6AA5723D352109F51B60E403 /* libPods-ottrelite-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ottrelite-example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = "ottrelite-example/AppDelegate.swift"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = "ottrelite-example/LaunchScreen.storyboard"; sourceTree = ""; }; - A906569D1BEA477988088ABE /* Settings.bundle */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = "wrapper.plug-in"; name = Settings.bundle; path = Settings.bundle; sourceTree = ""; }; D58924470BCFCCD8276C61DF /* Pods-ottrelite-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ottrelite-example.debug.xcconfig"; path = "Target Support Files/Pods-ottrelite-example/Pods-ottrelite-example.debug.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; + 53BC140C272B40FD89C85399 /* Settings.bundle */ = {isa = PBXFileReference; name = "Settings.bundle"; path = "Settings.bundle"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.plug-in; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -50,7 +51,7 @@ 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, - A906569D1BEA477988088ABE /* Settings.bundle */, + 53BC140C272B40FD89C85399 /* Settings.bundle */, ); name = "ottrelite-example"; sourceTree = ""; @@ -168,6 +169,7 @@ D410E1A8A7878985640D08B5 /* PrivacyInfo.xcprivacy in Resources */, 59F21F8927D7433DB835433E /* BuildFile in Resources */, 0D2572EC485D4E2CBAB9778D /* Settings.bundle in Resources */, + 3C4851E87A3846E28CE9A682 /* Settings.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; };