Roku app tracing (with Perfetto)
Starting with Roku OS 15.1, you can use Perfetto to record, analyze, and visualize traces of your Roku apps to pinpoint where you can reduce resource consumption and optimize performance. Tracing captures and visualizes the events in your app on a timeline, which provides you with a detailed graphical view of what your app is doing over time.
With Roku ECP and a Websocket client, you can launch your app, record and save a trace, and then open it in Perfetto. You can then explore the trace in Perfetto by using the WASD keys on your keyboard to zoom and pan, and your mouse to expand process tracks (rows) into their constituent thread tracks. You can also execute SQL-based queries in Perfetto.

Prerequisites
To record a trace, developers need the following:
- Roku OS 15.1 (or later)
- A Roku device with developer mode enabled.
-
Roku app (you can record a trace with an app running in a sideloaded, beta, or production environment).
- For a sideloaded app, the manifest must enable the run_as_process attribute (run_as_process=1).
- For a beta or production app, the developer must own the app (your device must be keyed with key used to sign the app package). T
- Trace recording app. You can record an app trace via ECP and a websocket client.
Using ECP to enable and record Perfetto traces
You can enable and record a Perfetto trace with ECP following these steps:
Enabling Perfetto
To enable tracing for an app, send the enable Perfetto command with the channel ID:
curl -X POST "http://192.168.1.86:8060/perfetto/enable/dev"
The ECP response will have the following syntax:
<?xml version="1.0" encoding="UTF-8" ?>
<perfetto-enable>
<enabled-channels>
<channel>dev</channel>
</enabled-channels>
<timestamp>1762473265350</timestamp>
<timestamp-end>1762473265350</timestamp-end>
<status>OK</status>
</perfetto-enable>
Once enabled, tracing starts automatically each time the app is launched
ECP does not have a corresponding disable Perfetto command. The list of enabled apps is cleared on reboot.
Recording a Perfetto trace
To record trace data for a session, using a websocket client to connect to the device (for example, websocat). The websocket emits a stream of bytes, which is the Protobuf-encoded Perfetto trace from the device.
websocat --binary ws://$ip:8060/perfetto-session > perfetto_data.trace
Data stops streaming when the client disconnects. If the client reconnects, the stream resumes.
- Roku only supports a single websocket connection at a time.
Adding custom trace data
You can use the roPerfetto BrightScript component to capture custom events in a Perfetto trace. The types of events you can record include instantaneous, duration, scoped, and flow events.
| Event | Description | Example | Snippet |
|---|---|---|---|
| Instantaneous | Events without a duration | keypress | |
| Duration | Events with a beginning and an end | long function | |
| Scoped | Similar to duration events, but the end-event is generated automatically when the object returned from the call is released. | |
|
| Flow | Creation of a “flow” of events from one piece of code to another. | A Task thread to the Render thread. | |
Visualizing trace files in Perfetto
You can open trace files with the Perfetto UI to anaylze them and pinpoint potential optimizations. Perfetto features a timeline view that provides a visual representation of the trace.
You can use the W,A,S,D keys on your keyboard to zoom and pan, and your mouse to expand process tracks (rows) into their constituent thread tracks.

Using PerfettoSQL to query traces
You can query the data in a trace using PerfettoSQL following these steps:
- Click Query (SQL) in the left sidebar in the Perfetto UI.
- Enter your query in the editor.
- Click Run Query.

The following examples demonstrate some of the use cases for querying your trace data:
| Use case | Query |
|---|---|
| Find long executions of swapBuffers which indicate places where the channel may be dropping frames | SELECT * FROM slice WHERE name = 'swapBuffers' ORDER BY dur DESC; |
| Find places where the channel is handling a key press with OnKeyEvent() | SELECT * FROM slice WHERE name = 'keyEvent' ORDER BY dur DESC; |
| Find all of the observers being called in the app. | SELECT * FROM slice WHERE name = 'observer.callback' ORDER BY dur DESC; |
| Find all of the places where the channel is calling setField. | SELECT * FROM slice WHERE name = 'roSGNode.setField' ORDER BY dur DESC; |