Skip to content

Commit

Permalink
Merge pull request #36414 from margelo/perunt/release-profiler
Browse files Browse the repository at this point in the history
Release profiler
  • Loading branch information
srikarparsi authored Mar 11, 2024
2 parents 4607e14 + 27f3671 commit 4c4e63f
Show file tree
Hide file tree
Showing 13 changed files with 424 additions and 6 deletions.
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,79 @@ Our React Native Android app now uses the `Hermes` JS engine which requires your

To make it easier to test things in web, we expose the Onyx object to the window, so you can easily do `Onyx.set('bla', 1)`.

----

# Release Profiler
Often, performance issue debugging occurs in debug builds, which can introduce errors from elements such as JS Garbage Collection, Hermes debug markers, or LLDB pauses.

`react-native-release-profiler` facilitates profiling within release builds for accurate local problem-solving and broad performance analysis in production to spot regressions or collect extensive device data. Therefore, we will utilize the production build version

### Getting Started with Source Maps
To accurately profile your application, generating source maps for Android and iOS is crucial. Here's how to enable them:
1. Enable source maps on Android
Ensure the following is set in your app's `android/app/build.gradle` file.

```jsx
project.ext.react = [
enableHermes: true,
hermesFlagsRelease: ["-O", "-output-source-map"], // <-- here, plus whichever flag was required to set this away from default
]
```

2. Enable source maps on IOS
Within Xcode head to the build phase - `Bundle React Native code and images`.

```jsx
export SOURCEMAP_FILE="$(pwd)/../main.jsbundle.map" // <-- here;

export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh
```
3. Install the necessary packages and CocoaPods dependencies:
```jsx
npm i && npm run pod-install
```
7. Depending on the platform you are targeting, run your Android/iOS app in production mode.
8. Upon completion, the generated source map can be found at:
Android: `android/app/build/generated/sourcemaps/react/productionRelease/index.android.bundle.map`
IOS: `main.jsbundle.map`

### Recording a Trace:
1. Ensure you have generated the source map as outlined above.
2. Launch the app in production mode.
2. Navigate to the feature you wish to profile.
3. Initiate the profiling session by tapping with four fingers to open the menu and selecting **`Use Profiling`**.
4. Close the menu and interact with the app.
5. After completing your interactions, tap with four fingers again and select to stop profiling.
6. You will be presented with a **`Share`** option to export the trace, which includes a trace file (`Profile<app version>.cpuprofile`) and build info (`AppInfo<app version>.json`).

Build info:
```jsx
{
appVersion: "1.0.0",
environment: "production",
platform: "IOS",
totalMemory: "3GB",
usedMemory: "300MB"
}
```

### How to symbolicate trace record:
1. You have two files: `AppInfo<app version>.json` and `Profile<app version>.cpuprofile`
2. Place the `Profile<app version>.cpuprofile` file at the root of your project.
3. If you have already generated a source map from the steps above for this branch, you can skip to the next step. Otherwise, obtain the app version from `AppInfo<app version>.json` switch to that branch and generate the source map as described.

`IMPORTANT:` You should generate the source map from the same branch as the trace was recorded.

4. Use the following commands to symbolicate the trace for Android and iOS, respectively:
Android: `npm run symbolicate-release:android`
IOS: `npm run symbolicate-release:ios`
5. A new file named `Profile_trace_for_<app version>-converted.json` will appear in your project's root folder.
6. Open this file in your tool of choice:
- SpeedScope ([https://www.speedscope.app](https://www.speedscope.app/))
- Perfetto UI (https://ui.perfetto.dev/)
- Google Chrome's Tracing UI (chrome://tracing)
---
# App Structure and Conventions
Expand Down
14 changes: 14 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,10 @@ PODS:
- React
- React-callinvoker
- React-Core
- react-native-release-profiler (0.1.6):
- glog
- RCT-Folly (= 2022.05.16.00)
- React-Core
- react-native-render-html (6.3.1):
- React-Core
- react-native-safe-area-context (4.8.2):
Expand Down Expand Up @@ -1443,6 +1447,8 @@ PODS:
- glog
- RCT-Folly (= 2022.05.16.00)
- React-Core
- RNShare (10.0.2):
- React-Core
- RNSound (0.11.2):
- React-Core
- RNSound/Core (= 0.11.2)
Expand Down Expand Up @@ -1546,6 +1552,7 @@ DEPENDENCIES:
- react-native-performance (from `../node_modules/react-native-performance`)
- react-native-plaid-link-sdk (from `../node_modules/react-native-plaid-link-sdk`)
- react-native-quick-sqlite (from `../node_modules/react-native-quick-sqlite`)
- react-native-release-profiler (from `../node_modules/react-native-release-profiler`)
- react-native-render-html (from `../node_modules/react-native-render-html`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- react-native-view-shot (from `../node_modules/react-native-view-shot`)
Expand Down Expand Up @@ -1591,6 +1598,7 @@ DEPENDENCIES:
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- RNShare (from `../node_modules/react-native-share`)
- RNSound (from `../node_modules/react-native-sound`)
- RNSVG (from `../node_modules/react-native-svg`)
- VisionCamera (from `../node_modules/react-native-vision-camera`)
Expand Down Expand Up @@ -1751,6 +1759,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-plaid-link-sdk"
react-native-quick-sqlite:
:path: "../node_modules/react-native-quick-sqlite"
react-native-release-profiler:
:path: "../node_modules/react-native-release-profiler"
react-native-render-html:
:path: "../node_modules/react-native-render-html"
react-native-safe-area-context:
Expand Down Expand Up @@ -1841,6 +1851,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-reanimated"
RNScreens:
:path: "../node_modules/react-native-screens"
RNShare:
:path: "../node_modules/react-native-share"
RNSound:
:path: "../node_modules/react-native-sound"
RNSVG:
Expand Down Expand Up @@ -1945,6 +1957,7 @@ SPEC CHECKSUMS:
react-native-performance: cef2b618d47b277fb5c3280b81a3aad1e72f2886
react-native-plaid-link-sdk: df1618a85a615d62ff34e34b76abb7a56497fbc1
react-native-quick-sqlite: bcc7a7a250a40222f18913a97cd356bf82d0a6c4
react-native-release-profiler: 86f2004d5f8c4fff17d90a5580513519a685d7ae
react-native-render-html: 96c979fe7452a0a41559685d2f83b12b93edac8c
react-native-safe-area-context: 0ee144a6170530ccc37a0fd9388e28d06f516a89
react-native-view-shot: 6b7ed61d77d88580fed10954d45fad0eb2d47688
Expand Down Expand Up @@ -1990,6 +2003,7 @@ SPEC CHECKSUMS:
RNReactNativeHapticFeedback: ec56a5f81c3941206fd85625fa669ffc7b4545f9
RNReanimated: 3850671fd0c67051ea8e1e648e8c3e86bf3a28eb
RNScreens: b582cb834dc4133307562e930e8fa914b8c04ef2
RNShare: 859ff710211285676b0bcedd156c12437ea1d564
RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852
RNSVG: ba3e7232f45e34b7b47e74472386cf4e1a676d0a
SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
Expand Down
4 changes: 4 additions & 0 deletions jest/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@ jest.mock('react-native-sound', () => {

return SoundMock;
});

jest.mock('react-native-share', () => ({
default: jest.fn(),
}));
37 changes: 37 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"analyze-packages": "ANALYZE_BUNDLE=true webpack --config config/webpack/webpack.common.js --env envFile=.env.production",
"symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map",
"symbolicate:ios": "npx metro-symbolicate main.jsbundle.map",
"symbolicate-release:ios": "scripts/release-profile.js --platform=ios",
"symbolicate-release:android": "scripts/release-profile.js --platform=android",
"test:e2e": "ts-node tests/e2e/testRunner.js --config ./config.local.ts",
"test:e2e:dev": "ts-node tests/e2e/testRunner.js --config ./config.dev.js",
"gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh",
Expand Down Expand Up @@ -155,10 +157,12 @@
"react-native-plaid-link-sdk": "10.8.0",
"react-native-qrcode-svg": "^6.2.0",
"react-native-quick-sqlite": "^8.0.0-beta.2",
"react-native-release-profiler": "^0.1.6",
"react-native-reanimated": "^3.7.2",
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "3.29.0",
"react-native-share": "^10.0.2",
"react-native-sound": "^0.11.2",
"react-native-svg": "14.1.0",
"react-native-tab-view": "^3.5.2",
Expand Down
63 changes: 63 additions & 0 deletions scripts/release-profile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env node
/* eslint-disable no-console */

const fs = require('fs');
const {execSync} = require('child_process');

// Function to parse command-line arguments into a key-value object
function parseCommandLineArguments() {
const args = process.argv.slice(2); // Skip node and script paths
const argsMap = {};
args.forEach((arg) => {
const [key, value] = arg.split('=');
if (key.startsWith('--')) {
argsMap[key.substring(2)] = value;
}
});
return argsMap;
}

// Function to find .cpuprofile files in the current directory
function findCpuProfileFiles() {
const files = fs.readdirSync(process.cwd());
// eslint-disable-next-line rulesdir/prefer-underscore-method
return files.filter((file) => file.endsWith('.cpuprofile'));
}

const argsMap = parseCommandLineArguments();

// Determine sourcemapPath based on the platform flag passed
let sourcemapPath;
if (argsMap.platform === 'ios') {
sourcemapPath = 'main.jsbundle.map';
} else if (argsMap.platform === 'android') {
sourcemapPath = 'android/app/build/generated/sourcemaps/react/productionRelease/index.android.bundle.map';
} else {
console.error('Please specify the platform using --platform=ios or --platform=android');
process.exit(1);
}

// Attempt to find .cpuprofile files
const cpuProfiles = findCpuProfileFiles();
if (cpuProfiles.length === 0) {
console.error('No .cpuprofile files found in the root directory.');
process.exit(1);
} else if (cpuProfiles.length > 1) {
console.error('Multiple .cpuprofile files found. Please specify which one to use by placing only one .cpuprofile in the root or specifying the filename as an argument.');
process.exit(1);
} else {
// Construct the command
const cpuprofileName = cpuProfiles[0];
const command = `npx react-native-release-profiler --local ${cpuprofileName} --sourcemap-path ${sourcemapPath}`;

console.log(`Executing: ${command}`);

// Execute the command
try {
const output = execSync(command, {stdio: 'inherit'});
console.log(output.toString());
} catch (error) {
console.error(`Error executing command: ${error}`);
process.exit(1);
}
}
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ const ONYXKEYS = {
/** Is the test tools modal open? */
IS_TEST_TOOLS_MODAL_OPEN: 'isTestToolsModalOpen',

/** Is app in profiling mode */
APP_PROFILING_IN_PROGRESS: 'isProfilingInProgress',

/** Stores information about active wallet transfer amount, selectedAccountID, status, etc */
WALLET_TRANSFER: 'walletTransfer',

Expand Down Expand Up @@ -553,6 +556,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.IS_LOADING_PAYMENT_METHODS]: boolean;
[ONYXKEYS.IS_LOADING_REPORT_DATA]: boolean;
[ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN]: boolean;
[ONYXKEYS.APP_PROFILING_IN_PROGRESS]: boolean;
[ONYXKEYS.IS_LOADING_APP]: boolean;
[ONYXKEYS.IS_SWITCHING_TO_OLD_DOT]: boolean;
[ONYXKEYS.WALLET_TRANSFER]: OnyxTypes.WalletTransfer;
Expand Down
Loading

0 comments on commit 4c4e63f

Please sign in to comment.