Chrome Tracing for Fun and Profit – Several People Are Coding

When you take into account all the factors that can have an effect on your app’s performance — OS version, CPU, GPU, memory, disk speed, network conditions, other currently running software, and so on— it turns out every desktop environment is as unique as a snowflake. It’s often impossible to exactly replicate a user’s environment to reproduce an issue they might be seeing. When Slack receives a report of a performance issue in its desktop app, we ask the user to trigger a special command that collects a performance trace through the Chrome Tracing system (using the contentTracing API in Electron). With the user’s permission, the trace is securely uploaded to Slack’s servers, where engineers can inspect the trace and hopefully track down the underlying problem. When we’ve completed our review, we delete the trace data.

You can only change what you can perceive. Without tools to observe the world with which we interact, we are powerless to change it.

DevTools should always be your first port of call for understanding and debugging a performance issue — but sometimes it doesn’t provide the answers you were looking for. Chrome Tracing lets you record a much wider array of performance-relevant data about the browser, so it can be helpful when tracking down a performance issue that isn’t strictly JavaScript-related; for instance, GPU issues or cases where one process is waiting on another process. As an example, in Electron apps, a common source of performance issues is over-eager communication (especially synchronous IPC) between the main and renderer processes.

It’s possible to collect trace data programmatically in Electron via the contentTracing API. The contentTracing API lets you specify what categories of tracing data you want recorded — the categories that you can pass to the API are the same ones that are listed in the chrome://tracing UI (the ones in the ‘disabled by default’ column are prefixed with disabled-by-default-, so e.g. if you want to record v8 sample data, you’ll need to specify disabled-by-default-v8.cpu_profiler). If you don’t specify what categories to include, Electron will record everything that’s not disabled by default, which can be a useful way to find out what categories of data you care about — you can click on an event in the tracing UI to see what category it belongs to.

The ‘ipc’ category is a useful one for debugging performance issues that cross between the browser and renderer processes.

Here’s an example of setting up a trace that records all the default tracing categories, and additionally records V8 sampling data in the renderer:

To demonstrate what this looks like, I’ve built a simple test app that simulates something that might be hard to debug with the standard DevTools trace. In the test app, the renderer process delegates some CPU-intensive work to the browser process. In a real app, there are lots of reasons you might do something similar: for instance, spell-checking can be quite memory intensive, so you might want to load up the spell-checking dictionary just once in the main process, and have all renderer processes delegate the work of actually checking spelling to the main process. In this test app, though, we just busy-wait for a second.

I built this example with Electron Fiddle, which is a great way to test out quick examples. Go ahead and install Fiddle, and when you’re done, load up the example work-doing app by pasting the Gist URL in the Load Fiddle box.

Try it yourself: first, open up DevTools with Cmd+Opt+I (or Ctrl+Shift+I). Switch to the ‘performance’ tab, start a recording in DevTools, and press the ‘do work’ button. Stop the recording and inspect the resulting trace. What’s using up the CPU?

The Devtools profile doesn’t tell the whole story.

It’s hard to say, because DevTools just shows a big gap. You could maybe try to guess what’s happening here, but it’s much easier to use Chrome Tracing.

Try tracing this again, but this time, use the ‘start recording’ button in the example app instead of DevTools. This will trigger Electron’s contentTracing API. Once the app is recording, click the ‘do work’ button, and when it’s done, click ‘stop recording’. The folder containing the resulting trace.json file will open (which is accomplished by Electron’s shell.openItem API). Open chrome://tracing in a Chrome tab, and drag that trace.json file into the chrome://tracing window. You’ll see something that looks like this:

The trace that Electron recorded includes events from the main process as well as the renderer process.

The AtomFrameHostMsg_Message event tells us that the main process is processing an IPC message, in this case for about 1,000 ms. Unfortunately, Electron doesn’t currently support sampling V8 stack traces in the main process, though there is an issue tracking the feature. Often, though, just the information that the main process is handling an IPC event is enough to figure out what’s going on.

The performance panel in DevTools uses the same infrastructure as Chrome Tracing under the hood to collect the information it displays. The file formats are the same — you can save a profile from DevTools and open it in chrome://tracing and vice-versa. When you record a performance profile in DevTools, it signals the Chrome Tracing infrastructure to begin recording with a predefined set of categories.

Source link