Skip to content

Commit 71acfea

Browse files
authored
Merge pull request #6 from callstackincubator/feat/deintegrate-otlp-exporter
feat: enabled option, 3 demo app variants, conditional Gradle plugin application, deintegrate OTLP exporter wrapper
2 parents 950d7a3 + 1681363 commit 71acfea

File tree

137 files changed

+7994
-8505
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

137 files changed

+7994
-8505
lines changed

.changeset/khaki-cups-sit.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ottrelite/interop-otel': patch
3+
---
4+
5+
feat!: deintegrate C++ OTLP exporter wrapper feature from package

.changeset/late-crabs-vanish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ottrelite/core': patch
3+
---
4+
5+
feat: enabled install option and setEnabled runtime method for Ottrelite that control runtime tracing enabled state

.changeset/slow-doors-eat.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@ottrelite/backend-wrapper-tracy': patch
3+
'@ottrelite/backend-platform': patch
4+
---
5+
6+
feat: defer createHybridObject to first access moment
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
version: '3'
22

33
services:
4+
proxy:
5+
# fix for OpenTelemetry JS always adding a trailing slash to the OTLP endpoints, which is not supported by Jaeger
6+
image: nginx:alpine
7+
ports:
8+
- '4318:4318'
9+
volumes:
10+
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
411
jaeger:
512
image: jaegertracing/jaeger:2.9.0
13+
hostname: jaeger
614
ports:
715
- '16686:16686' # Jaeger UI
816
- '4317:4317' # OTLP gRPC receiver
9-
- '4318:4318' # OTLP HTTP receiver
17+
- '4319:4319' # OTLP HTTP receiver for proxy (4318 served by nginx)
18+
command:
19+
[
20+
'--set=receivers.otlp.protocols.http.endpoint=0.0.0.0:4319',
21+
'--set=receivers.otlp.protocols.grpc.endpoint=0.0.0.0:4317',
22+
]
1023
environment:
11-
- LOG_LEVEL=debug
24+
- LOG_LEVEL=verbose

examples/backend-jaeger/nginx.conf

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
server {
2+
listen 4318;
3+
server_name _;
4+
5+
6+
location / {
7+
# remove trailing slashes
8+
rewrite ^/(.*)/+$ /$1 break;
9+
10+
proxy_pass http://jaeger:4319;
11+
proxy_set_header Host $host;
12+
proxy_set_header X-Real-IP $remote_addr;
13+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
14+
proxy_set_header X-Forwarded-Proto $scheme;
15+
}
16+
}

examples/ottrelite-consumer-lib/src/index.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,31 @@ import type { NitroOttreliteConsumerLibPlatform } from './specs/NitroOttreliteCo
55

66
export type { NitroOttreliteConsumerLib } from './specs/NitroOttreliteConsumerLib';
77

8-
const NitroOttreliteConsumerLibHybridObject =
9-
NitroModules.createHybridObject<NitroOttreliteConsumerLibCpp>(
10-
'NitroOttreliteConsumerLibCpp'
11-
);
12-
13-
const NitroOttreliteConsumerLibPlatformHybridObject =
14-
NitroModules.createHybridObject<NitroOttreliteConsumerLibPlatform>(
15-
'NitroOttreliteConsumerLibPlatform'
16-
);
17-
188
export class CppConsumerLib {
9+
static hybridObject: NitroOttreliteConsumerLibCpp;
1910
static generateImage(width: number, height: number): number[] {
20-
return NitroOttreliteConsumerLibHybridObject.generateImage(width, height);
11+
if (!this.hybridObject) {
12+
this.hybridObject =
13+
NitroModules.createHybridObject<NitroOttreliteConsumerLibCpp>(
14+
'NitroOttreliteConsumerLibCpp'
15+
);
16+
}
17+
18+
return this.hybridObject.generateImage(width, height);
2119
}
2220
}
2321

2422
export class PlatformConsumerLib {
23+
static hybridObject: NitroOttreliteConsumerLibPlatform;
24+
2525
static generateImage(width: number, height: number): number[] {
26-
return NitroOttreliteConsumerLibPlatformHybridObject.generateImage(
27-
width,
28-
height
29-
);
26+
if (!this.hybridObject) {
27+
this.hybridObject =
28+
NitroModules.createHybridObject<NitroOttreliteConsumerLibPlatform>(
29+
'NitroOttreliteConsumerLibPlatform'
30+
);
31+
}
32+
33+
return this.hybridObject.generateImage(width, height);
3034
}
3135
}

examples/rn-app/README.md

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,18 @@ This is a demonstrator application for the Ottrelite project, showcasing how to
66
- [Getting Started](#getting-started)
77
- [Step 1: Start Metro](#step-1-start-metro)
88
- [Step 2: Build and run the app](#step-2-build-and-run-the-app)
9+
- [Build types](#build-types)
10+
- [Debug build](#debug-build)
11+
- [Release build](#release-build)
12+
- [Profiling build](#profiling-build)
913
- [Android](#android)
14+
- [Debug build](#debug-build-1)
15+
- [Release build](#release-build-1)
16+
- [Profiling build](#profiling-build-1)
1017
- [iOS](#ios)
18+
- [Debug build](#debug-build-2)
19+
- [Release build](#release-build-2)
20+
- [Profiling build](#profiling-build-2)
1121
- [Step 3: Trace the application](#step-3-trace-the-application)
1222

1323
---
@@ -30,12 +40,57 @@ pnpm start
3040

3141
With Metro running, open a new terminal window/pane from the root of your React Native project, and use one of the following commands to build and run your Android or iOS app:
3242

43+
### Build types
44+
45+
46+
#### Debug build
47+
48+
The `debug` build variant (Android) or `Debug` configuration (iOS) will build the app in debug mode, with tracing enabled by default.
49+
50+
```sh
51+
pnpm android
52+
```
53+
54+
#### Release build
55+
56+
The `release` build variant (Android) or `Release` configuration (iOS) will build the app in release mode with the env var `DISABLE_TRACING` set to `true`, causing Ottrelite tracing to be disabled at runtime.
57+
58+
**Please note** that disabling of tracing at runtime happens only via `pnpm {android,ios}:release` script, not if building from Android Studio or Xcode, in which case tracing will be disabled. This is just the exemplary setup for this demo, in a usual case you'd likely want the inverted logic, in which case tracing is disabled by default and you only enable it when the env var is set instead. Handling of this env var is done by Babel plugin `transform-inline-environment-variables` and the `pnpm {android,ios}:release` script which passes the proper value of `DISABLE_TRACING` env var.
59+
60+
```sh
61+
pnpm android:release
62+
```
63+
64+
#### Profiling build
65+
66+
The `profiling` build variant (Android) or `Profiling` configuration (iOS) inherits everything from the `release` build variant and will build the app in profiling mode. This is the perfect mode that gives reliable performance behaviour after all compile-time optimizations.
67+
68+
```sh
69+
pnpm android:profiling
70+
```
71+
3372
### Android
3473

74+
The script [`android/app/build.gradle`](android/app/build.gradle) configures Ottrelite's Gradle plugin to inject tracing instrumentation for RN internals only for the `profiling` variant.
75+
76+
#### Debug build
77+
3578
```sh
3679
pnpm android
3780
```
3881

82+
#### Release build
83+
84+
```sh
85+
pnpm android:release
86+
```
87+
88+
#### Profiling build
89+
90+
```sh
91+
pnpm android:profiling
92+
```
93+
3994
### iOS
4095

4196
For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps).
@@ -54,13 +109,25 @@ bundle exec pod install
54109

55110
For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html).
56111

112+
#### Debug build
113+
57114
```sh
58115
pnpm ios
59116
```
60117

61-
If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device.
118+
#### Release build
119+
120+
```sh
121+
pnpm ios:release
122+
```
123+
124+
#### Profiling build
62125

63-
This is one way to run the app — you can also build it directly from Android Studio or Xcode.
126+
```sh
127+
pnpm ios:profiling
128+
```
129+
130+
If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device.
64131

65132
## Step 3: Trace the application
66133

examples/rn-app/android/app/build.gradle

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ apply plugin: "org.jetbrains.kotlin.android"
33
apply plugin: "com.callstack.ottrelite"
44
apply plugin: "com.facebook.react"
55

6+
ottreliteTracing {
7+
// only instrument RN internals tracing in profiling build variant
8+
rnInstrumentationVariants = ["profiling"]
9+
}
10+
611
/**
712
* This is the configuration block to customize your React Native Android app.
813
* By default you don't need to apply any configuration, just uncomment the lines you need.
@@ -98,6 +103,12 @@ android {
98103
debug {
99104
signingConfig signingConfigs.debug
100105
}
106+
profiling {
107+
initWith release
108+
matchingFallbacks = ["debug", "release"]
109+
debuggable = true
110+
signingConfig signingConfigs.debug
111+
}
101112
release {
102113
// Caution! In production, you need to generate your own keystore file.
103114
// see https://reactnative.dev/docs/signed-apk-android.

examples/rn-app/android/buildSrc/src/main/kotlin/com/callstack/plugin/OttreliteInstrumentation.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.callstack.plugin
22

3-
import com.android.build.api.instrumentation.*
3+
import com.android.build.api.instrumentation.AsmClassVisitorFactory
4+
import com.android.build.api.instrumentation.ClassContext
5+
import com.android.build.api.instrumentation.ClassData
6+
import com.android.build.api.instrumentation.InstrumentationParameters
47
import org.gradle.api.provider.Property
58
import org.gradle.api.tasks.Input
69
import org.objectweb.asm.ClassVisitor
@@ -53,7 +56,7 @@ abstract class OttreliteClassVisitorFactory :
5356
(access and Opcodes.ACC_STATIC) != 0
5457

5558
if (isTargetMethod && replacementDescriptor != null) {
56-
println("Modifying method: $className.$name$descriptor")
59+
println("[OttreliteInstrumentation] Modifying method: $className.$name$descriptor")
5760

5861
// Return a new MethodVisitor that will replace the method's body.
5962
return replacementDescriptor.methodVisitorType.getMethodVisitor(

examples/rn-app/android/buildSrc/src/main/kotlin/com/callstack/plugin/OttreliteTracingPlugin.kt

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,82 @@ import com.android.build.api.instrumentation.InstrumentationScope
55
import com.android.build.api.variant.AndroidComponentsExtension
66
import org.gradle.api.Plugin
77
import org.gradle.api.Project
8+
import org.gradle.api.provider.SetProperty
9+
10+
open class OttreliteTracingExtension(project: Project) {
11+
/**
12+
* Set of variant names where tracing should be enabled.
13+
* If empty, tracing will be applied to all variants.
14+
*
15+
* Example usage in build.gradle:
16+
* ```
17+
* ottreliteTracing {
18+
* rnInstrumentationVariants = ["debug", "profiling"]
19+
* }
20+
* ```
21+
*/
22+
val rnInstrumentationVariants: SetProperty<String> =
23+
project.objects.setProperty(String::class.java)
24+
.convention(emptySet())
25+
}
826

927
class OttreliteTracingPlugin : Plugin<Project> {
1028
override fun apply(project: Project) {
29+
val extension = project.extensions.create(
30+
"ottreliteTracing",
31+
OttreliteTracingExtension::class.java,
32+
project
33+
)
34+
1135
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
1236

37+
extension.rnInstrumentationVariants.get().let { instrumentedVariantsWhitelist ->
38+
val description =
39+
if (instrumentedVariantsWhitelist.isEmpty()) {
40+
"all variants"
41+
} else {
42+
"variants: ${instrumentedVariantsWhitelist.joinToString(", ")}"
43+
}
44+
project.logger.lifecycle(
45+
"[OttreliteTracing] Configuration will instrument $description"
46+
)
47+
}
48+
1349
androidComponents.onVariants { variant ->
14-
variant.instrumentation.transformClassesWith(
15-
OttreliteClassVisitorFactory::class.java,
16-
InstrumentationScope.ALL
17-
) { params ->
18-
params.targetClassName.set("com.facebook.systrace.Systrace")
19-
params.timestamp.set(System.currentTimeMillis())
50+
val variantName = variant.name
51+
val shouldInstrument = shouldInstrumentVariant(variantName, extension)
52+
53+
if (shouldInstrument) {
54+
project.logger.lifecycle("[OttreliteTracing] Enabling instrumentation for variant '$variantName'")
55+
56+
variant.instrumentation.transformClassesWith(
57+
OttreliteClassVisitorFactory::class.java,
58+
InstrumentationScope.ALL
59+
) { params ->
60+
params.targetClassName.set("com.facebook.systrace.Systrace")
61+
params.timestamp.set(System.currentTimeMillis())
62+
}
63+
variant.instrumentation.setAsmFramesComputationMode(
64+
FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
65+
)
66+
} else {
67+
project.logger.lifecycle("[OttreliteTracing] Skipping instrumentation for variant '$variantName'")
2068
}
21-
variant.instrumentation.setAsmFramesComputationMode(
22-
FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
23-
)
2469
}
2570
}
71+
72+
private fun shouldInstrumentVariant(
73+
variantName: String,
74+
extension: OttreliteTracingExtension
75+
): Boolean {
76+
val rnInstrumentationVariants = extension.rnInstrumentationVariants.get()
77+
78+
// if enabled variants are specified, only instrument those
79+
if (rnInstrumentationVariants.isNotEmpty()) {
80+
return rnInstrumentationVariants.contains(variantName)
81+
}
82+
83+
// otherwise, instrument all variants
84+
return true
85+
}
2686
}

0 commit comments

Comments
 (0)