diff --git a/docs/profile-hermes.md b/docs/profile-hermes.md new file mode 100644 index 00000000000..3f4daefb478 --- /dev/null +++ b/docs/profile-hermes.md @@ -0,0 +1,155 @@ +--- +id: profile-hermes +title: Profiling with Hermes +--- + +You can visualize JavaScript's performance in a React Native app using [Hermes](https://github.com/facebook/hermes). Hermes is a small and lightweight JavaScript engine optimized for running React Native on Android (you can [read more about using it with React Native here](hermes). Hermes helps improve app performance and also exposes ways to analyze the performance of the JavaScript that it runs. + +In this section, you will learn how to profile your React Native app running on Hermes and how to visualize the profile using [the Performance tab on Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference) + +> Be sure to [enable hermes in your app](hermes) before you get started! + +Follow the instructions below to get started profiling: + +1. [Record a Hermes sampling profile](profile-hermes.md#record-a-hermes-sampling-profile) +2. [Execute command from CLI](profile-hermes.md#execute-command-from-cli) +3. [Open the downloaded profile on Chrome DevTools](profile-hermes.md#open-the-downloaded-profile-on-chrome-devtools) + +## Record a Hermes sampling profile + +To record a sampling profiler from the Developer Menu: + +1. Navigate to your running Metro server terminal. +2. Press `d` to open the **Developer Menu.** +3. Select **Enable Sampling Profiler.** +4. Execute your JavaScript by in your app (press buttons, etc.) +5. Open the **Developer Menu** by pressing `d` again. +6. Select **Disable Sampling Profiler** to stop recording and save the sampling profiler. + +A toast will show the location where the sampling profiler has been saved, usually in `/data/user/0/com.appName/cache/*.cpuprofile` + +Toast Notification of Profile saving + +## Execute command from CLI + +You can use the [React Native CLI](https://github.com/react-native-community/cli) to convert the Hermes tracing profile to Chrome tracing profile, and then pull it to your local machine using: + +```sh +npx react-native profile-hermes [destinationDir] +``` + +### Enabling source map + +A source map is used to enhance the profile and associate trace events with the application code. You can automatically generate a source map when converting the Hermes tracing profile to a Chrome tracing profile by enabling `bundleInDebug` if the app is running in development mode. This allows React Native to build the bundle during its running process. Here's how: + +1. In your app's `android/app/build.gradle` file, add: + +```java +project.ext.react = [ + bundleInDebug: true, +] +``` + +> Be sure to clean the build whenever you make any changes to `build.gradle` + +2. Clean the build by running: + +```sh +cd android && ./gradlew clean +``` + +3. Run your app: + +```sh +npx react-native run-android +``` + +### Common errors + +#### `adb: no devices/emulators found` or `adb: device offline` + +- **Why this happens** The CLI cannot access the device or emulator (through adb) you are using to run the app. +- **How to fix** Make sure your Android device/emulator is connected and running. The command only works when it can access adb. + +#### `There is no file in the cache/ directory` + +- **Why this happens** The CLI cannot find any **.cpuprofile** file in your app's **cache/** directory. You might have forgotten to record a profile from the device. +- **How to fix** Follow the [instructions](profile-hermes.md#record-a-hermes-sampling-profile) to enable/disable profiler from device. + +#### `Error: your_profile_name.cpuprofile is an empty file` + +- **Why this happens** The profile is empty, it might be because Hermes is not running correctly. +- **How to fix** Make sure your app is running on the latest version of Hermes. + +## Open the downloaded profile in Chrome DevTools + +To open the profile in Chrome DevTools: + +1. Open Chrome DevTools. +2. Select the **Performance** tab. +3. Right click and choose **Load profile...** + + Loading a performance profile on Chrome DevTools + +## How does the Hermes Profile Transformer work? + +The Hermes Sample Profile is of the `JSON object format`, while the format that Google's DevTools supports is `JSON Array Format`. (More information about the formats can be found on the [Trace Event Format Document](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview)) + +```ts +export interface HermesCPUProfile { + traceEvents: SharedEventProperties[]; + samples: HermesSample[]; + stackFrames: { [key in string]: HermesStackFrame }; +} +``` + +The Hermes profile has most of its information encoded into the `samples` and `stackFrames` properties. Each sample is a snapshot of the function call stack at that particular timestamp as each sample has a `sf` property which corresponds to a function call. + +```ts +export interface HermesSample { + cpu: string; + name: string; + ts: string; + pid: number; + tid: string; + weight: string; + /** + * Will refer to an element in the stackFrames object of the Hermes Profile + */ + sf: number; + stackFrameData?: HermesStackFrame; +} +``` + +The information about a function call can be found in `stackFrames` which contains key-object pairs, where the key is the `sf` number and the corresponding object gives us all the relevant information about the function including the `sf` number of its parent function. This parent-child relationship can be traced upwards to find the information of all the functions running at a particular timestamp. + +```ts +export interface HermesStackFrame { + line: string; + column: string; + funcLine: string; + funcColumn: string; + name: string; + category: string; + /** + * A parent function may or may not exist + */ + parent?: number; +} +``` + +At this point, you should define a few more terms, namely: + +1. Nodes: The objects corresponding to `sf` numbers in `stackFrames` +2. Active Nodes: The nodes which are currently running at a particular timestamp. A node is classified as running if its `sf` number is in the function call stack. This call stack can be obtained from the `sf` number of the sample and tracing upwards till parent `sf`s are available + +The `samples` and the `stackFrames` in tandem can then be used to generate all the start and end events at the corresponding timestamps, wherein: + +1. Start Nodes/Events: Nodes absent in the previous sample's function call stack but present in the current sample's. +2. End Nodes/Events: Nodes present in the previous sample's function call stack but absent in the current sample's. + +CallStack Terms Explained + +You can now construct a `flamechart` of function calls as you have all the function information including its start and end timestamps. + +The `hermes-profile-transformer` can convert any profile generated using Hermes into a format that can be directly displayed in Chrome DevTools. More information about this can be found on [ `@react-native-community/hermes-profile-transformer` ](https://github.com/react-native-community/hermes-profile-transformer) diff --git a/website/i18n/en.json b/website/i18n/en.json index 7bd5d1aaa5c..ae7e9f4f12e 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -272,6 +272,9 @@ "pressevent": { "title": "PressEvent Object Type" }, + "profile-hermes": { + "title": "Profiling with Hermes" + }, "profiling": { "title": "Profiling" }, @@ -284,6 +287,9 @@ "props": { "title": "Props" }, + "publishing-to-app-store": { + "title": "Publishing to Apple App Store" + }, "pushnotificationios": { "title": "🚧 PushNotificationIOS" }, diff --git a/website/sidebars.json b/website/sidebars.json index 3b26a4073f3..29558f57b9e 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -39,7 +39,8 @@ "performance", "optimizing-flatlist-configuration", "ram-bundles-inline-requires", - "profiling" + "profiling", + "profile-hermes" ], "JavaScript Runtime": ["javascript-environment", "timers", "hermes"], "Connectivity": ["network", "security"], diff --git a/website/static/docs/assets/CallStackDemo.jpg b/website/static/docs/assets/CallStackDemo.jpg new file mode 100644 index 00000000000..987ad8dff89 Binary files /dev/null and b/website/static/docs/assets/CallStackDemo.jpg differ diff --git a/website/static/docs/assets/HermesProfileSaved.png b/website/static/docs/assets/HermesProfileSaved.png new file mode 100644 index 00000000000..9213b810ac9 Binary files /dev/null and b/website/static/docs/assets/HermesProfileSaved.png differ diff --git a/website/static/docs/assets/openChromeProfile.png b/website/static/docs/assets/openChromeProfile.png new file mode 100644 index 00000000000..332bea045d2 Binary files /dev/null and b/website/static/docs/assets/openChromeProfile.png differ diff --git a/website/yarn.lock b/website/yarn.lock index 0d9429dcbb6..c7cd882335b 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -9,7 +9,7 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== @@ -47,7 +47,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.11.0", "@babel/generator@^7.8.4": +"@babel/generator@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.0.tgz#4b90c78d8c12825024568cbe83ee6c9af193585c" integrity sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ== @@ -137,7 +137,7 @@ "@babel/traverse" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helper-function-name@^7.10.4", "@babel/helper-function-name@^7.8.3": +"@babel/helper-function-name@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== @@ -146,7 +146,7 @@ "@babel/template" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helper-get-function-arity@^7.10.4", "@babel/helper-get-function-arity@^7.8.3": +"@babel/helper-get-function-arity@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== @@ -242,7 +242,7 @@ dependencies: "@babel/types" "^7.11.0" -"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0", "@babel/helper-split-export-declaration@^7.8.3": +"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== @@ -264,7 +264,7 @@ "@babel/traverse" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helpers@^7.10.4", "@babel/helpers@^7.8.4": +"@babel/helpers@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== @@ -273,7 +273,7 @@ "@babel/traverse" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/highlight@^7.0.0", "@babel/highlight@^7.10.4", "@babel/highlight@^7.8.3": +"@babel/highlight@^7.0.0", "@babel/highlight@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== @@ -282,7 +282,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.10.4", "@babel/parser@^7.11.0", "@babel/parser@^7.11.1", "@babel/parser@^7.8.3", "@babel/parser@^7.8.4": +"@babel/parser@^7.10.4", "@babel/parser@^7.11.0", "@babel/parser@^7.11.1": version "7.11.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.3.tgz#9e1eae46738bcd08e23e867bab43e7b95299a8f9" integrity sha512-REo8xv7+sDxkKvoxEywIdsNFiZLybwdI7hcT5uEPyQrSMB4YQ973BfC9OOrD/81MaIjh6UxdulIQXkjmiH3PcA== @@ -910,7 +910,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.10.4", "@babel/template@^7.8.3": +"@babel/template@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== @@ -919,7 +919,7 @@ "@babel/parser" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/traverse@^7.10.4", "@babel/traverse@^7.11.0", "@babel/traverse@^7.8.4", "@babel/traverse@^7.9.0": +"@babel/traverse@^7.10.4", "@babel/traverse@^7.11.0", "@babel/traverse@^7.9.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.0.tgz#9b996ce1b98f53f7c3e4175115605d56ed07dd24" integrity sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg== @@ -934,7 +934,7 @@ globals "^11.1.0" lodash "^4.17.19" -"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.9.0": +"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.4.4", "@babel/types@^7.9.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d" integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA== @@ -4676,7 +4676,7 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.0, json5@^2.1.2: +json5@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== @@ -4957,7 +4957,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@~4.17.10: +lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@~4.17.10: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== @@ -6760,7 +6760,7 @@ regenerate@^1.4.0: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== -regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.4: +regenerator-runtime@^0.13.4: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==