How to merge JS profiler traces into a Perfetto trace file

When debugging performance in Vega apps, the KPI Visualizer generates two types of trace data: Perfetto traces (system-level CPU, GPU, frame timing) and JS Profiler traces (JavaScript function-level execution). By default these are separate files, but merging them lets you see exactly which JS functions were running during frame drops or input delays. This guide walks through how to combine them for side-by-side analysis in Perfetto UI.

The JS Profiler trace and Perfetto trace files are located in a session-specific subdirectory (e.g. 2024-11-13_15-27-56) under the output/ directory of your app project. The exact path may vary depending on your tooling version — older versions used a generated/ directory.

Perfetto trace files follow the naming convention iter_*_vs_trace, while JS profiler trace files follow iter_*_trace*-original.json.

Follow the steps below to merge the JS function traces into a Perfetto trace file so you can analyze them alongside each other in Perfetto UI.

Convert Perfetto trace file to JSON

  1. Go to https://ui.perfetto.dev/ and open the Perfetto binary file iter_*_vs_trace, then click “Convert to .json” in the left-hand pane.

This will download a file called trace.json to your Downloads directory. Rename this file to iter_*_vs_trace.json to match the naming convention of the Perfetto binary file.

Merge JS Profiler trace file into Perfetto trace file

  1. Open the JS Profiler trace file iter_*_trace*-original.json in a text editor. It contains a JSON array of trace event objects. Copy all the objects inside the array (without the outer [ and ] delimiters). For example, the file will look like this:
[
    {
        "ts": 10281056953,
        "pid": 2,
        "tid": 29,
        "ph": "B",
        "name": "[root]",
        "cat": "root",
        "args": {
            "name": "[root]",
            "category": "root",
            "url": null,
            "line": null,
            "column": null,
            "params": null,
            "allocatedCategory": "root",
            "allocatedName": "[root]"
        }
    },
    ...
]

Copy everything between (but not including) the outer [ and ].

  1. Open the Perfetto trace JSON file iter_*_vs_trace.json created in step [1].

  2. Paste the JS profiler trace events at the beginning of the "traceEvents" array in the Perfetto trace JSON file. Make sure to add a comma after the last pasted object to maintain valid JSON array syntax. The result should look like this:

{"traceEvents":[
    {"ts":10281056953,"pid":2,"tid":29,"ph":"B","name":"[root]","cat":"root","args":{}},
    ...
    {"args":{"name":"foo"},"cat":"bar","name":"thread_name","ph":"M","pid":0,"tid":0,"ts":0}
]}

Note: The pid in the JS profiler trace file doesn’t align with the pid in the Perfetto trace file. This can lead to incorrect process names being shown for JS profiler track events in Perfetto UI. To avoid confusion, replace the pid values in the JS profiler trace events with the PID of your app process (found in the Perfetto traces).

  1. Save the file as iter_*_combined-js-profiler-perfetto-traces.json.

  2. Open the merged file in ui.perfetto.dev to analyze JS profiler output alongside Perfetto traces.

  3. Search for [root] in the Perfetto search bar to find the JS profiler traces swimlane.

Related resources

Last updated: Mar 11, 2026