From 1167db088544d3c09c3a889e2dcb8b1f54b72a70 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 7 Jul 2020 20:13:05 +0700 Subject: [PATCH 01/53] added adb commands --- .vscode/settings.json | 6 +- package.json | 1 + packages/cli/package.json | 1 + .../src/commands/profile/downloadProfile.ts | 84 +++++++++++++++++++ packages/cli/src/commands/profile/listFile.ts | 35 ++++++++ packages/cli/src/commands/profile/pullFile.ts | 38 +++++++++ tsconfig.json | 2 +- yarn.lock | 41 ++++++++- 8 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 packages/cli/src/commands/profile/downloadProfile.ts create mode 100644 packages/cli/src/commands/profile/listFile.ts create mode 100644 packages/cli/src/commands/profile/pullFile.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 230a6715c..d2b294630 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,8 @@ { - "editor.rulers": [ - 80 - ], + "editor.rulers": [80], "files.exclude": { "**/.git": true, - "**/node_modules": true, + "**/node_modules": false, "**/build": true }, "editor.codeActionsOnSave": { diff --git a/package.json b/package.json index 995298903..1cd1e24ac 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@types/mkdirp": "^0.5.2", "@types/node": "^8.0.0", "@types/node-fetch": "^2.3.7", + "adbkit": "^2.11.1", "babel-jest": "^25.2.4", "babel-plugin-module-resolver": "^3.2.0", "chalk": "^3.0.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index c2fa803d6..963be7489 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,6 +33,7 @@ "@react-native-community/cli-server-api": "^4.10.1", "@react-native-community/cli-tools": "^4.10.1", "@react-native-community/cli-types": "^4.10.1", + "adbkit": "^2.11.1", "chalk": "^3.0.0", "command-exists": "^1.2.8", "commander": "^2.19.0", diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts new file mode 100644 index 000000000..f3df1892b --- /dev/null +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -0,0 +1,84 @@ +// @ts-ignore untyped +import getEnvironmentInfo from '../../tools/envinfo'; +import {logger} from '@react-native-community/cli-tools'; +import {Config} from '@react-native-community/cli-types'; +import releaseChecker from '../../tools/releaseChecker'; +import { listFiles } from './listFile'; +import { pullFile } from './pullFile'; + +// const info = async function getInfo(_argv: Array, ctx: Config) { +// try { +// logger.info('Fetching system and libraries information...'); +// const output = await getEnvironmentInfo(false); +// logger.log(output); +// } catch (err) { +// logger.error(`Unable to print environment info.\n${err}`); +// } finally { +// await releaseChecker(ctx.root); +// } +// }; +type Options = { + fileName?: string; + }; +const promise = require('adbkit/bluebird'); +const adb = require('adbkit/lib/adb'); +const client = adb.createClient(); +function download(dstPath: string){ + listFiles('com.awesomeproject'); + pullFile(dstPath); +} + +const hermesProfile = async function downloadProfile(_argv: Array, ctx: Config, options: Options) { + try{ + logger.info('Downloading the latest Hermes Sampling Profiler from your Android device...'); + const output = await + + } + catch (err){ + logger.error(`Unable to download the Hermes Sampling Profiler.\n${err}`); + } + finally{ + await + } +}; + +// type Command = { +// name: string, +// description?: string, +// func: (argv: Array, config: ConfigT, args: Object) => ?Promise, +// options?: Array<{ +// name: string, +// description?: string, +// parse?: (val: string) => any, +// default?: +// | string +// | boolean +// | number +// | ((config: ConfigT) => string | boolean | number), +// }>, +// examples?: Array<{ +// desc: string, +// cmd: string, +// }>, +// }; + +export default { + name: 'profile-hermes ', + description: 'Download the Hermes Sampling Profiler to the directory of the local machine', + func: hermesProfile, + //options: download the latest or filename + options: [ + { + name: '--filename ', + description: 'Filename of the profile to be downloaded', + }, + ], + examples: [ + { + desc: 'Download the Hermes Sampling Profiler to the directory of the local machine', + cmd: 'profile-hermes /users/name/desktop', + }, + ], +}; + + diff --git a/packages/cli/src/commands/profile/listFile.ts b/packages/cli/src/commands/profile/listFile.ts new file mode 100644 index 000000000..a283e38c1 --- /dev/null +++ b/packages/cli/src/commands/profile/listFile.ts @@ -0,0 +1,35 @@ +const promise = require('adbkit/bluebird'); +const adb = require('adbkit/lib/adb'); +const client = adb.createClient(); + +//packageName begins with 'com.projectName' +export function listFiles(packageName: string) { + client + .listDevices() + .then(function(devices: any) { + return promise.map(devices, function(device: any) { + //adb shell + //List files in the directory /data/user/0/packageName + return client + .shell( + device.id, + `run-as ${packageName} ls data/user/0/${packageName}/cache`, + ) + .then(function(files: any) { + files.forEach(function(file: any) { + if (file.isFile()) { + console.log('[%s] Found file "%s"', device.id, file.name); + } + }); + }); + }); + }) + .then(function() { + console.log( + 'Done checking /data/user/0/packageName files on connected devices', + ); + }) + .catch(function(err: any) { + console.error('Something went wrong:', err.stack); + }); +} diff --git a/packages/cli/src/commands/profile/pullFile.ts b/packages/cli/src/commands/profile/pullFile.ts new file mode 100644 index 000000000..1edd57445 --- /dev/null +++ b/packages/cli/src/commands/profile/pullFile.ts @@ -0,0 +1,38 @@ +const promise = require('adbkit/bluebird'); +const fs = require('fs'); +const adb = require('adbkit/lib/adb'); +const client = adb.createClient(); + +export function pullFile(dstPath: string) { + client + .listDevices() + .then(function(devices) { + return promise.map(devices, function(device) { + return client + .pull(devices.id, '/sdcard/latest.cpuprofile') + .then(function(transfer) { + return new Promise(function(resolve, reject) { + transfer.on('progress', function(stats) { + console.log( + '[%s] Pulled %d bytes so far', + device.id, + stats.bytesTransferred, + ); + }); + transfer.on('end', function() { + console.log('[%s] Pull complete', device.id); + resolve(device.id); + }); + transfer.on('error', reject); + transfer.pipe(fs.createWriteStream(dstPath)); + }); + }); + }); + }) + .then(function() { + console.log('Done pulling /system/build.prop from all connected devices'); + }) + .catch(function(err) { + console.error('Something went wrong:', err.stack); + }); +} diff --git a/tsconfig.json b/tsconfig.json index bc0c60880..3ffb7f911 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es2017", "module": "commonjs", - "lib": ["es2017"], + "lib": ["es2017", "esnext.asynciterable"], "declaration": true, "declarationMap": true, "composite": true, diff --git a/yarn.lock b/yarn.lock index e92dd6b0d..9e0a3de98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2547,6 +2547,31 @@ acorn@^7.1.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== +adbkit-logcat@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/adbkit-logcat/-/adbkit-logcat-1.1.0.tgz#01d7f9b0cef9093a30bcb3b007efff301508962f" + integrity sha1-Adf5sM75CTowvLOwB+//MBUIli8= + +adbkit-monkey@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/adbkit-monkey/-/adbkit-monkey-1.0.1.tgz#f291be701a2efc567a63fc7aa6afcded31430be1" + integrity sha1-8pG+cBou/FZ6Y/x6pq/N7TFDC+E= + dependencies: + async "~0.2.9" + +adbkit@^2.11.1: + version "2.11.1" + resolved "https://registry.yarnpkg.com/adbkit/-/adbkit-2.11.1.tgz#7da847fe561254f3121088947bc1907ef053e894" + integrity sha512-hDTiRg9NX3HQt7WoDAPCplUpvzr4ZzQa2lq7BdTTJ/iOZ6O7YNAs6UYD8sFAiBEcYHDRIyq3cm9sZP6uZnhvXw== + dependencies: + adbkit-logcat "^1.1.0" + adbkit-monkey "~1.0.1" + bluebird "~2.9.24" + commander "^2.3.0" + debug "~2.6.3" + node-forge "^0.7.1" + split "~0.3.3" + agent-base@4, agent-base@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" @@ -3079,6 +3104,11 @@ bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bluebird@~2.9.24: + version "2.9.34" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.9.34.tgz#2f7b4ec80216328a9fddebdf69c8d4942feff7d8" + integrity sha1-L3tOyAIWMoqf3evfacjUlC/v99g= + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -3681,7 +3711,7 @@ command-exists@^1.2.6, command-exists@^1.2.8: resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291" integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw== -commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.3: +commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@^2.3.0, commander@~2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -4224,7 +4254,7 @@ deasync@^0.1.14: bindings "^1.5.0" node-addon-api "^1.7.1" -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9, debug@~2.6.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -10544,6 +10574,13 @@ split@^1.0.0: dependencies: through "2" +split@~0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" + integrity sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8= + dependencies: + through "2" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" From 20477216dbcba7f70b7bf2279556dcfc88bc55b1 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 21 Jul 2020 22:38:29 +0700 Subject: [PATCH 02/53] feat: implemented download hermes profile command --- packages/cli/src/commands/index.ts | 2 + .../src/commands/profile/downloadProfile.ts | 175 ++++++++++-------- packages/cli/src/commands/profile/index.ts | 74 ++++++++ packages/cli/src/commands/profile/listFile.ts | 35 ---- packages/cli/src/commands/profile/pullFile.ts | 38 ---- 5 files changed, 177 insertions(+), 147 deletions(-) create mode 100644 packages/cli/src/commands/profile/index.ts delete mode 100644 packages/cli/src/commands/profile/listFile.ts delete mode 100644 packages/cli/src/commands/profile/pullFile.ts diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 94fdebfbf..218780212 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -11,6 +11,7 @@ import info from './info/info'; import config from './config/config'; import init from './init'; import doctor from './doctor'; +import profile from './profile'; export const projectCommands = [ start, @@ -24,6 +25,7 @@ export const projectCommands = [ info, config, doctor, + profile, ] as Command[]; export const detachedCommands = [init, doctor] as DetachedCommand[]; diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts index f3df1892b..623ea9bce 100644 --- a/packages/cli/src/commands/profile/downloadProfile.ts +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -1,84 +1,111 @@ // @ts-ignore untyped -import getEnvironmentInfo from '../../tools/envinfo'; -import {logger} from '@react-native-community/cli-tools'; import {Config} from '@react-native-community/cli-types'; -import releaseChecker from '../../tools/releaseChecker'; -import { listFiles } from './listFile'; -import { pullFile } from './pullFile'; +import {execSync} from 'child_process'; +//import {projectConfig} from '../../../../platform-android/src/config/index'; +import {logger, CLIError} from '@react-native-community/cli-tools'; +import chalk from 'chalk'; +import fs from 'fs'; -// const info = async function getInfo(_argv: Array, ctx: Config) { -// try { -// logger.info('Fetching system and libraries information...'); -// const output = await getEnvironmentInfo(false); -// logger.log(output); -// } catch (err) { -// logger.error(`Unable to print environment info.\n${err}`); -// } finally { -// await releaseChecker(ctx.root); -// } -// }; -type Options = { - fileName?: string; - }; -const promise = require('adbkit/bluebird'); -const adb = require('adbkit/lib/adb'); -const client = adb.createClient(); -function download(dstPath: string){ - listFiles('com.awesomeproject'); - pullFile(dstPath); +//get the last modified hermes profile +function getLatestFile(packageName: string): string { + try { + const file = execSync(`adb shell run-as ${packageName} ls cache/ -tp | grep -v /$ | head -1 + `); + //console.log(file.toString()); + return file.toString().trim(); + //return parsePackagename(packages.toString()); + } catch (e) { + throw new Error(e); + } } +//get the package name of the running React Native app +function getPackageName(config: Config) { + const androidProject = config.project.android; -const hermesProfile = async function downloadProfile(_argv: Array, ctx: Config, options: Options) { - try{ - logger.info('Downloading the latest Hermes Sampling Profiler from your Android device...'); - const output = await + if (!androidProject) { + throw new CLIError(` + Android project not found. Are you sure this is a React Native project? + If your Android files are located in a non-standard location (e.g. not inside \'android\' folder), consider setting + \`project.android.sourceDir\` option to point to a new location. +`); + } + const {manifestPath} = androidProject; + const androidManifest = fs.readFileSync(manifestPath, 'utf8'); - } - catch (err){ - logger.error(`Unable to download the Hermes Sampling Profiler.\n${err}`); - } - finally{ - await - } -}; + let packageNameMatchArray = androidManifest.match(/package="(.+?)"/); + if (!packageNameMatchArray || packageNameMatchArray.length === 0) { + throw new CLIError( + 'Failed to build the app: No package name found. Found errors in /src/main/AndroidManifest.xml', + ); + } -// type Command = { -// name: string, -// description?: string, -// func: (argv: Array, config: ConfigT, args: Object) => ?Promise, -// options?: Array<{ -// name: string, -// description?: string, -// parse?: (val: string) => any, -// default?: -// | string -// | boolean -// | number -// | ((config: ConfigT) => string | boolean | number), -// }>, -// examples?: Array<{ -// desc: string, -// cmd: string, -// }>, -// }; + let packageName = packageNameMatchArray[1]; + + if (!validatePackageName(packageName)) { + logger.warn( + `Invalid application's package name "${chalk.bgRed( + packageName, + )}" in 'AndroidManifest.xml'. Read guidelines for setting the package name here: ${chalk.underline.dim( + 'https://developer.android.com/studio/build/application-id', + )}`, + ); + } + return packageName; +} +// Validates that the package name is correct +function validatePackageName(packageName: string) { + return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(packageName); +} -export default { - name: 'profile-hermes ', - description: 'Download the Hermes Sampling Profiler to the directory of the local machine', - func: hermesProfile, - //options: download the latest or filename - options: [ - { - name: '--filename ', - description: 'Filename of the profile to be downloaded', - }, - ], - examples: [ - { - desc: 'Download the Hermes Sampling Profiler to the directory of the local machine', - cmd: 'profile-hermes /users/name/desktop', - }, - ], -}; +/** + * Executes the commands to pull a hermes profile + * Commands: + * adb shell run-as com.rnhermesapp cp cache/sampling-profiler-trace1502707982002849976.cpuprofile /sdcard/latest.cpuprofile + * adb pull /sdcard/latest.cpuprofile + */ +//const packageName = projectConfig(".",{} )?.packageName; //TODO: get AndroidProjectConfig +export async function downloadProfile( + ctx: Config, + dstPath?: string, + fileName?: string, +) { + try { + const packageName = getPackageName(ctx); + // const projectConfigResult = projectConfig(ctx.root, {}); + // let packageName; + // if (projectConfigResult !== null) { + // packageName = projectConfigResult.packageName; + // } else { + // packageName = ''; //cannot get packageName since config is empty + // } + let file; + if (fileName !== undefined) { + file = fileName; + } else { + file = await getLatestFile(packageName); + } + logger.info(`File to be pulled: ${file}`); + // console.log(`adb shell run-as ${packageName} ls`); + // execSync(`adb shell run-as ${packageName} ls`); + execSync(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); + + //if not specify destination path, pull to the current directory + if (dstPath === undefined) { + //execSync(`adb pull /sdcard/${file} ${process.cwd()}`); + execSync(`adb pull /sdcard/${file} ${ctx.root}`); + console.log( + 'Successfully pulled the file to the current root working directory', + ); + } + //if specified destination path, pull to that directory + else { + execSync(`adb pull /sdcard/${file} ${dstPath}`); + console.log(`Successfully pulled the file to ${dstPath}`); + } + //return ''; + } catch (e) { + throw new Error(e.message); + } +} diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile/index.ts new file mode 100644 index 000000000..29c28f49b --- /dev/null +++ b/packages/cli/src/commands/profile/index.ts @@ -0,0 +1,74 @@ +// @ts-ignore untyped +import {logger} from '@react-native-community/cli-tools'; +import {Config} from '@react-native-community/cli-types'; +import {downloadProfile} from './downloadProfile'; + +type Options = { + fileName?: string; +}; + +async function profile( + [dstPath]: Array, + ctx: Config, + options: Options, +) { + try { + logger.info( + 'Downloading a Hermes Sampling Profiler from your Android device...', + ); + //logger.log(`options ${JSON.stringify(options)}`); + // logger.log(`${typeof options}`); + // logger.log(`${options.fileName}`); + if (options.fileName) { + //logger.log(options.fileName); + await downloadProfile(ctx, dstPath, options.fileName); + } else { + logger.info('No filename is provided, pulling latest file'); + await downloadProfile(ctx, dstPath, undefined); + } + //logger.log(output); + } catch (err) { + logger.error(`Unable to download the Hermes Sampling Profiler.\n${err}`); + } +} + +// type Command = { +// name: string, +// description?: string, +// func: (argv: Array, config: ConfigT, args: Object) => ?Promise, +// options?: Array<{ +// name: string, +// description?: string, +// parse?: (val: string) => any, +// default?: +// | string +// | boolean +// | number +// | ((config: ConfigT) => string | boolean | number), +// }>, +// examples?: Array<{ +// desc: string, +// cmd: string, +// }>, +// }; + +export default { + name: 'profile-hermes [destinationDir]', //profile-hermes ls + description: + 'Download the Hermes Sampling Profiler to the directory of the local machine', + func: profile, //how to give the args for this func: an array of arguments + options: [ + //options: download the latest or fileName + { + name: '--fileName [string]', + description: 'Filename of the profile to be downloaded', + }, + ], + examples: [ + { + desc: + 'Download the Hermes Sampling Profiler to the directory of the local machine', + cmd: 'profile-hermes /Users/phuonganh/Desktop', + }, + ], +}; diff --git a/packages/cli/src/commands/profile/listFile.ts b/packages/cli/src/commands/profile/listFile.ts deleted file mode 100644 index a283e38c1..000000000 --- a/packages/cli/src/commands/profile/listFile.ts +++ /dev/null @@ -1,35 +0,0 @@ -const promise = require('adbkit/bluebird'); -const adb = require('adbkit/lib/adb'); -const client = adb.createClient(); - -//packageName begins with 'com.projectName' -export function listFiles(packageName: string) { - client - .listDevices() - .then(function(devices: any) { - return promise.map(devices, function(device: any) { - //adb shell - //List files in the directory /data/user/0/packageName - return client - .shell( - device.id, - `run-as ${packageName} ls data/user/0/${packageName}/cache`, - ) - .then(function(files: any) { - files.forEach(function(file: any) { - if (file.isFile()) { - console.log('[%s] Found file "%s"', device.id, file.name); - } - }); - }); - }); - }) - .then(function() { - console.log( - 'Done checking /data/user/0/packageName files on connected devices', - ); - }) - .catch(function(err: any) { - console.error('Something went wrong:', err.stack); - }); -} diff --git a/packages/cli/src/commands/profile/pullFile.ts b/packages/cli/src/commands/profile/pullFile.ts deleted file mode 100644 index 1edd57445..000000000 --- a/packages/cli/src/commands/profile/pullFile.ts +++ /dev/null @@ -1,38 +0,0 @@ -const promise = require('adbkit/bluebird'); -const fs = require('fs'); -const adb = require('adbkit/lib/adb'); -const client = adb.createClient(); - -export function pullFile(dstPath: string) { - client - .listDevices() - .then(function(devices) { - return promise.map(devices, function(device) { - return client - .pull(devices.id, '/sdcard/latest.cpuprofile') - .then(function(transfer) { - return new Promise(function(resolve, reject) { - transfer.on('progress', function(stats) { - console.log( - '[%s] Pulled %d bytes so far', - device.id, - stats.bytesTransferred, - ); - }); - transfer.on('end', function() { - console.log('[%s] Pull complete', device.id); - resolve(device.id); - }); - transfer.on('error', reject); - transfer.pipe(fs.createWriteStream(dstPath)); - }); - }); - }); - }) - .then(function() { - console.log('Done pulling /system/build.prop from all connected devices'); - }) - .catch(function(err) { - console.error('Something went wrong:', err.stack); - }); -} From 4af2cbea5391a6909c34ea51bd3ade3c45986c5e Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Wed, 22 Jul 2020 14:19:45 +0700 Subject: [PATCH 03/53] deleted unused adbkit packages --- packages/cli/package.json | 1 - .../cli/src/commands/profile/downloadProfile.ts | 13 ++++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 963be7489..c2fa803d6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,7 +33,6 @@ "@react-native-community/cli-server-api": "^4.10.1", "@react-native-community/cli-tools": "^4.10.1", "@react-native-community/cli-types": "^4.10.1", - "adbkit": "^2.11.1", "chalk": "^3.0.0", "command-exists": "^1.2.8", "commander": "^2.19.0", diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts index 623ea9bce..98ace74f2 100644 --- a/packages/cli/src/commands/profile/downloadProfile.ts +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -6,7 +6,9 @@ import {logger, CLIError} from '@react-native-community/cli-tools'; import chalk from 'chalk'; import fs from 'fs'; -//get the last modified hermes profile +/** + * get the last modified hermes profile + */ function getLatestFile(packageName: string): string { try { const file = execSync(`adb shell run-as ${packageName} ls cache/ -tp | grep -v /$ | head -1 @@ -18,7 +20,9 @@ function getLatestFile(packageName: string): string { throw new Error(e); } } -//get the package name of the running React Native app +/** + * get the package name of the running React Native app + */ function getPackageName(config: Config) { const androidProject = config.project.android; @@ -52,7 +56,10 @@ function getPackageName(config: Config) { } return packageName; } -// Validates that the package name is correct +/** Validates that the package name is correct + * + */ + function validatePackageName(packageName: string) { return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(packageName); } From 98e1ecf41d7aa15a1dd55276e8f63ce453aba824 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Wed, 22 Jul 2020 14:45:36 +0700 Subject: [PATCH 04/53] changed files excluded node modules --- .vscode/settings.json | 2 +- package.json | 1 - yarn.lock | 41 ++--------------------------------------- 3 files changed, 3 insertions(+), 41 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d2b294630..149aceb95 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,7 @@ "editor.rulers": [80], "files.exclude": { "**/.git": true, - "**/node_modules": false, + "**/node_modules": true, "**/build": true }, "editor.codeActionsOnSave": { diff --git a/package.json b/package.json index 1cd1e24ac..995298903 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "@types/mkdirp": "^0.5.2", "@types/node": "^8.0.0", "@types/node-fetch": "^2.3.7", - "adbkit": "^2.11.1", "babel-jest": "^25.2.4", "babel-plugin-module-resolver": "^3.2.0", "chalk": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index 9e0a3de98..e92dd6b0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2547,31 +2547,6 @@ acorn@^7.1.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== -adbkit-logcat@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/adbkit-logcat/-/adbkit-logcat-1.1.0.tgz#01d7f9b0cef9093a30bcb3b007efff301508962f" - integrity sha1-Adf5sM75CTowvLOwB+//MBUIli8= - -adbkit-monkey@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/adbkit-monkey/-/adbkit-monkey-1.0.1.tgz#f291be701a2efc567a63fc7aa6afcded31430be1" - integrity sha1-8pG+cBou/FZ6Y/x6pq/N7TFDC+E= - dependencies: - async "~0.2.9" - -adbkit@^2.11.1: - version "2.11.1" - resolved "https://registry.yarnpkg.com/adbkit/-/adbkit-2.11.1.tgz#7da847fe561254f3121088947bc1907ef053e894" - integrity sha512-hDTiRg9NX3HQt7WoDAPCplUpvzr4ZzQa2lq7BdTTJ/iOZ6O7YNAs6UYD8sFAiBEcYHDRIyq3cm9sZP6uZnhvXw== - dependencies: - adbkit-logcat "^1.1.0" - adbkit-monkey "~1.0.1" - bluebird "~2.9.24" - commander "^2.3.0" - debug "~2.6.3" - node-forge "^0.7.1" - split "~0.3.3" - agent-base@4, agent-base@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" @@ -3104,11 +3079,6 @@ bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bluebird@~2.9.24: - version "2.9.34" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.9.34.tgz#2f7b4ec80216328a9fddebdf69c8d4942feff7d8" - integrity sha1-L3tOyAIWMoqf3evfacjUlC/v99g= - bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -3711,7 +3681,7 @@ command-exists@^1.2.6, command-exists@^1.2.8: resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291" integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw== -commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@^2.3.0, commander@~2.20.3: +commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -4254,7 +4224,7 @@ deasync@^0.1.14: bindings "^1.5.0" node-addon-api "^1.7.1" -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9, debug@~2.6.3: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -10574,13 +10544,6 @@ split@^1.0.0: dependencies: through "2" -split@~0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" - integrity sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8= - dependencies: - through "2" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" From 24a248b66977ab2f63e09ca2c44ed5ae42b08503 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Wed, 22 Jul 2020 22:43:02 +0700 Subject: [PATCH 05/53] handled edge case when there's no file --- .../src/commands/profile/downloadProfile.ts | 35 +++++-------------- packages/cli/src/commands/profile/index.ts | 30 ++-------------- 2 files changed, 12 insertions(+), 53 deletions(-) diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts index 98ace74f2..c54604ef9 100644 --- a/packages/cli/src/commands/profile/downloadProfile.ts +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -1,7 +1,5 @@ -// @ts-ignore untyped import {Config} from '@react-native-community/cli-types'; import {execSync} from 'child_process'; -//import {projectConfig} from '../../../../platform-android/src/config/index'; import {logger, CLIError} from '@react-native-community/cli-tools'; import chalk from 'chalk'; import fs from 'fs'; @@ -13,9 +11,8 @@ function getLatestFile(packageName: string): string { try { const file = execSync(`adb shell run-as ${packageName} ls cache/ -tp | grep -v /$ | head -1 `); - //console.log(file.toString()); + return file.toString().trim(); - //return parsePackagename(packages.toString()); } catch (e) { throw new Error(e); } @@ -70,8 +67,6 @@ function validatePackageName(packageName: string) { * adb shell run-as com.rnhermesapp cp cache/sampling-profiler-trace1502707982002849976.cpuprofile /sdcard/latest.cpuprofile * adb pull /sdcard/latest.cpuprofile */ - -//const packageName = projectConfig(".",{} )?.packageName; //TODO: get AndroidProjectConfig export async function downloadProfile( ctx: Config, dstPath?: string, @@ -80,38 +75,26 @@ export async function downloadProfile( try { const packageName = getPackageName(ctx); - // const projectConfigResult = projectConfig(ctx.root, {}); - // let packageName; - // if (projectConfigResult !== null) { - // packageName = projectConfigResult.packageName; - // } else { - // packageName = ''; //cannot get packageName since config is empty - // } - let file; - if (fileName !== undefined) { - file = fileName; - } else { - file = await getLatestFile(packageName); + const file = fileName || (await getLatestFile(packageName)); + if (!file) { + logger.error( + 'There is no file in the cache/ directory. Did you record a profile from the developer menu?', + ); + process.exit(1); } logger.info(`File to be pulled: ${file}`); - // console.log(`adb shell run-as ${packageName} ls`); - // execSync(`adb shell run-as ${packageName} ls`); execSync(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); //if not specify destination path, pull to the current directory if (dstPath === undefined) { - //execSync(`adb pull /sdcard/${file} ${process.cwd()}`); execSync(`adb pull /sdcard/${file} ${ctx.root}`); - console.log( - 'Successfully pulled the file to the current root working directory', - ); + console.log(`Successfully pulled the file to ${ctx.root}/${file}`); } //if specified destination path, pull to that directory else { execSync(`adb pull /sdcard/${file} ${dstPath}`); - console.log(`Successfully pulled the file to ${dstPath}`); + console.log(`Successfully pulled the file to ${dstPath}/${file}`); } - //return ''; } catch (e) { throw new Error(e.message); } diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile/index.ts index 29c28f49b..7a3ed7157 100644 --- a/packages/cli/src/commands/profile/index.ts +++ b/packages/cli/src/commands/profile/index.ts @@ -16,47 +16,23 @@ async function profile( logger.info( 'Downloading a Hermes Sampling Profiler from your Android device...', ); - //logger.log(`options ${JSON.stringify(options)}`); - // logger.log(`${typeof options}`); - // logger.log(`${options.fileName}`); + if (options.fileName) { - //logger.log(options.fileName); await downloadProfile(ctx, dstPath, options.fileName); } else { logger.info('No filename is provided, pulling latest file'); await downloadProfile(ctx, dstPath, undefined); } - //logger.log(output); } catch (err) { logger.error(`Unable to download the Hermes Sampling Profiler.\n${err}`); } } -// type Command = { -// name: string, -// description?: string, -// func: (argv: Array, config: ConfigT, args: Object) => ?Promise, -// options?: Array<{ -// name: string, -// description?: string, -// parse?: (val: string) => any, -// default?: -// | string -// | boolean -// | number -// | ((config: ConfigT) => string | boolean | number), -// }>, -// examples?: Array<{ -// desc: string, -// cmd: string, -// }>, -// }; - export default { - name: 'profile-hermes [destinationDir]', //profile-hermes ls + name: 'profile-hermes [destinationDir]', description: 'Download the Hermes Sampling Profiler to the directory of the local machine', - func: profile, //how to give the args for this func: an array of arguments + func: profile, options: [ //options: download the latest or fileName { From b720bafed4674e70d0fbb56dac573d08398f82e5 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Wed, 22 Jul 2020 22:53:47 +0700 Subject: [PATCH 06/53] deleted unnecessary changes --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 3ffb7f911..bc0c60880 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es2017", "module": "commonjs", - "lib": ["es2017", "esnext.asynciterable"], + "lib": ["es2017"], "declaration": true, "declarationMap": true, "composite": true, From 4c0fb457f0128c00f61c3e274d4041ce8b0c1532 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Wed, 22 Jul 2020 22:56:19 +0700 Subject: [PATCH 07/53] deleted unnecessary changes 2 --- .vscode/settings.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 149aceb95..230a6715c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { - "editor.rulers": [80], + "editor.rulers": [ + 80 + ], "files.exclude": { "**/.git": true, "**/node_modules": true, From b5d1d3cbd1268aeecf487cfa43e664d6bab8e9a2 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 28 Jul 2020 16:01:01 +0700 Subject: [PATCH 08/53] added transformer files --- packages/cli/package.json | 3 + .../src/commands/profile/EventInterfaces.ts | 266 +++++++++++++ packages/cli/src/commands/profile/Phases.ts | 30 ++ .../cli/src/commands/profile/cpuProfiler.ts | 374 ++++++++++++++++++ 4 files changed, 673 insertions(+) create mode 100644 packages/cli/src/commands/profile/EventInterfaces.ts create mode 100644 packages/cli/src/commands/profile/Phases.ts create mode 100644 packages/cli/src/commands/profile/cpuProfiler.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index c2fa803d6..75288201e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,6 +33,9 @@ "@react-native-community/cli-server-api": "^4.10.1", "@react-native-community/cli-tools": "^4.10.1", "@react-native-community/cli-types": "^4.10.1", + "@types/axios": "^0.14.0", + "@types/source-map": "^0.5.7", + "axios": "^0.19.2", "chalk": "^3.0.0", "command-exists": "^1.2.8", "commander": "^2.19.0", diff --git a/packages/cli/src/commands/profile/EventInterfaces.ts b/packages/cli/src/commands/profile/EventInterfaces.ts new file mode 100644 index 000000000..a25aa1b6e --- /dev/null +++ b/packages/cli/src/commands/profile/EventInterfaces.ts @@ -0,0 +1,266 @@ +import {EventsPhase} from './Phases'; + +export interface SharedEventProperties { + /** + * name of the event + */ + name?: string; + /** + * event category + */ + cat?: string; + /** + * tracing clock timestamp + */ + ts?: number; + /** + * process ID + */ + pid?: number; + /** + * thread ID + */ + tid?: number; + /** + * event type (phase) + */ + ph: EventsPhase; + /** + * id for a stackFrame object + */ + sf?: number; + /** + * thread clock timestamp + */ + tts?: number; + /** + * a fixed color name + */ + cname?: string; + /** + * event arguments + */ + args?: {[key in string]: any}; +} + +interface DurationEventBegin extends SharedEventProperties { + ph: EventsPhase.DURATION_EVENTS_BEGIN; +} + +interface DurationEventEnd extends SharedEventProperties { + ph: EventsPhase.DURATION_EVENTS_END; +} + +export type DurationEvent = DurationEventBegin | DurationEventEnd; + +export interface CompleteEvent extends SharedEventProperties { + ph: EventsPhase.COMPLETE_EVENTS; + dur: number; +} + +export interface MetadataEvent extends SharedEventProperties { + ph: EventsPhase.METADATA_EVENTS; +} + +export interface SampleEvent extends SharedEventProperties { + ph: EventsPhase.SAMPLE_EVENTS; +} + +interface ObjectEventCreated extends SharedEventProperties { + ph: EventsPhase.OBJECT_EVENTS_CREATED; + scope?: string; +} + +interface ObjectEventSnapshot extends SharedEventProperties { + ph: EventsPhase.OBJECT_EVENTS_SNAPSHOT; + scope?: string; +} + +interface ObjectEventDestroyed extends SharedEventProperties { + ph: EventsPhase.OBJECT_EVENTS_DESTROYED; + scope?: string; +} + +export type ObjectEvent = + | ObjectEventCreated + | ObjectEventSnapshot + | ObjectEventDestroyed; + +export interface ClockSyncEvent extends SharedEventProperties { + ph: EventsPhase.CLOCK_SYNC_EVENTS; + args: { + sync_id: string; + issue_ts?: number; + }; +} + +interface ContextEventEnter extends SharedEventProperties { + ph: EventsPhase.CONTEXT_EVENTS_ENTER; +} + +interface ContextEventLeave extends SharedEventProperties { + ph: EventsPhase.CONTEXT_EVENTS_LEAVE; +} + +export type ContextEvent = ContextEventEnter | ContextEventLeave; + +interface AsyncEventStart extends SharedEventProperties { + ph: EventsPhase.ASYNC_EVENTS_NESTABLE_START; + id: number; + scope?: string; +} + +interface AsyncEventInstant extends SharedEventProperties { + ph: EventsPhase.ASYNC_EVENTS_NESTABLE_INSTANT; + id: number; + scope?: string; +} + +interface AsyncEventEnd extends SharedEventProperties { + ph: EventsPhase.ASYNC_EVENTS_NESTABLE_END; + id: number; + scope?: string; +} + +export type AsyncEvent = AsyncEventStart | AsyncEventInstant | AsyncEventEnd; + +export interface InstantEvent extends SharedEventProperties { + ph: EventsPhase.INSTANT_EVENTS; + s: string; +} + +export interface CounterEvent extends SharedEventProperties { + ph: EventsPhase.COUNTER_EVENTS; +} + +interface FlowEventStart extends SharedEventProperties { + ph: EventsPhase.FLOW_EVENTS_START; +} + +interface FlowEventStep extends SharedEventProperties { + ph: EventsPhase.FLOW_EVENTS_STEP; +} +interface FlowEventEnd extends SharedEventProperties { + ph: EventsPhase.FLOW_EVENTS_END; +} + +export type FlowEvent = FlowEventStart | FlowEventStep | FlowEventEnd; + +interface MemoryDumpGlobal extends SharedEventProperties { + ph: EventsPhase.MEMORY_DUMP_EVENTS_GLOBAL; + id: string; +} + +interface MemoryDumpProcess extends SharedEventProperties { + ph: EventsPhase.MEMORY_DUMP_EVENTS_PROCESS; + id: string; +} +export type MemoryDumpEvent = MemoryDumpGlobal | MemoryDumpProcess; + +export interface MarkEvent extends SharedEventProperties { + ph: EventsPhase.MARK_EVENTS; +} + +export interface LinkedIDEvent extends SharedEventProperties { + ph: EventsPhase.LINKED_ID_EVENTS; + id: number; + args: { + linked_id: number; + }; +} + +export type Event = + | DurationEvent + | CompleteEvent + | MetadataEvent + | SampleEvent + | ObjectEvent + | ClockSyncEvent + | ContextEvent + | AsyncEvent + | InstantEvent + | CounterEvent + | FlowEvent + | MemoryDumpEvent + | MarkEvent + | LinkedIDEvent; + +/** + * Each item in the stackFrames object of the hermes profile + */ +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; +} +/** + * Each item in the samples array of the hermes profile + */ +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; +} + +/** + * Hermes Profile Interface + */ +export interface HermesCPUProfile { + traceEvents: SharedEventProperties[]; + samples: HermesSample[]; + stackFrames: {[key in string]: HermesStackFrame}; +} + +export interface CPUProfileChunk { + id: string; + pid: number; + tid: string; + startTime: number; + nodes: CPUProfileChunkNode[]; + samples: number[]; + timeDeltas: number[]; +} + +export interface CPUProfileChunkNode { + id: number; + callFrame: { + line: string; + column: string; + funcLine: string; + funcColumn: string; + name: string; + url?: string; + category: string; + }; + parent?: number; +} + +export type CPUProfileChunker = { + nodes: CPUProfileChunkNode[]; + sampleNumbers: number[]; + timeDeltas: number[]; +}; + +export interface SourceMap { + version: string; + sources: string[]; + sourceContent: string[]; + x_facebook_sources: {names: string[]; mappings: string}[] | null; + names: string[]; + mappings: string; +} diff --git a/packages/cli/src/commands/profile/Phases.ts b/packages/cli/src/commands/profile/Phases.ts new file mode 100644 index 000000000..63e900fd5 --- /dev/null +++ b/packages/cli/src/commands/profile/Phases.ts @@ -0,0 +1,30 @@ +export enum EventsPhase { + DURATION_EVENTS_BEGIN = 'B', + DURATION_EVENTS_END = 'E', + COMPLETE_EVENTS = 'X', + INSTANT_EVENTS = 'I', + COUNTER_EVENTS = 'C', + ASYNC_EVENTS_NESTABLE_START = 'b', + ASYNC_EVENTS_NESTABLE_INSTANT = 'n', + ASYNC_EVENTS_NESTABLE_END = 'e', + FLOW_EVENTS_START = 's', + FLOW_EVENTS_STEP = 't', + FLOW_EVENTS_END = 'f', + SAMPLE_EVENTS = 'P', + OBJECT_EVENTS_CREATED = 'N', + OBJECT_EVENTS_SNAPSHOT = 'O', + OBJECT_EVENTS_DESTROYED = 'D', + METADATA_EVENTS = 'M', + MEMORY_DUMP_EVENTS_GLOBAL = 'V', + MEMORY_DUMP_EVENTS_PROCESS = 'v', + MARK_EVENTS = 'R', + CLOCK_SYNC_EVENTS = 'c', + CONTEXT_EVENTS_ENTER = '(', + CONTEXT_EVENTS_LEAVE = ')', + // Deprecated + ASYNC_EVENTS_START = 'S', + ASYNC_EVENTS_STEP_INTO = 'T', + ASYNC_EVENTS_STEP_PAST = 'p', + ASYNC_EVENTS_END = 'F', + LINKED_ID_EVENTS = '=', +} diff --git a/packages/cli/src/commands/profile/cpuProfiler.ts b/packages/cli/src/commands/profile/cpuProfiler.ts new file mode 100644 index 000000000..2adb16594 --- /dev/null +++ b/packages/cli/src/commands/profile/cpuProfiler.ts @@ -0,0 +1,374 @@ +/** + * @license Copyright 2020 The Lighthouse Authors. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ + +/** + * @fileoverview + * + * This model converts the `Profile` and `ProfileChunk` mega trace events from the `disabled-by-default-v8.cpu_profiler` + * category into B/E-style trace events that main-thread-tasks.js already knows how to parse into a task tree. + * + * The CPU profiler measures where time is being spent by sampling the stack (See https://www.jetbrains.com/help/profiler/Profiling_Guidelines__Choosing_the_Right_Profiling_Mode.html + * for a generic description of the differences between tracing and sampling). + * + * A `Profile` event is a record of the stack that was being executed at different sample points in time. + * It has a structure like this: + * + * nodes: [function A, function B, function C] + * samples: [node with id 2, node with id 1, ...] + * timeDeltas: [4125μs since last sample, 121μs since last sample, ...] + * + * Helpful prior art: + * @see https://cs.chromium.org/chromium/src/third_party/devtools-frontend/src/front_end/sdk/CPUProfileDataModel.js?sq=package:chromium&g=0&l=42 + * @see https://github.com/v8/v8/blob/99ca333b0efba3236954b823101315aefeac51ab/tools/profile.js + * @see https://github.com/jlfwong/speedscope/blob/9ed1eb192cb7e9dac43a5f25bd101af169dc654a/src/import/chrome.ts#L200 + */ + +import axios, { AxiosResponse } from 'axios'; +import { SourceMapConsumer, RawSourceMap } from 'source-map'; + +import { + DurationEvent, + HermesCPUProfile, + HermesSample, + HermesStackFrame, + CPUProfileChunk, + CPUProfileChunkNode, + CPUProfileChunker, + SourceMap, +} from './EventInterfaces'; +import { EventsPhase } from './Phases'; + +export class CpuProfilerModel { + _profile: CPUProfileChunk; + _nodesById: Map; + _activeNodeArraysById: Map; + + constructor(profile: CPUProfileChunk) { + this._profile = profile; + this._nodesById = this._createNodeMap(); + this._activeNodeArraysById = this._createActiveNodeArrays(); + } + + /** + * Initialization function to enable O(1) access to nodes by node ID. + * @return {Map { + /** @type {Map} */ + const map: Map = new Map< + number, + CPUProfileChunkNode + >(); + for (const node of this._profile.nodes) { + map.set(node.id, node); + } + + return map; + } + + /** + * Initialization function to enable O(1) access to the set of active nodes in the stack by node ID. + * @return Map + */ + _createActiveNodeArrays(): Map { + const map: Map = new Map(); + + /** + * Given a nodeId, `getActiveNodes` gets all the parent nodes in reversed call order + * @param {number} id + */ + const getActiveNodes = (id: number): number[] => { + if (map.has(id)) return map.get(id) || []; + + const node = this._nodesById.get(id); + if (!node) throw new Error(`No such node ${id}`); + if (node.parent) { + const array = getActiveNodes(node.parent).concat([id]); + map.set(id, array); + return array; + } else { + return [id]; + } + }; + + for (const node of this._profile.nodes) { + map.set(node.id, getActiveNodes(node.id)); + } + return map; + } + + /** + * Returns all the node IDs in a stack when a specific nodeId is at the top of the stack + * (i.e. a stack's node ID and the node ID of all of its parents). + */ + _getActiveNodeIds(nodeId: number): number[] { + const activeNodeIds = this._activeNodeArraysById.get(nodeId); + if (!activeNodeIds) throw new Error(`No such node ID ${nodeId}`); + return activeNodeIds; + } + + /** + * Generates the necessary B/E-style trace events for a single transition from stack A to stack B + * at the given timestamp. + * + * Example: + * + * timestamp 1234 + * previousNodeIds 1,2,3 + * currentNodeIds 1,2,4 + * + * yields [end 3 at ts 1234, begin 4 at ts 1234] + * + * @param {number} timestamp + * @param {Array} previousNodeIds + * @param {Array} currentNodeIds + * @returns {Array} + */ + _createStartEndEventsForTransition( + timestamp: number, + previousNodeIds: number[], + currentNodeIds: number[] + ): DurationEvent[] { + // Start nodes are the nodes which are present only in the currentNodeIds and not in PreviousNodeIds + const startNodes: CPUProfileChunkNode[] = currentNodeIds + .filter(id => !previousNodeIds.includes(id)) + .map(id => this._nodesById.get(id)!); + // End nodes are the nodes which are present only in the PreviousNodeIds and not in CurrentNodeIds + const endNodes: CPUProfileChunkNode[] = previousNodeIds + .filter(id => !currentNodeIds.includes(id)) + .map(id => this._nodesById.get(id)!); + + /** + * Create a Duration Event from CPUProfileChunkNodes. + * @param {CPUProfileChunkNode} node + * @return {DurationEvent} */ + const createEvent = (node: CPUProfileChunkNode): DurationEvent => ({ + ts: timestamp, + pid: this._profile.pid, + tid: Number(this._profile.tid), + ph: EventsPhase.DURATION_EVENTS_BEGIN, + name: node.callFrame.name, + cat: node.callFrame.category, + args: { data: { callFrame: node.callFrame } }, + }); + + const startEvents: DurationEvent[] = startNodes + .map(createEvent) + .map(evt => ({ ...evt, ph: EventsPhase.DURATION_EVENTS_BEGIN })); + const endEvents: DurationEvent[] = endNodes + .map(createEvent) + .map(evt => ({ ...evt, ph: EventsPhase.DURATION_EVENTS_END })); + return [...endEvents.reverse(), ...startEvents]; + } + + /** + * Creates B/E-style trace events from a CpuProfile object created by `collectProfileEvents()` + * @return {DurationEvent} + */ + createStartEndEvents(): DurationEvent[] { + const profile = this._profile; + const length = profile.samples.length; + if (profile.timeDeltas.length !== length) + throw new Error(`Invalid CPU profile length`); + + const events: DurationEvent[] = []; + + let timestamp = profile.startTime; + let lastActiveNodeIds: number[] = []; + for (let i = 0; i < profile.samples.length; i++) { + const nodeId = profile.samples[i]; + const timeDelta = Math.max(profile.timeDeltas[i], 1); + const node = this._nodesById.get(nodeId); + if (!node) throw new Error(`Missing node ${nodeId}`); + + timestamp += timeDelta; + const activeNodeIds = this._getActiveNodeIds(nodeId); + events.push( + ...this._createStartEndEventsForTransition( + timestamp, + lastActiveNodeIds, + activeNodeIds + ) + ); + lastActiveNodeIds = activeNodeIds; + } + + events.push( + ...this._createStartEndEventsForTransition( + timestamp, + lastActiveNodeIds, + [] + ) + ); + + return events; + } + + /** + * Creates B/E-style trace events from a CpuProfile object created by `collectProfileEvents()` + * @param {CPUProfileChunk} profile + */ + static createStartEndEvents(profile: CPUProfileChunk) { + const model = new CpuProfilerModel(profile); + return model.createStartEndEvents(); + } + + /** + * Converts the Hermes Sample into a single CpuProfileChunk object for consumption + * by `createStartEndEvents()`. + * + * @param {HermesCPUProfile} profile + * @throws Profile must have atleast one sample + * @return {Array} + */ + static collectProfileEvents(profile: HermesCPUProfile): CPUProfileChunk { + if (profile.samples.length >= 0) { + const { samples, stackFrames } = profile; + // Assumption: The sample will have a single process + const pid: number = samples[0].pid; + // Assumption: Javascript is single threaded, so there should only be one thread throughout + const tid: string = samples[0].tid; + // TODO: What role does id play in string parsing + const id: string = '0x1'; + const startTime: number = Number(samples[0].ts); + const { nodes, sampleNumbers, timeDeltas } = this.constructNodes( + samples, + stackFrames + ); + return { + id, + pid, + tid, + startTime, + nodes, + samples: sampleNumbers, + timeDeltas, + }; + } else { + throw new Error('The hermes profile has zero samples'); + } + } + + /** + * Constructs CPUProfileChunk Nodes and the resultant samples and time deltas to be inputted into the + * CPUProfileChunk object which will be processed to give createStartEndEvents() + * + * @param {HermesSample} samples + * @param {Map} stackFrames + * @return { Array, Array, Array } + */ + static constructNodes( + samples: HermesSample[], + stackFrames: { [key in string]: HermesStackFrame } + ): CPUProfileChunker { + samples = samples.map((sample: HermesSample) => { + sample.stackFrameData = stackFrames[sample.sf]; + return sample; + }); + const stackFrameIds: string[] = Object.keys(stackFrames); + // TODO: What should URLs be? Change name to get things from SourceMaps + + const profileNodes: CPUProfileChunkNode[] = stackFrameIds.map( + (stackFrameId: string) => { + return { + id: Number(stackFrameId), + callFrame: { + line: stackFrames[stackFrameId].line, + column: stackFrames[stackFrameId].column, + funcLine: stackFrames[stackFrameId].funcLine, + funcColumn: stackFrames[stackFrameId].funcColumn, + name: stackFrames[stackFrameId].name, + category: stackFrames[stackFrameId].category, + url: 'TODO', + }, + parent: stackFrames[stackFrameId].parent, + }; + } + ); + const returnedSamples: number[] = []; + const timeDeltas: number[] = []; + let lastTimeStamp = Number(samples[0].ts); + samples.forEach((sample: HermesSample, idx: number) => { + returnedSamples.push(sample.sf); + if (idx === 0) { + timeDeltas.push(0); + } else { + const timeDiff = Number(sample.ts) - lastTimeStamp; + lastTimeStamp = Number(sample.ts); + timeDeltas.push(timeDiff); + } + }); + + return { + nodes: profileNodes, + sampleNumbers: returnedSamples, + timeDeltas, + }; + } + + /** + * Refer to the source maps for the `index.bundle` file. Throws error if debug server not running + * @param {string} metroServerURL + * @param {DurationEvent[]} chromeEvents + * @throws {Debug Server not running} + * @returns {DurationEvent[]} + */ + static async changeNamesToSourceMaps( + metroServerURL: string, + chromeEvents: DurationEvent[] + ): Promise { + /** + * To eliminate Metro Server depedency, sourceMap needs to be the input of this function NOT + * metroServerURL + */ + const response = (await axios.get(metroServerURL)) as AxiosResponse< + SourceMap + >; + if (!response.data) { + throw new Error('Incorrect Debug Port Provided'); + } + const sourceMap: SourceMap = response.data; + const rawSourceMap: RawSourceMap = { + version: Number(sourceMap.version), + file: 'index.bundle', + sources: sourceMap.sources, + mappings: sourceMap.mappings, + names: sourceMap.names, + }; + + const consumer = await new SourceMapConsumer(rawSourceMap); + const events = chromeEvents.map((event: DurationEvent) => { + const sm = consumer.originalPositionFor({ + line: Number(event.args?.data.callFrame.line), + column: Number(event.args?.data.callFrame.column), + }); + event.args = { + data: { + callFrame: { + ...event.args?.data.callFrame, + url: sm.source, + line: sm.line, + column: sm.column, + }, + }, + }; + /** + * The name in source maps (for reasons I don't understand) is sometimes null, so OR-ing this + * to ensure a name is assigned. + * In case a name wasn't found, the URL is used + * Eg: /Users/saphal/Desktop/app/node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js => MessageQueue.js + */ + event.name = + sm.name || + (event.args?.data.callFrame.url + ? event.args?.data.callFrame.url.split('/').pop() + : event.args?.data.callFrame.name); + return event; + }); + consumer.destroy(); + return events; + } +} \ No newline at end of file From 89837ae1b54d7b9fa3cfef8beeb45f0c1a5f81be Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 28 Jul 2020 16:05:22 +0700 Subject: [PATCH 09/53] implemented --verbose flag --- .../src/commands/profile/downloadProfile.ts | 94 +++++++++++++++---- packages/cli/src/commands/profile/index.ts | 30 ++++-- 2 files changed, 100 insertions(+), 24 deletions(-) diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts index c54604ef9..7109e0b0d 100644 --- a/packages/cli/src/commands/profile/downloadProfile.ts +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -3,9 +3,11 @@ import {execSync} from 'child_process'; import {logger, CLIError} from '@react-native-community/cli-tools'; import chalk from 'chalk'; import fs from 'fs'; +import path from 'path'; +import os from 'os'; /** - * get the last modified hermes profile + * Get the last modified hermes profile */ function getLatestFile(packageName: string): string { try { @@ -18,7 +20,7 @@ function getLatestFile(packageName: string): string { } } /** - * get the package name of the running React Native app + * Get the package name of the running React Native app */ function getPackageName(config: Config) { const androidProject = config.project.android; @@ -63,18 +65,15 @@ function validatePackageName(packageName: string) { /** * Executes the commands to pull a hermes profile - * Commands: - * adb shell run-as com.rnhermesapp cp cache/sampling-profiler-trace1502707982002849976.cpuprofile /sdcard/latest.cpuprofile - * adb pull /sdcard/latest.cpuprofile */ export async function downloadProfile( ctx: Config, - dstPath?: string, + dstPath: string, fileName?: string, ) { try { const packageName = getPackageName(ctx); - + //if not specify fileName, pull the latest file const file = fileName || (await getLatestFile(packageName)); if (!file) { logger.error( @@ -82,19 +81,80 @@ export async function downloadProfile( ); process.exit(1); } - logger.info(`File to be pulled: ${file}`); - execSync(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); - //if not specify destination path, pull to the current directory - if (dstPath === undefined) { - execSync(`adb pull /sdcard/${file} ${ctx.root}`); - console.log(`Successfully pulled the file to ${ctx.root}/${file}`); + if (!dstPath) { + dstPath = ctx.root; } - //if specified destination path, pull to that directory - else { - execSync(`adb pull /sdcard/${file} ${dstPath}`); - console.log(`Successfully pulled the file to ${dstPath}/${file}`); + logger.info(`File to be pulled: ${file}`); + if (logger.isVerbose()) { + logger.info('Internal commands run to pull the file: '); + logger.debug(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); + logger.debug(`adb pull /sdcard/${file} ${dstPath}`); } + //Copy the file from device's data to sdcard, then pull the file to a temp directory + execSync(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); + const tmpDir = path.join(os.tmpdir(), file); + console.log('temp dir: ', tmpDir); + execSync(`adb pull /sdcard/${file} ${tmpDir}`); + + //Run transformer tool to convert from Hermes to Chrome format + + execSync(`adb pull /sdcard/${file} ${dstPath}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + execSync(`adb pull /sdcard/${file} ${tmpDir}`); + + //Run transformer tool to convert from Hermes to Chrome format + + execSync(`adb pull /sdcard/${file} ${dstPath}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + execSync(`adb pull /sdcard/${file} ${tmpDir}`); + + //Run transformer tool to convert from Hermes to Chrome format + + execSync(`adb pull /sdcard/${file} ${dstPath}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + execSync(`adb pull /sdcard/${file} ${tmpDir}`); + + //Run transformer tool to convert from Hermes to Chrome format + + execSync(`adb pull /sdcard/${file} ${dstPath}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + execSync(`adb pull /sdcard/${file} ${tmpDir}`); + + //Run transformer tool to convert from Hermes to Chrome format + + execSync(`adb pull /sdcard/${file} ${dstPath}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + execSync(`adb pull /sdcard/${file} ${tmpDir}`); + + //Run transformer tool to convert from Hermes to Chrome format + + execSync(`adb pull /sdcard/${file} ${dstPath}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + execSync(`adb pull /sdcard/${file} ${tmpDir}`); + + //Run transformer tool to convert from Hermes to Chrome format + + execSync(`adb pull /sdcard/${file} ${dstPath}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + execSync(`adb pull /sdcard/${file} ${tmpDir}`); + + //Run transformer tool to convert from Hermes to Chrome format + + execSync(`adb pull /sdcard/${file} ${dstPath}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + execSync(`adb pull /sdcard/${file} ${tmpDir}`); + + //Run transformer tool to convert from Hermes to Chrome format + + execSync(`adb pull /sdcard/${file} ${dstPath}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + execSync(`adb pull /sdcard/${file} ${tmpDir}`); + + //Run transformer tool to convert from Hermes to Chrome format + + execSync(`adb pull /sdcard/${file} ${dstPath}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); } catch (e) { throw new Error(e.message); } diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile/index.ts index 7a3ed7157..27330a8e0 100644 --- a/packages/cli/src/commands/profile/index.ts +++ b/packages/cli/src/commands/profile/index.ts @@ -4,7 +4,10 @@ import {Config} from '@react-native-community/cli-types'; import {downloadProfile} from './downloadProfile'; type Options = { + verbose: boolean; fileName?: string; + raw?: boolean; + sourceMapPath?: string; }; async function profile( @@ -16,13 +19,13 @@ async function profile( logger.info( 'Downloading a Hermes Sampling Profiler from your Android device...', ); - - if (options.fileName) { - await downloadProfile(ctx, dstPath, options.fileName); - } else { + if (!options.fileName) { logger.info('No filename is provided, pulling latest file'); - await downloadProfile(ctx, dstPath, undefined); } + if (options.verbose) { + logger.setVerbose(true); + } + await downloadProfile(ctx, dstPath, options.fileName); } catch (err) { logger.error(`Unable to download the Hermes Sampling Profiler.\n${err}`); } @@ -34,10 +37,23 @@ export default { 'Download the Hermes Sampling Profiler to the directory of the local machine', func: profile, options: [ - //options: download the latest or fileName + //options: specify fileName as a string { name: '--fileName [string]', - description: 'Filename of the profile to be downloaded', + description: + 'Filename of the profile to be downloaded, eg. sampling-profiler-trace8593107139682635366.cpuprofile', + }, + { + name: '--verbose', + description: 'Listing adb commands that are run internally', + }, + { + name: '--raw', + description: 'Pulling original Hermes formatted profile', + }, + { + name: 'source-map-path', + description: 'Providing the local path to your source map bundle', }, ], examples: [ From 3e7c6f4700dcee363243440313c6b3dfb859e71a Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 28 Jul 2020 22:33:47 +0700 Subject: [PATCH 10/53] integrated transformer tool with CLI --- packages/cli/package.json | 2 + .../{cpuProfiler.ts => cpuProfilerModel.ts} | 136 +++++------------- .../src/commands/profile/downloadProfile.ts | 69 ++------- packages/cli/src/commands/profile/index.ts | 11 +- .../cli/src/commands/profile/sourceMapper.ts | 59 ++++++++ .../cli/src/commands/profile/transformer.ts | 37 +++++ yarn.lock | 67 ++++++--- 7 files changed, 211 insertions(+), 170 deletions(-) rename packages/cli/src/commands/profile/{cpuProfiler.ts => cpuProfilerModel.ts} (73%) create mode 100644 packages/cli/src/commands/profile/sourceMapper.ts create mode 100644 packages/cli/src/commands/profile/transformer.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 75288201e..ba0b18f50 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,6 +34,7 @@ "@react-native-community/cli-tools": "^4.10.1", "@react-native-community/cli-types": "^4.10.1", "@types/axios": "^0.14.0", + "@types/node": "^14.0.26", "@types/source-map": "^0.5.7", "axios": "^0.19.2", "chalk": "^3.0.0", @@ -62,6 +63,7 @@ "pretty-format": "^25.2.0", "semver": "^6.3.0", "serve-static": "^1.13.1", + "source-map": "^0.7.3", "strip-ansi": "^5.2.0", "sudo-prompt": "^9.0.0", "wcwidth": "^1.0.1" diff --git a/packages/cli/src/commands/profile/cpuProfiler.ts b/packages/cli/src/commands/profile/cpuProfilerModel.ts similarity index 73% rename from packages/cli/src/commands/profile/cpuProfiler.ts rename to packages/cli/src/commands/profile/cpuProfilerModel.ts index 2adb16594..c16b71012 100644 --- a/packages/cli/src/commands/profile/cpuProfiler.ts +++ b/packages/cli/src/commands/profile/cpuProfilerModel.ts @@ -2,6 +2,12 @@ * @license Copyright 2020 The Lighthouse Authors. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + * This file is heavily derived from `https://github.com/GoogleChrome/lighthouse/blob/0422daa9b1b8528dd8436860b153134bd0f959f1/lighthouse-core/lib/tracehouse/cpu-profile-model.js` + * and has been modified by Saphal Patro (email: saphal1998@gmail.com) + * The following changes have been made to the original file: + * 1. Converted code to Typescript and defined necessary types + * 2. Wrote a method @see collectProfileEvents to convert the Hermes Samples to Profile Chunks supported by Lighthouse Parser + * 3. Modified @see constructNodes to work with the Hermes Samples and StackFrames */ /** @@ -26,9 +32,6 @@ * @see https://github.com/jlfwong/speedscope/blob/9ed1eb192cb7e9dac43a5f25bd101af169dc654a/src/import/chrome.ts#L200 */ -import axios, { AxiosResponse } from 'axios'; -import { SourceMapConsumer, RawSourceMap } from 'source-map'; - import { DurationEvent, HermesCPUProfile, @@ -37,9 +40,8 @@ import { CPUProfileChunk, CPUProfileChunkNode, CPUProfileChunker, - SourceMap, } from './EventInterfaces'; -import { EventsPhase } from './Phases'; +import {EventsPhase} from './Phases'; export class CpuProfilerModel { _profile: CPUProfileChunk; @@ -81,10 +83,14 @@ export class CpuProfilerModel { * @param {number} id */ const getActiveNodes = (id: number): number[] => { - if (map.has(id)) return map.get(id) || []; + if (map.has(id)) { + return map.get(id) || []; + } const node = this._nodesById.get(id); - if (!node) throw new Error(`No such node ${id}`); + if (!node) { + throw new Error(`No such node ${id}`); + } if (node.parent) { const array = getActiveNodes(node.parent).concat([id]); map.set(id, array); @@ -106,7 +112,9 @@ export class CpuProfilerModel { */ _getActiveNodeIds(nodeId: number): number[] { const activeNodeIds = this._activeNodeArraysById.get(nodeId); - if (!activeNodeIds) throw new Error(`No such node ID ${nodeId}`); + if (!activeNodeIds) { + throw new Error(`No such node ID ${nodeId}`); + } return activeNodeIds; } @@ -130,7 +138,7 @@ export class CpuProfilerModel { _createStartEndEventsForTransition( timestamp: number, previousNodeIds: number[], - currentNodeIds: number[] + currentNodeIds: number[], ): DurationEvent[] { // Start nodes are the nodes which are present only in the currentNodeIds and not in PreviousNodeIds const startNodes: CPUProfileChunkNode[] = currentNodeIds @@ -152,15 +160,15 @@ export class CpuProfilerModel { ph: EventsPhase.DURATION_EVENTS_BEGIN, name: node.callFrame.name, cat: node.callFrame.category, - args: { data: { callFrame: node.callFrame } }, + args: {data: {callFrame: node.callFrame}}, }); const startEvents: DurationEvent[] = startNodes .map(createEvent) - .map(evt => ({ ...evt, ph: EventsPhase.DURATION_EVENTS_BEGIN })); + .map(evt => ({...evt, ph: EventsPhase.DURATION_EVENTS_BEGIN})); const endEvents: DurationEvent[] = endNodes .map(createEvent) - .map(evt => ({ ...evt, ph: EventsPhase.DURATION_EVENTS_END })); + .map(evt => ({...evt, ph: EventsPhase.DURATION_EVENTS_END})); return [...endEvents.reverse(), ...startEvents]; } @@ -171,8 +179,9 @@ export class CpuProfilerModel { createStartEndEvents(): DurationEvent[] { const profile = this._profile; const length = profile.samples.length; - if (profile.timeDeltas.length !== length) - throw new Error(`Invalid CPU profile length`); + if (profile.timeDeltas.length !== length) { + throw new Error('Invalid CPU profile length'); + } const events: DurationEvent[] = []; @@ -182,7 +191,9 @@ export class CpuProfilerModel { const nodeId = profile.samples[i]; const timeDelta = Math.max(profile.timeDeltas[i], 1); const node = this._nodesById.get(nodeId); - if (!node) throw new Error(`Missing node ${nodeId}`); + if (!node) { + throw new Error(`Missing node ${nodeId}`); + } timestamp += timeDelta; const activeNodeIds = this._getActiveNodeIds(nodeId); @@ -190,8 +201,8 @@ export class CpuProfilerModel { ...this._createStartEndEventsForTransition( timestamp, lastActiveNodeIds, - activeNodeIds - ) + activeNodeIds, + ), ); lastActiveNodeIds = activeNodeIds; } @@ -200,8 +211,8 @@ export class CpuProfilerModel { ...this._createStartEndEventsForTransition( timestamp, lastActiveNodeIds, - [] - ) + [], + ), ); return events; @@ -226,7 +237,7 @@ export class CpuProfilerModel { */ static collectProfileEvents(profile: HermesCPUProfile): CPUProfileChunk { if (profile.samples.length >= 0) { - const { samples, stackFrames } = profile; + const {samples, stackFrames} = profile; // Assumption: The sample will have a single process const pid: number = samples[0].pid; // Assumption: Javascript is single threaded, so there should only be one thread throughout @@ -234,9 +245,9 @@ export class CpuProfilerModel { // TODO: What role does id play in string parsing const id: string = '0x1'; const startTime: number = Number(samples[0].ts); - const { nodes, sampleNumbers, timeDeltas } = this.constructNodes( + const {nodes, sampleNumbers, timeDeltas} = this.constructNodes( samples, - stackFrames + stackFrames, ); return { id, @@ -262,31 +273,25 @@ export class CpuProfilerModel { */ static constructNodes( samples: HermesSample[], - stackFrames: { [key in string]: HermesStackFrame } + stackFrames: {[key in string]: HermesStackFrame}, ): CPUProfileChunker { samples = samples.map((sample: HermesSample) => { sample.stackFrameData = stackFrames[sample.sf]; return sample; }); const stackFrameIds: string[] = Object.keys(stackFrames); - // TODO: What should URLs be? Change name to get things from SourceMaps - const profileNodes: CPUProfileChunkNode[] = stackFrameIds.map( (stackFrameId: string) => { + const stackFrame = stackFrames[stackFrameId]; return { id: Number(stackFrameId), callFrame: { - line: stackFrames[stackFrameId].line, - column: stackFrames[stackFrameId].column, - funcLine: stackFrames[stackFrameId].funcLine, - funcColumn: stackFrames[stackFrameId].funcColumn, - name: stackFrames[stackFrameId].name, - category: stackFrames[stackFrameId].category, - url: 'TODO', + ...stackFrame, + url: stackFrame.name, }, parent: stackFrames[stackFrameId].parent, }; - } + }, ); const returnedSamples: number[] = []; const timeDeltas: number[] = []; @@ -308,67 +313,4 @@ export class CpuProfilerModel { timeDeltas, }; } - - /** - * Refer to the source maps for the `index.bundle` file. Throws error if debug server not running - * @param {string} metroServerURL - * @param {DurationEvent[]} chromeEvents - * @throws {Debug Server not running} - * @returns {DurationEvent[]} - */ - static async changeNamesToSourceMaps( - metroServerURL: string, - chromeEvents: DurationEvent[] - ): Promise { - /** - * To eliminate Metro Server depedency, sourceMap needs to be the input of this function NOT - * metroServerURL - */ - const response = (await axios.get(metroServerURL)) as AxiosResponse< - SourceMap - >; - if (!response.data) { - throw new Error('Incorrect Debug Port Provided'); - } - const sourceMap: SourceMap = response.data; - const rawSourceMap: RawSourceMap = { - version: Number(sourceMap.version), - file: 'index.bundle', - sources: sourceMap.sources, - mappings: sourceMap.mappings, - names: sourceMap.names, - }; - - const consumer = await new SourceMapConsumer(rawSourceMap); - const events = chromeEvents.map((event: DurationEvent) => { - const sm = consumer.originalPositionFor({ - line: Number(event.args?.data.callFrame.line), - column: Number(event.args?.data.callFrame.column), - }); - event.args = { - data: { - callFrame: { - ...event.args?.data.callFrame, - url: sm.source, - line: sm.line, - column: sm.column, - }, - }, - }; - /** - * The name in source maps (for reasons I don't understand) is sometimes null, so OR-ing this - * to ensure a name is assigned. - * In case a name wasn't found, the URL is used - * Eg: /Users/saphal/Desktop/app/node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js => MessageQueue.js - */ - event.name = - sm.name || - (event.args?.data.callFrame.url - ? event.args?.data.callFrame.url.split('/').pop() - : event.args?.data.callFrame.name); - return event; - }); - consumer.destroy(); - return events; - } -} \ No newline at end of file +} diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts index 7109e0b0d..62bf38878 100644 --- a/packages/cli/src/commands/profile/downloadProfile.ts +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -5,6 +5,9 @@ import chalk from 'chalk'; import fs from 'fs'; import path from 'path'; import os from 'os'; +/* For source map testing */ +// import axios from 'axios'; +import {transformer} from './transformer'; /** * Get the last modified hermes profile @@ -70,6 +73,7 @@ export async function downloadProfile( ctx: Config, dstPath: string, fileName?: string, + sourceMapPath?: string, ) { try { const packageName = getPackageName(ctx); @@ -98,61 +102,18 @@ export async function downloadProfile( execSync(`adb pull /sdcard/${file} ${tmpDir}`); //Run transformer tool to convert from Hermes to Chrome format + //find the bundle file name + const events = await transformer(tmpDir, sourceMapPath, 'index.bundle'); + // console.log( + // `${dstPath}/${path.basename(file, '.cpuprofile')}-converted.json`, + // ); + fs.writeFileSync( + `${dstPath}/${path.basename(file, '.cpuprofile')}-converted.json`, + JSON.stringify(events, undefined, 4), + 'utf-8', + ); - execSync(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}`); - execSync(`adb pull /sdcard/${file} ${tmpDir}`); - - //Run transformer tool to convert from Hermes to Chrome format - - execSync(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}`); - execSync(`adb pull /sdcard/${file} ${tmpDir}`); - - //Run transformer tool to convert from Hermes to Chrome format - - execSync(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}`); - execSync(`adb pull /sdcard/${file} ${tmpDir}`); - - //Run transformer tool to convert from Hermes to Chrome format - - execSync(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}`); - execSync(`adb pull /sdcard/${file} ${tmpDir}`); - - //Run transformer tool to convert from Hermes to Chrome format - - execSync(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}`); - execSync(`adb pull /sdcard/${file} ${tmpDir}`); - - //Run transformer tool to convert from Hermes to Chrome format - - execSync(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}`); - execSync(`adb pull /sdcard/${file} ${tmpDir}`); - - //Run transformer tool to convert from Hermes to Chrome format - - execSync(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}`); - execSync(`adb pull /sdcard/${file} ${tmpDir}`); - - //Run transformer tool to convert from Hermes to Chrome format - - execSync(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}`); - execSync(`adb pull /sdcard/${file} ${tmpDir}`); - - //Run transformer tool to convert from Hermes to Chrome format - - execSync(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}`); - execSync(`adb pull /sdcard/${file} ${tmpDir}`); - - //Run transformer tool to convert from Hermes to Chrome format - + //Pull the hermes profile to dstPath execSync(`adb pull /sdcard/${file} ${dstPath}`); logger.success(`Successfully pulled the file to ${dstPath}/${file}`); } catch (e) { diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile/index.ts index 27330a8e0..89c4fedfb 100644 --- a/packages/cli/src/commands/profile/index.ts +++ b/packages/cli/src/commands/profile/index.ts @@ -25,7 +25,12 @@ async function profile( if (options.verbose) { logger.setVerbose(true); } - await downloadProfile(ctx, dstPath, options.fileName); + await downloadProfile( + ctx, + dstPath, + options.fileName, + options.sourceMapPath, + ); } catch (err) { logger.error(`Unable to download the Hermes Sampling Profiler.\n${err}`); } @@ -52,8 +57,8 @@ export default { description: 'Pulling original Hermes formatted profile', }, { - name: 'source-map-path', - description: 'Providing the local path to your source map bundle', + name: '--sourceMapPath [string]', + description: 'Providing the local path to your source map output path', }, ], examples: [ diff --git a/packages/cli/src/commands/profile/sourceMapper.ts b/packages/cli/src/commands/profile/sourceMapper.ts new file mode 100644 index 000000000..37f83c299 --- /dev/null +++ b/packages/cli/src/commands/profile/sourceMapper.ts @@ -0,0 +1,59 @@ +import {SourceMapConsumer, RawSourceMap} from 'source-map'; +import {SourceMap, DurationEvent} from './EventInterfaces'; + +/** + * Refer to the source maps for the bundleFileName. Throws error if args not set up in ChromeEvents + * @param {SourceMap} sourceMap + * @param {DurationEvent[]} chromeEvents + * @throws {Source Maps not found} + * @returns {DurationEvent[]} + */ +export const changeNamesToSourceMaps = async ( + sourceMap: SourceMap, + chromeEvents: DurationEvent[], + indexBundleFileName: string | undefined, +): Promise => { + // SEE: Should file here be an optional parameter, so take indexBundleFileName as a parameter and use + // a default name of `index.bundle` + const rawSourceMap: RawSourceMap = { + version: Number(sourceMap.version), + file: indexBundleFileName || 'index.bundle', + sources: sourceMap.sources, + mappings: sourceMap.mappings, + names: sourceMap.names, + }; + + const consumer = await new SourceMapConsumer(rawSourceMap); + const events = chromeEvents.map((event: DurationEvent) => { + if (event.args) { + const sm = consumer.originalPositionFor({ + line: Number(event.args.data.callFrame.line), + column: Number(event.args.data.callFrame.column), + }); + event.args = { + data: { + callFrame: { + ...event.args.data.callFrame, + url: sm.source, + line: sm.line, + column: sm.column, + }, + }, + }; + /** + * The name in source maps (for reasons I don't understand) is sometimes null, so OR-ing this + * to ensure a name is assigned. + * In case a name wasn't found, the URL is used + * Eg: /Users/saphal/Desktop/app/node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js => MessageQueue.js + */ + event.name = + sm.name || + (event.args.data.callFrame.url + ? event.args.data.callFrame.url.split('/').pop() + : event.args.data.callFrame.name); + } + return event; + }); + consumer.destroy(); + return events; +}; diff --git a/packages/cli/src/commands/profile/transformer.ts b/packages/cli/src/commands/profile/transformer.ts new file mode 100644 index 000000000..d213cdd6a --- /dev/null +++ b/packages/cli/src/commands/profile/transformer.ts @@ -0,0 +1,37 @@ +import fs from 'fs'; + +import {CpuProfilerModel} from './cpuProfilerModel'; +import {changeNamesToSourceMaps} from './sourceMapper'; +import {DurationEvent, SourceMap} from './EventInterfaces'; + +export const transformer = async ( + profilePath: string, + sourceMapPath: string | undefined, + bundleFileName: string | undefined, +): Promise => { + // console.log('profilePath: ', profilePath); + // console.log('souceMapPath: ', sourceMapPath); + // console.log('bundleFileName: ', bundleFileName); + const hermesProfile = JSON.parse(fs.readFileSync(profilePath, 'utf-8')); + //console.log('done parsing hermes profile'); + const profileChunk = CpuProfilerModel.collectProfileEvents(hermesProfile); + //console.log('done constructing the profile chunk'); + const profiler = new CpuProfilerModel(profileChunk); + const chromeEvents = profiler.createStartEndEvents(); + //console.log(chromeEvents[0]); + //console.log('done generating chrome events'); + if (sourceMapPath) { + const sourceMap: SourceMap = JSON.parse( + fs.readFileSync(sourceMapPath, 'utf-8'), + ); + //console.log('done parsing source map file'); + const events = await changeNamesToSourceMaps( + sourceMap, + chromeEvents, + bundleFileName, + ); + //console.log(events[0]); + return events; + } + return chromeEvents; +}; diff --git a/yarn.lock b/yarn.lock index e92dd6b0d..d924a0120 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2128,6 +2128,13 @@ dependencies: type-detect "4.0.8" +"@types/axios@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46" + integrity sha1-7CMA++fX3d1+udOr+HmZlkyvzkY= + dependencies: + axios "*" + "@types/babel__core@^7.1.0": version "7.1.6" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.6.tgz#16ff42a5ae203c9af1c6e190ed1f30f83207b610" @@ -2354,7 +2361,7 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@>= 8", "@types/node@^8.0.0": +"@types/node@*", "@types/node@>= 8", "@types/node@^14.0.26", "@types/node@^8.0.0": version "8.10.59" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.59.tgz#9e34261f30183f9777017a13d185dfac6b899e04" integrity sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ== @@ -2395,6 +2402,13 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" +"@types/source-map@^0.5.7": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@types/source-map/-/source-map-0.5.7.tgz#165eeb583c1ef00196fe4ef4da5d7832b03b275b" + integrity sha512-LrnsgZIfJaysFkv9rRJp4/uAyqw87oVed3s1hhF83nwbo9c7MG9g5DqR0seHP+lkX4ldmMrVolPjQSe2ZfD0yA== + dependencies: + source-map "*" + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -2737,7 +2751,7 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= -array-includes@^3.0.3: +array-includes@^3.0.3, array-includes@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== @@ -2882,6 +2896,13 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== +axios@*, axios@^0.19.2: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + babel-eslint@10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed" @@ -4231,7 +4252,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@3.1.0: +debug@3.1.0, debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -4762,9 +4783,9 @@ eslint-module-utils@^2.4.1: pkg-dir "^2.0.0" eslint-plugin-eslint-comments@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.1.2.tgz#4ef6c488dbe06aa1627fea107b3e5d059fc8a395" - integrity sha512-QexaqrNeteFfRTad96W+Vi4Zj1KFbkHHNMMaHZEYcovKav6gdomyGzaxSDSL3GoIyUOo078wRAdYlu1caiauIQ== + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz#9e1cd7b4413526abb313933071d7aba05ca12ffa" + integrity sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ== dependencies: escape-string-regexp "^1.0.5" ignore "^5.0.5" @@ -5305,6 +5326,13 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -7161,11 +7189,11 @@ jsprim@^1.2.2: verror "1.10.0" jsx-ast-utils@^2.0.1: - version "2.2.3" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f" - integrity sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA== + version "2.4.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e" + integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w== dependencies: - array-includes "^3.0.3" + array-includes "^3.1.1" object.assign "^4.1.0" kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: @@ -10021,13 +10049,20 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.1.5, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0: +resolve@^1.1.5, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: version "1.15.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== dependencies: path-parse "^1.0.6" +resolve@^1.9.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -10482,6 +10517,11 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= +source-map@*, source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -10492,11 +10532,6 @@ source-map@^0.5.0, source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - spdx-correct@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" From cbc0c28f5e61d06d9d4c1afc873284466f4f9b63 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 28 Jul 2020 22:57:35 +0700 Subject: [PATCH 11/53] implemented --raw flag --- .../src/commands/profile/downloadProfile.ts | 48 ++++++++++++------- packages/cli/src/commands/profile/index.ts | 23 ++++++--- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts index 62bf38878..ea42f498d 100644 --- a/packages/cli/src/commands/profile/downloadProfile.ts +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -74,6 +74,7 @@ export async function downloadProfile( dstPath: string, fileName?: string, sourceMapPath?: string, + raw?: boolean, ) { try { const packageName = getPackageName(ctx); @@ -97,25 +98,36 @@ export async function downloadProfile( } //Copy the file from device's data to sdcard, then pull the file to a temp directory execSync(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); - const tmpDir = path.join(os.tmpdir(), file); - console.log('temp dir: ', tmpDir); - execSync(`adb pull /sdcard/${file} ${tmpDir}`); - - //Run transformer tool to convert from Hermes to Chrome format - //find the bundle file name - const events = await transformer(tmpDir, sourceMapPath, 'index.bundle'); - // console.log( - // `${dstPath}/${path.basename(file, '.cpuprofile')}-converted.json`, - // ); - fs.writeFileSync( - `${dstPath}/${path.basename(file, '.cpuprofile')}-converted.json`, - JSON.stringify(events, undefined, 4), - 'utf-8', - ); + //If --raw, pull the hermes profile to dstPath + if (raw) { + execSync(`adb pull /sdcard/${file} ${dstPath}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + } + //Else: transform the profile to Chrome format and pull it to dstPath + else { + const tmpDir = path.join(os.tmpdir(), file); + console.log('temp dir: ', tmpDir); + execSync(`adb pull /sdcard/${file} ${tmpDir}`); - //Pull the hermes profile to dstPath - execSync(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + //Run transformer tool to convert from Hermes to Chrome format + //TODO: find the bundle file name (default is "index.bundle"); + const events = await transformer(tmpDir, sourceMapPath, 'index.bundle'); + // console.log( + // `${dstPath}/${path.basename(file, '.cpuprofile')}-converted.json`, + // ); + const transformedFile = `${dstPath}/${path.basename( + file, + '.cpuprofile', + )}-converted.json`; + fs.writeFileSync( + transformedFile, + JSON.stringify(events, undefined, 4), + 'utf-8', + ); + logger.success( + `Successfully converted to Chrome tracing format and pulled the file to ${transformedFile}`, + ); + } } catch (e) { throw new Error(e.message); } diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile/index.ts index 89c4fedfb..b4fda1c73 100644 --- a/packages/cli/src/commands/profile/index.ts +++ b/packages/cli/src/commands/profile/index.ts @@ -25,12 +25,23 @@ async function profile( if (options.verbose) { logger.setVerbose(true); } - await downloadProfile( - ctx, - dstPath, - options.fileName, - options.sourceMapPath, - ); + if (options.raw) { + await downloadProfile( + ctx, + dstPath, + options.fileName, + options.sourceMapPath, + true, + ); + } else { + await downloadProfile( + ctx, + dstPath, + options.fileName, + options.sourceMapPath, + false, + ); + } } catch (err) { logger.error(`Unable to download the Hermes Sampling Profiler.\n${err}`); } From 28a55d405304643b486db45b950e431db1d91eb7 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Thu, 30 Jul 2020 11:13:50 +0700 Subject: [PATCH 12/53] deleted changelog comments and fixed small changes --- .../src/commands/profile/downloadProfile.ts | 15 +++---- packages/cli/src/commands/profile/index.ts | 44 +++++++++++-------- .../cli/src/commands/profile/transformer.ts | 9 ---- 3 files changed, 30 insertions(+), 38 deletions(-) diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts index ea42f498d..b62574f17 100644 --- a/packages/cli/src/commands/profile/downloadProfile.ts +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -5,8 +5,6 @@ import chalk from 'chalk'; import fs from 'fs'; import path from 'path'; import os from 'os'; -/* For source map testing */ -// import axios from 'axios'; import {transformer} from './transformer'; /** @@ -91,6 +89,7 @@ export async function downloadProfile( dstPath = ctx.root; } logger.info(`File to be pulled: ${file}`); + //if '--verbose' is enabled if (logger.isVerbose()) { logger.info('Internal commands run to pull the file: '); logger.debug(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); @@ -98,6 +97,7 @@ export async function downloadProfile( } //Copy the file from device's data to sdcard, then pull the file to a temp directory execSync(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); + //If --raw, pull the hermes profile to dstPath if (raw) { execSync(`adb pull /sdcard/${file} ${dstPath}`); @@ -106,26 +106,21 @@ export async function downloadProfile( //Else: transform the profile to Chrome format and pull it to dstPath else { const tmpDir = path.join(os.tmpdir(), file); - console.log('temp dir: ', tmpDir); execSync(`adb pull /sdcard/${file} ${tmpDir}`); //Run transformer tool to convert from Hermes to Chrome format - //TODO: find the bundle file name (default is "index.bundle"); const events = await transformer(tmpDir, sourceMapPath, 'index.bundle'); - // console.log( - // `${dstPath}/${path.basename(file, '.cpuprofile')}-converted.json`, - // ); - const transformedFile = `${dstPath}/${path.basename( + const transformedFilePath = `${dstPath}/${path.basename( file, '.cpuprofile', )}-converted.json`; fs.writeFileSync( - transformedFile, + transformedFilePath, JSON.stringify(events, undefined, 4), 'utf-8', ); logger.success( - `Successfully converted to Chrome tracing format and pulled the file to ${transformedFile}`, + `Successfully converted to Chrome tracing format and pulled the file to ${transformedFilePath}`, ); } } catch (e) { diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile/index.ts index b4fda1c73..362901a30 100644 --- a/packages/cli/src/commands/profile/index.ts +++ b/packages/cli/src/commands/profile/index.ts @@ -25,23 +25,30 @@ async function profile( if (options.verbose) { logger.setVerbose(true); } - if (options.raw) { - await downloadProfile( - ctx, - dstPath, - options.fileName, - options.sourceMapPath, - true, - ); - } else { - await downloadProfile( - ctx, - dstPath, - options.fileName, - options.sourceMapPath, - false, - ); - } + await downloadProfile( + ctx, + dstPath, + options.fileName, + options.sourceMapPath, + options.raw, + ); + // if (options.raw) { + // await downloadProfile( + // ctx, + // dstPath, + // options.fileName, + // options.sourceMapPath, + // true, + // ); + // } else { + // await downloadProfile( + // ctx, + // dstPath, + // options.fileName, + // options.sourceMapPath, + // false, + // ); + // } } catch (err) { logger.error(`Unable to download the Hermes Sampling Profiler.\n${err}`); } @@ -53,7 +60,6 @@ export default { 'Download the Hermes Sampling Profiler to the directory of the local machine', func: profile, options: [ - //options: specify fileName as a string { name: '--fileName [string]', description: @@ -69,7 +75,7 @@ export default { }, { name: '--sourceMapPath [string]', - description: 'Providing the local path to your source map output path', + description: 'Provide the local path to your source map file', }, ], examples: [ diff --git a/packages/cli/src/commands/profile/transformer.ts b/packages/cli/src/commands/profile/transformer.ts index d213cdd6a..22e45ae5f 100644 --- a/packages/cli/src/commands/profile/transformer.ts +++ b/packages/cli/src/commands/profile/transformer.ts @@ -9,28 +9,19 @@ export const transformer = async ( sourceMapPath: string | undefined, bundleFileName: string | undefined, ): Promise => { - // console.log('profilePath: ', profilePath); - // console.log('souceMapPath: ', sourceMapPath); - // console.log('bundleFileName: ', bundleFileName); const hermesProfile = JSON.parse(fs.readFileSync(profilePath, 'utf-8')); - //console.log('done parsing hermes profile'); const profileChunk = CpuProfilerModel.collectProfileEvents(hermesProfile); - //console.log('done constructing the profile chunk'); const profiler = new CpuProfilerModel(profileChunk); const chromeEvents = profiler.createStartEndEvents(); - //console.log(chromeEvents[0]); - //console.log('done generating chrome events'); if (sourceMapPath) { const sourceMap: SourceMap = JSON.parse( fs.readFileSync(sourceMapPath, 'utf-8'), ); - //console.log('done parsing source map file'); const events = await changeNamesToSourceMaps( sourceMap, chromeEvents, bundleFileName, ); - //console.log(events[0]); return events; } return chromeEvents; From 680eccbe62deecbe32b6aaa8fac28238eff3c1bb Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Thu, 30 Jul 2020 11:53:32 +0700 Subject: [PATCH 13/53] changed wording --- packages/cli/src/commands/profile/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile/index.ts index 362901a30..eeb61ee0d 100644 --- a/packages/cli/src/commands/profile/index.ts +++ b/packages/cli/src/commands/profile/index.ts @@ -75,7 +75,7 @@ export default { }, { name: '--sourceMapPath [string]', - description: 'Provide the local path to your source map file', + description: 'The local path to your source map file', }, ], examples: [ From ef6bb10a855d6f5bafcb65beef3878e9e63de17d Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Thu, 30 Jul 2020 18:03:15 +0700 Subject: [PATCH 14/53] deleted unused event types and remove unused dev dependencies --- packages/cli/package.json | 3 - .../src/commands/profile/EventInterfaces.ts | 132 +----------------- packages/cli/src/commands/profile/index.ts | 17 --- yarn.lock | 25 +--- 4 files changed, 3 insertions(+), 174 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ba0b18f50..3a673ac75 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,10 +33,7 @@ "@react-native-community/cli-server-api": "^4.10.1", "@react-native-community/cli-tools": "^4.10.1", "@react-native-community/cli-types": "^4.10.1", - "@types/axios": "^0.14.0", - "@types/node": "^14.0.26", "@types/source-map": "^0.5.7", - "axios": "^0.19.2", "chalk": "^3.0.0", "command-exists": "^1.2.8", "commander": "^2.19.0", diff --git a/packages/cli/src/commands/profile/EventInterfaces.ts b/packages/cli/src/commands/profile/EventInterfaces.ts index a25aa1b6e..613958441 100644 --- a/packages/cli/src/commands/profile/EventInterfaces.ts +++ b/packages/cli/src/commands/profile/EventInterfaces.ts @@ -53,137 +53,7 @@ interface DurationEventEnd extends SharedEventProperties { export type DurationEvent = DurationEventBegin | DurationEventEnd; -export interface CompleteEvent extends SharedEventProperties { - ph: EventsPhase.COMPLETE_EVENTS; - dur: number; -} - -export interface MetadataEvent extends SharedEventProperties { - ph: EventsPhase.METADATA_EVENTS; -} - -export interface SampleEvent extends SharedEventProperties { - ph: EventsPhase.SAMPLE_EVENTS; -} - -interface ObjectEventCreated extends SharedEventProperties { - ph: EventsPhase.OBJECT_EVENTS_CREATED; - scope?: string; -} - -interface ObjectEventSnapshot extends SharedEventProperties { - ph: EventsPhase.OBJECT_EVENTS_SNAPSHOT; - scope?: string; -} - -interface ObjectEventDestroyed extends SharedEventProperties { - ph: EventsPhase.OBJECT_EVENTS_DESTROYED; - scope?: string; -} - -export type ObjectEvent = - | ObjectEventCreated - | ObjectEventSnapshot - | ObjectEventDestroyed; - -export interface ClockSyncEvent extends SharedEventProperties { - ph: EventsPhase.CLOCK_SYNC_EVENTS; - args: { - sync_id: string; - issue_ts?: number; - }; -} - -interface ContextEventEnter extends SharedEventProperties { - ph: EventsPhase.CONTEXT_EVENTS_ENTER; -} - -interface ContextEventLeave extends SharedEventProperties { - ph: EventsPhase.CONTEXT_EVENTS_LEAVE; -} - -export type ContextEvent = ContextEventEnter | ContextEventLeave; - -interface AsyncEventStart extends SharedEventProperties { - ph: EventsPhase.ASYNC_EVENTS_NESTABLE_START; - id: number; - scope?: string; -} - -interface AsyncEventInstant extends SharedEventProperties { - ph: EventsPhase.ASYNC_EVENTS_NESTABLE_INSTANT; - id: number; - scope?: string; -} - -interface AsyncEventEnd extends SharedEventProperties { - ph: EventsPhase.ASYNC_EVENTS_NESTABLE_END; - id: number; - scope?: string; -} - -export type AsyncEvent = AsyncEventStart | AsyncEventInstant | AsyncEventEnd; - -export interface InstantEvent extends SharedEventProperties { - ph: EventsPhase.INSTANT_EVENTS; - s: string; -} - -export interface CounterEvent extends SharedEventProperties { - ph: EventsPhase.COUNTER_EVENTS; -} - -interface FlowEventStart extends SharedEventProperties { - ph: EventsPhase.FLOW_EVENTS_START; -} - -interface FlowEventStep extends SharedEventProperties { - ph: EventsPhase.FLOW_EVENTS_STEP; -} -interface FlowEventEnd extends SharedEventProperties { - ph: EventsPhase.FLOW_EVENTS_END; -} - -export type FlowEvent = FlowEventStart | FlowEventStep | FlowEventEnd; - -interface MemoryDumpGlobal extends SharedEventProperties { - ph: EventsPhase.MEMORY_DUMP_EVENTS_GLOBAL; - id: string; -} - -interface MemoryDumpProcess extends SharedEventProperties { - ph: EventsPhase.MEMORY_DUMP_EVENTS_PROCESS; - id: string; -} -export type MemoryDumpEvent = MemoryDumpGlobal | MemoryDumpProcess; - -export interface MarkEvent extends SharedEventProperties { - ph: EventsPhase.MARK_EVENTS; -} - -export interface LinkedIDEvent extends SharedEventProperties { - ph: EventsPhase.LINKED_ID_EVENTS; - id: number; - args: { - linked_id: number; - }; -} - -export type Event = - | DurationEvent - | CompleteEvent - | MetadataEvent - | SampleEvent - | ObjectEvent - | ClockSyncEvent - | ContextEvent - | AsyncEvent - | InstantEvent - | CounterEvent - | FlowEvent - | MemoryDumpEvent - | MarkEvent - | LinkedIDEvent; +export type Event = DurationEvent; /** * Each item in the stackFrames object of the hermes profile diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile/index.ts index eeb61ee0d..f7d3d30a0 100644 --- a/packages/cli/src/commands/profile/index.ts +++ b/packages/cli/src/commands/profile/index.ts @@ -32,23 +32,6 @@ async function profile( options.sourceMapPath, options.raw, ); - // if (options.raw) { - // await downloadProfile( - // ctx, - // dstPath, - // options.fileName, - // options.sourceMapPath, - // true, - // ); - // } else { - // await downloadProfile( - // ctx, - // dstPath, - // options.fileName, - // options.sourceMapPath, - // false, - // ); - // } } catch (err) { logger.error(`Unable to download the Hermes Sampling Profiler.\n${err}`); } diff --git a/yarn.lock b/yarn.lock index d924a0120..173da72fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2128,13 +2128,6 @@ dependencies: type-detect "4.0.8" -"@types/axios@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46" - integrity sha1-7CMA++fX3d1+udOr+HmZlkyvzkY= - dependencies: - axios "*" - "@types/babel__core@^7.1.0": version "7.1.6" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.6.tgz#16ff42a5ae203c9af1c6e190ed1f30f83207b610" @@ -2361,7 +2354,7 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@>= 8", "@types/node@^14.0.26", "@types/node@^8.0.0": +"@types/node@*", "@types/node@>= 8", "@types/node@^8.0.0": version "8.10.59" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.59.tgz#9e34261f30183f9777017a13d185dfac6b899e04" integrity sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ== @@ -2896,13 +2889,6 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== -axios@*, axios@^0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== - dependencies: - follow-redirects "1.5.10" - babel-eslint@10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed" @@ -4252,7 +4238,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@3.1.0, debug@=3.1.0: +debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -5326,13 +5312,6 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" - for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" From 2285b38f5717985d67d2c6c3ff37c1d88473c70d Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Thu, 6 Aug 2020 22:36:20 +0700 Subject: [PATCH 15/53] generated source map through --- .vscode/settings.json | 6 ++---- .../cli/src/commands/bundle/buildBundle.ts | 5 ++++- packages/cli/src/commands/bundle/bundle.ts | 3 +++ .../src/commands/profile/downloadProfile.ts | 20 ++++++++++++++++--- packages/cli/src/commands/profile/index.ts | 2 +- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 230a6715c..d2b294630 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,8 @@ { - "editor.rulers": [ - 80 - ], + "editor.rulers": [80], "files.exclude": { "**/.git": true, - "**/node_modules": true, + "**/node_modules": false, "**/build": true }, "editor.codeActionsOnSave": { diff --git a/packages/cli/src/commands/bundle/buildBundle.ts b/packages/cli/src/commands/bundle/buildBundle.ts index 72abc912e..c087c2dac 100644 --- a/packages/cli/src/commands/bundle/buildBundle.ts +++ b/packages/cli/src/commands/bundle/buildBundle.ts @@ -44,11 +44,13 @@ async function buildBundle( ctx: Config, output: typeof outputBundle = outputBundle, ) { + //console.log('not load metro config yet'); const config = await loadMetroConfig(ctx, { maxWorkers: args.maxWorkers, resetCache: args.resetCache, config: args.config, }); + //console.log('loaded metro config'); if (config.resolver.platforms.indexOf(args.platform) === -1) { logger.error( @@ -84,8 +86,9 @@ async function buildBundle( minify: args.minify !== undefined ? args.minify : !args.dev, platform: args.platform, }; - + //console.log('not build server yet'); const server = new Server(config); + //console.log('built new server'); try { const bundle = await output.build(server, requestOpts); diff --git a/packages/cli/src/commands/bundle/bundle.ts b/packages/cli/src/commands/bundle/bundle.ts index 95caa81bc..de46dce9a 100644 --- a/packages/cli/src/commands/bundle/bundle.ts +++ b/packages/cli/src/commands/bundle/bundle.ts @@ -18,6 +18,9 @@ function bundleWithOutput( args: CommandLineArgs, output: any, // untyped metro/src/shared/output/bundle or metro/src/shared/output/RamBundle ) { + // console.log('config: ', config); + // console.log('args: ', args); + // console.log('output: ', output); return buildBundle(args, config, output); } diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts index b62574f17..274275185 100644 --- a/packages/cli/src/commands/profile/downloadProfile.ts +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -105,11 +105,25 @@ export async function downloadProfile( } //Else: transform the profile to Chrome format and pull it to dstPath else { - const tmpDir = path.join(os.tmpdir(), file); - execSync(`adb pull /sdcard/${file} ${tmpDir}`); + const fileTmpDir = path.join(os.tmpdir(), file); + execSync(`adb pull /sdcard/${file} ${fileTmpDir}`); + + //Generating the bundle and source map files + if (!sourceMapPath) { + execSync( + `react-native bundle --entry-file index.js --bundle-output ${os.tmpdir()}/index.bundle --sourcemap-output ${os.tmpdir()}/index.map`, + ); + + //buildBundle(args, ctx, output); + } + sourceMapPath = sourceMapPath || `${os.tmpdir()}/index.map`; //Run transformer tool to convert from Hermes to Chrome format - const events = await transformer(tmpDir, sourceMapPath, 'index.bundle'); + const events = await transformer( + fileTmpDir, + sourceMapPath, + 'index.bundle', + ); const transformedFilePath = `${dstPath}/${path.basename( file, '.cpuprofile', diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile/index.ts index f7d3d30a0..7d9a432d8 100644 --- a/packages/cli/src/commands/profile/index.ts +++ b/packages/cli/src/commands/profile/index.ts @@ -57,7 +57,7 @@ export default { description: 'Pulling original Hermes formatted profile', }, { - name: '--sourceMapPath [string]', + name: '--sourceMapPath [string]', //sourcemap-path description: 'The local path to your source map file', }, ], From 652290dc71cfdf0eecfcd0cf415b3db79cb364b3 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Mon, 10 Aug 2020 10:38:26 +0700 Subject: [PATCH 16/53] fixed SHA-1 error --- packages/cli/src/commands/profile/downloadProfile.ts | 11 +++++++---- packages/cli/src/commands/profile/transformer.ts | 5 +++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts index 274275185..8a97c8ba2 100644 --- a/packages/cli/src/commands/profile/downloadProfile.ts +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -75,6 +75,7 @@ export async function downloadProfile( raw?: boolean, ) { try { + console.log('start the profile command'); const packageName = getPackageName(ctx); //if not specify fileName, pull the latest file const file = fileName || (await getLatestFile(packageName)); @@ -105,18 +106,20 @@ export async function downloadProfile( } //Else: transform the profile to Chrome format and pull it to dstPath else { - const fileTmpDir = path.join(os.tmpdir(), file); + const osTmpDir = os.tmpdir(); + const fileTmpDir = path.join(osTmpDir, file); execSync(`adb pull /sdcard/${file} ${fileTmpDir}`); //Generating the bundle and source map files if (!sourceMapPath) { + console.log('begin to build bundle'); execSync( - `react-native bundle --entry-file index.js --bundle-output ${os.tmpdir()}/index.bundle --sourcemap-output ${os.tmpdir()}/index.map`, + `react-native bundle --entry-file index.js --bundle-output ${osTmpDir}/index.bundle --sourcemap-output ${osTmpDir}/index.map`, ); - + sourceMapPath = `${osTmpDir}/index.map`; + //console.log('sourceMapPath: ', sourceMapPath); //buildBundle(args, ctx, output); } - sourceMapPath = sourceMapPath || `${os.tmpdir()}/index.map`; //Run transformer tool to convert from Hermes to Chrome format const events = await transformer( diff --git a/packages/cli/src/commands/profile/transformer.ts b/packages/cli/src/commands/profile/transformer.ts index 22e45ae5f..700ab9b96 100644 --- a/packages/cli/src/commands/profile/transformer.ts +++ b/packages/cli/src/commands/profile/transformer.ts @@ -9,7 +9,12 @@ export const transformer = async ( sourceMapPath: string | undefined, bundleFileName: string | undefined, ): Promise => { + console.log('sourceMapPath: ', sourceMapPath); + console.log('profile: ', profilePath); + //TODO: parse JSON file: check whether the file is empty const hermesProfile = JSON.parse(fs.readFileSync(profilePath, 'utf-8')); + + console.log('hermes profile: ', hermesProfile.samples[0]); const profileChunk = CpuProfilerModel.collectProfileEvents(hermesProfile); const profiler = new CpuProfilerModel(profileChunk); const chromeEvents = profiler.createStartEndEvents(); From f5604818ee0ff58ab29f7831901f786ef787e3db Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Mon, 10 Aug 2020 23:13:44 +0700 Subject: [PATCH 17/53] implemented the source map steps --- packages/cli/package.json | 3 + .../src/commands/profile/downloadProfile.ts | 107 ++++++++++++++++-- packages/cli/src/commands/profile/index.ts | 17 ++- .../cli/src/commands/profile/sourceMapper.ts | 1 + .../cli/src/commands/profile/transformer.ts | 7 +- yarn.lock | 25 +++- 6 files changed, 139 insertions(+), 21 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 3a673ac75..9b8a7e15d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,7 +33,9 @@ "@react-native-community/cli-server-api": "^4.10.1", "@react-native-community/cli-tools": "^4.10.1", "@react-native-community/cli-types": "^4.10.1", + "@types/ip": "^1.1.0", "@types/source-map": "^0.5.7", + "axios": "^0.19.2", "chalk": "^3.0.0", "command-exists": "^1.2.8", "commander": "^2.19.0", @@ -46,6 +48,7 @@ "glob": "^7.1.3", "graceful-fs": "^4.1.3", "inquirer": "^3.0.6", + "ip": "^1.1.5", "leven": "^3.1.0", "lodash": "^4.17.15", "metro": "^0.58.0", diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts index 8a97c8ba2..6b9aaf4d6 100644 --- a/packages/cli/src/commands/profile/downloadProfile.ts +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -6,6 +6,9 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; import {transformer} from './transformer'; +import axios, {AxiosResponse} from 'axios'; +import ip from 'ip'; +import {SourceMap} from './EventInterfaces'; /** * Get the last modified hermes profile @@ -64,6 +67,83 @@ function validatePackageName(packageName: string) { return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(packageName); } +async function getSourcemapFromServer(): Promise> { + const DEBUG_SERVER_PORT = '8081'; + const IP_ADDRESS = ip.address(); + const PLATFORM = 'android'; + const requestURL = `http://${IP_ADDRESS}:${DEBUG_SERVER_PORT}/index.map?platform=${PLATFORM}&dev=true`; + return (await axios.get(requestURL)) as AxiosResponse; +} + +async function getSourcemapPath( + ctx: Config, + generateSourceMap?: boolean, +): Promise { + const osTmpDir = os.tmpdir(); + //If '--generate-sourcemap, generate the bundle and source map files and store them in os.tmpDir() + if (generateSourceMap) { + console.log('generate new source map'); + //Store the file in os.tmpDir() + const sourceMapPath = path.join(osTmpDir, 'index.map'); + + const sourceMapResult = await getSourcemapFromServer(); + if (sourceMapResult) { + console.log('Request from server: done finding the source map'); + fs.writeFileSync( + `${sourceMapPath}`, + JSON.stringify(sourceMapResult.data), + 'utf-8', + ); + console.log('Request from server: done writing the source map'); + } else { + console.log('begin to build bundle'); + execSync( + `react-native bundle --entry-file index.js --bundle-output ${path.join( + osTmpDir, + 'index.bundle', + )} --sourcemap-output ${sourceMapPath}`, + ); + } + logger.info( + `Successfully generated the source map and store it in ${sourceMapPath}`, + ); + return `${sourceMapPath}`; + } + + //Find the sourcemap if it exists + else { + console.log('find the existing source map'); + //Find from local machine + //QUESTION: why is my source map path different from Jani's + //(android/app/build/generated/sourcemaps/react/debug/index.android.bundle.map) + const sourceMapDir = path.join( + ctx.root, + 'android', + 'app', + 'build', + 'intermediates', //'generated', + 'sourcemaps', + 'react', + 'debug', + 'index.android.bundle.packager.map', + ); + console.log(sourceMapDir); + if (fs.existsSync(sourceMapDir)) { + console.log('get the sourcemap if it exists from local machine'); + return sourceMapDir; + } else { + //Request from server + const sourceMapResult = await getSourcemapFromServer(); + fs.writeFileSync( + `${path.join(osTmpDir, 'index.map')}`, + JSON.stringify(sourceMapResult.data), + 'utf-8', + ); + return `${path.join(osTmpDir, 'index.map')}`; + } + } +} + /** * Executes the commands to pull a hermes profile */ @@ -73,9 +153,10 @@ export async function downloadProfile( fileName?: string, sourceMapPath?: string, raw?: boolean, + generateSourceMap?: boolean, ) { try { - console.log('start the profile command'); + //console.log('start the profile command'); const packageName = getPackageName(ctx); //if not specify fileName, pull the latest file const file = fileName || (await getLatestFile(packageName)); @@ -85,11 +166,12 @@ export async function downloadProfile( ); process.exit(1); } + logger.info(`File to be pulled: ${file}`); + //if not specify destination path, pull to the current directory if (!dstPath) { dstPath = ctx.root; } - logger.info(`File to be pulled: ${file}`); //if '--verbose' is enabled if (logger.isVerbose()) { logger.info('Internal commands run to pull the file: '); @@ -104,21 +186,26 @@ export async function downloadProfile( execSync(`adb pull /sdcard/${file} ${dstPath}`); logger.success(`Successfully pulled the file to ${dstPath}/${file}`); } + //Else: transform the profile to Chrome format and pull it to dstPath else { const osTmpDir = os.tmpdir(); const fileTmpDir = path.join(osTmpDir, file); execSync(`adb pull /sdcard/${file} ${fileTmpDir}`); - //Generating the bundle and source map files + //If path to source map is not given if (!sourceMapPath) { - console.log('begin to build bundle'); - execSync( - `react-native bundle --entry-file index.js --bundle-output ${osTmpDir}/index.bundle --sourcemap-output ${osTmpDir}/index.map`, - ); - sourceMapPath = `${osTmpDir}/index.map`; - //console.log('sourceMapPath: ', sourceMapPath); - //buildBundle(args, ctx, output); + //Get or generate the source map + sourceMapPath = await getSourcemapPath(ctx, generateSourceMap); + //Run without source map + if (sourceMapPath && sourceMapPath.length === 0) { + logger.warn( + 'Cannot generate or find bundle and source map, running the transformer without source map', + ); + logger.info( + 'Instructions on how to get source map:\n Go to directory android/app/build.gradle \n Set bundleInDebug: true', + ); + } } //Run transformer tool to convert from Hermes to Chrome format diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile/index.ts index 7d9a432d8..9cda87905 100644 --- a/packages/cli/src/commands/profile/index.ts +++ b/packages/cli/src/commands/profile/index.ts @@ -7,7 +7,8 @@ type Options = { verbose: boolean; fileName?: string; raw?: boolean; - sourceMapPath?: string; + sourcemapPath?: string; + generateSourcemap?: boolean; }; async function profile( @@ -29,8 +30,9 @@ async function profile( ctx, dstPath, options.fileName, - options.sourceMapPath, + options.sourcemapPath, options.raw, + options.generateSourcemap, ); } catch (err) { logger.error(`Unable to download the Hermes Sampling Profiler.\n${err}`); @@ -57,15 +59,20 @@ export default { description: 'Pulling original Hermes formatted profile', }, { - name: '--sourceMapPath [string]', //sourcemap-path - description: 'The local path to your source map file', + name: '--sourcemap-path [string]', + description: + 'The local path to your source map file, eg. Users/.../Desktop/sourcemap.json', + }, + { + name: '--generate-sourcemap', + description: 'Generate the JS bundle and source map', }, ], examples: [ { desc: 'Download the Hermes Sampling Profiler to the directory of the local machine', - cmd: 'profile-hermes /Users/phuonganh/Desktop', + cmd: 'profile-hermes /Users/.../Desktop', }, ], }; diff --git a/packages/cli/src/commands/profile/sourceMapper.ts b/packages/cli/src/commands/profile/sourceMapper.ts index 37f83c299..cb5674e54 100644 --- a/packages/cli/src/commands/profile/sourceMapper.ts +++ b/packages/cli/src/commands/profile/sourceMapper.ts @@ -39,6 +39,7 @@ export const changeNamesToSourceMaps = async ( column: sm.column, }, }, + sm, }; /** * The name in source maps (for reasons I don't understand) is sometimes null, so OR-ing this diff --git a/packages/cli/src/commands/profile/transformer.ts b/packages/cli/src/commands/profile/transformer.ts index 700ab9b96..ea87b7e1a 100644 --- a/packages/cli/src/commands/profile/transformer.ts +++ b/packages/cli/src/commands/profile/transformer.ts @@ -9,12 +9,11 @@ export const transformer = async ( sourceMapPath: string | undefined, bundleFileName: string | undefined, ): Promise => { - console.log('sourceMapPath: ', sourceMapPath); - console.log('profile: ', profilePath); + //console.log('sourceMapPath: ', sourceMapPath); + //console.log('profile: ', profilePath); //TODO: parse JSON file: check whether the file is empty const hermesProfile = JSON.parse(fs.readFileSync(profilePath, 'utf-8')); - - console.log('hermes profile: ', hermesProfile.samples[0]); + //console.log('hermes profile: ', hermesProfile.samples[0]); const profileChunk = CpuProfilerModel.collectProfileEvents(hermesProfile); const profiler = new CpuProfilerModel(profileChunk); const chromeEvents = profiler.createStartEndEvents(); diff --git a/yarn.lock b/yarn.lock index 173da72fd..de3aef4be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2276,6 +2276,13 @@ dependencies: "@types/hapi__joi" "*" +"@types/ip@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/ip/-/ip-1.1.0.tgz#aec4f5bfd49e4a4c53b590d88c36eb078827a7c0" + integrity sha512-dwNe8gOoF70VdL6WJBwVHtQmAX4RMd62M+mAB9HQFjG1/qiCLM/meRy95Pd14FYBbEDwCq7jgJs89cHpLBu4HQ== + dependencies: + "@types/node" "*" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -2889,6 +2896,13 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== +axios@^0.19.2: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + babel-eslint@10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed" @@ -4238,7 +4252,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@3.1.0: +debug@3.1.0, debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -5312,6 +5326,13 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -6080,7 +6101,7 @@ ip-regex@^2.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= -ip@1.1.5: +ip@1.1.5, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= From e3fe0629f376f2bba5591cb0f52d0d2344ef7db4 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 11 Aug 2020 16:31:37 +0700 Subject: [PATCH 18/53] added transformer package and profile-hermes.md --- docs/profile-hermes.md | 61 ++++ packages/cli/package.json | 1 + .../src/commands/profile/EventInterfaces.ts | 136 -------- packages/cli/src/commands/profile/Phases.ts | 30 -- .../src/commands/profile/cpuProfilerModel.ts | 316 ------------------ .../src/commands/profile/downloadProfile.ts | 60 +++- packages/cli/src/commands/profile/index.ts | 10 +- .../cli/src/commands/profile/sourceMapper.ts | 60 ---- .../cli/src/commands/profile/transformer.ts | 32 -- yarn.lock | 7 + 10 files changed, 118 insertions(+), 595 deletions(-) create mode 100644 docs/profile-hermes.md delete mode 100644 packages/cli/src/commands/profile/EventInterfaces.ts delete mode 100644 packages/cli/src/commands/profile/Phases.ts delete mode 100644 packages/cli/src/commands/profile/cpuProfilerModel.ts delete mode 100644 packages/cli/src/commands/profile/sourceMapper.ts delete mode 100644 packages/cli/src/commands/profile/transformer.ts diff --git a/docs/profile-hermes.md b/docs/profile-hermes.md new file mode 100644 index 000000000..78c4a98b2 --- /dev/null +++ b/docs/profile-hermes.md @@ -0,0 +1,61 @@ +# Visualize JavaScript's performance in a React Native app using Hermes + +## How to profile your app using Hermes + +- Instructions on how to enable Hermes: [Using Hermes](https://reactnative.dev/docs/hermes) +- How to profile the app: + - Open Developer Menu with `Cmd+M` or Shake the device. Select `Enable Sampling Profiler` + - Execute JavaScript by using the app's functions (pressing buttons, etc.) + - Open Developer Menu again, select `Disable Sampling Profiler`. A toast shows the location where the sampling profiler is saved - usually in `/data/user/0/com.appName/cache/*.cpuprofile`. + +## How to pull the sampling profiler to your local machine + +### React Native CLI + +Usage: + +### `react-native profile-hermes [destinationDir]` + +Pull and convert a Hermes tracing profile to Chrome tracing profile, then store them in the directory of the local machine. `destinationDir` is optional, if provided, pull the file to that directory (if not present, pull to the current React Native app root directory) + +Options: + +#### `--fileName [string]` + +File name of the profile to be downloaded, eg. sampling-profiler-trace8593107139682635366.cpuprofile. If not provided, pull the latest file + +#### `--verbose` + +Listing adb commands that are run internally to pull the file from Android device + +#### `--raw` + +Pull the original Hermes tracing profile without any transformation + +#### `--sourcemap-path [string]` + +The local path to where source map file is stored, eg. Users/.../Desktop/sourcemap.json + +#### `--generate-sourcemap` + +Generate the JS bundle and source map + +## Common errors encountered during the process + +#### adb: no devices/emulators found + +Solution: 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 + +User may have forgotten to record a profile from the device (instruction on how to enable/ disable profiler is above) + +## Testing plan + +Using `yarn link` as instructed by the CLI's [Testing Plan](https://github.com/MLH-Fellowship/cli/blob/master/CONTRIBUTING.md#testing-your-changes) does not work for us. + +### Reason: + +Get the error `ReferenceError: SHA-1 for file ... is not computed` even if we run `react-native start --watchFolders /path/to/cloned/cli/`. That's because we use the command `react-native bundle` within our code, which creates a new Metro Server that can't find the symlinked folder + +### Solution: diff --git a/packages/cli/package.json b/packages/cli/package.json index 9b8a7e15d..279257d83 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -47,6 +47,7 @@ "fs-extra": "^8.1.0", "glob": "^7.1.3", "graceful-fs": "^4.1.3", + "hermes-profile-transformer": "^0.0.2", "inquirer": "^3.0.6", "ip": "^1.1.5", "leven": "^3.1.0", diff --git a/packages/cli/src/commands/profile/EventInterfaces.ts b/packages/cli/src/commands/profile/EventInterfaces.ts deleted file mode 100644 index 613958441..000000000 --- a/packages/cli/src/commands/profile/EventInterfaces.ts +++ /dev/null @@ -1,136 +0,0 @@ -import {EventsPhase} from './Phases'; - -export interface SharedEventProperties { - /** - * name of the event - */ - name?: string; - /** - * event category - */ - cat?: string; - /** - * tracing clock timestamp - */ - ts?: number; - /** - * process ID - */ - pid?: number; - /** - * thread ID - */ - tid?: number; - /** - * event type (phase) - */ - ph: EventsPhase; - /** - * id for a stackFrame object - */ - sf?: number; - /** - * thread clock timestamp - */ - tts?: number; - /** - * a fixed color name - */ - cname?: string; - /** - * event arguments - */ - args?: {[key in string]: any}; -} - -interface DurationEventBegin extends SharedEventProperties { - ph: EventsPhase.DURATION_EVENTS_BEGIN; -} - -interface DurationEventEnd extends SharedEventProperties { - ph: EventsPhase.DURATION_EVENTS_END; -} - -export type DurationEvent = DurationEventBegin | DurationEventEnd; - -export type Event = DurationEvent; - -/** - * Each item in the stackFrames object of the hermes profile - */ -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; -} -/** - * Each item in the samples array of the hermes profile - */ -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; -} - -/** - * Hermes Profile Interface - */ -export interface HermesCPUProfile { - traceEvents: SharedEventProperties[]; - samples: HermesSample[]; - stackFrames: {[key in string]: HermesStackFrame}; -} - -export interface CPUProfileChunk { - id: string; - pid: number; - tid: string; - startTime: number; - nodes: CPUProfileChunkNode[]; - samples: number[]; - timeDeltas: number[]; -} - -export interface CPUProfileChunkNode { - id: number; - callFrame: { - line: string; - column: string; - funcLine: string; - funcColumn: string; - name: string; - url?: string; - category: string; - }; - parent?: number; -} - -export type CPUProfileChunker = { - nodes: CPUProfileChunkNode[]; - sampleNumbers: number[]; - timeDeltas: number[]; -}; - -export interface SourceMap { - version: string; - sources: string[]; - sourceContent: string[]; - x_facebook_sources: {names: string[]; mappings: string}[] | null; - names: string[]; - mappings: string; -} diff --git a/packages/cli/src/commands/profile/Phases.ts b/packages/cli/src/commands/profile/Phases.ts deleted file mode 100644 index 63e900fd5..000000000 --- a/packages/cli/src/commands/profile/Phases.ts +++ /dev/null @@ -1,30 +0,0 @@ -export enum EventsPhase { - DURATION_EVENTS_BEGIN = 'B', - DURATION_EVENTS_END = 'E', - COMPLETE_EVENTS = 'X', - INSTANT_EVENTS = 'I', - COUNTER_EVENTS = 'C', - ASYNC_EVENTS_NESTABLE_START = 'b', - ASYNC_EVENTS_NESTABLE_INSTANT = 'n', - ASYNC_EVENTS_NESTABLE_END = 'e', - FLOW_EVENTS_START = 's', - FLOW_EVENTS_STEP = 't', - FLOW_EVENTS_END = 'f', - SAMPLE_EVENTS = 'P', - OBJECT_EVENTS_CREATED = 'N', - OBJECT_EVENTS_SNAPSHOT = 'O', - OBJECT_EVENTS_DESTROYED = 'D', - METADATA_EVENTS = 'M', - MEMORY_DUMP_EVENTS_GLOBAL = 'V', - MEMORY_DUMP_EVENTS_PROCESS = 'v', - MARK_EVENTS = 'R', - CLOCK_SYNC_EVENTS = 'c', - CONTEXT_EVENTS_ENTER = '(', - CONTEXT_EVENTS_LEAVE = ')', - // Deprecated - ASYNC_EVENTS_START = 'S', - ASYNC_EVENTS_STEP_INTO = 'T', - ASYNC_EVENTS_STEP_PAST = 'p', - ASYNC_EVENTS_END = 'F', - LINKED_ID_EVENTS = '=', -} diff --git a/packages/cli/src/commands/profile/cpuProfilerModel.ts b/packages/cli/src/commands/profile/cpuProfilerModel.ts deleted file mode 100644 index c16b71012..000000000 --- a/packages/cli/src/commands/profile/cpuProfilerModel.ts +++ /dev/null @@ -1,316 +0,0 @@ -/** - * @license Copyright 2020 The Lighthouse Authors. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - * This file is heavily derived from `https://github.com/GoogleChrome/lighthouse/blob/0422daa9b1b8528dd8436860b153134bd0f959f1/lighthouse-core/lib/tracehouse/cpu-profile-model.js` - * and has been modified by Saphal Patro (email: saphal1998@gmail.com) - * The following changes have been made to the original file: - * 1. Converted code to Typescript and defined necessary types - * 2. Wrote a method @see collectProfileEvents to convert the Hermes Samples to Profile Chunks supported by Lighthouse Parser - * 3. Modified @see constructNodes to work with the Hermes Samples and StackFrames - */ - -/** - * @fileoverview - * - * This model converts the `Profile` and `ProfileChunk` mega trace events from the `disabled-by-default-v8.cpu_profiler` - * category into B/E-style trace events that main-thread-tasks.js already knows how to parse into a task tree. - * - * The CPU profiler measures where time is being spent by sampling the stack (See https://www.jetbrains.com/help/profiler/Profiling_Guidelines__Choosing_the_Right_Profiling_Mode.html - * for a generic description of the differences between tracing and sampling). - * - * A `Profile` event is a record of the stack that was being executed at different sample points in time. - * It has a structure like this: - * - * nodes: [function A, function B, function C] - * samples: [node with id 2, node with id 1, ...] - * timeDeltas: [4125μs since last sample, 121μs since last sample, ...] - * - * Helpful prior art: - * @see https://cs.chromium.org/chromium/src/third_party/devtools-frontend/src/front_end/sdk/CPUProfileDataModel.js?sq=package:chromium&g=0&l=42 - * @see https://github.com/v8/v8/blob/99ca333b0efba3236954b823101315aefeac51ab/tools/profile.js - * @see https://github.com/jlfwong/speedscope/blob/9ed1eb192cb7e9dac43a5f25bd101af169dc654a/src/import/chrome.ts#L200 - */ - -import { - DurationEvent, - HermesCPUProfile, - HermesSample, - HermesStackFrame, - CPUProfileChunk, - CPUProfileChunkNode, - CPUProfileChunker, -} from './EventInterfaces'; -import {EventsPhase} from './Phases'; - -export class CpuProfilerModel { - _profile: CPUProfileChunk; - _nodesById: Map; - _activeNodeArraysById: Map; - - constructor(profile: CPUProfileChunk) { - this._profile = profile; - this._nodesById = this._createNodeMap(); - this._activeNodeArraysById = this._createActiveNodeArrays(); - } - - /** - * Initialization function to enable O(1) access to nodes by node ID. - * @return {Map { - /** @type {Map} */ - const map: Map = new Map< - number, - CPUProfileChunkNode - >(); - for (const node of this._profile.nodes) { - map.set(node.id, node); - } - - return map; - } - - /** - * Initialization function to enable O(1) access to the set of active nodes in the stack by node ID. - * @return Map - */ - _createActiveNodeArrays(): Map { - const map: Map = new Map(); - - /** - * Given a nodeId, `getActiveNodes` gets all the parent nodes in reversed call order - * @param {number} id - */ - const getActiveNodes = (id: number): number[] => { - if (map.has(id)) { - return map.get(id) || []; - } - - const node = this._nodesById.get(id); - if (!node) { - throw new Error(`No such node ${id}`); - } - if (node.parent) { - const array = getActiveNodes(node.parent).concat([id]); - map.set(id, array); - return array; - } else { - return [id]; - } - }; - - for (const node of this._profile.nodes) { - map.set(node.id, getActiveNodes(node.id)); - } - return map; - } - - /** - * Returns all the node IDs in a stack when a specific nodeId is at the top of the stack - * (i.e. a stack's node ID and the node ID of all of its parents). - */ - _getActiveNodeIds(nodeId: number): number[] { - const activeNodeIds = this._activeNodeArraysById.get(nodeId); - if (!activeNodeIds) { - throw new Error(`No such node ID ${nodeId}`); - } - return activeNodeIds; - } - - /** - * Generates the necessary B/E-style trace events for a single transition from stack A to stack B - * at the given timestamp. - * - * Example: - * - * timestamp 1234 - * previousNodeIds 1,2,3 - * currentNodeIds 1,2,4 - * - * yields [end 3 at ts 1234, begin 4 at ts 1234] - * - * @param {number} timestamp - * @param {Array} previousNodeIds - * @param {Array} currentNodeIds - * @returns {Array} - */ - _createStartEndEventsForTransition( - timestamp: number, - previousNodeIds: number[], - currentNodeIds: number[], - ): DurationEvent[] { - // Start nodes are the nodes which are present only in the currentNodeIds and not in PreviousNodeIds - const startNodes: CPUProfileChunkNode[] = currentNodeIds - .filter(id => !previousNodeIds.includes(id)) - .map(id => this._nodesById.get(id)!); - // End nodes are the nodes which are present only in the PreviousNodeIds and not in CurrentNodeIds - const endNodes: CPUProfileChunkNode[] = previousNodeIds - .filter(id => !currentNodeIds.includes(id)) - .map(id => this._nodesById.get(id)!); - - /** - * Create a Duration Event from CPUProfileChunkNodes. - * @param {CPUProfileChunkNode} node - * @return {DurationEvent} */ - const createEvent = (node: CPUProfileChunkNode): DurationEvent => ({ - ts: timestamp, - pid: this._profile.pid, - tid: Number(this._profile.tid), - ph: EventsPhase.DURATION_EVENTS_BEGIN, - name: node.callFrame.name, - cat: node.callFrame.category, - args: {data: {callFrame: node.callFrame}}, - }); - - const startEvents: DurationEvent[] = startNodes - .map(createEvent) - .map(evt => ({...evt, ph: EventsPhase.DURATION_EVENTS_BEGIN})); - const endEvents: DurationEvent[] = endNodes - .map(createEvent) - .map(evt => ({...evt, ph: EventsPhase.DURATION_EVENTS_END})); - return [...endEvents.reverse(), ...startEvents]; - } - - /** - * Creates B/E-style trace events from a CpuProfile object created by `collectProfileEvents()` - * @return {DurationEvent} - */ - createStartEndEvents(): DurationEvent[] { - const profile = this._profile; - const length = profile.samples.length; - if (profile.timeDeltas.length !== length) { - throw new Error('Invalid CPU profile length'); - } - - const events: DurationEvent[] = []; - - let timestamp = profile.startTime; - let lastActiveNodeIds: number[] = []; - for (let i = 0; i < profile.samples.length; i++) { - const nodeId = profile.samples[i]; - const timeDelta = Math.max(profile.timeDeltas[i], 1); - const node = this._nodesById.get(nodeId); - if (!node) { - throw new Error(`Missing node ${nodeId}`); - } - - timestamp += timeDelta; - const activeNodeIds = this._getActiveNodeIds(nodeId); - events.push( - ...this._createStartEndEventsForTransition( - timestamp, - lastActiveNodeIds, - activeNodeIds, - ), - ); - lastActiveNodeIds = activeNodeIds; - } - - events.push( - ...this._createStartEndEventsForTransition( - timestamp, - lastActiveNodeIds, - [], - ), - ); - - return events; - } - - /** - * Creates B/E-style trace events from a CpuProfile object created by `collectProfileEvents()` - * @param {CPUProfileChunk} profile - */ - static createStartEndEvents(profile: CPUProfileChunk) { - const model = new CpuProfilerModel(profile); - return model.createStartEndEvents(); - } - - /** - * Converts the Hermes Sample into a single CpuProfileChunk object for consumption - * by `createStartEndEvents()`. - * - * @param {HermesCPUProfile} profile - * @throws Profile must have atleast one sample - * @return {Array} - */ - static collectProfileEvents(profile: HermesCPUProfile): CPUProfileChunk { - if (profile.samples.length >= 0) { - const {samples, stackFrames} = profile; - // Assumption: The sample will have a single process - const pid: number = samples[0].pid; - // Assumption: Javascript is single threaded, so there should only be one thread throughout - const tid: string = samples[0].tid; - // TODO: What role does id play in string parsing - const id: string = '0x1'; - const startTime: number = Number(samples[0].ts); - const {nodes, sampleNumbers, timeDeltas} = this.constructNodes( - samples, - stackFrames, - ); - return { - id, - pid, - tid, - startTime, - nodes, - samples: sampleNumbers, - timeDeltas, - }; - } else { - throw new Error('The hermes profile has zero samples'); - } - } - - /** - * Constructs CPUProfileChunk Nodes and the resultant samples and time deltas to be inputted into the - * CPUProfileChunk object which will be processed to give createStartEndEvents() - * - * @param {HermesSample} samples - * @param {Map} stackFrames - * @return { Array, Array, Array } - */ - static constructNodes( - samples: HermesSample[], - stackFrames: {[key in string]: HermesStackFrame}, - ): CPUProfileChunker { - samples = samples.map((sample: HermesSample) => { - sample.stackFrameData = stackFrames[sample.sf]; - return sample; - }); - const stackFrameIds: string[] = Object.keys(stackFrames); - const profileNodes: CPUProfileChunkNode[] = stackFrameIds.map( - (stackFrameId: string) => { - const stackFrame = stackFrames[stackFrameId]; - return { - id: Number(stackFrameId), - callFrame: { - ...stackFrame, - url: stackFrame.name, - }, - parent: stackFrames[stackFrameId].parent, - }; - }, - ); - const returnedSamples: number[] = []; - const timeDeltas: number[] = []; - let lastTimeStamp = Number(samples[0].ts); - samples.forEach((sample: HermesSample, idx: number) => { - returnedSamples.push(sample.sf); - if (idx === 0) { - timeDeltas.push(0); - } else { - const timeDiff = Number(sample.ts) - lastTimeStamp; - lastTimeStamp = Number(sample.ts); - timeDeltas.push(timeDiff); - } - }); - - return { - nodes: profileNodes, - sampleNumbers: returnedSamples, - timeDeltas, - }; - } -} diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile/downloadProfile.ts index 6b9aaf4d6..66db97d34 100644 --- a/packages/cli/src/commands/profile/downloadProfile.ts +++ b/packages/cli/src/commands/profile/downloadProfile.ts @@ -5,13 +5,13 @@ import chalk from 'chalk'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import {transformer} from './transformer'; import axios, {AxiosResponse} from 'axios'; import ip from 'ip'; -import {SourceMap} from './EventInterfaces'; +import {transformer} from 'hermes-profile-transformer'; /** * Get the last modified hermes profile + * @param packageName */ function getLatestFile(packageName: string): string { try { @@ -23,8 +23,10 @@ function getLatestFile(packageName: string): string { throw new Error(e); } } + /** * Get the package name of the running React Native app + * @param config */ function getPackageName(config: Config) { const androidProject = config.project.android; @@ -59,10 +61,11 @@ function getPackageName(config: Config) { } return packageName; } -/** Validates that the package name is correct - * - */ +/** + * Validates that the package name is correct + * @param packageName + */ function validatePackageName(packageName: string) { return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(packageName); } @@ -75,6 +78,11 @@ async function getSourcemapFromServer(): Promise> { return (await axios.get(requestURL)) as AxiosResponse; } +/** + * Find or generate source map + * @param ctx + * @param generateSourceMap + */ async function getSourcemapPath( ctx: Config, generateSourceMap?: boolean, @@ -116,17 +124,29 @@ async function getSourcemapPath( //Find from local machine //QUESTION: why is my source map path different from Jani's //(android/app/build/generated/sourcemaps/react/debug/index.android.bundle.map) - const sourceMapDir = path.join( - ctx.root, - 'android', - 'app', - 'build', - 'intermediates', //'generated', - 'sourcemaps', - 'react', - 'debug', - 'index.android.bundle.packager.map', - ); + const sourceMapDir = + path.join( + ctx.root, + 'android', + 'app', + 'build', + 'intermediates', //'generated', + 'sourcemaps', + 'react', + 'debug', + 'index.android.bundle.packager.map', + ) || + path.join( + ctx.root, + 'android', + 'app', + 'build', + 'generated', + 'sourcemaps', + 'react', + 'debug', + 'index.android.bundle.map', + ); console.log(sourceMapDir); if (fs.existsSync(sourceMapDir)) { console.log('get the sourcemap if it exists from local machine'); @@ -145,7 +165,13 @@ async function getSourcemapPath( } /** - * Executes the commands to pull a hermes profile + * Pull and convert a Hermes tracing profile to Chrome tracing profile + * @param ctx + * @param dstPath + * @param fileName + * @param sourceMapPath + * @param raw + * @param generateSourceMap */ export async function downloadProfile( ctx: Config, diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile/index.ts index 9cda87905..ea3fc1f3c 100644 --- a/packages/cli/src/commands/profile/index.ts +++ b/packages/cli/src/commands/profile/index.ts @@ -42,21 +42,23 @@ async function profile( export default { name: 'profile-hermes [destinationDir]', description: - 'Download the Hermes Sampling Profiler to the directory of the local machine', + 'Pull and convert a Hermes tracing profile to Chrome tracing profile, then store them in the directory of the local machine', func: profile, options: [ { name: '--fileName [string]', description: - 'Filename of the profile to be downloaded, eg. sampling-profiler-trace8593107139682635366.cpuprofile', + 'File name of the profile to be downloaded, eg. sampling-profiler-trace8593107139682635366.cpuprofile', }, { name: '--verbose', - description: 'Listing adb commands that are run internally', + description: + 'Listing adb commands that are run internally to pull the file from Android device', }, { name: '--raw', - description: 'Pulling original Hermes formatted profile', + description: + 'Pull the original Hermes tracing profile without any transformation', }, { name: '--sourcemap-path [string]', diff --git a/packages/cli/src/commands/profile/sourceMapper.ts b/packages/cli/src/commands/profile/sourceMapper.ts deleted file mode 100644 index cb5674e54..000000000 --- a/packages/cli/src/commands/profile/sourceMapper.ts +++ /dev/null @@ -1,60 +0,0 @@ -import {SourceMapConsumer, RawSourceMap} from 'source-map'; -import {SourceMap, DurationEvent} from './EventInterfaces'; - -/** - * Refer to the source maps for the bundleFileName. Throws error if args not set up in ChromeEvents - * @param {SourceMap} sourceMap - * @param {DurationEvent[]} chromeEvents - * @throws {Source Maps not found} - * @returns {DurationEvent[]} - */ -export const changeNamesToSourceMaps = async ( - sourceMap: SourceMap, - chromeEvents: DurationEvent[], - indexBundleFileName: string | undefined, -): Promise => { - // SEE: Should file here be an optional parameter, so take indexBundleFileName as a parameter and use - // a default name of `index.bundle` - const rawSourceMap: RawSourceMap = { - version: Number(sourceMap.version), - file: indexBundleFileName || 'index.bundle', - sources: sourceMap.sources, - mappings: sourceMap.mappings, - names: sourceMap.names, - }; - - const consumer = await new SourceMapConsumer(rawSourceMap); - const events = chromeEvents.map((event: DurationEvent) => { - if (event.args) { - const sm = consumer.originalPositionFor({ - line: Number(event.args.data.callFrame.line), - column: Number(event.args.data.callFrame.column), - }); - event.args = { - data: { - callFrame: { - ...event.args.data.callFrame, - url: sm.source, - line: sm.line, - column: sm.column, - }, - }, - sm, - }; - /** - * The name in source maps (for reasons I don't understand) is sometimes null, so OR-ing this - * to ensure a name is assigned. - * In case a name wasn't found, the URL is used - * Eg: /Users/saphal/Desktop/app/node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js => MessageQueue.js - */ - event.name = - sm.name || - (event.args.data.callFrame.url - ? event.args.data.callFrame.url.split('/').pop() - : event.args.data.callFrame.name); - } - return event; - }); - consumer.destroy(); - return events; -}; diff --git a/packages/cli/src/commands/profile/transformer.ts b/packages/cli/src/commands/profile/transformer.ts deleted file mode 100644 index ea87b7e1a..000000000 --- a/packages/cli/src/commands/profile/transformer.ts +++ /dev/null @@ -1,32 +0,0 @@ -import fs from 'fs'; - -import {CpuProfilerModel} from './cpuProfilerModel'; -import {changeNamesToSourceMaps} from './sourceMapper'; -import {DurationEvent, SourceMap} from './EventInterfaces'; - -export const transformer = async ( - profilePath: string, - sourceMapPath: string | undefined, - bundleFileName: string | undefined, -): Promise => { - //console.log('sourceMapPath: ', sourceMapPath); - //console.log('profile: ', profilePath); - //TODO: parse JSON file: check whether the file is empty - const hermesProfile = JSON.parse(fs.readFileSync(profilePath, 'utf-8')); - //console.log('hermes profile: ', hermesProfile.samples[0]); - const profileChunk = CpuProfilerModel.collectProfileEvents(hermesProfile); - const profiler = new CpuProfilerModel(profileChunk); - const chromeEvents = profiler.createStartEndEvents(); - if (sourceMapPath) { - const sourceMap: SourceMap = JSON.parse( - fs.readFileSync(sourceMapPath, 'utf-8'), - ); - const events = await changeNamesToSourceMaps( - sourceMap, - chromeEvents, - bundleFileName, - ); - return events; - } - return chromeEvents; -}; diff --git a/yarn.lock b/yarn.lock index de3aef4be..d268016d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5765,6 +5765,13 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hermes-profile-transformer@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/hermes-profile-transformer/-/hermes-profile-transformer-0.0.2.tgz#4d75bf5bce89846203988a80f5f7e038c70bf115" + integrity sha512-NsnLtAjbR+g5WAnX70jqA/XVmrvpNnOFWvjrrL5SkoOjy/UK1+H5ynSQr1gZEFYOkWFhQUwqJRA/PokKprGtig== + dependencies: + source-map "^0.7.3" + hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" From ca07916e60550ae269215f1375dc76873657868b Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 11 Aug 2020 10:43:21 +0100 Subject: [PATCH 19/53] Rename command to profile-hermes --- packages/cli/src/commands/index.ts | 2 +- .../src/commands/{profile => profile-hermes}/downloadProfile.ts | 0 packages/cli/src/commands/{profile => profile-hermes}/index.ts | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename packages/cli/src/commands/{profile => profile-hermes}/downloadProfile.ts (100%) rename packages/cli/src/commands/{profile => profile-hermes}/index.ts (100%) diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 218780212..b4d9d6a8e 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -11,7 +11,7 @@ import info from './info/info'; import config from './config/config'; import init from './init'; import doctor from './doctor'; -import profile from './profile'; +import profile from './profile-hermes'; export const projectCommands = [ start, diff --git a/packages/cli/src/commands/profile/downloadProfile.ts b/packages/cli/src/commands/profile-hermes/downloadProfile.ts similarity index 100% rename from packages/cli/src/commands/profile/downloadProfile.ts rename to packages/cli/src/commands/profile-hermes/downloadProfile.ts diff --git a/packages/cli/src/commands/profile/index.ts b/packages/cli/src/commands/profile-hermes/index.ts similarity index 100% rename from packages/cli/src/commands/profile/index.ts rename to packages/cli/src/commands/profile-hermes/index.ts From 867b6b2d7f9bc54f5a7db3848d0e26122c3fbb26 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 11 Aug 2020 10:49:41 +0100 Subject: [PATCH 20/53] Improve command line output examples --- packages/cli/src/commands/profile-hermes/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/commands/profile-hermes/index.ts b/packages/cli/src/commands/profile-hermes/index.ts index ea3fc1f3c..5dfa96ce7 100644 --- a/packages/cli/src/commands/profile-hermes/index.ts +++ b/packages/cli/src/commands/profile-hermes/index.ts @@ -42,7 +42,7 @@ async function profile( export default { name: 'profile-hermes [destinationDir]', description: - 'Pull and convert a Hermes tracing profile to Chrome tracing profile, then store them in the directory of the local machine', + 'Pull and convert a Hermes tracing profile to Chrome tracing profile, then store it in the directory of the local machine', func: profile, options: [ { @@ -53,7 +53,7 @@ export default { { name: '--verbose', description: - 'Listing adb commands that are run internally to pull the file from Android device', + 'Lists adb commands that are run internally when pulling the file from Android device', }, { name: '--raw', @@ -63,7 +63,7 @@ export default { { name: '--sourcemap-path [string]', description: - 'The local path to your source map file, eg. Users/.../Desktop/sourcemap.json', + 'The local path to your source map file, eg. /tmp/sourcemap.json', }, { name: '--generate-sourcemap', @@ -73,8 +73,8 @@ export default { examples: [ { desc: - 'Download the Hermes Sampling Profiler to the directory of the local machine', - cmd: 'profile-hermes /Users/.../Desktop', + 'Download the Hermes Sampling Profiler to the directory on the local machine', + cmd: 'profile-hermes /tmp', }, ], }; From 7ee8fc697bac07bb2229b306729989dd95c0fe1a Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 11 Aug 2020 11:39:55 +0100 Subject: [PATCH 21/53] Extract source map functionality to its own file --- .../profile-hermes/downloadProfile.ts | 107 ++-------------- .../commands/profile-hermes/sourcemapUtils.ts | 114 ++++++++++++++++++ 2 files changed, 122 insertions(+), 99 deletions(-) create mode 100644 packages/cli/src/commands/profile-hermes/sourcemapUtils.ts diff --git a/packages/cli/src/commands/profile-hermes/downloadProfile.ts b/packages/cli/src/commands/profile-hermes/downloadProfile.ts index 66db97d34..71f68467c 100644 --- a/packages/cli/src/commands/profile-hermes/downloadProfile.ts +++ b/packages/cli/src/commands/profile-hermes/downloadProfile.ts @@ -5,10 +5,8 @@ import chalk from 'chalk'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import axios, {AxiosResponse} from 'axios'; -import ip from 'ip'; import {transformer} from 'hermes-profile-transformer'; - +import {findSourcemap, generateSourcemap} from './sourcemapUtils'; /** * Get the last modified hermes profile * @param packageName @@ -70,100 +68,6 @@ function validatePackageName(packageName: string) { return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(packageName); } -async function getSourcemapFromServer(): Promise> { - const DEBUG_SERVER_PORT = '8081'; - const IP_ADDRESS = ip.address(); - const PLATFORM = 'android'; - const requestURL = `http://${IP_ADDRESS}:${DEBUG_SERVER_PORT}/index.map?platform=${PLATFORM}&dev=true`; - return (await axios.get(requestURL)) as AxiosResponse; -} - -/** - * Find or generate source map - * @param ctx - * @param generateSourceMap - */ -async function getSourcemapPath( - ctx: Config, - generateSourceMap?: boolean, -): Promise { - const osTmpDir = os.tmpdir(); - //If '--generate-sourcemap, generate the bundle and source map files and store them in os.tmpDir() - if (generateSourceMap) { - console.log('generate new source map'); - //Store the file in os.tmpDir() - const sourceMapPath = path.join(osTmpDir, 'index.map'); - - const sourceMapResult = await getSourcemapFromServer(); - if (sourceMapResult) { - console.log('Request from server: done finding the source map'); - fs.writeFileSync( - `${sourceMapPath}`, - JSON.stringify(sourceMapResult.data), - 'utf-8', - ); - console.log('Request from server: done writing the source map'); - } else { - console.log('begin to build bundle'); - execSync( - `react-native bundle --entry-file index.js --bundle-output ${path.join( - osTmpDir, - 'index.bundle', - )} --sourcemap-output ${sourceMapPath}`, - ); - } - logger.info( - `Successfully generated the source map and store it in ${sourceMapPath}`, - ); - return `${sourceMapPath}`; - } - - //Find the sourcemap if it exists - else { - console.log('find the existing source map'); - //Find from local machine - //QUESTION: why is my source map path different from Jani's - //(android/app/build/generated/sourcemaps/react/debug/index.android.bundle.map) - const sourceMapDir = - path.join( - ctx.root, - 'android', - 'app', - 'build', - 'intermediates', //'generated', - 'sourcemaps', - 'react', - 'debug', - 'index.android.bundle.packager.map', - ) || - path.join( - ctx.root, - 'android', - 'app', - 'build', - 'generated', - 'sourcemaps', - 'react', - 'debug', - 'index.android.bundle.map', - ); - console.log(sourceMapDir); - if (fs.existsSync(sourceMapDir)) { - console.log('get the sourcemap if it exists from local machine'); - return sourceMapDir; - } else { - //Request from server - const sourceMapResult = await getSourcemapFromServer(); - fs.writeFileSync( - `${path.join(osTmpDir, 'index.map')}`, - JSON.stringify(sourceMapResult.data), - 'utf-8', - ); - return `${path.join(osTmpDir, 'index.map')}`; - } - } -} - /** * Pull and convert a Hermes tracing profile to Chrome tracing profile * @param ctx @@ -179,7 +83,7 @@ export async function downloadProfile( fileName?: string, sourceMapPath?: string, raw?: boolean, - generateSourceMap?: boolean, + shouldGenerateSourcemap?: boolean, ) { try { //console.log('start the profile command'); @@ -222,7 +126,12 @@ export async function downloadProfile( //If path to source map is not given if (!sourceMapPath) { //Get or generate the source map - sourceMapPath = await getSourcemapPath(ctx, generateSourceMap); + if (shouldGenerateSourcemap) { + sourceMapPath = await generateSourcemap(); + } else { + sourceMapPath = await findSourcemap(ctx); + } + //Run without source map if (sourceMapPath && sourceMapPath.length === 0) { logger.warn( diff --git a/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts b/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts new file mode 100644 index 000000000..58d6ceae3 --- /dev/null +++ b/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts @@ -0,0 +1,114 @@ +import {Config} from '@react-native-community/cli-types'; +import {execSync} from 'child_process'; +import {logger} from '@react-native-community/cli-tools'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import axios, {AxiosResponse} from 'axios'; +import ip from 'ip'; + +function getTempFilePath(filename: string) { + return path.join(os.tmpdir(), filename); +} + +function writeJsonSync(targetPath: string, data: any, format: boolean = false) { + let json; + try { + json = format ? JSON.stringify(data, null, 2) : JSON.stringify(data); + } catch (e) { + logger.error( + `Failed to serialize data to json before writing to ${targetPath}`, + e, + ); + } + + try { + fs.writeFileSync(targetPath, json, 'utf-8'); + } catch (e) { + logger.error(`Failed to write json to ${targetPath}`, e); + } +} + +async function getSourcemapFromServer(): Promise> { + const DEBUG_SERVER_PORT = '8081'; + const IP_ADDRESS = ip.address(); + const PLATFORM = 'android'; + const requestURL = `http://${IP_ADDRESS}:${DEBUG_SERVER_PORT}/index.map?platform=${PLATFORM}&dev=true`; + return (await axios.get(requestURL)) as AxiosResponse; +} + +/** + * Generate a sourcemap either by fetching it from a running metro server + * or by running react-native bundle with sourcemaps enables + */ +export async function generateSourcemap(): Promise { + // fetch the source map to a temp directory + const sourceMapPath = getTempFilePath('index.map'); + const sourceMapResult = await getSourcemapFromServer(); + + if (sourceMapResult) { + if (logger.isVerbose()) { + logger.debug('Using source maps from Metro packager server'); + } + + writeJsonSync(sourceMapPath, sourceMapResult.data); + } else { + if (logger.isVerbose()) { + logger.debug('Generating source maps using `react-native bundle`'); + } + + execSync( + `react-native bundle --entry-file index.js --bundle-output ${getTempFilePath( + 'index.bundle', + )} --sourcemap-output ${sourceMapPath}`, + ); + } + + logger.info( + `Successfully generated the source map and stored it in ${sourceMapPath}`, + ); + + return sourceMapPath; +} + +/** + * + * @param ctx + */ +export async function findSourcemap(ctx: Config): Promise { + const intermediateBuildPath = path.join( + ctx.root, + 'android', + 'app', + 'build', + 'intermediates', //'generated', + 'sourcemaps', + 'react', + 'debug', + 'index.android.bundle.packager.map', + ); + + const generatedBuildPath = path.join( + ctx.root, + 'android', + 'app', + 'build', + 'generated', + 'sourcemaps', + 'react', + 'debug', + 'index.android.bundle.map', + ); + + if (fs.existsSync(generatedBuildPath)) { + return Promise.resolve(generatedBuildPath); + } else if (fs.existsSync(intermediateBuildPath)) { + return Promise.resolve(intermediateBuildPath); + } else { + const sourcemapResult = await getSourcemapFromServer(); + const tempPath = getTempFilePath('index.map'); + writeJsonSync(tempPath, sourcemapResult.data); + + return tempPath; + } +} From 4e0dd95dd741b674d2bc70c9a197fd9c32d89442 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 11 Aug 2020 12:03:30 +0100 Subject: [PATCH 22/53] Readability improvements (@todo: fix @TODO comments) --- .../profile-hermes/downloadProfile.ts | 39 ++++++++++++------- .../cli/src/commands/profile-hermes/index.ts | 6 +-- .../commands/profile-hermes/sourcemapUtils.ts | 17 ++++++-- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/packages/cli/src/commands/profile-hermes/downloadProfile.ts b/packages/cli/src/commands/profile-hermes/downloadProfile.ts index 71f68467c..9fd9f51d2 100644 --- a/packages/cli/src/commands/profile-hermes/downloadProfile.ts +++ b/packages/cli/src/commands/profile-hermes/downloadProfile.ts @@ -13,6 +13,7 @@ import {findSourcemap, generateSourcemap} from './sourcemapUtils'; */ function getLatestFile(packageName: string): string { try { + // @TODO: Only check for *.cpuprofile files const file = execSync(`adb shell run-as ${packageName} ls cache/ -tp | grep -v /$ | head -1 `); @@ -22,6 +23,14 @@ function getLatestFile(packageName: string): string { } } +function execSyncWithLog(command: string) { + if (logger.isVerbose()) { + logger.debug(command); + } + + return execSync(command); +} + /** * Get the package name of the running React Native app * @param config @@ -86,9 +95,9 @@ export async function downloadProfile( shouldGenerateSourcemap?: boolean, ) { try { - //console.log('start the profile command'); const packageName = getPackageName(ctx); - //if not specify fileName, pull the latest file + + // if file name is not specified, pull the latest file from device const file = fileName || (await getLatestFile(packageName)); if (!file) { logger.error( @@ -96,32 +105,31 @@ export async function downloadProfile( ); process.exit(1); } + logger.info(`File to be pulled: ${file}`); - //if not specify destination path, pull to the current directory - if (!dstPath) { - dstPath = ctx.root; - } - //if '--verbose' is enabled + //if destination path is not specified, pull to the current directory + dstPath = dstPath || ctx.root; + if (logger.isVerbose()) { logger.info('Internal commands run to pull the file: '); - logger.debug(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); - logger.debug(`adb pull /sdcard/${file} ${dstPath}`); } + //Copy the file from device's data to sdcard, then pull the file to a temp directory - execSync(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); + execSyncWithLog(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); //If --raw, pull the hermes profile to dstPath if (raw) { - execSync(`adb pull /sdcard/${file} ${dstPath}`); + execSyncWithLog(`adb pull /sdcard/${file} ${dstPath}`); logger.success(`Successfully pulled the file to ${dstPath}/${file}`); } //Else: transform the profile to Chrome format and pull it to dstPath else { const osTmpDir = os.tmpdir(); - const fileTmpDir = path.join(osTmpDir, file); - execSync(`adb pull /sdcard/${file} ${fileTmpDir}`); + const tempFilePath = path.join(osTmpDir, file); + + execSyncWithLog(`adb pull /sdcard/${file} ${tempFilePath}`); //If path to source map is not given if (!sourceMapPath) { @@ -133,7 +141,7 @@ export async function downloadProfile( } //Run without source map - if (sourceMapPath && sourceMapPath.length === 0) { + if (!sourceMapPath) { logger.warn( 'Cannot generate or find bundle and source map, running the transformer without source map', ); @@ -145,10 +153,11 @@ export async function downloadProfile( //Run transformer tool to convert from Hermes to Chrome format const events = await transformer( - fileTmpDir, + tempFilePath, sourceMapPath, 'index.bundle', ); + const transformedFilePath = `${dstPath}/${path.basename( file, '.cpuprofile', diff --git a/packages/cli/src/commands/profile-hermes/index.ts b/packages/cli/src/commands/profile-hermes/index.ts index 5dfa96ce7..3c095a70f 100644 --- a/packages/cli/src/commands/profile-hermes/index.ts +++ b/packages/cli/src/commands/profile-hermes/index.ts @@ -11,7 +11,7 @@ type Options = { generateSourcemap?: boolean; }; -async function profile( +async function profileHermes( [dstPath]: Array, ctx: Config, options: Options, @@ -35,7 +35,7 @@ async function profile( options.generateSourcemap, ); } catch (err) { - logger.error(`Unable to download the Hermes Sampling Profiler.\n${err}`); + logger.error(`Unable to download the Hermes Sampling Profile.\n${err}`); } } @@ -43,7 +43,7 @@ export default { name: 'profile-hermes [destinationDir]', description: 'Pull and convert a Hermes tracing profile to Chrome tracing profile, then store it in the directory of the local machine', - func: profile, + func: profileHermes, options: [ { name: '--fileName [string]', diff --git a/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts b/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts index 58d6ceae3..fc2daf374 100644 --- a/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts +++ b/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts @@ -11,10 +11,10 @@ function getTempFilePath(filename: string) { return path.join(os.tmpdir(), filename); } -function writeJsonSync(targetPath: string, data: any, format: boolean = false) { +function writeJsonSync(targetPath: string, data: any) { let json; try { - json = format ? JSON.stringify(data, null, 2) : JSON.stringify(data); + json = JSON.stringify(data); } catch (e) { logger.error( `Failed to serialize data to json before writing to ${targetPath}`, @@ -29,12 +29,21 @@ function writeJsonSync(targetPath: string, data: any, format: boolean = false) { } } -async function getSourcemapFromServer(): Promise> { +// @TODO +// Remove AxiosResponse, return Promise where SourceMap +// is imported from the hermes-profiler-transformer +async function getSourcemapFromServer(): Promise> { const DEBUG_SERVER_PORT = '8081'; const IP_ADDRESS = ip.address(); const PLATFORM = 'android'; const requestURL = `http://${IP_ADDRESS}:${DEBUG_SERVER_PORT}/index.map?platform=${PLATFORM}&dev=true`; - return (await axios.get(requestURL)) as AxiosResponse; + + // @TODO + // Use node-fetch instead of axios + // Check for return http status code, if > 400 it's an error and + // we should return null instead of the source map string + + return (await axios.get(requestURL)) as AxiosResponse; } /** From c422253472aacec0cb79edec426a9169aa88558b Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 11 Aug 2020 22:39:11 +0700 Subject: [PATCH 23/53] upgrade hermes-profile-transformer package implemented @TODO get only the .cpuprofile from cache/ add a few verbose logs edited profile-hermes.md --- docs/profile-hermes.md | 60 ++++++++++++++++--- packages/cli/package.json | 2 +- .../profile-hermes/downloadProfile.ts | 18 +++--- .../commands/profile-hermes/sourcemapUtils.ts | 16 +++-- yarn.lock | 8 +-- 5 files changed, 78 insertions(+), 26 deletions(-) diff --git a/docs/profile-hermes.md b/docs/profile-hermes.md index 78c4a98b2..02bcaa1dd 100644 --- a/docs/profile-hermes.md +++ b/docs/profile-hermes.md @@ -10,13 +10,13 @@ ## How to pull the sampling profiler to your local machine -### React Native CLI +### React Native CLI command Usage: -### `react-native profile-hermes [destinationDir]` +### `npx react-native profile-hermes [destinationDir]` -Pull and convert a Hermes tracing profile to Chrome tracing profile, then store them in the directory of the local machine. `destinationDir` is optional, if provided, pull the file to that directory (if not present, pull to the current React Native app root directory) +Pull and convert a Hermes tracing profile to Chrome tracing profile, then store it in the directory of the local machine. `destinationDir` is optional, if provided, pull the file to that directory (if not present, pull to the current React Native app root directory) Options: @@ -26,7 +26,7 @@ File name of the profile to be downloaded, eg. sampling-profiler-trace8593107139 #### `--verbose` -Listing adb commands that are run internally to pull the file from Android device +Lists adb commands that are run internally to pull the file from Android device #### `--raw` @@ -34,19 +34,47 @@ Pull the original Hermes tracing profile without any transformation #### `--sourcemap-path [string]` -The local path to where source map file is stored, eg. Users/.../Desktop/sourcemap.json +The local path to your source map file, eg. /tmp/sourcemap.json #### `--generate-sourcemap` Generate the JS bundle and source map +### Notes on source map + +This step is recommended in order for the source map to be generated for the use of this command: + +#### Enable `bundleInDebug: true` if the app is running in development mode: + +- In the `android/app/build.gradle` file of the React Native app that you use to test, add + +```sh +project.ext.react = [ + bundleInDebug: true, +] +``` + +- Clean the build by running this command + +```sh +cd android && ./gradlew clean +``` + +- Run your app as normal + +```sh +npx react-native run-android +``` + +- This allows React Native to build the bundle during its running process + ## Common errors encountered during the process -#### adb: no devices/emulators found +- #### adb: no devices/emulators found Solution: 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 +- #### There is no file in the cache/ directory User may have forgotten to record a profile from the device (instruction on how to enable/ disable profiler is above) @@ -59,3 +87,21 @@ Using `yarn link` as instructed by the CLI's [Testing Plan](https://github.com/M Get the error `ReferenceError: SHA-1 for file ... is not computed` even if we run `react-native start --watchFolders /path/to/cloned/cli/`. That's because we use the command `react-native bundle` within our code, which creates a new Metro Server that can't find the symlinked folder ### Solution: + +- In the `/package.json` file of the React Native app that you use to test, add + +```sh +"dependencies": { + "@react-native-community/cli": "file:/path/to/cloned/cli/", + }, + ... +"resolutions": { + "@react-native-community/cli": "file:/path/to/cloned/cli/" + }, +``` + +- Every time we make any changes to the CLI source code, run the command below to update the changes to this project + +```sh +rm -rf node_modules/@react-native-community/cli && yarn --check-files +``` diff --git a/packages/cli/package.json b/packages/cli/package.json index 279257d83..426e69562 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -47,7 +47,7 @@ "fs-extra": "^8.1.0", "glob": "^7.1.3", "graceful-fs": "^4.1.3", - "hermes-profile-transformer": "^0.0.2", + "hermes-profile-transformer": "^0.0.3", "inquirer": "^3.0.6", "ip": "^1.1.5", "leven": "^3.1.0", diff --git a/packages/cli/src/commands/profile-hermes/downloadProfile.ts b/packages/cli/src/commands/profile-hermes/downloadProfile.ts index 9fd9f51d2..a645e8880 100644 --- a/packages/cli/src/commands/profile-hermes/downloadProfile.ts +++ b/packages/cli/src/commands/profile-hermes/downloadProfile.ts @@ -5,7 +5,7 @@ import chalk from 'chalk'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import {transformer} from 'hermes-profile-transformer'; +import transformer from 'hermes-profile-transformer'; import {findSourcemap, generateSourcemap} from './sourcemapUtils'; /** * Get the last modified hermes profile @@ -13,10 +13,8 @@ import {findSourcemap, generateSourcemap} from './sourcemapUtils'; */ function getLatestFile(packageName: string): string { try { - // @TODO: Only check for *.cpuprofile files - const file = execSync(`adb shell run-as ${packageName} ls cache/ -tp | grep -v /$ | head -1 + const file = execSync(`adb shell run-as ${packageName} ls cache/ -tp | grep -v /$ | egrep '\.cpuprofile' | head -1 `); - return file.toString().trim(); } catch (e) { throw new Error(e); @@ -90,7 +88,7 @@ export async function downloadProfile( ctx: Config, dstPath: string, fileName?: string, - sourceMapPath?: string, + sourcemapPath?: string, raw?: boolean, shouldGenerateSourcemap?: boolean, ) { @@ -132,16 +130,16 @@ export async function downloadProfile( execSyncWithLog(`adb pull /sdcard/${file} ${tempFilePath}`); //If path to source map is not given - if (!sourceMapPath) { + if (!sourcemapPath) { //Get or generate the source map if (shouldGenerateSourcemap) { - sourceMapPath = await generateSourcemap(); + sourcemapPath = await generateSourcemap(); } else { - sourceMapPath = await findSourcemap(ctx); + sourcemapPath = await findSourcemap(ctx); } //Run without source map - if (!sourceMapPath) { + if (!sourcemapPath) { logger.warn( 'Cannot generate or find bundle and source map, running the transformer without source map', ); @@ -154,7 +152,7 @@ export async function downloadProfile( //Run transformer tool to convert from Hermes to Chrome format const events = await transformer( tempFilePath, - sourceMapPath, + sourcemapPath, 'index.bundle', ); diff --git a/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts b/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts index fc2daf374..19273f2ee 100644 --- a/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts +++ b/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts @@ -5,6 +5,7 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; import axios, {AxiosResponse} from 'axios'; +import {SourceMap} from 'hermes-profile-transformer'; import ip from 'ip'; function getTempFilePath(filename: string) { @@ -32,7 +33,7 @@ function writeJsonSync(targetPath: string, data: any) { // @TODO // Remove AxiosResponse, return Promise where SourceMap // is imported from the hermes-profiler-transformer -async function getSourcemapFromServer(): Promise> { +async function getSourcemapFromServer(): Promise> { const DEBUG_SERVER_PORT = '8081'; const IP_ADDRESS = ip.address(); const PLATFORM = 'android'; @@ -43,7 +44,7 @@ async function getSourcemapFromServer(): Promise> { // Check for return http status code, if > 400 it's an error and // we should return null instead of the source map string - return (await axios.get(requestURL)) as AxiosResponse; + return (await axios.get(requestURL)) as AxiosResponse; } /** @@ -59,13 +60,11 @@ export async function generateSourcemap(): Promise { if (logger.isVerbose()) { logger.debug('Using source maps from Metro packager server'); } - writeJsonSync(sourceMapPath, sourceMapResult.data); } else { if (logger.isVerbose()) { logger.debug('Generating source maps using `react-native bundle`'); } - execSync( `react-native bundle --entry-file index.js --bundle-output ${getTempFilePath( 'index.bundle', @@ -110,11 +109,20 @@ export async function findSourcemap(ctx: Config): Promise { ); if (fs.existsSync(generatedBuildPath)) { + if (logger.isVerbose()) { + logger.debug(`Getting the source map from ${generateSourcemap}`); + } return Promise.resolve(generatedBuildPath); } else if (fs.existsSync(intermediateBuildPath)) { + if (logger.isVerbose()) { + logger.debug(`Getting the source map from ${intermediateBuildPath}`); + } return Promise.resolve(intermediateBuildPath); } else { const sourcemapResult = await getSourcemapFromServer(); + if (logger.isVerbose()) { + logger.debug('Using source maps from Metro packager server'); + } const tempPath = getTempFilePath('index.map'); writeJsonSync(tempPath, sourcemapResult.data); diff --git a/yarn.lock b/yarn.lock index d268016d5..ba23c2ac1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5765,10 +5765,10 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hermes-profile-transformer@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/hermes-profile-transformer/-/hermes-profile-transformer-0.0.2.tgz#4d75bf5bce89846203988a80f5f7e038c70bf115" - integrity sha512-NsnLtAjbR+g5WAnX70jqA/XVmrvpNnOFWvjrrL5SkoOjy/UK1+H5ynSQr1gZEFYOkWFhQUwqJRA/PokKprGtig== +hermes-profile-transformer@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/hermes-profile-transformer/-/hermes-profile-transformer-0.0.3.tgz#ef264e56add319cd12f6fb799b208ced4d8c0797" + integrity sha512-atBQiLWV8Sd4bF6Md2I1oh+m77zE/9MHyDZbd6i6599y0UwD8hASp7VARk8/EdCU4ukWjbjO3Tc4Xm6CR4c2VA== dependencies: source-map "^0.7.3" From 3d9d04e5701236d7f663f0135c4c43c12115253b Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Thu, 13 Aug 2020 14:35:40 +0700 Subject: [PATCH 24/53] implemented @TODO: remove axios and use node-fetch add the usage to commands.md delete the profile-hermes.md --- docs/commands.md | 44 +++++++ docs/profile-hermes.md | 107 ------------------ .../cli/src/commands/profile-hermes/index.ts | 6 +- .../commands/profile-hermes/sourcemapUtils.ts | 49 ++++---- 4 files changed, 66 insertions(+), 140 deletions(-) delete mode 100644 docs/profile-hermes.md diff --git a/docs/commands.md b/docs/commands.md index 970c97be8..5a30ec63e 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -17,6 +17,7 @@ React Native CLI comes with following commands: - [`uninstall`](#uninstall) - [`unlink`](#unlink) - [`upgrade`](#upgrade) +- [`profile-hermes`](#profile-hermes) ### `bundle` @@ -546,3 +547,46 @@ Upgrade your app's template files to the specified or latest npm version using [ Using this command is a recommended way of upgrading relatively simple React Native apps with not too many native libraries linked. The more iOS and Android build files are modified, the higher chance for a conflicts. The command will guide you on how to continue upgrade process manually in case of failure. _Note: If you'd like to upgrade using this method from React Native version lower than 0.59.0, you may use a standalone version of this CLI: `npx @react-native-community/cli upgrade`._ + +### `profile-hermes` + +Usage: + +```sh +react-native profile-hermes [destinationDir] +``` + +Pull and convert a Hermes tracing profile to Chrome tracing profile, then store it in the directory of the local machine. + +- `destinationDir` is optional, if provided, pull the file to that directory + > default: pull to the current React Native app root directory + +#### Options + +#### `--fileName [string]` + +File name of the profile to be downloaded, eg. sampling-profiler-trace8593107139682635366.cpuprofile. + +> default: pull the latest file + +#### `--verbose` + +Lists commands and steps that are run internally to pull the file from Android device + +#### `--raw` + +Pulls the original Hermes tracing profile without any transformation + +#### `--sourcemap-path [string]` + +The local path to your source map file if you generated it manually, ex. `/tmp/sourcemap.json` + +#### `--generate-sourcemap` + +Generate the JS bundle and source map in `os.tmpdir()` + +### Notes on source map + +This step is recommended in order for the source map to be generated: + +If you are planning on building a debug APK, that will run without the packager, by invoking `./gradlew assembleDebug` you can simply set `bundleInDebug: true` in your app/build.gradle file, inside the `project.ext.react` map. diff --git a/docs/profile-hermes.md b/docs/profile-hermes.md deleted file mode 100644 index 02bcaa1dd..000000000 --- a/docs/profile-hermes.md +++ /dev/null @@ -1,107 +0,0 @@ -# Visualize JavaScript's performance in a React Native app using Hermes - -## How to profile your app using Hermes - -- Instructions on how to enable Hermes: [Using Hermes](https://reactnative.dev/docs/hermes) -- How to profile the app: - - Open Developer Menu with `Cmd+M` or Shake the device. Select `Enable Sampling Profiler` - - Execute JavaScript by using the app's functions (pressing buttons, etc.) - - Open Developer Menu again, select `Disable Sampling Profiler`. A toast shows the location where the sampling profiler is saved - usually in `/data/user/0/com.appName/cache/*.cpuprofile`. - -## How to pull the sampling profiler to your local machine - -### React Native CLI command - -Usage: - -### `npx react-native profile-hermes [destinationDir]` - -Pull and convert a Hermes tracing profile to Chrome tracing profile, then store it in the directory of the local machine. `destinationDir` is optional, if provided, pull the file to that directory (if not present, pull to the current React Native app root directory) - -Options: - -#### `--fileName [string]` - -File name of the profile to be downloaded, eg. sampling-profiler-trace8593107139682635366.cpuprofile. If not provided, pull the latest file - -#### `--verbose` - -Lists adb commands that are run internally to pull the file from Android device - -#### `--raw` - -Pull the original Hermes tracing profile without any transformation - -#### `--sourcemap-path [string]` - -The local path to your source map file, eg. /tmp/sourcemap.json - -#### `--generate-sourcemap` - -Generate the JS bundle and source map - -### Notes on source map - -This step is recommended in order for the source map to be generated for the use of this command: - -#### Enable `bundleInDebug: true` if the app is running in development mode: - -- In the `android/app/build.gradle` file of the React Native app that you use to test, add - -```sh -project.ext.react = [ - bundleInDebug: true, -] -``` - -- Clean the build by running this command - -```sh -cd android && ./gradlew clean -``` - -- Run your app as normal - -```sh -npx react-native run-android -``` - -- This allows React Native to build the bundle during its running process - -## Common errors encountered during the process - -- #### adb: no devices/emulators found - -Solution: 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 - -User may have forgotten to record a profile from the device (instruction on how to enable/ disable profiler is above) - -## Testing plan - -Using `yarn link` as instructed by the CLI's [Testing Plan](https://github.com/MLH-Fellowship/cli/blob/master/CONTRIBUTING.md#testing-your-changes) does not work for us. - -### Reason: - -Get the error `ReferenceError: SHA-1 for file ... is not computed` even if we run `react-native start --watchFolders /path/to/cloned/cli/`. That's because we use the command `react-native bundle` within our code, which creates a new Metro Server that can't find the symlinked folder - -### Solution: - -- In the `/package.json` file of the React Native app that you use to test, add - -```sh -"dependencies": { - "@react-native-community/cli": "file:/path/to/cloned/cli/", - }, - ... -"resolutions": { - "@react-native-community/cli": "file:/path/to/cloned/cli/" - }, -``` - -- Every time we make any changes to the CLI source code, run the command below to update the changes to this project - -```sh -rm -rf node_modules/@react-native-community/cli && yarn --check-files -``` diff --git a/packages/cli/src/commands/profile-hermes/index.ts b/packages/cli/src/commands/profile-hermes/index.ts index 3c095a70f..01b3ce6b1 100644 --- a/packages/cli/src/commands/profile-hermes/index.ts +++ b/packages/cli/src/commands/profile-hermes/index.ts @@ -53,12 +53,12 @@ export default { { name: '--verbose', description: - 'Lists adb commands that are run internally when pulling the file from Android device', + 'Lists commands and steps that are run internally when pulling the file from Android device', }, { name: '--raw', description: - 'Pull the original Hermes tracing profile without any transformation', + 'Pulls the original Hermes tracing profile without any transformation', }, { name: '--sourcemap-path [string]', @@ -67,7 +67,7 @@ export default { }, { name: '--generate-sourcemap', - description: 'Generate the JS bundle and source map', + description: 'Generates the JS bundle and source map', }, ], examples: [ diff --git a/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts b/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts index 19273f2ee..2c867d4c5 100644 --- a/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts +++ b/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts @@ -1,12 +1,11 @@ import {Config} from '@react-native-community/cli-types'; -import {execSync} from 'child_process'; -import {logger} from '@react-native-community/cli-tools'; +import {logger, CLIError} from '@react-native-community/cli-tools'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import axios, {AxiosResponse} from 'axios'; import {SourceMap} from 'hermes-profile-transformer'; import ip from 'ip'; +import nodeFetch from 'node-fetch'; function getTempFilePath(filename: string) { return path.join(os.tmpdir(), filename); @@ -30,26 +29,24 @@ function writeJsonSync(targetPath: string, data: any) { } } -// @TODO -// Remove AxiosResponse, return Promise where SourceMap -// is imported from the hermes-profiler-transformer -async function getSourcemapFromServer(): Promise> { +async function getSourcemapFromServer(): Promise { + logger.debug('Getting source maps from Metro packager server'); const DEBUG_SERVER_PORT = '8081'; const IP_ADDRESS = ip.address(); const PLATFORM = 'android'; - const requestURL = `http://${IP_ADDRESS}:${DEBUG_SERVER_PORT}/index.map?platform=${PLATFORM}&dev=true`; - - // @TODO - // Use node-fetch instead of axios - // Check for return http status code, if > 400 it's an error and - // we should return null instead of the source map string - return (await axios.get(requestURL)) as AxiosResponse; + const requestURL = `http://${IP_ADDRESS}:${DEBUG_SERVER_PORT}/index.map?platform=${PLATFORM}&dev=true`; + const result = await nodeFetch(requestURL); + if (result.status >= 400) { + logger.error('Cannot get source map from running metro server'); + throw new CLIError(`Fetch request failed with status ${result.status}.`); + } + const data = await result.json(); + return data as SourceMap; } /** - * Generate a sourcemap either by fetching it from a running metro server - * or by running react-native bundle with sourcemaps enables + * Generate a sourcemap by fetching it from a running metro server */ export async function generateSourcemap(): Promise { // fetch the source map to a temp directory @@ -60,22 +57,14 @@ export async function generateSourcemap(): Promise { if (logger.isVerbose()) { logger.debug('Using source maps from Metro packager server'); } - writeJsonSync(sourceMapPath, sourceMapResult.data); - } else { - if (logger.isVerbose()) { - logger.debug('Generating source maps using `react-native bundle`'); - } - execSync( - `react-native bundle --entry-file index.js --bundle-output ${getTempFilePath( - 'index.bundle', - )} --sourcemap-output ${sourceMapPath}`, + writeJsonSync(sourceMapPath, sourceMapResult); + logger.info( + `Successfully generated the source map and stored it in ${sourceMapPath}`, ); + } else { + logger.error('Cannot generate source maps from Metro packager server`'); } - logger.info( - `Successfully generated the source map and stored it in ${sourceMapPath}`, - ); - return sourceMapPath; } @@ -124,7 +113,7 @@ export async function findSourcemap(ctx: Config): Promise { logger.debug('Using source maps from Metro packager server'); } const tempPath = getTempFilePath('index.map'); - writeJsonSync(tempPath, sourcemapResult.data); + writeJsonSync(tempPath, sourcemapResult); return tempPath; } From 29e55cac6393f408cbfbdd422e83811fda02cfe4 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Thu, 13 Aug 2020 15:20:30 +0700 Subject: [PATCH 25/53] deleted small unintended changes and removed axios --- packages/cli/package.json | 1 - packages/cli/src/commands/bundle/buildBundle.ts | 4 ---- packages/cli/src/commands/bundle/bundle.ts | 3 --- 3 files changed, 8 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 426e69562..b887a6805 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -35,7 +35,6 @@ "@react-native-community/cli-types": "^4.10.1", "@types/ip": "^1.1.0", "@types/source-map": "^0.5.7", - "axios": "^0.19.2", "chalk": "^3.0.0", "command-exists": "^1.2.8", "commander": "^2.19.0", diff --git a/packages/cli/src/commands/bundle/buildBundle.ts b/packages/cli/src/commands/bundle/buildBundle.ts index c087c2dac..fa0a6007c 100644 --- a/packages/cli/src/commands/bundle/buildBundle.ts +++ b/packages/cli/src/commands/bundle/buildBundle.ts @@ -44,13 +44,11 @@ async function buildBundle( ctx: Config, output: typeof outputBundle = outputBundle, ) { - //console.log('not load metro config yet'); const config = await loadMetroConfig(ctx, { maxWorkers: args.maxWorkers, resetCache: args.resetCache, config: args.config, }); - //console.log('loaded metro config'); if (config.resolver.platforms.indexOf(args.platform) === -1) { logger.error( @@ -86,9 +84,7 @@ async function buildBundle( minify: args.minify !== undefined ? args.minify : !args.dev, platform: args.platform, }; - //console.log('not build server yet'); const server = new Server(config); - //console.log('built new server'); try { const bundle = await output.build(server, requestOpts); diff --git a/packages/cli/src/commands/bundle/bundle.ts b/packages/cli/src/commands/bundle/bundle.ts index de46dce9a..95caa81bc 100644 --- a/packages/cli/src/commands/bundle/bundle.ts +++ b/packages/cli/src/commands/bundle/bundle.ts @@ -18,9 +18,6 @@ function bundleWithOutput( args: CommandLineArgs, output: any, // untyped metro/src/shared/output/bundle or metro/src/shared/output/RamBundle ) { - // console.log('config: ', config); - // console.log('args: ', args); - // console.log('output: ', output); return buildBundle(args, config, output); } From 1078bdb8fc52758c22f76d06aa4d417e1fd3adc5 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Thu, 13 Aug 2020 16:27:35 +0700 Subject: [PATCH 26/53] removed axios package and deleted unnecessary changes --- .vscode/settings.json | 6 ++++-- yarn.lock | 16 +--------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d2b294630..230a6715c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,10 @@ { - "editor.rulers": [80], + "editor.rulers": [ + 80 + ], "files.exclude": { "**/.git": true, - "**/node_modules": false, + "**/node_modules": true, "**/build": true }, "editor.codeActionsOnSave": { diff --git a/yarn.lock b/yarn.lock index ba23c2ac1..a827f4f2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2896,13 +2896,6 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== -axios@^0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== - dependencies: - follow-redirects "1.5.10" - babel-eslint@10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed" @@ -4252,7 +4245,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@3.1.0, debug@=3.1.0: +debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -5326,13 +5319,6 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" - for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" From e050ab208276747fd4a664e3adaaf36e2d0225af Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Thu, 13 Aug 2020 16:35:54 +0700 Subject: [PATCH 27/53] removed source-map related packages --- packages/cli/package.json | 2 -- yarn.lock | 17 +++++------------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index b887a6805..88f2534ea 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,7 +34,6 @@ "@react-native-community/cli-tools": "^4.10.1", "@react-native-community/cli-types": "^4.10.1", "@types/ip": "^1.1.0", - "@types/source-map": "^0.5.7", "chalk": "^3.0.0", "command-exists": "^1.2.8", "commander": "^2.19.0", @@ -63,7 +62,6 @@ "pretty-format": "^25.2.0", "semver": "^6.3.0", "serve-static": "^1.13.1", - "source-map": "^0.7.3", "strip-ansi": "^5.2.0", "sudo-prompt": "^9.0.0", "wcwidth": "^1.0.1" diff --git a/yarn.lock b/yarn.lock index a827f4f2c..4915738cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2402,13 +2402,6 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" -"@types/source-map@^0.5.7": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@types/source-map/-/source-map-0.5.7.tgz#165eeb583c1ef00196fe4ef4da5d7832b03b275b" - integrity sha512-LrnsgZIfJaysFkv9rRJp4/uAyqw87oVed3s1hhF83nwbo9c7MG9g5DqR0seHP+lkX4ldmMrVolPjQSe2ZfD0yA== - dependencies: - source-map "*" - "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -10510,11 +10503,6 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@*, source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -10525,6 +10513,11 @@ source-map@^0.5.0, source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + spdx-correct@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" From b955db9fd12575d07945e81f72bd6a35f6d42109 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Thu, 13 Aug 2020 19:16:22 +0700 Subject: [PATCH 28/53] added new line after each logger --- .../profile-hermes/downloadProfile.ts | 16 +++++++-------- .../cli/src/commands/profile-hermes/index.ts | 5 ++--- .../commands/profile-hermes/sourcemapUtils.ts | 20 +++++++++---------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/cli/src/commands/profile-hermes/downloadProfile.ts b/packages/cli/src/commands/profile-hermes/downloadProfile.ts index a645e8880..857b898d4 100644 --- a/packages/cli/src/commands/profile-hermes/downloadProfile.ts +++ b/packages/cli/src/commands/profile-hermes/downloadProfile.ts @@ -23,7 +23,7 @@ function getLatestFile(packageName: string): string { function execSyncWithLog(command: string) { if (logger.isVerbose()) { - logger.debug(command); + logger.debug(`${command}\n`); } return execSync(command); @@ -99,18 +99,18 @@ export async function downloadProfile( const file = fileName || (await getLatestFile(packageName)); if (!file) { logger.error( - 'There is no file in the cache/ directory. Did you record a profile from the developer menu?', + 'There is no file in the cache/ directory. Did you record a profile from the developer menu?\n', ); process.exit(1); } - logger.info(`File to be pulled: ${file}`); + logger.info(`File to be pulled: ${file}\n`); //if destination path is not specified, pull to the current directory dstPath = dstPath || ctx.root; if (logger.isVerbose()) { - logger.info('Internal commands run to pull the file: '); + logger.info('Internal commands run to pull the file:\n'); } //Copy the file from device's data to sdcard, then pull the file to a temp directory @@ -119,7 +119,7 @@ export async function downloadProfile( //If --raw, pull the hermes profile to dstPath if (raw) { execSyncWithLog(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}\n`); } //Else: transform the profile to Chrome format and pull it to dstPath @@ -141,10 +141,10 @@ export async function downloadProfile( //Run without source map if (!sourcemapPath) { logger.warn( - 'Cannot generate or find bundle and source map, running the transformer without source map', + 'Cannot generate or find bundle and source map, running the transformer without source map\n', ); logger.info( - 'Instructions on how to get source map:\n Go to directory android/app/build.gradle \n Set bundleInDebug: true', + 'Instructions on how to get source map: set `bundleInDebug: true` in your app/build.gradle file, inside the `project.ext.react` map.\n', ); } } @@ -166,7 +166,7 @@ export async function downloadProfile( 'utf-8', ); logger.success( - `Successfully converted to Chrome tracing format and pulled the file to ${transformedFilePath}`, + `Successfully converted to Chrome tracing format and pulled the file to ${transformedFilePath}\n`, ); } } catch (e) { diff --git a/packages/cli/src/commands/profile-hermes/index.ts b/packages/cli/src/commands/profile-hermes/index.ts index 01b3ce6b1..57b7139c0 100644 --- a/packages/cli/src/commands/profile-hermes/index.ts +++ b/packages/cli/src/commands/profile-hermes/index.ts @@ -1,4 +1,3 @@ -// @ts-ignore untyped import {logger} from '@react-native-community/cli-tools'; import {Config} from '@react-native-community/cli-types'; import {downloadProfile} from './downloadProfile'; @@ -18,10 +17,10 @@ async function profileHermes( ) { try { logger.info( - 'Downloading a Hermes Sampling Profiler from your Android device...', + 'Downloading a Hermes Sampling Profiler from your Android device...\n', ); if (!options.fileName) { - logger.info('No filename is provided, pulling latest file'); + logger.info('No filename is provided, pulling latest file\n'); } if (options.verbose) { logger.setVerbose(true); diff --git a/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts b/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts index 2c867d4c5..e2d58298d 100644 --- a/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts +++ b/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts @@ -17,7 +17,7 @@ function writeJsonSync(targetPath: string, data: any) { json = JSON.stringify(data); } catch (e) { logger.error( - `Failed to serialize data to json before writing to ${targetPath}`, + `Failed to serialize data to json before writing to ${targetPath}\n`, e, ); } @@ -25,12 +25,12 @@ function writeJsonSync(targetPath: string, data: any) { try { fs.writeFileSync(targetPath, json, 'utf-8'); } catch (e) { - logger.error(`Failed to write json to ${targetPath}`, e); + logger.error(`Failed to write json to ${targetPath}\n`, e); } } async function getSourcemapFromServer(): Promise { - logger.debug('Getting source maps from Metro packager server'); + logger.debug('Getting source maps from Metro packager server\n'); const DEBUG_SERVER_PORT = '8081'; const IP_ADDRESS = ip.address(); const PLATFORM = 'android'; @@ -38,7 +38,7 @@ async function getSourcemapFromServer(): Promise { const requestURL = `http://${IP_ADDRESS}:${DEBUG_SERVER_PORT}/index.map?platform=${PLATFORM}&dev=true`; const result = await nodeFetch(requestURL); if (result.status >= 400) { - logger.error('Cannot get source map from running metro server'); + logger.error('Cannot get source map from running metro server\n'); throw new CLIError(`Fetch request failed with status ${result.status}.`); } const data = await result.json(); @@ -55,14 +55,14 @@ export async function generateSourcemap(): Promise { if (sourceMapResult) { if (logger.isVerbose()) { - logger.debug('Using source maps from Metro packager server'); + logger.debug('Using source maps from Metro packager server\n'); } writeJsonSync(sourceMapPath, sourceMapResult); logger.info( - `Successfully generated the source map and stored it in ${sourceMapPath}`, + `Successfully generated the source map and stored it in ${sourceMapPath}\n`, ); } else { - logger.error('Cannot generate source maps from Metro packager server`'); + logger.error('Cannot generate source maps from Metro packager server\n`'); } return sourceMapPath; @@ -99,18 +99,18 @@ export async function findSourcemap(ctx: Config): Promise { if (fs.existsSync(generatedBuildPath)) { if (logger.isVerbose()) { - logger.debug(`Getting the source map from ${generateSourcemap}`); + logger.debug(`Getting the source map from ${generateSourcemap}\n`); } return Promise.resolve(generatedBuildPath); } else if (fs.existsSync(intermediateBuildPath)) { if (logger.isVerbose()) { - logger.debug(`Getting the source map from ${intermediateBuildPath}`); + logger.debug(`Getting the source map from ${intermediateBuildPath}\n`); } return Promise.resolve(intermediateBuildPath); } else { const sourcemapResult = await getSourcemapFromServer(); if (logger.isVerbose()) { - logger.debug('Using source maps from Metro packager server'); + logger.debug('Using source maps from Metro packager server\n'); } const tempPath = getTempFilePath('index.map'); writeJsonSync(tempPath, sourcemapResult); From b37a804261cb435beae90213268ec314b7619f6d Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Fri, 14 Aug 2020 11:56:48 +0700 Subject: [PATCH 29/53] upgraded hermes-profile-transformer package to latest --- packages/cli/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 869cdf529..1b64aa225 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -45,7 +45,7 @@ "fs-extra": "^8.1.0", "glob": "^7.1.3", "graceful-fs": "^4.1.3", - "hermes-profile-transformer": "^0.0.3", + "hermes-profile-transformer": "^0.0.6", "inquirer": "^3.0.6", "ip": "^1.1.5", "leven": "^3.1.0", diff --git a/yarn.lock b/yarn.lock index b3e0597ca..005f5c608 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5749,10 +5749,10 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hermes-profile-transformer@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/hermes-profile-transformer/-/hermes-profile-transformer-0.0.3.tgz#ef264e56add319cd12f6fb799b208ced4d8c0797" - integrity sha512-atBQiLWV8Sd4bF6Md2I1oh+m77zE/9MHyDZbd6i6599y0UwD8hASp7VARk8/EdCU4ukWjbjO3Tc4Xm6CR4c2VA== +hermes-profile-transformer@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz#bd0f5ecceda80dd0ddaae443469ab26fb38fc27b" + integrity sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ== dependencies: source-map "^0.7.3" From 2f9377832b9ad13d35a25015c2214bca69e3536a Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 18 Aug 2020 11:55:38 +0700 Subject: [PATCH 30/53] moved profile-hermes folder out of cli to a separate package cli-hermes --- packages/cli-hermes/package.json | 21 +++++++++++++++++++ packages/cli-hermes/src/index.ts | 1 + .../src/profileHermes}/downloadProfile.ts | 0 .../src/profileHermes}/index.ts | 0 .../src/profileHermes}/sourcemapUtils.ts | 0 packages/cli-hermes/tsconfig.json | 8 +++++++ 6 files changed, 30 insertions(+) create mode 100644 packages/cli-hermes/package.json create mode 100644 packages/cli-hermes/src/index.ts rename packages/{cli/src/commands/profile-hermes => cli-hermes/src/profileHermes}/downloadProfile.ts (100%) rename packages/{cli/src/commands/profile-hermes => cli-hermes/src/profileHermes}/index.ts (100%) rename packages/{cli/src/commands/profile-hermes => cli-hermes/src/profileHermes}/sourcemapUtils.ts (100%) create mode 100644 packages/cli-hermes/tsconfig.json diff --git a/packages/cli-hermes/package.json b/packages/cli-hermes/package.json new file mode 100644 index 000000000..7c738df98 --- /dev/null +++ b/packages/cli-hermes/package.json @@ -0,0 +1,21 @@ +{ + "name": "@react-native-community/cli-hermes", + "version": "4.11.0", + "license": "MIT", + "main": "build/index.js", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@react-native-community/cli-tools": "^4.11.0" + }, + "files": [ + "build", + "!*.d.ts", + "!*.map", + "native_modules.gradle" + ], + "devDependencies": { + "@react-native-community/cli-types": "^4.10.1" + } +} diff --git a/packages/cli-hermes/src/index.ts b/packages/cli-hermes/src/index.ts new file mode 100644 index 000000000..c895f67c3 --- /dev/null +++ b/packages/cli-hermes/src/index.ts @@ -0,0 +1 @@ +export {default as commands} from './profileHermes'; diff --git a/packages/cli/src/commands/profile-hermes/downloadProfile.ts b/packages/cli-hermes/src/profileHermes/downloadProfile.ts similarity index 100% rename from packages/cli/src/commands/profile-hermes/downloadProfile.ts rename to packages/cli-hermes/src/profileHermes/downloadProfile.ts diff --git a/packages/cli/src/commands/profile-hermes/index.ts b/packages/cli-hermes/src/profileHermes/index.ts similarity index 100% rename from packages/cli/src/commands/profile-hermes/index.ts rename to packages/cli-hermes/src/profileHermes/index.ts diff --git a/packages/cli/src/commands/profile-hermes/sourcemapUtils.ts b/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts similarity index 100% rename from packages/cli/src/commands/profile-hermes/sourcemapUtils.ts rename to packages/cli-hermes/src/profileHermes/sourcemapUtils.ts diff --git a/packages/cli-hermes/tsconfig.json b/packages/cli-hermes/tsconfig.json new file mode 100644 index 000000000..1ec7ea396 --- /dev/null +++ b/packages/cli-hermes/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build" + }, + "references": [{"path": "../tools"}, {"path": "../cli-types"}] +} From ed90c7e747ef081544a313381410298bcf2098ca Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 18 Aug 2020 16:15:44 +0700 Subject: [PATCH 31/53] deleted the command from projectCommands in cli --- packages/cli/src/commands/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index b4d9d6a8e..94fdebfbf 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -11,7 +11,6 @@ import info from './info/info'; import config from './config/config'; import init from './init'; import doctor from './doctor'; -import profile from './profile-hermes'; export const projectCommands = [ start, @@ -25,7 +24,6 @@ export const projectCommands = [ info, config, doctor, - profile, ] as Command[]; export const detachedCommands = [init, doctor] as DetachedCommand[]; From aacd874d455383fe076f8f202c940e6721ebd6fb Mon Sep 17 00:00:00 2001 From: Saphal Patro Date: Tue, 18 Aug 2020 19:01:20 +0700 Subject: [PATCH 32/53] re-added the command in the projectCommands --- packages/cli-hermes/package.json | 7 ++++--- packages/cli-hermes/src/index.ts | 2 +- packages/cli/package.json | 1 + packages/cli/src/commands/index.ts | 2 ++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/cli-hermes/package.json b/packages/cli-hermes/package.json index 7c738df98..cf17b6378 100644 --- a/packages/cli-hermes/package.json +++ b/packages/cli-hermes/package.json @@ -7,13 +7,14 @@ "access": "public" }, "dependencies": { - "@react-native-community/cli-tools": "^4.11.0" + "@react-native-community/cli-tools": "^4.11.0", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5" }, "files": [ "build", "!*.d.ts", - "!*.map", - "native_modules.gradle" + "!*.map" ], "devDependencies": { "@react-native-community/cli-types": "^4.10.1" diff --git a/packages/cli-hermes/src/index.ts b/packages/cli-hermes/src/index.ts index c895f67c3..a76fc72d0 100644 --- a/packages/cli-hermes/src/index.ts +++ b/packages/cli-hermes/src/index.ts @@ -1 +1 @@ -export {default as commands} from './profileHermes'; +export {default} from './profileHermes'; diff --git a/packages/cli/package.json b/packages/cli/package.json index 1b64aa225..7935579c0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,6 +33,7 @@ "@react-native-community/cli-server-api": "^4.11.0", "@react-native-community/cli-tools": "^4.11.0", "@react-native-community/cli-types": "^4.10.1", + "@react-native-community/cli-hermes": "^4.11.0", "@types/ip": "^1.1.0", "chalk": "^3.0.0", "command-exists": "^1.2.8", diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 94fdebfbf..95cd0739d 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -11,6 +11,7 @@ import info from './info/info'; import config from './config/config'; import init from './init'; import doctor from './doctor'; +import profile from '@react-native-community/cli-hermes'; export const projectCommands = [ start, @@ -24,6 +25,7 @@ export const projectCommands = [ info, config, doctor, + profile, ] as Command[]; export const detachedCommands = [init, doctor] as DetachedCommand[]; From 86cba713b43842f0bbf805992d1c507419e0a209 Mon Sep 17 00:00:00 2001 From: Saphal Patro Date: Tue, 18 Aug 2020 19:16:24 +0700 Subject: [PATCH 33/53] removed unused dependencies from cli --- packages/cli/package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 7935579c0..739796c64 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -30,10 +30,10 @@ "dependencies": { "@hapi/joi": "^15.0.3", "@react-native-community/cli-debugger-ui": "^4.9.0", + "@react-native-community/cli-hermes": "^4.11.0", "@react-native-community/cli-server-api": "^4.11.0", "@react-native-community/cli-tools": "^4.11.0", "@react-native-community/cli-types": "^4.10.1", - "@react-native-community/cli-hermes": "^4.11.0", "@types/ip": "^1.1.0", "chalk": "^3.0.0", "command-exists": "^1.2.8", @@ -46,9 +46,7 @@ "fs-extra": "^8.1.0", "glob": "^7.1.3", "graceful-fs": "^4.1.3", - "hermes-profile-transformer": "^0.0.6", "inquirer": "^3.0.6", - "ip": "^1.1.5", "leven": "^3.1.0", "lodash": "^4.17.15", "metro": "^0.58.0", From 48b1b0f0d12cf4290ccb73e16d13d9702aec69eb Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 18 Aug 2020 22:32:08 +0700 Subject: [PATCH 34/53] added cli-hermes to the list of yarn link --- CONTRIBUTING.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a784b4611..fc76b5865 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,6 +25,7 @@ This repository is split into two packages: _Note: you must use the `--watchFolders` flag with the `start` command when testing the CLI with `yarn link` like this. Otherwise Metro can't find the symlinked folder and this may result in errors such as `ReferenceError: SHA-1 for file ... is not computed`._ ### Setup + Because of a modular design of the CLI, we recommend developing using symbolic links to its packages. This way you can use it seamlessly in the tested project, as you'd use the locally installed CLI. Here's what you need to run in the terminal: #### yarn v1 @@ -33,10 +34,12 @@ Because of a modular design of the CLI, we recommend developing using symbolic l cd /path/to/cloned/cli/ yarn link-packages ``` + And then: + ```sh cd /my/new/react-native/project/ -yarn link "@react-native-community/cli-platform-ios" "@react-native-community/cli-platform-android" "@react-native-community/cli" "@react-native-community/cli-server-api" "@react-native-community/cli-types" "@react-native-community/cli-tools" "@react-native-community/cli-debugger-ui" +yarn link "@react-native-community/cli-platform-ios" "@react-native-community/cli-platform-android" "@react-native-community/cli" "@react-native-community/cli-server-api" "@react-native-community/cli-types" "@react-native-community/cli-tools" "@react-native-community/cli-debugger-ui" "@react-native-community/cli-hermes" ``` Once you're done with testing and you'd like to get back to regular setup, run `yarn unlink` instead of `yarn link` from above command. Then `yarn install --force`. @@ -51,6 +54,7 @@ yarn link /path/to/cloned/cli/ --all When you'd like to revert to a regular setup, you will need to revert the changes made to the `resolutions` field of `package.json`. ### Running + ```sh yarn react-native start --watchFolders /path/to/cloned/cli/ yarn react-native run-android From e3ff688cf6e023a0c3256f4c30940dfa273a23f8 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 18 Aug 2020 22:47:54 +0700 Subject: [PATCH 35/53] removed unused @types/ip dependency --- packages/cli/package.json | 1 - yarn.lock | 7 ------- 2 files changed, 8 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 739796c64..1d49f8238 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,7 +34,6 @@ "@react-native-community/cli-server-api": "^4.11.0", "@react-native-community/cli-tools": "^4.11.0", "@react-native-community/cli-types": "^4.10.1", - "@types/ip": "^1.1.0", "chalk": "^3.0.0", "command-exists": "^1.2.8", "commander": "^2.19.0", diff --git a/yarn.lock b/yarn.lock index 005f5c608..8fbfc9b3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2276,13 +2276,6 @@ dependencies: "@types/hapi__joi" "*" -"@types/ip@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@types/ip/-/ip-1.1.0.tgz#aec4f5bfd49e4a4c53b590d88c36eb078827a7c0" - integrity sha512-dwNe8gOoF70VdL6WJBwVHtQmAX4RMd62M+mAB9HQFjG1/qiCLM/meRy95Pd14FYBbEDwCq7jgJs89cHpLBu4HQ== - dependencies: - "@types/node" "*" - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" From a0ad665737be20578a3605eaa38bfae670390e42 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 18 Aug 2020 22:52:29 +0700 Subject: [PATCH 36/53] added @types/ip dependency in cli-hermes --- packages/cli-hermes/package.json | 1 + yarn.lock | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/packages/cli-hermes/package.json b/packages/cli-hermes/package.json index cf17b6378..70f7f41d0 100644 --- a/packages/cli-hermes/package.json +++ b/packages/cli-hermes/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "@react-native-community/cli-tools": "^4.11.0", + "@types/ip": "^1.1.0", "hermes-profile-transformer": "^0.0.6", "ip": "^1.1.5" }, diff --git a/yarn.lock b/yarn.lock index 31ea6331e..eac08a908 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2276,6 +2276,13 @@ dependencies: "@types/hapi__joi" "*" +"@types/ip@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/ip/-/ip-1.1.0.tgz#aec4f5bfd49e4a4c53b590d88c36eb078827a7c0" + integrity sha512-dwNe8gOoF70VdL6WJBwVHtQmAX4RMd62M+mAB9HQFjG1/qiCLM/meRy95Pd14FYBbEDwCq7jgJs89cHpLBu4HQ== + dependencies: + "@types/node" "*" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" From 1735396cb78311645bccae15d7a3666853771339 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 18 Aug 2020 23:05:42 +0700 Subject: [PATCH 37/53] moved @types/ip to dev dependency --- packages/cli-hermes/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli-hermes/package.json b/packages/cli-hermes/package.json index 70f7f41d0..7eb67967c 100644 --- a/packages/cli-hermes/package.json +++ b/packages/cli-hermes/package.json @@ -8,7 +8,6 @@ }, "dependencies": { "@react-native-community/cli-tools": "^4.11.0", - "@types/ip": "^1.1.0", "hermes-profile-transformer": "^0.0.6", "ip": "^1.1.5" }, @@ -18,6 +17,7 @@ "!*.map" ], "devDependencies": { - "@react-native-community/cli-types": "^4.10.1" + "@react-native-community/cli-types": "^4.10.1", + "@types/ip": "^1.1.0" } } From 1b0438ba20fa32b7244af1e738c9465ac31ebb8c Mon Sep 17 00:00:00 2001 From: Saphal Patro Date: Tue, 18 Aug 2020 23:14:31 +0700 Subject: [PATCH 38/53] added type definition for cli-hermes --- packages/cli-hermes/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli-hermes/package.json b/packages/cli-hermes/package.json index 7eb67967c..fca9a2624 100644 --- a/packages/cli-hermes/package.json +++ b/packages/cli-hermes/package.json @@ -6,6 +6,7 @@ "publishConfig": { "access": "public" }, + "types": "dist/index.d.ts", "dependencies": { "@react-native-community/cli-tools": "^4.11.0", "hermes-profile-transformer": "^0.0.6", From 5ebb4ba43d92f38792220e1573dc0f2d8df75559 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 18 Aug 2020 23:29:16 +0700 Subject: [PATCH 39/53] fixed type definition --- packages/cli-hermes/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-hermes/package.json b/packages/cli-hermes/package.json index fca9a2624..a905a307c 100644 --- a/packages/cli-hermes/package.json +++ b/packages/cli-hermes/package.json @@ -6,7 +6,7 @@ "publishConfig": { "access": "public" }, - "types": "dist/index.d.ts", + "types": "build/index.d.ts", "dependencies": { "@react-native-community/cli-tools": "^4.11.0", "hermes-profile-transformer": "^0.0.6", From 236b0384aebf7ee415e4fc888154ebbf7b9412e9 Mon Sep 17 00:00:00 2001 From: Saphal Patro <31125345+saphal1998@users.noreply.github.com> Date: Tue, 18 Aug 2020 22:25:32 +0530 Subject: [PATCH 40/53] Added cli-hermes ref to tsconfig in packages/cli --- packages/cli/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index daf679bae..f3d42e37b 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -8,6 +8,7 @@ {"path": "../tools"}, {"path": "../cli-types"}, {"path": "../debugger-ui"}, - {"path": "../cli-server-api"} + {"path": "../cli-server-api"}, + {"path": "../cli-hermes"} ] } From f7cb65880b1fcef58a9eb564e3c488b51a2bcde7 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen <47696418+jessieAnhNguyen@users.noreply.github.com> Date: Wed, 19 Aug 2020 22:44:48 +0700 Subject: [PATCH 41/53] Removed unnecessary code comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Pierzchała --- packages/cli-hermes/src/profileHermes/sourcemapUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts b/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts index e2d58298d..674175347 100644 --- a/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts +++ b/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts @@ -78,7 +78,7 @@ export async function findSourcemap(ctx: Config): Promise { 'android', 'app', 'build', - 'intermediates', //'generated', + 'intermediates', 'sourcemaps', 'react', 'debug', From c85506e431e3a7e4f61b412f47cfaf7a2d20ba8d Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Wed, 19 Aug 2020 23:00:06 +0700 Subject: [PATCH 42/53] made changes based on PR review --- docs/commands.md | 10 +- packages/cli-hermes/package.json | 1 + packages/cli-hermes/src/index.ts | 3 +- .../src/profileHermes/downloadProfile.ts | 101 +++++------------- .../cli-hermes/src/profileHermes/index.ts | 24 ++--- .../src/profileHermes/sourcemapUtils.ts | 73 ++++++------- packages/cli/src/commands/index.ts | 4 +- .../src/commands/runAndroid/index.ts | 40 +------ packages/platform-android/src/index.ts | 1 + .../src/utils/getAndroidProject.ts | 56 ++++++++++ yarn.lock | 8 ++ 11 files changed, 150 insertions(+), 171 deletions(-) create mode 100644 packages/platform-android/src/utils/getAndroidProject.ts diff --git a/docs/commands.md b/docs/commands.md index 5a30ec63e..c7b7d4ed6 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -569,10 +569,6 @@ File name of the profile to be downloaded, eg. sampling-profiler-trace8593107139 > default: pull the latest file -#### `--verbose` - -Lists commands and steps that are run internally to pull the file from Android device - #### `--raw` Pulls the original Hermes tracing profile without any transformation @@ -585,6 +581,12 @@ The local path to your source map file if you generated it manually, ex. `/tmp/s Generate the JS bundle and source map in `os.tmpdir()` +#### '--port [number]', + +The running metro server port number + +> default: 8081 + ### Notes on source map This step is recommended in order for the source map to be generated: diff --git a/packages/cli-hermes/package.json b/packages/cli-hermes/package.json index a905a307c..478aa9ad0 100644 --- a/packages/cli-hermes/package.json +++ b/packages/cli-hermes/package.json @@ -9,6 +9,7 @@ "types": "build/index.d.ts", "dependencies": { "@react-native-community/cli-tools": "^4.11.0", + "chalk": "^4.1.0", "hermes-profile-transformer": "^0.0.6", "ip": "^1.1.5" }, diff --git a/packages/cli-hermes/src/index.ts b/packages/cli-hermes/src/index.ts index a76fc72d0..6d7cef0e7 100644 --- a/packages/cli-hermes/src/index.ts +++ b/packages/cli-hermes/src/index.ts @@ -1 +1,2 @@ -export {default} from './profileHermes'; +import profileHermes from './profileHermes/index'; +export default profileHermes; diff --git a/packages/cli-hermes/src/profileHermes/downloadProfile.ts b/packages/cli-hermes/src/profileHermes/downloadProfile.ts index 857b898d4..66ffdedaf 100644 --- a/packages/cli-hermes/src/profileHermes/downloadProfile.ts +++ b/packages/cli-hermes/src/profileHermes/downloadProfile.ts @@ -1,12 +1,15 @@ import {Config} from '@react-native-community/cli-types'; import {execSync} from 'child_process'; import {logger, CLIError} from '@react-native-community/cli-tools'; -import chalk from 'chalk'; import fs from 'fs'; import path from 'path'; import os from 'os'; import transformer from 'hermes-profile-transformer'; import {findSourcemap, generateSourcemap} from './sourcemapUtils'; +import { + getAndroidProject, + getPackageName, +} from '@react-native-community/cli-platform-android'; /** * Get the last modified hermes profile * @param packageName @@ -22,59 +25,10 @@ function getLatestFile(packageName: string): string { } function execSyncWithLog(command: string) { - if (logger.isVerbose()) { - logger.debug(`${command}\n`); - } - + logger.debug(`${command}`); return execSync(command); } -/** - * Get the package name of the running React Native app - * @param config - */ -function getPackageName(config: Config) { - const androidProject = config.project.android; - - if (!androidProject) { - throw new CLIError(` - Android project not found. Are you sure this is a React Native project? - If your Android files are located in a non-standard location (e.g. not inside \'android\' folder), consider setting - \`project.android.sourceDir\` option to point to a new location. -`); - } - const {manifestPath} = androidProject; - const androidManifest = fs.readFileSync(manifestPath, 'utf8'); - - let packageNameMatchArray = androidManifest.match(/package="(.+?)"/); - if (!packageNameMatchArray || packageNameMatchArray.length === 0) { - throw new CLIError( - 'Failed to build the app: No package name found. Found errors in /src/main/AndroidManifest.xml', - ); - } - - let packageName = packageNameMatchArray[1]; - - if (!validatePackageName(packageName)) { - logger.warn( - `Invalid application's package name "${chalk.bgRed( - packageName, - )}" in 'AndroidManifest.xml'. Read guidelines for setting the package name here: ${chalk.underline.dim( - 'https://developer.android.com/studio/build/application-id', - )}`, - ); - } - return packageName; -} - -/** - * Validates that the package name is correct - * @param packageName - */ -function validatePackageName(packageName: string) { - return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(packageName); -} - /** * Pull and convert a Hermes tracing profile to Chrome tracing profile * @param ctx @@ -91,65 +45,64 @@ export async function downloadProfile( sourcemapPath?: string, raw?: boolean, shouldGenerateSourcemap?: boolean, + port?: string, ) { try { - const packageName = getPackageName(ctx); + const androidProject = getAndroidProject(ctx); + const packageName = getPackageName(androidProject); - // if file name is not specified, pull the latest file from device + // If file name is not specified, pull the latest file from device const file = fileName || (await getLatestFile(packageName)); if (!file) { - logger.error( - 'There is no file in the cache/ directory. Did you record a profile from the developer menu?\n', + throw new CLIError( + 'There is no file in the cache/ directory. Did you record a profile from the developer menu?', ); - process.exit(1); } - logger.info(`File to be pulled: ${file}\n`); + logger.info(`File to be pulled: ${file}`); - //if destination path is not specified, pull to the current directory + // If destination path is not specified, pull to the current directory dstPath = dstPath || ctx.root; - if (logger.isVerbose()) { - logger.info('Internal commands run to pull the file:\n'); - } + logger.debug('Internal commands run to pull the file:'); - //Copy the file from device's data to sdcard, then pull the file to a temp directory + // Copy the file from device's data to sdcard, then pull the file to a temp directory execSyncWithLog(`adb shell run-as ${packageName} cp cache/${file} /sdcard`); - //If --raw, pull the hermes profile to dstPath + // If --raw, pull the hermes profile to dstPath if (raw) { execSyncWithLog(`adb pull /sdcard/${file} ${dstPath}`); - logger.success(`Successfully pulled the file to ${dstPath}/${file}\n`); + logger.success(`Successfully pulled the file to ${dstPath}/${file}`); } - //Else: transform the profile to Chrome format and pull it to dstPath + // Else: transform the profile to Chrome format and pull it to dstPath else { const osTmpDir = os.tmpdir(); const tempFilePath = path.join(osTmpDir, file); execSyncWithLog(`adb pull /sdcard/${file} ${tempFilePath}`); - //If path to source map is not given + // If path to source map is not given if (!sourcemapPath) { - //Get or generate the source map + // Get or generate the source map if (shouldGenerateSourcemap) { - sourcemapPath = await generateSourcemap(); + sourcemapPath = await generateSourcemap(port); } else { - sourcemapPath = await findSourcemap(ctx); + sourcemapPath = await findSourcemap(ctx, port); } - //Run without source map + // Run without source map if (!sourcemapPath) { logger.warn( - 'Cannot generate or find bundle and source map, running the transformer without source map\n', + 'Cannot find source maps, running the transformer without it', ); logger.info( - 'Instructions on how to get source map: set `bundleInDebug: true` in your app/build.gradle file, inside the `project.ext.react` map.\n', + 'Instructions on how to get source maps: set `bundleInDebug: true` in your app/build.gradle file, inside the `project.ext.react` map.', ); } } - //Run transformer tool to convert from Hermes to Chrome format + // Run transformer tool to convert from Hermes to Chrome format const events = await transformer( tempFilePath, sourcemapPath, @@ -166,7 +119,7 @@ export async function downloadProfile( 'utf-8', ); logger.success( - `Successfully converted to Chrome tracing format and pulled the file to ${transformedFilePath}\n`, + `Successfully converted to Chrome tracing format and pulled the file to ${transformedFilePath}`, ); } } catch (e) { diff --git a/packages/cli-hermes/src/profileHermes/index.ts b/packages/cli-hermes/src/profileHermes/index.ts index 57b7139c0..a824cb5d9 100644 --- a/packages/cli-hermes/src/profileHermes/index.ts +++ b/packages/cli-hermes/src/profileHermes/index.ts @@ -1,13 +1,13 @@ -import {logger} from '@react-native-community/cli-tools'; +import {logger, CLIError} from '@react-native-community/cli-tools'; import {Config} from '@react-native-community/cli-types'; import {downloadProfile} from './downloadProfile'; type Options = { - verbose: boolean; fileName?: string; raw?: boolean; sourcemapPath?: string; generateSourcemap?: boolean; + port?: string; }; async function profileHermes( @@ -17,13 +17,10 @@ async function profileHermes( ) { try { logger.info( - 'Downloading a Hermes Sampling Profiler from your Android device...\n', + 'Downloading a Hermes Sampling Profiler from your Android device...', ); if (!options.fileName) { - logger.info('No filename is provided, pulling latest file\n'); - } - if (options.verbose) { - logger.setVerbose(true); + logger.info('No filename is provided, pulling latest file'); } await downloadProfile( ctx, @@ -32,9 +29,10 @@ async function profileHermes( options.sourcemapPath, options.raw, options.generateSourcemap, + options.port, ); } catch (err) { - logger.error(`Unable to download the Hermes Sampling Profile.\n${err}`); + throw new CLIError('Failed to download the Hermes Sampling Profile.', err); } } @@ -49,11 +47,6 @@ export default { description: 'File name of the profile to be downloaded, eg. sampling-profiler-trace8593107139682635366.cpuprofile', }, - { - name: '--verbose', - description: - 'Lists commands and steps that are run internally when pulling the file from Android device', - }, { name: '--raw', description: @@ -68,6 +61,11 @@ export default { name: '--generate-sourcemap', description: 'Generates the JS bundle and source map', }, + { + name: '--port [number]', + default: process.env.RCT_METRO_PORT || 8081, + parse: (val: number) => String(val), + }, ], examples: [ { diff --git a/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts b/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts index e2d58298d..cb028bd94 100644 --- a/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts +++ b/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts @@ -5,7 +5,7 @@ import path from 'path'; import os from 'os'; import {SourceMap} from 'hermes-profile-transformer'; import ip from 'ip'; -import nodeFetch from 'node-fetch'; +import {fetch} from '@react-native-community/cli-tools'; function getTempFilePath(filename: string) { return path.join(os.tmpdir(), filename); @@ -16,8 +16,8 @@ function writeJsonSync(targetPath: string, data: any) { try { json = JSON.stringify(data); } catch (e) { - logger.error( - `Failed to serialize data to json before writing to ${targetPath}\n`, + throw new CLIError( + `Failed to serialize data to json before writing to ${targetPath}`, e, ); } @@ -25,54 +25,58 @@ function writeJsonSync(targetPath: string, data: any) { try { fs.writeFileSync(targetPath, json, 'utf-8'); } catch (e) { - logger.error(`Failed to write json to ${targetPath}\n`, e); + throw new CLIError(`Failed to write json to ${targetPath}`, e); } } -async function getSourcemapFromServer(): Promise { - logger.debug('Getting source maps from Metro packager server\n'); - const DEBUG_SERVER_PORT = '8081'; +async function getSourcemapFromServer( + port?: string, +): Promise { + logger.debug('Getting source maps from Metro packager server'); + const DEBUG_SERVER_PORT = port || '8081'; const IP_ADDRESS = ip.address(); const PLATFORM = 'android'; const requestURL = `http://${IP_ADDRESS}:${DEBUG_SERVER_PORT}/index.map?platform=${PLATFORM}&dev=true`; - const result = await nodeFetch(requestURL); - if (result.status >= 400) { - logger.error('Cannot get source map from running metro server\n'); - throw new CLIError(`Fetch request failed with status ${result.status}.`); + try { + const {data} = await fetch(requestURL); + return data as SourceMap; + } catch (e) { + return undefined; } - const data = await result.json(); - return data as SourceMap; } /** * Generate a sourcemap by fetching it from a running metro server */ -export async function generateSourcemap(): Promise { - // fetch the source map to a temp directory +export async function generateSourcemap( + port?: string, +): Promise { + // Fetch the source map to a temp directory const sourceMapPath = getTempFilePath('index.map'); - const sourceMapResult = await getSourcemapFromServer(); + const sourceMapResult = await getSourcemapFromServer(port); if (sourceMapResult) { - if (logger.isVerbose()) { - logger.debug('Using source maps from Metro packager server\n'); - } + logger.debug('Using source maps from Metro packager server'); writeJsonSync(sourceMapPath, sourceMapResult); logger.info( - `Successfully generated the source map and stored it in ${sourceMapPath}\n`, + `Successfully obtained the source map and stored it in ${sourceMapPath}`, ); + return sourceMapPath; } else { - logger.error('Cannot generate source maps from Metro packager server\n`'); + logger.error('Cannot obtain source maps from Metro packager server'); + return undefined; } - - return sourceMapPath; } /** * * @param ctx */ -export async function findSourcemap(ctx: Config): Promise { +export async function findSourcemap( + ctx: Config, + port?: string, +): Promise { const intermediateBuildPath = path.join( ctx.root, 'android', @@ -98,23 +102,12 @@ export async function findSourcemap(ctx: Config): Promise { ); if (fs.existsSync(generatedBuildPath)) { - if (logger.isVerbose()) { - logger.debug(`Getting the source map from ${generateSourcemap}\n`); - } - return Promise.resolve(generatedBuildPath); + logger.debug(`Getting the source map from ${generateSourcemap}`); + return generatedBuildPath; } else if (fs.existsSync(intermediateBuildPath)) { - if (logger.isVerbose()) { - logger.debug(`Getting the source map from ${intermediateBuildPath}\n`); - } - return Promise.resolve(intermediateBuildPath); + logger.debug(`Getting the source map from ${intermediateBuildPath}`); + return intermediateBuildPath; } else { - const sourcemapResult = await getSourcemapFromServer(); - if (logger.isVerbose()) { - logger.debug('Using source maps from Metro packager server\n'); - } - const tempPath = getTempFilePath('index.map'); - writeJsonSync(tempPath, sourcemapResult); - - return tempPath; + return generateSourcemap(port); } } diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 95cd0739d..fc5b657fe 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -11,7 +11,7 @@ import info from './info/info'; import config from './config/config'; import init from './init'; import doctor from './doctor'; -import profile from '@react-native-community/cli-hermes'; +import profileHermes from '@react-native-community/cli-hermes'; export const projectCommands = [ start, @@ -25,7 +25,7 @@ export const projectCommands = [ info, config, doctor, - profile, + profileHermes, ] as Command[]; export const detachedCommands = [init, doctor] as DetachedCommand[]; diff --git a/packages/platform-android/src/commands/runAndroid/index.ts b/packages/platform-android/src/commands/runAndroid/index.ts index b0ed8b54d..4a13c5169 100644 --- a/packages/platform-android/src/commands/runAndroid/index.ts +++ b/packages/platform-android/src/commands/runAndroid/index.ts @@ -22,11 +22,7 @@ import { CLIError, } from '@react-native-community/cli-tools'; import warnAboutManuallyLinkedLibs from '../../link/warnAboutManuallyLinkedLibs'; - -// Validates that the package name is correct -function validatePackageName(packageName: string) { - return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(packageName); -} +import {getAndroidProject, getPackageName} from '../../utils/getAndroidProject'; function displayWarnings(config: Config, args: Flags) { warnAboutManuallyLinkedLibs(config); @@ -64,15 +60,7 @@ type AndroidProject = NonNullable; */ async function runAndroid(_argv: Array, config: Config, args: Flags) { displayWarnings(config, args); - const androidProject = config.project.android; - - if (!androidProject) { - throw new CLIError(` - Android project not found. Are you sure this is a React Native project? - If your Android files are located in a non-standard location (e.g. not inside \'android\' folder), consider setting - \`project.android.sourceDir\` option to point to a new location. -`); - } + const androidProject = getAndroidProject(config); if (args.jetifier) { logger.info( @@ -126,30 +114,8 @@ function buildAndRun(args: Flags, androidProject: AndroidProject) { const cmd = process.platform.startsWith('win') ? 'gradlew.bat' : './gradlew'; // "app" is usually the default value for Android apps with only 1 app - const {appName, manifestPath} = androidProject; const {appFolder} = args; - const androidManifest = fs.readFileSync(manifestPath, 'utf8'); - - let packageNameMatchArray = androidManifest.match(/package="(.+?)"/); - if (!packageNameMatchArray || packageNameMatchArray.length === 0) { - throw new CLIError( - `Failed to build the app: No package name found. Found errors in ${chalk.underline.dim( - `${appFolder || appName}/src/main/AndroidManifest.xml`, - )}`, - ); - } - - let packageName = packageNameMatchArray[1]; - - if (!validatePackageName(packageName)) { - logger.warn( - `Invalid application's package name "${chalk.bgRed( - packageName, - )}" in 'AndroidManifest.xml'. Read guidelines for setting the package name here: ${chalk.underline.dim( - 'https://developer.android.com/studio/build/application-id', - )}`, - ); // we can also directly add the package naming rules here - } + const packageName = getPackageName(androidProject, appFolder); const adbPath = getAdbPath(); if (args.deviceId) { diff --git a/packages/platform-android/src/index.ts b/packages/platform-android/src/index.ts index 828fcb43b..d03bb9749 100644 --- a/packages/platform-android/src/index.ts +++ b/packages/platform-android/src/index.ts @@ -5,3 +5,4 @@ export {default as linkConfig} from './link'; export {default as commands} from './commands'; export {projectConfig, dependencyConfig} from './config'; +export {getAndroidProject, getPackageName} from './utils/getAndroidProject'; diff --git a/packages/platform-android/src/utils/getAndroidProject.ts b/packages/platform-android/src/utils/getAndroidProject.ts new file mode 100644 index 000000000..64d9c7031 --- /dev/null +++ b/packages/platform-android/src/utils/getAndroidProject.ts @@ -0,0 +1,56 @@ +import {Config, AndroidProjectConfig} from '@react-native-community/cli-types'; +import {logger, CLIError} from '@react-native-community/cli-tools'; +import fs from 'fs'; +import chalk from 'chalk'; + +export function getAndroidProject(config: Config) { + const androidProject = config.project.android; + + if (!androidProject) { + throw new CLIError(` + Android project not found. Are you sure this is a React Native project? + If your Android files are located in a non-standard location (e.g. not inside \'android\' folder), consider setting + \`project.android.sourceDir\` option to point to a new location. + `); + } + return androidProject; +} + +/** + * Get the package name of the running React Native app + * @param config + */ +export function getPackageName( + androidProject: AndroidProjectConfig, + appFolder?: string, +) { + const {appName, manifestPath} = androidProject; + const androidManifest = fs.readFileSync(manifestPath, 'utf8'); + + let packageNameMatchArray = androidManifest.match(/package="(.+?)"/); + if (!packageNameMatchArray || packageNameMatchArray.length === 0) { + throw new CLIError( + `Failed to build the app: No package name found. Found errors in ${chalk.underline.dim( + `${appFolder || appName}/src/main/AndroidManifest.xml`, + )}`, + ); + } + + let packageName = packageNameMatchArray[1]; + + if (!validatePackageName(packageName)) { + logger.warn( + `Invalid application's package name "${chalk.bgRed( + packageName, + )}" in 'AndroidManifest.xml'. Read guidelines for setting the package name here: ${chalk.underline.dim( + 'https://developer.android.com/studio/build/application-id', + )}`, + ); + } + return packageName; +} + +// Validates that the package name is correct +function validatePackageName(packageName: string) { + return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(packageName); +} diff --git a/yarn.lock b/yarn.lock index eac08a908..d73b34964 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3455,6 +3455,14 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" From b7dbb5e8c04cc531ccb91e0b2cc7b53f928fc883 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Wed, 19 Aug 2020 23:03:42 +0700 Subject: [PATCH 43/53] added a verbose log --- packages/cli-hermes/src/profileHermes/sourcemapUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts b/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts index 0d53d430f..15b6d3331 100644 --- a/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts +++ b/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts @@ -59,7 +59,7 @@ export async function generateSourcemap( if (sourceMapResult) { logger.debug('Using source maps from Metro packager server'); writeJsonSync(sourceMapPath, sourceMapResult); - logger.info( + logger.debug( `Successfully obtained the source map and stored it in ${sourceMapPath}`, ); return sourceMapPath; From 27f14d9fd0a64458b13d7e6bd2388d7313df2360 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Wed, 19 Aug 2020 23:25:15 +0700 Subject: [PATCH 44/53] added cli-platform-android as a dependency --- packages/cli-hermes/package.json | 1 + packages/cli-hermes/src/profileHermes/downloadProfile.ts | 2 +- packages/cli-hermes/tsconfig.json | 6 +++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/cli-hermes/package.json b/packages/cli-hermes/package.json index 478aa9ad0..d5f66d712 100644 --- a/packages/cli-hermes/package.json +++ b/packages/cli-hermes/package.json @@ -9,6 +9,7 @@ "types": "build/index.d.ts", "dependencies": { "@react-native-community/cli-tools": "^4.11.0", + "@react-native-community/cli-platform-android": "^4.11.0", "chalk": "^4.1.0", "hermes-profile-transformer": "^0.0.6", "ip": "^1.1.5" diff --git a/packages/cli-hermes/src/profileHermes/downloadProfile.ts b/packages/cli-hermes/src/profileHermes/downloadProfile.ts index 66ffdedaf..7f21d3e9f 100644 --- a/packages/cli-hermes/src/profileHermes/downloadProfile.ts +++ b/packages/cli-hermes/src/profileHermes/downloadProfile.ts @@ -123,6 +123,6 @@ export async function downloadProfile( ); } } catch (e) { - throw new Error(e.message); + throw new CLIError('Failed to download the sampling profiler'); } } diff --git a/packages/cli-hermes/tsconfig.json b/packages/cli-hermes/tsconfig.json index 1ec7ea396..b9a076f6d 100644 --- a/packages/cli-hermes/tsconfig.json +++ b/packages/cli-hermes/tsconfig.json @@ -4,5 +4,9 @@ "rootDir": "src", "outDir": "build" }, - "references": [{"path": "../tools"}, {"path": "../cli-types"}] + "references": [ + {"path": "../tools"}, + {"path": "../cli-types"}, + {"path": "../cli-platform-android"} + ] } From 02263ae5e69f853deffb4a147022479d419c1c5b Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Wed, 19 Aug 2020 23:41:01 +0700 Subject: [PATCH 45/53] added cli-platform-android as a dependency in cli --- packages/cli/package.json | 1 + packages/cli/tsconfig.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 1d49f8238..d796526b8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,6 +34,7 @@ "@react-native-community/cli-server-api": "^4.11.0", "@react-native-community/cli-tools": "^4.11.0", "@react-native-community/cli-types": "^4.10.1", + "@react-native-community/cli-platform-android": "^4.11.0", "chalk": "^3.0.0", "command-exists": "^1.2.8", "commander": "^2.19.0", diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index f3d42e37b..7d4c1f4c7 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -9,6 +9,7 @@ {"path": "../cli-types"}, {"path": "../debugger-ui"}, {"path": "../cli-server-api"}, - {"path": "../cli-hermes"} + {"path": "../cli-hermes"}, + {"path": "../cli-platform-android"} ] } From 8fc1b01c4827c39b58cd9fdb24c4ed7a34d94fd0 Mon Sep 17 00:00:00 2001 From: Saphal Patro <31125345+saphal1998@users.noreply.github.com> Date: Wed, 19 Aug 2020 22:15:44 +0530 Subject: [PATCH 46/53] Fixing path in tsconfig --- packages/cli-hermes/tsconfig.json | 6 +++++- packages/cli/tsconfig.json | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/cli-hermes/tsconfig.json b/packages/cli-hermes/tsconfig.json index 1ec7ea396..1544a66ab 100644 --- a/packages/cli-hermes/tsconfig.json +++ b/packages/cli-hermes/tsconfig.json @@ -4,5 +4,9 @@ "rootDir": "src", "outDir": "build" }, - "references": [{"path": "../tools"}, {"path": "../cli-types"}] + "references": [ + {"path": "../tools"}, + {"path": "../cli-types"}, + {"path": "../platform-android"} + ] } diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index f3d42e37b..623427a2d 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -9,6 +9,7 @@ {"path": "../cli-types"}, {"path": "../debugger-ui"}, {"path": "../cli-server-api"}, - {"path": "../cli-hermes"} + {"path": "../cli-hermes"}, + {"path": "../platform-android"} ] } From 44782496c430a5f23f566a47bc5e9da2bbc4e698 Mon Sep 17 00:00:00 2001 From: Saphal Patro <31125345+saphal1998@users.noreply.github.com> Date: Wed, 19 Aug 2020 22:22:58 +0530 Subject: [PATCH 47/53] Added type defs of platform-android to cli-hermes, path fixed --- packages/cli-hermes/package.json | 1 + packages/cli-hermes/tsconfig.json | 6 +++++- packages/cli/package.json | 1 - packages/cli/tsconfig.json | 3 +-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/cli-hermes/package.json b/packages/cli-hermes/package.json index 478aa9ad0..d5f66d712 100644 --- a/packages/cli-hermes/package.json +++ b/packages/cli-hermes/package.json @@ -9,6 +9,7 @@ "types": "build/index.d.ts", "dependencies": { "@react-native-community/cli-tools": "^4.11.0", + "@react-native-community/cli-platform-android": "^4.11.0", "chalk": "^4.1.0", "hermes-profile-transformer": "^0.0.6", "ip": "^1.1.5" diff --git a/packages/cli-hermes/tsconfig.json b/packages/cli-hermes/tsconfig.json index 1ec7ea396..1544a66ab 100644 --- a/packages/cli-hermes/tsconfig.json +++ b/packages/cli-hermes/tsconfig.json @@ -4,5 +4,9 @@ "rootDir": "src", "outDir": "build" }, - "references": [{"path": "../tools"}, {"path": "../cli-types"}] + "references": [ + {"path": "../tools"}, + {"path": "../cli-types"}, + {"path": "../platform-android"} + ] } diff --git a/packages/cli/package.json b/packages/cli/package.json index d796526b8..1d49f8238 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,7 +34,6 @@ "@react-native-community/cli-server-api": "^4.11.0", "@react-native-community/cli-tools": "^4.11.0", "@react-native-community/cli-types": "^4.10.1", - "@react-native-community/cli-platform-android": "^4.11.0", "chalk": "^3.0.0", "command-exists": "^1.2.8", "commander": "^2.19.0", diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 623427a2d..f3d42e37b 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -9,7 +9,6 @@ {"path": "../cli-types"}, {"path": "../debugger-ui"}, {"path": "../cli-server-api"}, - {"path": "../cli-hermes"}, - {"path": "../platform-android"} + {"path": "../cli-hermes"} ] } From 93acfcddac885fa039544dea293f8a0ede93439d Mon Sep 17 00:00:00 2001 From: Saphal Patro <31125345+saphal1998@users.noreply.github.com> Date: Wed, 19 Aug 2020 22:27:59 +0530 Subject: [PATCH 48/53] Fixed chalk version on cli-hermes --- packages/cli-hermes/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-hermes/package.json b/packages/cli-hermes/package.json index d5f66d712..9e76ae880 100644 --- a/packages/cli-hermes/package.json +++ b/packages/cli-hermes/package.json @@ -10,7 +10,7 @@ "dependencies": { "@react-native-community/cli-tools": "^4.11.0", "@react-native-community/cli-platform-android": "^4.11.0", - "chalk": "^4.1.0", + "chalk": "^3.0.0", "hermes-profile-transformer": "^0.0.6", "ip": "^1.1.5" }, From 96071605fe1bf4a9586ad09f53330f036f9a9862 Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Fri, 21 Aug 2020 22:52:44 +0700 Subject: [PATCH 49/53] removed unnecessary comment --- packages/platform-android/src/commands/runAndroid/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/platform-android/src/commands/runAndroid/index.ts b/packages/platform-android/src/commands/runAndroid/index.ts index 4a13c5169..65b818067 100644 --- a/packages/platform-android/src/commands/runAndroid/index.ts +++ b/packages/platform-android/src/commands/runAndroid/index.ts @@ -113,7 +113,6 @@ function buildAndRun(args: Flags, androidProject: AndroidProject) { process.chdir(androidProject.sourceDir); const cmd = process.platform.startsWith('win') ? 'gradlew.bat' : './gradlew'; - // "app" is usually the default value for Android apps with only 1 app const {appFolder} = args; const packageName = getPackageName(androidProject, appFolder); From 3e97a315aef9d906755896c696472838f9b0b37e Mon Sep 17 00:00:00 2001 From: Jessie Anh Nguyen Date: Tue, 1 Sep 2020 19:11:09 +0700 Subject: [PATCH 50/53] changed --filename flag and throw real error --- packages/cli-hermes/src/profileHermes/downloadProfile.ts | 6 +++--- packages/cli-hermes/src/profileHermes/index.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/cli-hermes/src/profileHermes/downloadProfile.ts b/packages/cli-hermes/src/profileHermes/downloadProfile.ts index 7f21d3e9f..201d8dd38 100644 --- a/packages/cli-hermes/src/profileHermes/downloadProfile.ts +++ b/packages/cli-hermes/src/profileHermes/downloadProfile.ts @@ -41,7 +41,7 @@ function execSyncWithLog(command: string) { export async function downloadProfile( ctx: Config, dstPath: string, - fileName?: string, + filename?: string, sourcemapPath?: string, raw?: boolean, shouldGenerateSourcemap?: boolean, @@ -52,7 +52,7 @@ export async function downloadProfile( const packageName = getPackageName(androidProject); // If file name is not specified, pull the latest file from device - const file = fileName || (await getLatestFile(packageName)); + const file = filename || (await getLatestFile(packageName)); if (!file) { throw new CLIError( 'There is no file in the cache/ directory. Did you record a profile from the developer menu?', @@ -123,6 +123,6 @@ export async function downloadProfile( ); } } catch (e) { - throw new CLIError('Failed to download the sampling profiler'); + throw e; } } diff --git a/packages/cli-hermes/src/profileHermes/index.ts b/packages/cli-hermes/src/profileHermes/index.ts index a824cb5d9..97b83746f 100644 --- a/packages/cli-hermes/src/profileHermes/index.ts +++ b/packages/cli-hermes/src/profileHermes/index.ts @@ -3,7 +3,7 @@ import {Config} from '@react-native-community/cli-types'; import {downloadProfile} from './downloadProfile'; type Options = { - fileName?: string; + filename?: string; raw?: boolean; sourcemapPath?: string; generateSourcemap?: boolean; @@ -19,13 +19,13 @@ async function profileHermes( logger.info( 'Downloading a Hermes Sampling Profiler from your Android device...', ); - if (!options.fileName) { + if (!options.filename) { logger.info('No filename is provided, pulling latest file'); } await downloadProfile( ctx, dstPath, - options.fileName, + options.filename, options.sourcemapPath, options.raw, options.generateSourcemap, @@ -43,7 +43,7 @@ export default { func: profileHermes, options: [ { - name: '--fileName [string]', + name: '--filename [string]', description: 'File name of the profile to be downloaded, eg. sampling-profiler-trace8593107139682635366.cpuprofile', }, From 75095c5679a743a23aa3bd42b9aad56d6957a40e Mon Sep 17 00:00:00 2001 From: Saphal Patro Date: Thu, 3 Sep 2020 18:19:48 +0530 Subject: [PATCH 51/53] Fix catch block in profile-hermes/index to match the error thrown --- packages/cli-hermes/src/profileHermes/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli-hermes/src/profileHermes/index.ts b/packages/cli-hermes/src/profileHermes/index.ts index 97b83746f..e7b1e4fef 100644 --- a/packages/cli-hermes/src/profileHermes/index.ts +++ b/packages/cli-hermes/src/profileHermes/index.ts @@ -1,4 +1,4 @@ -import {logger, CLIError} from '@react-native-community/cli-tools'; +import {logger} from '@react-native-community/cli-tools'; import {Config} from '@react-native-community/cli-types'; import {downloadProfile} from './downloadProfile'; @@ -32,7 +32,7 @@ async function profileHermes( options.port, ); } catch (err) { - throw new CLIError('Failed to download the Hermes Sampling Profile.', err); + throw err; } } From 32a917e49a30fdc8ddadb558ccb79d3ab2a183c7 Mon Sep 17 00:00:00 2001 From: Saphal Patro Date: Thu, 3 Sep 2020 18:52:03 +0530 Subject: [PATCH 52/53] Typecast error as CLIError --- packages/cli-hermes/src/profileHermes/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli-hermes/src/profileHermes/index.ts b/packages/cli-hermes/src/profileHermes/index.ts index e7b1e4fef..6ab7da33b 100644 --- a/packages/cli-hermes/src/profileHermes/index.ts +++ b/packages/cli-hermes/src/profileHermes/index.ts @@ -1,4 +1,4 @@ -import {logger} from '@react-native-community/cli-tools'; +import {logger, CLIError} from '@react-native-community/cli-tools'; import {Config} from '@react-native-community/cli-types'; import {downloadProfile} from './downloadProfile'; @@ -32,7 +32,7 @@ async function profileHermes( options.port, ); } catch (err) { - throw err; + throw err as CLIError; } } From 8d6fb8fe4ed1dfc50496800cceee968ea2cb10df Mon Sep 17 00:00:00 2001 From: Saphal Patro Date: Fri, 4 Sep 2020 16:05:08 +0530 Subject: [PATCH 53/53] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed `filename` nit in the `commands.md` file - Improved debugging by logging an error in case source maps could not be fetched Co-authored-by: Michał Pierzchała --- docs/commands.md | 2 +- packages/cli-hermes/src/profileHermes/sourcemapUtils.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/commands.md b/docs/commands.md index c7b7d4ed6..c05e42f02 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -563,7 +563,7 @@ Pull and convert a Hermes tracing profile to Chrome tracing profile, then store #### Options -#### `--fileName [string]` +#### `--filename [string]` File name of the profile to be downloaded, eg. sampling-profiler-trace8593107139682635366.cpuprofile. diff --git a/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts b/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts index 15b6d3331..b1f3ee3df 100644 --- a/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts +++ b/packages/cli-hermes/src/profileHermes/sourcemapUtils.ts @@ -42,6 +42,7 @@ async function getSourcemapFromServer( const {data} = await fetch(requestURL); return data as SourceMap; } catch (e) { + logger.debug(`Failed to fetch source map from "${requestURL}"`); return undefined; } }