Skip to content

Commit

Permalink
Merge pull request #4640 from Expensify/marcaaron-performanceStatsDump
Browse files Browse the repository at this point in the history
[No QA] Simplify capturing performance metrics on release builds
  • Loading branch information
Tim Szot authored Aug 13, 2021
2 parents 5acd236 + d52fd83 commit 8b10c1c
Show file tree
Hide file tree
Showing 11 changed files with 108 additions and 3 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ NGROK_URL=https://expensify-user.ngrok.io/
USE_NGROK=false
USE_WEB_PROXY=false
USE_WDYR=false
CAPTURE_METRICS=false
29 changes: 28 additions & 1 deletion PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,39 @@
### Why Did You Render?
- Why Did You Render (WDYR) sends console notifications about potentially avoidable component re-renders.
- It can also help to simply track when and why a certain component re-renders.
- To enable it, set `USE_WDYR=true` in your `.env` file.
- To enable it, set `USE_WDYR=true` in your `.env` file.
- You can add or exclude tracked components by their `displayName` in `wdyr.js`.
- Open the browser console to see WDYR notifications.

**Suggested** [Why Did You Render docs](https://github.com/welldone-software/why-did-you-render)

### Performance Metrics (Opt-In on local release builds)

To capture reliable performance metrics for native app launch we must test against a release build. To make this easier for everyone to do we created an opt-in tool (using [`react-native-performance`](https://github.com/oblador/react-native-performance) that will capture metrics and display them in an alert once the app becomes interactive. To set this up just set `CAPTURE_METRICS=true` in your `.env` file then create a release build on iOS or Android. The metrics this tool shows are as follows:

- `nativeLaunch` - Total time for the native process to intialize
- `runJSBundle` - Total time to parse and execute the JS bundle
- `timeToInteractive` - Rough TTI (Time to Interactive). Includes native init time + sidebar UI partially loaded

#### How to create a Release Build on Android

- Create a keystore by running `keytool -genkey -v -keystore your_key_name.keystore -alias your_key_alias -keyalg RSA -keysize 2048 -validity 10000`
- Fill out all the prompts with any info and give it a password
- Drag the generated keystore to `/android/app`
- Hardcode the values to the gradle config like so:

```
signingConfigs {
release {
storeFile file('your_key_name.keystore')
storePassword 'Password1'
keyAlias 'your_key_alias'
keyPassword 'Password1'
}
```
- Delete any existing apps off emulator or device
- Run `react-native run-android --variant release`

## Reconciliation

React is pretty smart and in many cases is able to tell if something needs to update. The process by which React goes about updating the "tree" or view heirarchy is called reconciliation. If React thinks something needs to update it will render it again. React also assumes that if a parent component rendered then it's child should also re-render.
Expand Down
8 changes: 7 additions & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ PODS:
- React-Core
- react-native-pdf (6.2.2):
- React-Core
- react-native-performance (2.0.0):
- React-Core
- react-native-plaid-link-sdk (7.0.5):
- Plaid (~> 2.1.2)
- React-Core
Expand Down Expand Up @@ -638,6 +640,7 @@ DEPENDENCIES:
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-pdf (from `../node_modules/react-native-pdf`)
- react-native-performance (from `../node_modules/react-native-performance/ios`)
- react-native-plaid-link-sdk (from `../node_modules/react-native-plaid-link-sdk`)
- "react-native-progress-bar-android (from `../node_modules/@react-native-community/progress-bar-android`)"
- "react-native-progress-view (from `../node_modules/@react-native-community/progress-view`)"
Expand Down Expand Up @@ -772,6 +775,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-community/netinfo"
react-native-pdf:
:path: "../node_modules/react-native-pdf"
react-native-performance:
:path: "../node_modules/react-native-performance/ios"
react-native-plaid-link-sdk:
:path: "../node_modules/react-native-plaid-link-sdk"
react-native-progress-bar-android:
Expand Down Expand Up @@ -872,7 +877,7 @@ SPEC CHECKSUMS:
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
EXHaptics: 337c160c148baa6f0e7166249f368965906e346b
FBLazyVector: 7b423f9e248eae65987838148c36eec1dbfe0b53
FBReactNativeSpec: c783a75db87c963c60afcd461fc38358805fe5ba
FBReactNativeSpec: 884d4cc2b011759361797a4035c47e10099393b5
Firebase: 54cdc8bc9c9b3de54f43dab86e62f5a76b47034f
FirebaseABTesting: 4cb61aeeb50f60680af1c01fff781dfaf9293916
FirebaseAnalytics: 4751d6a49598a2b58da678cc07df696bcd809ab9
Expand Down Expand Up @@ -922,6 +927,7 @@ SPEC CHECKSUMS:
react-native-image-picker: 4089335b89b625d4e34d53fb249c48a7a791b3ea
react-native-netinfo: 52cf0ee8342548a485e28f4b09e56b477567244d
react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f
react-native-performance: 8edfa2bbc9a2af4a02f01d342118e413a95145e0
react-native-plaid-link-sdk: 1a6593e2d3d790e8113c29178d883eb883f8c032
react-native-progress-bar-android: ce95a69f11ac580799021633071368d08aaf9ad8
react-native-progress-view: 5816e8a6be812c2b122c6225a2a3db82d9008640
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"react-native-modal": "^11.10.0",
"react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#2908a47dee13d99ce756bebb75740ed2f27c2d2e",
"react-native-pdf": "^6.2.2",
"react-native-performance": "^2.0.0",
"react-native-permissions": "^3.0.1",
"react-native-picker-select": "8.0.4",
"react-native-plaid-link-sdk": "^7.0.5",
Expand Down
1 change: 1 addition & 0 deletions src/CONFIG.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@ export default {
DEFAULT: '/favicon.png',
UNREAD: '/favicon-unread.png',
},
CAPTURE_METRICS: lodashGet(Config, 'CAPTURE_METRICS', false),
};
39 changes: 38 additions & 1 deletion src/libs/Performance.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _ from 'underscore';
import lodashTransform from 'lodash/transform';
import canCapturePerformanceMetrics from './canCapturePerformanceMetrics';

/**
* Deep diff between two objects. Useful for figuring out what changed about an object from one render to the next so
Expand All @@ -24,7 +25,43 @@ function diffObject(object, base) {
return changes(object, base);
}

/**
* Sets up an observer to capture events recorded in the native layer before the app fully initializes.
*/
function setupPerformanceObserver() {
if (!canCapturePerformanceMetrics()) {
return;
}

const performance = require('react-native-performance').default;
const PerformanceObserver = require('react-native-performance').PerformanceObserver;
new PerformanceObserver((list) => {
if (list.getEntries().find(entry => entry.name === 'nativeLaunchEnd')) {
performance.measure('nativeLaunch', 'nativeLaunchStart', 'nativeLaunchEnd');

// eslint-disable-next-line no-undef
if (__DEV__) {
performance.measure('jsBundleDownload', 'downloadStart', 'downloadEnd');
} else {
performance.measure('runJsBundle', 'runJsBundleStart', 'runJsBundleEnd');
}
}
}).observe({type: 'react-native-mark', buffered: true});
}

/**
* Outputs performance stats. We alert these so that they are easy to access in release builds.
*/
function printPerformanceMetrics() {
const performance = require('react-native-performance').default;
const entries = _.map(performance.getEntriesByType('measure'), entry => ({
name: entry.name, duration: Math.floor(entry.duration),
}));
alert(JSON.stringify(entries, null, 4));
}

export {
// eslint-disable-next-line import/prefer-default-export
diffObject,
printPerformanceMetrics,
setupPerformanceObserver,
};
11 changes: 11 additions & 0 deletions src/libs/actions/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import CONST from '../../CONST';
import CONFIG from '../../CONFIG';
import Firebase from '../Firebase';
import ROUTES from '../../ROUTES';
import {printPerformanceMetrics} from '../Performance';
import canCapturePerformanceMetrics from '../canCapturePerformanceMetrics';

let currentUserAccountID;
Onyx.connect({
Expand Down Expand Up @@ -58,6 +60,15 @@ function setSidebarLoaded() {

Onyx.set(ONYXKEYS.IS_SIDEBAR_LOADED, true);
Firebase.stopTrace(CONST.TIMING.SIDEBAR_LOADED);

if (!canCapturePerformanceMetrics()) {
return;
}

const performance = require('react-native-performance').default;
performance.mark('sidebarLoadEnd');
performance.measure('timeToInteractive', 'nativeLaunchStart', 'sidebarLoadEnd');
printPerformanceMetrics();
}

export {
Expand Down
2 changes: 2 additions & 0 deletions src/libs/canCapturePerformanceMetrics/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// We don't capture performance metrics on web as there are enough tools available
export default () => false;
10 changes: 10 additions & 0 deletions src/libs/canCapturePerformanceMetrics/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import CONFIG from '../../CONFIG';

/**
* Enables capturing performance stats.
*
* @returns {Boolean}
*/
export default function canCapturePerformanceMetrics() {
return Boolean(CONFIG.CAPTURE_METRICS);
}
4 changes: 4 additions & 0 deletions src/setup/index.native.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {setupPerformanceObserver} from '../libs/Performance';

// Setup Flipper plugins when on dev
export default function () {
// eslint-disable-next-line no-undef
Expand All @@ -7,4 +9,6 @@ export default function () {
const AsyncStorage = require('@react-native-async-storage/async-storage').default;
RNAsyncStorageFlipper(AsyncStorage);
}

setupPerformanceObserver();
}

0 comments on commit 8b10c1c

Please sign in to comment.