Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hermes-specific support for the trace event format #458

Merged
merged 5 commits into from
Dec 26, 2023

Conversation

zacharyfmarion
Copy link
Contributor

@zacharyfmarion zacharyfmarion commented Dec 26, 2023

Profiles that are transformed into the hermes trace event format are guaranteed to have specific arguments that supply metadata that is useful for debugging - namely the filename, line + col number that the function call originated from, with sourcemaps applied. This PR adds specific support for this information to the trace event importer. This means that we can have the Frame name be just the name of the function, since we know all the information we want to be displayed in the UI is captured in the frame info, which makes the traces cleaner to look at.

Before After
Screenshot 2023-12-26 at 2 40 01 PM Screenshot 2023-12-26 at 2 39 13 PM
Screenshot 2023-12-26 at 2 41 03 PM Screenshot 2023-12-26 at 2 41 29 PM

@coveralls
Copy link

coveralls commented Dec 26, 2023

Coverage Status

coverage: 43.517% (+0.1%) from 43.39%
when pulling a8aabb2 on zacharyfmarion:zac/hermes-specfic-changes
into 88f4fe6 on jlfwong:main.

@@ -48,12 +48,30 @@ interface TraceEvent {
cat?: string

// Any arguments provided for the event. Some of the event types have required argument fields, otherwise, you can put any information you wish in here. The arguments are displayed in Trace Viewer when you view an event in the analysis section.
args: any
args?: any
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the spec this is not a required field for some event types, so marked it as optional. Happy to change back if this was intentional

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fine

Copy link
Owner

@jlfwong jlfwong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! High level looks good. A few minor requests inline, and then more generally please change all of the isHermesProfile arguments to be enum args instead of an ExporterSource with current values of UNKNOWN or HERMES. That way this will extend well if we want to specialize for other trace events types in the future, and also makes call-sites more self-documenting

@@ -669,6 +739,8 @@ export function importTraceEvents(rawProfile: Trace): ProfileGroup {
return sampleListToProfileGroup(rawProfile)
} else if (isTraceEventObject(rawProfile)) {
return eventListToProfileGroup(rawProfile.traceEvents)
} else if (isHermesTraceEventList(rawProfile)) {
return eventListToProfileGroup(rawProfile, true)
Copy link
Owner

@jlfwong jlfwong Dec 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As much as possible, I like to use enums rather than bare boolean arguments because it makes it clearer what's going on at the call-site. The true here isn't self documenting, but if this was e.g. return eventListToProfileGroup(rawProfile, ExportSource.HERMES) it would be really clear

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense - generally I just make any function that takes in more than one obvious parameter take in an object so the meaning of every param is clear at the callsite, but I saw that it isn't a pattern in the codebase. Using an enum is super readable, will do that

function isHermesTraceEventList(maybeEventList: any): maybeEventList is HermesTraceEvent[] {
if (!isTraceEventList(maybeEventList)) return false

return maybeEventList.every(el => isHermesTraceEvent(el.args))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the sake of hermes profile import speed, it's IMO fine to just check the first event. Otherwise this seems like it might be needlessly slow.

Comment on lines 690 to 699
const requiredProperties: Array<keyof HermesTraceEventArgs> = [
'line',
'column',
'name',
'category',
'url',
'params',
'allocatedCategory',
'allocatedName',
]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Browsers probably optimize this anyway, but just for sanity, let's move this array outside of the call-stack. This seems like it would fit well as a constant directly below HermesTraceEventArgs as heremesTraceEventRequiredProperties

@zacharyfmarion
Copy link
Contributor Author

@jlfwong thanks for the feedback, pushed up some fixes!

// We just check the first element to avoid iterating over all trace events,
// and asumme that if the first one is formatted like a hermes profile then
// all events will be
return isHermesTraceEvent(maybeEventList[0].args)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[0]?.args please!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, this is guarded in isTraceEventList

@jlfwong jlfwong merged commit 4feb1e5 into jlfwong:main Dec 26, 2023
6 checks passed
@jlfwong
Copy link
Owner

jlfwong commented Dec 26, 2023

Amazing!

If you write a markdown description (just in a comment here is fine) for React Native profiling, then I'll copy that into the wiki and update the README to link to it to indicate there's specific support for React Native via Hermes.

Here's an example of such a page: https://github.com/jlfwong/speedscope/wiki/Importing-from-pprof-(go)

@zacharyfmarion
Copy link
Contributor Author

zacharyfmarion commented Dec 27, 2023

Importing from Hermes

Hermes is a javascript engine developed by facebook for use in react-native applications. For the most up-to-date instructions on how to take a profile, see Profiling with Hermes.

Profiling in Development Mode

To record a sampling profiler from the Dev Menu:

  1. Navigate to your running Metro server terminal.
  2. Press d to open the Dev Menu.
  3. Select Enable Sampling Profiler.
Screenshot 2023-12-27 at 8 51 15 AM
  1. Execute your JavaScript by in your app (press buttons, etc.)
  2. Open the Dev Menu by pressing d again.
  3. Select Enable Sampling Profiler again (this is currently a bug and might correctly say disable in the future) to stop recording and save the sampling profiler.

A toast will show the location where the sampling profiler has been saved, usually in /data/user/0/com.appName/cache/*.cpuprofile

You can then extract the profile from your emulator / device using the following command:

npx react-native@latest profile-hermes [destinationDir]

To view, drag and drop the profile from destinationDir into Speedscope.

Profiling in Release Mode

To profile hermes in a release build of your app, you can use the react-native-release-profiler npm package:

yarn add react-native-release-profiler
cd ios && pod install
  1. Build your app in release mode
  2. Start a profiling session:
import { startProfiling } from 'react-native-release-profiler'

startProfiling()
  1. Stop the profiling session:
import { stopProfiling } from 'react-native-release-profiler'

// `true` to save the trace to the phone's downloads folder, `false` otherwise
const path = await stopProfiling(true)
  1. Download and process the performance trace from your phone to your PC:
  • On Android:

First find your app id. It should look something like com.mypackage and be visible in app/build.gradle in the defaultConfig section:

android {
    defaultConfig {
        applicationId "com.profilern" // <-- This one!
        // ...
    }
}

Then run:

npx react-native-release-profiler --fromDownload --appId <your appId>
  • On iOS:
npx react-native-release-profiler --local <path to profile>

To view, drag and drop the profile saved in your current working directory into Spedscope. It should be transformed with sourcemaps!

@zacharyfmarion
Copy link
Contributor Author

@jlfwong took a pass at some docs ^. Profiling in release mode is something that Margelo recently worked one while contracting for my company, so honestly these are probably the most up-to-date / complete docs that exist anywhere right now.

@jlfwong
Copy link
Owner

jlfwong commented Dec 27, 2023

@zacharyfmarion Great! Added to the wiki, and updated the README.

Whenever you'd like to do an awareness push on Twitter or LinkedIn or wherever, let me know and I'll help signal boost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants