Roku app tracing (with Perfetto)

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.

The BrightScript Language extension for VSCode lets you launch your app, record and save a trace, and then view it in Perfetto directly (alternatively, you can use ECP to record a trace and view 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.

perfetto-ui-overview

Prerequisites

To record a trace, developers need the following:

Using VSCode to enable and record Perfetto traces

You can enable and record a Perfetto trace with the BrightScript Language extension for VSCode following these steps:

Enabling Perfetto

  1. Verify that you have done the following:

    1. Installed VSCode.
    2. Installed the latest version of BrightScript Language extension for VSCode.
    3. Created a launch.json configuration file in your app directory.
    4. Updated your Roku device to Roku OS 15.2 (or later)
  2. In the launch.json file, add the following profiling object to the configurations object:

    {
        "version": "0.2.0",
        "configurations": [
          {
            "type": "brightscript",
            "request": "launch",
            "name": "BrightScript Debug: Launch",
            "host": "<Roku device IP address>",
            "password": "<Roku device password>",
    
             //add the following to enable Perfetto tracing   
            "profiling": {
                "tracing": {
                    "enable": true,
                }
              }
            }
        ]
    }

Recording a Perfetto trace

To record trace data for a session in VSCode, follow these steps:

  1. In VS Code, add your app folder (select File > Open Folder).

    perfetto-ui-overview - roku600px

  2. Select Run>Start Debugging (or press F5) to sideload your app. Tracing begins automatically.

  3. Test your app.

  4. When you are done testing, click the red Stop Perfetto Tracing button.

    perfetto-ui-overview - roku400px

  5. The recorded Perfetto trace opens in a new window. Use the WASD keys on your keyboard to zoom and pan, and use your mouse to expand process tracks (rows) into their constituent thread tracks.

    perfetto-ui-overview - roku600px

Capturing heap graphs

To capture the BrightScript heap graph, follow these steps:

  1. Start recording a trace and test your app.

  2. Click the Capture heap snapshot button one or more times during the recording.

    perfetto-ui-overview - roku400px

  3. Click the Stop Perfetto Tracing button when you are done testing.

  4. The Java heap graph in the Current Selection tab displays the SceneGraph and BrightScript objects as a flamegraph. You can sort the objects either by allocation size or object count, ordering callstacks from left-to-right according to which have the largest size or count.

    perfetto-ui-overview - roku600px

  5. A single Perfetto trace can hold multiple heap graphs, with each heap graph represented by a selectable Heap Profile event.

    perfetto-ui-overview - roku600px

Shortest Path used to display heap graph The heap graph is always shown as the shortest path from a root to any given object. This can sometimes lead to charts that might have unexpected structure. For example, if you have a Scene that owns a Grid which in turn owns a ContentNode, you might expect a chart reflecting this:

== MyScene ==========
-- MyGrid ---------
    - MyContentNode -

However, if there is also a reference to the content node from directly from the domain - perhaps because it is referenced by a local variable, then this path will be preferentially displayed:

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.

Visualizing trace files in Perfetto

You can open trace files with the Perfetto UI to analyze 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.

perfetto-visualize

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.

EventDescriptionExampleSnippet
InstantaneousEvents without a durationkeypress
tracer = CreateObject("roPerfetto")
tracer.instantEvent("my_instant_event")
tracer.instantEvent("my_instant_event", {debug_1: 42, debug_2: "hello"})
DurationEvents with a beginning and an endlong function
sub myfunc()
tracer = CreateObject("roPerfetto")
params = {debug_1: 42, debug_2: "hello"}
tracer.beginEvent("my_duration_event", params)
do_stuff()
tracer.endEvent()
end sub
ScopedSimilar to duration events, but the end-event is generated automatically when the object returned from the call is released.
sub myfunc()
tracer = CreateObject("roPerfetto")
params = {debug_1: 42, debug_2: "hello"}
scoped_event = tracer.createScopedEvent("my_scoped_event", params)
do_stuff()
' end event auto-created when scoped_event is released.
end sub
FlowCreation of a “flow” of events from one piece of code to another.A Task thread to the Render thread.
sub func1()
flowId = 42 ' A user-selected unique unsigned integer identifier
tracer = CreateObject("roPerfetto")
tracer.flowEvent(flowId, "my_flow_event_1")
end sub
sub func2()
flowId = 42
tracer = CreateObject("roPerfetto")
tracer.flowEvent(flowId, "my_flow_event_2")
end sub
sub func3()
flowId = 42
tracer = CreateObject("roPerfetto")
tracer.terminateFlow(flowId, "my_flow_event_3")
end sub
perfetto-visualize

Using PerfettoSQL to query traces

You can query the data in a trace using PerfettoSQL following these steps:

  1. Click Query (SQL) in the left sidebar in the Perfetto UI.
  2. Enter your query in the editor.
  3. Click Run Query.
perfetto-sql

The following examples demonstrate some of the use cases for querying your trace data:

Use caseQuery
Find all rendezvous in order of duration (long rendezvous often cause dropped frames and may indicate inefficient observer functions).SELECT * FROM slices WHERE name = 'rendezvous' ORDER BY dur DESC;
Find long executions of swapBuffers which indicate places where the app may be dropping framesSELECT * FROM slice WHERE name = 'swapBuffers' ORDER BY dur DESC;
Find places where the app 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 app is calling setField.SELECT * FROM slice WHERE name = 'roSGNode.setField' ORDER BY dur DESC;

Glossary

Roku's Perfetto-based app tracing solution exposes a number of terms that Roku developers may be unfamiliar with:

TermDefintion
SdkLauncherA Roku OS plugin that provides an environment for running SDK apps in a sandboxed process.
PR_uiThe main BrightScript thread.
RenderThreadThe primary SceneGraph render thread.
AuxRenderThreadA Roku OS thread used to offload some rendering tasks from the main UI thread. This helps improve the responsiveness and smoothness of the user interface.
TN:[function name]A Task thread spawned by a Task node . The function name represents the name of the Task function defined by the app.
ExecBrightScriptA slice representing the BrightScript engine processing app code.
renderA slice representing the process of drawing or composing a frame for display.
swapBuffersA slice representing the operation of presenting a newly rendered frame to the display.
consumeAllTasksA slice representing the render thread as it is processing messages from the Render Thread Queue that are waiting. These can include rendezvous as well as messages sent using the roRenderThreadQueue component.
bscCopyToDomainExA slice representing data being copied. For example, when getting or setting a field on a Task node from the Render thread.

Video tutorial


This following video demonstrates how 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.