Capturing Main Thread and GPU Flame Charts in Chromium
This article explains the underlying mechanisms the Chromium Performance panel uses to record and visualize main thread activity and GPU frames as flame charts. It covers how the Chrome DevTools Protocol initiates tracing, how the V8 engine and browser instrumentation capture main thread execution, how the GPU process tracks frame rendering, and how DevTools processes this raw data into a visual timeline.
The Tracing Infrastructure
When you click the "Record" button in the Chromium Performance panel,
the DevTools frontend communicates with the browser's backend using the
Chrome DevTools Protocol (CDP). Specifically, it sends the
Tracing.start command.
This command activates Perfetto, Chromium's underlying performance
instrumentation and tracing system. Perfetto enables thousands of
dormant, low-overhead trace points (TRACE_EVENT macros)
embedded directly within the Chromium C++ codebase. As the browser runs,
these trace points emit data containing a timestamp, process ID, thread
ID, event name, and execution phase into a shared memory buffer.
Capturing Main Thread Activity
The main thread flame chart is constructed by combining two different data collection methods: instrumentation tracing for browser tasks and CPU sampling for JavaScript execution.
1. Browser Instrumentation For native browser
operations—such as HTML parsing, style recalculation, layout, and
paint—Chromium relies on exact instrumentation. When the main thread
begins calculating a layout, it emits a "Begin" (B) trace
event. When it finishes, it emits an "End" (E) event.
Because these operations are relatively coarse, recording their exact
start and end times does not significantly degrade browser
performance.
2. JavaScript CPU Sampling Recording exact start and end events for every individual JavaScript function call would add massive overhead and skew the performance results. Instead, when tracing begins, DevTools instructs the V8 JavaScript engine to turn on its CPU Profiler.
The V8 profiler uses sampling. At regular intervals (typically every 100 microseconds), a separate profiler thread interrupts the main thread and takes a snapshot of the current JavaScript call stack. If the same functions appear on the call stack across multiple consecutive samples, V8 deduces that those functions were executing continuously during that time window.
Capturing GPU Frames
Tracking a single visual frame is complex because a frame does not live on a single thread. It travels across a pipeline: from the Renderer's Main thread, to the Compositor thread, and finally to the GPU process (specifically the Viz display compositor).
To capture this for the "Frames" and "GPU" sections of the flame
chart, Chromium uses asynchronous trace events linked by a unique
identifier, often a FrameSeqId (Frame Sequence ID).
- BeginFrame: The GPU process emits a hardware VSync
signal, triggering a
BeginFrameevent carrying a unique ID. - Main and Compositor Threads: The main thread
processes input, runs animations, and commits updates to the Compositor
thread. The Compositor thread divides the page into tiles and packages
them into a
CompositorFrame, passing the unique ID along. - GPU Process Execution: The Viz thread in the GPU
process receives the
CompositorFrame, aggregates it with frames from other browser components, and issues low-level graphics commands (via Vulkan, Metal, DirectX, or OpenGL) to draw the pixels. - Presentation: The GPU swaps the frame buffers to display the image on the screen, emitting a final trace event marking the end of that specific frame ID's lifecycle.
Additionally, the GPU track in the flame chart directly measures the time the GPU hardware spends executing command buffers by logging events before and after GPU tasks are flushed to the graphics driver.
Constructing the Flame Chart
When you stop the recording, DevTools calls Tracing.end.
Perfetto flushes the memory buffers and streams the raw trace events
(formatted as JSON or Protobuf) back to the DevTools frontend. DevTools
then parses this data to draw the flame chart:
- Sorting and Stacking: For the main thread, DevTools
isolates all events sharing the main thread's ID and sorts them
chronologically. It uses a stack-based algorithm: when it encounters a
B(Begin) event, it pushes the task onto a stack; when it encounters anE(End) event, it pops the task off. The duration betweenBandEdetermines the horizontal width of the bar in the flame chart, while the depth of the stack determines its vertical position. - Merging JS Samples: DevTools takes the
high-frequency V8 call stack samples and overlays them onto the
instrumented browser tasks. If a
FunctionCallbrowser event occurred between millisecond 10 and 20, DevTools injects all V8 JavaScript stack samples recorded during that specific 10-millisecond window directly underneath theFunctionCallbar. - Correlating Frames: To draw the GPU and Frames
tracks, DevTools scans the trace for the asynchronous frame IDs. It
stitches together the lifecycle of each ID across the Main, Compositor,
and GPU threads. Based on the total time taken from
BeginFrameto screen presentation, DevTools color-codes the frame block (e.g., green for on-time frames, red for dropped frames that missed the VSync deadline).