Skip to content

Commit b614c96

Browse files
janicduplessisfacebook-github-bot
authored andcommitted
Setup a Macrobenchmark for RNTester (#49486)
Summary: Benchmark to test changes from #49449 Might be nice to have some version of this in the repo. ## Changelog: [INTERNAL] [ADDED] - Setup a Macrobenchmark for RNTester Pull Request resolved: #49486 Test Plan: ### Methodology Picked various JS file from websites (facebook, instagram) to artificially grow RN tester bundle somewhat realistically. The files are required lazily from a button press callback to simulate the code being included, but not executed, as it would be in a large app that uses lazy requires for the different screens. I've also made the RN tester screens lazy so all their code is not loaded initially. This is more representative of real apps. Note this is implemented in a hacky way just for the purpose of this test. It would actually be nice to implement this properly. The tests were made using low end device Samsung Galaxy A03s. ### Compression ON with 10.5 mb bundle #### Peak allocated memory 60.9 mb #### ReactInstance.loadJSBundler 148.64 ms #### Benchmark timeToFullDisplayMs min 1,825.0, median 1,911.1, max 1,994.8 timeToInitialDisplayMs min 834.9, median 860.9, max 903.9 #### APK Size: 22.9 mb Download size: 14.5 mb ### Compression OFF with 10.5 mb bundle #### Peak allocated memory 51.5 mb #### ReactInstance.loadJSBundler 946 us #### Benchmark timeToFullDisplayMs min 1,752.8, median 1,827.2, max 1,977.5 timeToInitialDisplayMs min 837.7, median 881.3, max 937.2 #### APK Size: 28 mb Download size: 14.5 mb Reviewed By: rshest Differential Revision: D70002286 Pulled By: cortinico fbshipit-source-id: 436597f439ba244649373870c1facefdb12297d9
1 parent f94dbb3 commit b614c96

16 files changed

+330
-86
lines changed

build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ plugins {
1212
alias(libs.plugins.download) apply false
1313
alias(libs.plugins.kotlin.android) apply false
1414
alias(libs.plugins.binary.compatibility.validator) apply true
15+
alias(libs.plugins.android.test) apply false
1516
}
1617

1718
val reactAndroidProperties = java.util.Properties()

packages/react-native/gradle/libs.versions.toml

+14-3
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ agp = "8.8.2"
1010
androidx-annotation = "1.6.0"
1111
androidx-appcompat = "1.7.0"
1212
androidx-autofill = "1.1.0"
13+
androidx-benchmark-macro-junit4 = "1.3.3"
14+
androidx-profileinstaller = "1.4.1"
1315
androidx-swiperefreshlayout = "1.1.0"
1416
androidx-test = "1.5.0"
17+
androidx-test-junit = "1.2.1"
1518
androidx-tracing = "1.1.0"
1619
assertj = "3.21.0"
1720
binary-compatibility-validator = "0.13.2"
1821
download = "5.4.0"
22+
espresso-core = "3.6.1"
1923
fbjni = "0.7.0"
2024
fresco = "3.6.0"
2125
infer-annotation = "0.18.0"
@@ -32,6 +36,7 @@ okhttp = "4.9.2"
3236
okio = "2.9.0"
3337
robolectric = "4.9.2"
3438
soloader = "0.12.1"
39+
uiautomator = "2.3.0"
3540
xstream = "1.4.20"
3641
yoga-proguard-annotations = "1.19.0"
3742
# Native Dependencies
@@ -44,14 +49,19 @@ glog="0.3.5"
4449
gtest="1.12.1"
4550

4651
[libraries]
52+
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
4753
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
4854
androidx-appcompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "androidx-appcompat" }
49-
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
5055
androidx-autofill = { module = "androidx.autofill:autofill", version.ref = "androidx-autofill" }
56+
androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidx-benchmark-macro-junit4" }
57+
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
58+
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-junit" }
59+
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "androidx-profileinstaller" }
5160
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "androidx-swiperefreshlayout" }
52-
androidx-tracing = { module = "androidx.tracing:tracing", version.ref = "androidx-tracing" }
53-
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test" }
5461
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test" }
62+
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test" }
63+
androidx-tracing = { module = "androidx.tracing:tracing", version.ref = "androidx-tracing" }
64+
androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
5565

5666
fbjni = { module = "com.facebook.fbjni:fbjni", version.ref = "fbjni" }
5767
fresco = { module = "com.facebook.fresco:fresco", version.ref = "fresco" }
@@ -84,3 +94,4 @@ download = { id = "de.undercouch.download", version.ref = "download" }
8494
nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus-publish" }
8595
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
8696
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
97+
android-test = { id = "com.android.test", version.ref = "agp" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import ReportFullyDrawnViewNativeComponent from './ReportFullyDrawnViewNativeComponent';
12+
13+
export default ReportFullyDrawnViewNativeComponent;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import {View} from 'react-native';
12+
13+
export default View;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import type {HostComponent} from 'react-native';
12+
import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes';
13+
14+
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
15+
16+
type NativeProps = $ReadOnly<{
17+
...ViewProps,
18+
}>;
19+
20+
export type ReportFullyDrawnViewType = HostComponent<ViewProps>;
21+
22+
export default (codegenNativeComponent<NativeProps>(
23+
'RNTReportFullyDrawnView',
24+
): ReportFullyDrawnViewType);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
plugins {
9+
alias(libs.plugins.android.test)
10+
alias(libs.plugins.kotlin.android)
11+
}
12+
13+
android {
14+
namespace = "com.example.benchmark"
15+
compileSdk = libs.versions.compileSdk.get().toInt()
16+
17+
defaultConfig {
18+
minSdk = libs.versions.minSdk.get().toInt()
19+
targetSdk = libs.versions.targetSdk.get().toInt()
20+
21+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
22+
}
23+
24+
buildTypes {
25+
// This benchmark buildType is used for benchmarking, and should function like your
26+
// release build (for example, with minification on). It"s signed with a debug key
27+
// for easy local/CI testing.
28+
create("benchmark") {
29+
isDebuggable = true
30+
signingConfig = getByName("debug").signingConfig
31+
matchingFallbacks += listOf("release")
32+
}
33+
}
34+
35+
flavorDimensions += listOf("vm")
36+
productFlavors {
37+
create("hermes") { dimension = "vm" }
38+
create("jsc") { dimension = "vm" }
39+
}
40+
41+
targetProjectPath = ":packages:rn-tester:android:app"
42+
experimentalProperties["android.experimental.self-instrumenting"] = true
43+
}
44+
45+
dependencies {
46+
implementation(libs.androidx.junit)
47+
implementation(libs.androidx.espresso.core)
48+
implementation(libs.androidx.uiautomator)
49+
implementation(libs.androidx.benchmark.macro.junit4)
50+
}
51+
52+
androidComponents { beforeVariants(selector().all()) { it.enable = it.buildType == "benchmark" } }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.uiapp.benchmark
9+
10+
import androidx.benchmark.macro.StartupMode
11+
import androidx.benchmark.macro.StartupTimingMetric
12+
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
13+
import androidx.test.ext.junit.runners.AndroidJUnit4
14+
import androidx.test.uiautomator.By
15+
import androidx.test.uiautomator.Until
16+
import org.junit.Rule
17+
import org.junit.Test
18+
import org.junit.runner.RunWith
19+
20+
/**
21+
* RNTester benchmarks.
22+
*
23+
* Run this benchmark from Android Studio to see startup measurements, and captured system traces
24+
* for investigating performance.
25+
*/
26+
@RunWith(AndroidJUnit4::class)
27+
class RNTesterStartupBenchmark {
28+
@get:Rule val benchmarkRule = MacrobenchmarkRule()
29+
30+
@Test
31+
fun startup() =
32+
benchmarkRule.measureRepeated(
33+
packageName = "com.facebook.react.uiapp",
34+
metrics = listOf(StartupTimingMetric()),
35+
iterations = 10,
36+
startupMode = StartupMode.COLD,
37+
setupBlock = { pressHome() }) {
38+
startActivityAndWait()
39+
40+
// Waits for an element that corresponds to fully drawn state
41+
device.wait(Until.hasObject(By.text("Components")), 10_000)
42+
device.waitForIdle()
43+
}
44+
}

packages/rn-tester/android/app/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ dependencies {
165165
"jscImplementation"(jscFlavor)
166166

167167
testImplementation(libs.junit)
168+
implementation(libs.androidx.profileinstaller)
168169
}
169170

170171
android {
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,97 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
34

4-
<uses-feature
5-
android:name="android.software.leanback"
6-
android:required="false" />
7-
<uses-feature
8-
android:name="android.hardware.touchscreen"
9-
android:required="false" />
10-
<uses-feature
11-
android:name="android.hardware.location.gps"
12-
android:required="false" />
13-
<uses-feature
14-
android:name="android.hardware.camera"
15-
android:required="false" />
5+
<uses-feature
6+
android:name="android.software.leanback"
7+
android:required="false" />
8+
<uses-feature
9+
android:name="android.hardware.touchscreen"
10+
android:required="false" />
11+
<uses-feature
12+
android:name="android.hardware.location.gps"
13+
android:required="false" />
14+
<uses-feature
15+
android:name="android.hardware.camera"
16+
android:required="false" />
1617

17-
<uses-permission android:name="android.permission.INTERNET" />
18-
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
19-
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
20-
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
21-
<uses-permission android:name="android.permission.VIBRATE"/>
22-
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
18+
<uses-permission android:name="android.permission.INTERNET" />
19+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
20+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
21+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
22+
<uses-permission android:name="android.permission.VIBRATE" />
23+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Just to show permissions example -->
24+
<uses-permission android:name="android.permission.CAMERA" />
25+
<uses-permission android:name="android.permission.READ_CALENDAR" />
26+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
2327

24-
<!--Just to show permissions example-->
25-
<uses-permission android:name="android.permission.CAMERA"/>
26-
<uses-permission android:name="android.permission.READ_CALENDAR"/>
27-
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
28+
<queries>
29+
<package android:name="com.facebook.katana" />
30+
<package android:name="com.facebook.lite" />
31+
<package android:name="com.facebook.android" />
2832

29-
<!--
30-
android:icon is used to display launcher icon on mobile devices.
31-
android:banner is used to display a rectangular banned launcher icon on Android TV devices.
32-
-->
33-
<application
34-
android:name=".RNTesterApplication"
35-
android:allowBackup="true"
36-
android:banner="@drawable/tv_banner"
37-
android:icon="@mipmap/ic_launcher"
38-
android:roundIcon="@mipmap/ic_launcher_round"
39-
android:label="@string/app_name"
40-
android:theme="@style/AppTheme"
41-
android:supportsRtl="true">
42-
<activity
43-
android:name=".RNTesterActivity"
33+
<intent>
34+
<action android:name="android.intent.action.VIEW" />
35+
36+
<category android:name="android.intent.category.BROWSABLE" />
37+
38+
<data android:scheme="https" />
39+
</intent>
40+
<intent>
41+
<action android:name="android.intent.action.VIEW" />
42+
43+
<data android:scheme="geo" />
44+
</intent>
45+
<intent>
46+
<action android:name="android.intent.action.DIAL" />
47+
48+
<data android:scheme="tel" />
49+
</intent>
50+
</queries>
51+
52+
<application
53+
android:name=".RNTesterApplication"
54+
android:allowBackup="true"
55+
android:banner="@drawable/tv_banner"
56+
android:icon="@mipmap/ic_launcher"
4457
android:label="@string/app_name"
45-
android:screenOrientation="fullSensor"
46-
android:launchMode="singleTask"
47-
android:configChanges="orientation|screenSize|uiMode"
48-
android:exported="true">
49-
<intent-filter>
50-
<action android:name="android.intent.action.MAIN" />
51-
<category android:name="android.intent.category.LAUNCHER" />
52-
<!-- Needed to properly create a launch intent when running on Android TV -->
53-
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
54-
</intent-filter>
55-
<intent-filter>
56-
<action android:name="android.intent.action.VIEW" />
57-
<category android:name="android.intent.category.DEFAULT" />
58-
<category android:name="android.intent.category.BROWSABLE" />
59-
<!-- Accepts URIs that begin with "rntester://example” -->
60-
<data android:scheme="rntester" android:host="example" />
61-
</intent-filter>
62-
</activity>
63-
<provider
64-
android:name="com.facebook.react.modules.blob.BlobProvider"
65-
android:authorities="@string/blob_provider_authority"
66-
android:exported="false"
67-
/>
68-
</application>
69-
<queries>
70-
<package android:name="com.facebook.katana" />
71-
<package android:name="com.facebook.lite" />
72-
<package android:name="com.facebook.android" />
73-
<intent>
74-
<action android:name="android.intent.action.VIEW" />
75-
<category android:name="android.intent.category.BROWSABLE" />
76-
<data android:scheme="https" />
77-
</intent>
78-
<intent>
79-
<action android:name="android.intent.action.VIEW" />
80-
<data android:scheme="geo" />
81-
</intent>
82-
<intent>
83-
<action android:name="android.intent.action.DIAL" />
84-
<data android:scheme="tel" />
85-
</intent>
86-
</queries>
58+
android:roundIcon="@mipmap/ic_launcher_round"
59+
android:supportsRtl="true"
60+
android:theme="@style/AppTheme">
61+
<profileable
62+
android:shell="true"
63+
tools:targetApi="29" />
64+
65+
<activity
66+
android:name=".RNTesterActivity"
67+
android:configChanges="orientation|screenSize|uiMode"
68+
android:exported="true"
69+
android:label="@string/app_name"
70+
android:launchMode="singleTask"
71+
android:screenOrientation="fullSensor">
72+
<intent-filter>
73+
<action android:name="android.intent.action.MAIN" />
74+
75+
<category android:name="android.intent.category.LAUNCHER" />
76+
<!-- Needed to properly create a launch intent when running on Android TV -->
77+
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
78+
</intent-filter>
79+
<intent-filter>
80+
<action android:name="android.intent.action.VIEW" />
81+
82+
<category android:name="android.intent.category.DEFAULT" />
83+
<category android:name="android.intent.category.BROWSABLE" />
84+
<!-- Accepts URIs that begin with "rntester://example” -->
85+
<data
86+
android:host="example"
87+
android:scheme="rntester" />
88+
</intent-filter>
89+
</activity>
90+
91+
<provider
92+
android:name="com.facebook.react.modules.blob.BlobProvider"
93+
android:authorities="@string/blob_provider_authority"
94+
android:exported="false" />
95+
</application>
8796

8897
</manifest>

0 commit comments

Comments
 (0)