Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
99f0606
test(rn): added e2e test steps to run benchmarks for ReactNative
lemnik Aug 15, 2025
12986c5
test(rn): added a skeleton structure for benchmarking the ReactNative…
lemnik Aug 15, 2025
defc422
test(rn): added metrics reporting to benchmarks
lemnik Aug 18, 2025
d389e95
test(rn): added the cross_layer_spans benchmark feature
lemnik Aug 18, 2025
7b761d7
chore(react-native): move benchmarks to new scenario launcher location
yousif-bugsnag Dec 2, 2025
9fddc2c
chore(react-native): reapply native config changes
yousif-bugsnag Dec 2, 2025
0a03bc2
add native integration tag to feature file
yousif-bugsnag Dec 2, 2025
4e6c0ca
add benchmark tag to feature file
yousif-bugsnag Dec 4, 2025
eb0f806
restart app between benchmarks
yousif-bugsnag Dec 5, 2025
b0855d1
ios native config changes
yousif-bugsnag Dec 5, 2025
65d332e
allow a long time for results
yousif-bugsnag Dec 5, 2025
640fd4e
bump android dependency for test utils
yousif-bugsnag Dec 5, 2025
9dbb09d
bump android dependency for test fixture
yousif-bugsnag Dec 8, 2025
a21b412
set largeHeap = true in android manifest
yousif-bugsnag Dec 9, 2025
e90d3f8
increase wait time again
yousif-bugsnag Dec 9, 2025
f883e01
reduce runs to 25k iterations
yousif-bugsnag Dec 10, 2025
010c005
ci(react-native): create separate pipeline for benchmarks
yousif-bugsnag Dec 10, 2025
fa7a56b
ci(react-native): add benchmarking env var
yousif-bugsnag Dec 10, 2025
7d8fad9
ci(react-native): enable benchmark feature for ios
yousif-bugsnag Dec 10, 2025
04a78cd
ci(react-native): disable android benchmarks for now
yousif-bugsnag Dec 11, 2025
629a511
ci(react-native): add pipeline block and manual trigger
yousif-bugsnag Dec 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .buildkite/react-native-pipeline.benchmark.block.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
steps:
- block: "Trigger react native benchmark pipeline"
key: "trigger-react-native-benchmark-pipeline"

- label: ":pipeline_upload: React native benchmark pipeline"
depends_on: "trigger-react-native-benchmark-pipeline"
agents:
queue: macos
timeout_in_minutes: 2
command: buildkite-agent pipeline upload .buildkite/react-native-pipeline.benchmark.yml
153 changes: 153 additions & 0 deletions .buildkite/react-native-pipeline.benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
agents:
queue: "opensource"

steps:
- group: "React Native Benchmark Tests"
steps:
#
# Build fixtures
#
- label: ':android: Build RN {{matrix}} benchmark fixture APK (New Arch)'
key: "build-react-native-android-fixture-benchmark-new-arch"
timeout_in_minutes: 30
agents:
queue: macos-15
env:
JAVA_VERSION: "17"
NODE_VERSION: "18"
RN_VERSION: "{{matrix}}"
NOTIFIER_VERSION: "8.0.0"
RCT_NEW_ARCH_ENABLED: "1"
BUILD_ANDROID: "true"
NATIVE_INTEGRATION: "1"
artifact_paths:
- "test/react-native/features/fixtures/generated/native-integration/new-arch/**/reactnative.apk"
commands:
- bundle install
- node test/react-native/scripts/generate-react-native-fixture.js
matrix:
- "0.80"
retry:
automatic:
- exit_status: "*"
limit: 1

- label: ':mac: Build RN {{matrix}} benchmark fixture ipa (New Arch)'
key: "build-react-native-ios-fixture-benchmark-new-arch"
timeout_in_minutes: 30
agents:
queue: "macos-15"
env:
NODE_VERSION: "18"
RN_VERSION: "{{matrix}}"
RCT_NEW_ARCH_ENABLED: "1"
NOTIFIER_VERSION: "8.0.0"
BUILD_IOS: "true"
XCODE_VERSION: "16.2.0"
NATIVE_INTEGRATION: "1"
artifact_paths:
- "test/react-native/features/fixtures/generated/native-integration/new-arch/**/output/reactnative.ipa"
commands:
- bundle install
- node test/react-native/scripts/generate-react-native-fixture.js
matrix:
- "0.80"
retry:
automatic:
- exit_status: "*"
limit: 1

#
# Run benchmarks
#
- label: ":bitbar: :android: RN {{matrix}} Android Benchmarks"
depends_on: "build-react-native-android-fixture-benchmark-new-arch"
timeout_in_minutes: 20
plugins:
artifacts#v1.9.0:
download: "test/react-native/features/fixtures/generated/native-integration/new-arch/{{matrix}}/reactnative.apk"
upload:
- "./test/react-native/maze_output/failed/*"
- "./test/react-native/maze_output/maze_output.zip"
- "./test/react-native/maze_output/metrics.csv"
docker-compose#v4.7.0:
pull: react-native-maze-runner
run: react-native-maze-runner
service-ports: true
command:
- --app=/app/features/fixtures/generated/native-integration/new-arch/{{matrix}}/reactnative.apk
- --farm=bb
- --device=ANDROID_13
- --a11y-locator
- --fail-fast
- --appium-version=1.22
- --no-tunnel
- --aws-public-ip
- --tags=@benchmark
test-collector#v1.10.2:
files: "reports/TEST-*.xml"
format: "junit"
branch: "^main|next$$"
api-token-env-name: "REACT_NATIVE_PERFORMANCE_BUILDKITE_ANALYTICS_TOKEN"
env:
RCT_NEW_ARCH_ENABLED: "1"
NATIVE_INTEGRATION: "1"
BENCHMARKS: "1"
RN_VERSION: "{{matrix}}"
retry:
manual:
permit_on_passed: true
automatic:
- exit_status: 103 # Appium session failed
limit: 2
concurrency: 25
concurrency_group: "bitbar"
concurrency_method: eager
matrix:
- "0.80"

- label: ":bitbar: :mac: RN {{matrix}} iOS Benchmarks"
depends_on: "build-react-native-ios-fixture-benchmark-new-arch"
timeout_in_minutes: 20
plugins:
artifacts#v1.9.0:
download: "test/react-native/features/fixtures/generated/native-integration/new-arch/{{matrix}}/output/reactnative.ipa"
upload:
- "./test/react-native/maze_output/failed/*"
- "./test/react-native/maze_output/maze_output.zip"
- "./test/react-native/maze_output/metrics.csv"
docker-compose#v4.12.0:
pull: react-native-maze-runner
run: react-native-maze-runner
service-ports: true
command:
- --app=/app/features/fixtures/generated/native-integration/new-arch/{{matrix}}/output/reactnative.ipa
- --farm=bb
- --device=IOS_14|IOS_15|IOS_16|IOS_17|IOS_18|IOS_26
- --a11y-locator
- --fail-fast
- --appium-version=1.22
- --no-tunnel
- --aws-public-ip
- --tags=@benchmark
test-collector#v1.10.2:
files: "reports/TEST-*.xml"
format: "junit"
branch: "^main|next$$"
api-token-env-name: "REACT_NATIVE_PERFORMANCE_BUILDKITE_ANALYTICS_TOKEN"
env:
RCT_NEW_ARCH_ENABLED: "1"
NATIVE_INTEGRATION: "1"
BENCHMARKS: "1"
RN_VERSION: "{{matrix}}"
retry:
manual:
permit_on_passed: true
automatic:
- exit_status: 103 # Appium session failed
limit: 2
concurrency: 25
concurrency_group: "bitbar"
concurrency_method: eager
matrix:
- "0.80"
4 changes: 4 additions & 0 deletions .buildkite/scripts/packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,9 @@
"pipeline": ".buildkite/expo-pipeline.full.yml",
"block": ".buildkite/expo-pipeline.full.block.yml",
"paths": []
},
{
"block": ".buildkite/react-native-pipeline.benchmark.block.yml",
"paths": []
}
]
2 changes: 1 addition & 1 deletion .buildkite/scripts/pipeline-trigger.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ packages.reverse().forEach(({ paths, block, pipeline, environment, skip }) => {
}

// Upload all pipelines if specified in the commit message
if (commitMessage.includes("[full ci]") ||
if (pipeline && commitMessage.includes("[full ci]") ||
isFullBuild ||
currentBranch === "main" ||
baseBranch === "main") {
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ services:
RN_VERSION:
EXPO_VERSION:
REACT_NATIVE_NAVIGATION:
BENCHMARKS:
MAZE_REPEATER_API_KEY: "${MAZE_REPEATER_API_KEY_RN:-}"
MAZE_HUB_REPEATER_API_KEY: "${MAZE_HUB_REPEATER_API_KEY_RN:-}"
ports:
Expand Down
19 changes: 19 additions & 0 deletions test/react-native/features/benchmarks/cross_layer_spans.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@benchmark @native_integration @ios_only
Feature: Cross-Layer Spans

Scenario Outline:
When I run benchmark "NativeNamedSpanBenchmark" configured as <options>
And I wait for 30 seconds
And I wait to receive at least 1 metrics
And I discard the oldest metric
And I relaunch the app after shutdown

Examples:
| options |
| "native nativeSpans" |
| "native nativeSpans jsSpans" |
| "native nativeSpans rendering" |
| "native nativeSpans cpu" |
| "native nativeSpans memory" |
| "native nativeSpans rendering cpu memory" |
| "native nativeSpans rendering cpu memory jsSpans" |
32 changes: 24 additions & 8 deletions test/react-native/features/steps/react-native-steps.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
When('I run {string}') do |scenario_name|
execute_command 'run-scenario', scenario_name
run_scenario scenario_name
end

When('I run benchmark {string} configured as {string}') do |benchmark_name, config|
execute_command 'run-benchmark', {
benchmark_name: benchmark_name,
config: config
}
end

When('I execute the command {string}') do |command|
Expand Down Expand Up @@ -113,7 +120,14 @@
)
end

def execute_command(action, scenario_name = '')
def run_scenario(scenario_name = '')
execute_command 'run-scenario', {
scenario_name: scenario_name,
payload: scenario_name
}
end

def execute_command(action, command_hash = nil)
address = if Maze.config.farm == :bb
if Maze.config.aws_public_ip
Maze.public_address
Expand All @@ -122,21 +136,23 @@ def execute_command(action, scenario_name = '')
end
else
case Maze::Helper.get_current_platform
when 'android'
'localhost:9339'
else
'bs-local.com:9339'
when 'android'
'localhost:9339'
else
'bs-local.com:9339'
end
end

command = {
action: action,
scenario_name: scenario_name,
payload: scenario_name,
endpoint: "http://#{address}/traces",
api_key: $api_key,
}

unless command_hash.nil?
command.merge! command_hash
end

$logger.debug("Queuing command: #{command}")
Maze::Server.commands.add command
end
Expand Down
8 changes: 8 additions & 0 deletions test/react-native/features/support/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
Maze.config.receive_requests_wait = 60
end

if ENV["BENCHMARKS"]
Maze.config.receive_requests_wait = 180
end

end

Before('@skip') do
Expand Down Expand Up @@ -59,4 +63,8 @@
current_version = ENV['RN_VERSION'].nil? ? 0 : ENV['RN_VERSION'].to_f
skip_this_scenario("Skipping scenario: Not running native integration fixture") unless ENV["NATIVE_INTEGRATION"]
skip_this_scenario("Skipping scenario: Not supported in 0.72") if Maze::Helper.get_current_platform == 'ios' && current_version == 0.72
end

Before('@benchmark') do |scenario|
skip_this_scenario("Skipping scenario: Not running benchmark tests") unless ENV["BENCHMARKS"]
end
3 changes: 2 additions & 1 deletion test/react-native/native-test-utils/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ android {
}

dependencies {
compileOnly("com.bugsnag:bugsnag-android-performance:1.16.0")
compileOnly("com.bugsnag:bugsnag-android-performance:2.0.0")
compileOnly("com.bugsnag:bugsnag-android-performance-impl:2.0.0")
implementation project(':bugsnag_react-native-performance')
implementation project(':bugsnag_plugin-react-native-span-access')
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import com.bugsnag.android.performance.AutoInstrument;
import com.bugsnag.android.performance.BugsnagPerformance;
import com.bugsnag.android.performance.EnabledMetrics;
import com.bugsnag.android.performance.PerformanceConfiguration;

import com.bugsnag.reactnative.performance.nativespans.BugsnagJavascriptSpansPlugin;
Expand Down Expand Up @@ -124,9 +125,31 @@ public static boolean startNativePerformance(Context context, Map<String, Object
config.setAutoInstrumentAppStarts(autoInstrumentAppStarts);
config.setAutoInstrumentActivities(autoInstrumentViewLoads ? AutoInstrument.FULL : AutoInstrument.OFF);
config.setAutoInstrumentRendering(true);
config.addPlugin(new BugsnagNativeSpansPlugin());
config.addPlugin(new BugsnagJavascriptSpansPlugin());
config.addPlugin(new BugsnagReactNativeAppStartPlugin());

if (configuration.containsKey("samplingProbability")) {
config.setSamplingProbability((Double)configuration.get("samplingProbability"));
}

if (configuration.containsKey("enabledMetrics")) {
Map<String, Object> metricsConfig = (Map<String, Object>)configuration.get("enabledMetrics");
EnabledMetrics enabledMetrics = new EnabledMetrics(
Boolean.TRUE.equals(metricsConfig.get("rendering")),
Boolean.TRUE.equals(metricsConfig.get("cpu")),
Boolean.TRUE.equals(metricsConfig.get("memory"))
);

config.setEnabledMetrics(enabledMetrics);
}

if (!configuration.containsKey("nativeSpans") || Boolean.TRUE.equals(configuration.get("nativeSpans"))) {
config.addPlugin(new BugsnagNativeSpansPlugin());
}
if (!configuration.containsKey("jsSpans") || Boolean.TRUE.equals(configuration.get("jsSpans"))) {
config.addPlugin(new BugsnagJavascriptSpansPlugin());
}
if (!configuration.containsKey("nativeAppStarts") || Boolean.TRUE.equals(configuration.get("nativeAppStarts"))) {
config.addPlugin(new BugsnagReactNativeAppStartPlugin());
}

BugsnagPerformance.start(config);
Log.d(TAG, "Native performance started successfully");
Expand Down
30 changes: 24 additions & 6 deletions test/react-native/native-test-utils/ios/BugsnagTestUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,33 @@ + (BOOL)startNativePerformanceWithConfiguration:(NSDictionary *)configuration {
config.autoInstrumentAppStarts = autoInstrumentAppStarts;
config.autoInstrumentViewControllers = autoInstrumentViewLoads;
config.autoInstrumentNetworkRequests = NO;
config.enabledMetrics.cpu = YES;
config.enabledMetrics.memory = YES;
config.enabledMetrics.rendering = YES;
config.internal.autoTriggerExportOnBatchSize = 1;
config.internal.clearPersistenceOnStart = YES;

[config addPlugin:[BugsnagNativeSpansPlugin new]];
[config addPlugin:[BugsnagJavascriptSpansPlugin new]];
[config addPlugin:[BugsnagReactNativeAppStartPlugin new]];
if (configuration[@"samplingProbability"]) {
config.samplingProbability = configuration[@"samplingProbability"];
}

if (configuration[@"enabledMetrics"]) {
NSDictionary *metricsConfig = configuration[@"enabledMetrics"];
config.enabledMetrics.rendering = [metricsConfig[@"rendering"] boolValue];
config.enabledMetrics.cpu = [metricsConfig[@"cpu"] boolValue];
config.enabledMetrics.memory = [metricsConfig[@"memory"] boolValue];
} else {
config.enabledMetrics.cpu = YES;
config.enabledMetrics.memory = YES;
config.enabledMetrics.rendering = YES;
}

if (!configuration[@"nativeSpans"] || [configuration[@"nativeSpans"] boolValue]) {
[config addPlugin:[BugsnagNativeSpansPlugin new]];
}
if (!configuration[@"jsSpans"] || [configuration[@"jsSpans"] boolValue]) {
[config addPlugin:[BugsnagJavascriptSpansPlugin new]];
}
if (!configuration[@"nativeAppStarts"] || [configuration[@"nativeAppStarts"] boolValue]) {
[config addPlugin:[BugsnagReactNativeAppStartPlugin new]];
}

[BugsnagPerformance startWithConfiguration:config];

Expand Down
Loading