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).

  1. BeginFrame: The GPU process emits a hardware VSync signal, triggering a BeginFrame event carrying a unique ID.
  2. 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.
  3. 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.
  4. 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 an E (End) event, it pops the task off. The duration between B and E determines 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 FunctionCall browser event occurred between millisecond 10 and 20, DevTools injects all V8 JavaScript stack samples recorded during that specific 10-millisecond window directly underneath the FunctionCall bar.
  • 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 BeginFrame to screen presentation, DevTools color-codes the frame block (e.g., green for on-time frames, red for dropped frames that missed the VSync deadline).