Skip to content

Performance Flame Charts

advanced17 min read

The Performance Panel Is Your Time Machine

The Memory panel tells you what is in memory. The Performance panel tells you what happened over time — every function call, every style recalculation, every paint, every frame. It is the single most powerful debugging tool in the browser, and most engineers barely scratch the surface.

A performance recording captures everything the main thread did during a time window. The flame chart is the visualization — a stacked bar chart where width is time and height is call depth.

Mental Model

Imagine recording a chef cooking a meal from above with a high-speed camera. Every action — chopping onions, boiling water, stirring sauce — is captured with exact start and end times. The flame chart is like playing that recording back: wider bars mean actions that took longer, stacked bars mean one action triggered another (stirring → checking temperature → adjusting heat). If the chef stood still for 200ms doing nothing (a "long task"), you would see a suspicious gap. The Performance panel gives you this X-ray vision for your JavaScript execution.

Recording a Performance Profile

  1. Open Chrome DevTools → Performance tab
  2. Click the Record button (circle icon) or press Ctrl+E / Cmd+E
  3. Perform the interaction you want to analyze
  4. Click Stop

For page load analysis, click the reload button (circular arrow) instead — it automatically starts recording, reloads the page, and stops when the page is loaded.

Info

Keep recordings short — 5 to 15 seconds. Long recordings produce massive amounts of data that are hard to navigate and slow to load. Focus on a single interaction or page transition.

Anatomy of the Performance Panel

The recording shows several lanes from top to bottom:

1. Frames Lane (FPS)

A bar chart of frame rates. Green bars mean the browser achieved a smooth frame. Red or short bars mean dropped frames. Hover over any bar to see the exact frame duration.

2. CPU Chart

A color-coded area chart showing CPU activity:

ColorActivityWhat It Means
YellowJavaScript execution (Scripting)Your JS code running — functions, event handlers, timers
PurpleStyle and Layout (Rendering)CSS recalculation, layout computation, tree building
GreenPaint and Composite (Painting)Pixel drawing, layer composition, GPU operations
GreyIdleMain thread is free — this is good
Red/OrangeSystemBrowser internal work, GC pauses

A healthy profile shows lots of grey (idle). A sluggish profile is wall-to-wall yellow and purple.

3. Main Thread Flame Chart

This is where the real information lives. Every function call is a bar. Width is execution time. Bars are stacked — a bar on top was called by the bar below it.

Reading Flame Charts

The flame chart is read top to bottom (unlike traditional flame graphs which go bottom to top). The topmost bars are the entry points (event handlers, timers, requestAnimationFrame callbacks), and bars below them are the functions they called.

┌──────────────── onClick ────────────────┐
│  ┌──── processData ────┐ ┌─ updateUI ─┐│
│  │ ┌ parse ┐ ┌ sort ──┐│ │ ┌ render ┐ ││
│  │ └───────┘ └────────┘│ │ └────────┘ ││
│  └─────────────────────┘ └────────────┘│
└─────────────────────────────────────────┘

In this example:

  • onClick is the entry point (widest bar — total time)
  • It calls processData and updateUI
  • processData calls parse and sort
  • sort is wider than parse — it takes more time
Quiz
In a flame chart, you see a function bar that spans 150ms. Inside it, there are two child function bars: one spanning 100ms and one spanning 30ms. What is the 'self time' of the parent function?

Self Time vs Total Time

This distinction is crucial and trips up many engineers:

  • Total time — how long the function was "on the stack," including time spent in functions it called
  • Self time — how long the function spent doing its own work, excluding child calls
function parent() {
  doSmallWork();     // 5ms
  child();           // 200ms (the child is slow)
  doMoreSmallWork(); // 3ms
}

parent() has:

  • Total time: ~208ms (entire execution including child)
  • Self time: ~8ms (just doSmallWork + doMoreSmallWork)

If you are looking for optimization targets, sort by self time. A function with high total time but low self time is not the bottleneck — one of its children is.

Quiz
You see a function with 800ms total time and 5ms self time in the flame chart. Is this function a performance bottleneck?

The Three Analysis Views

Below the flame chart, DevTools offers three tabular views of the same data:

Bottom-Up View

Groups by function and sorts by self time. Start here. This immediately tells you which individual functions consumed the most CPU time, regardless of who called them.

Call Tree (Top-Down) View

Shows the call hierarchy from entry points down. Useful for understanding the path that led to expensive work. "Which user action triggers which expensive chain?"

Event Log

A chronological list of all events, function calls, and browser activities. Useful for finding the exact sequence of events and their timestamps.

ViewBest ForSort By
Bottom-UpFinding which functions are slowestSelf Time — highest self time = biggest optimization target
Call TreeUnderstanding what triggers expensive workTotal Time — shows the full execution path
Event LogFinding exact timing and event sequencesTimestamp — chronological order of everything

Identifying Long Tasks

A long task is any task that blocks the main thread for more than 50ms. Long tasks are the primary cause of poor Interaction to Next Paint (INP) scores because user input cannot be processed while a long task is running.

In the flame chart, long tasks are marked with a red triangle in the top-right corner of the task bar. The red area shows how much of the task exceeds the 50ms threshold.

┌────────────── Long Task (120ms) ──────────────┐
│                                    ████████████│ ← red area (70ms over budget)
│ ┌── handleClick ──────────────────────────────┐│
│ │ ┌── processItems(5000) ─────────────────────┐│
│ │ │         (this is the bottleneck)          ││
│ │ └───────────────────────────────────────────┘│
│ └──────────────────────────────────────────────┘│
└─────────────────────────────────────────────────┘
Quiz
A user clicks a button. The Performance panel shows the click handler takes 180ms. The user sees the UI update after 180ms. What is the impact on INP?

Understanding Rendering Phases

When the browser needs to update the visual display, it goes through a pipeline. These phases appear in the flame chart as purple and green bars:

In the flame chart:

  • Recalculate Style (purple) — triggered by CSS changes, class toggling
  • Layout (purple) — triggered by geometry changes (width, height, position, adding/removing elements)
  • Paint (green) — triggered by visual changes (color, background, shadow)
  • Composite Layers (green) — always happens, usually very fast

If you see Layout bars that are unexpectedly wide, you likely have layout thrashing (reading layout properties after writing style changes in a loop).

User Timing Marks

You can add your own markers to the flame chart using the User Timing API:

performance.mark('data-fetch-start');

const data = await fetch('/api/items');

performance.mark('data-fetch-end');
performance.measure('Data Fetch', 'data-fetch-start', 'data-fetch-end');

performance.mark('render-start');
renderItems(data);
performance.mark('render-end');
performance.measure('Render Items', 'render-start', 'render-end');

These marks appear as vertical lines in the Timings lane of the Performance panel. Measures appear as labeled bars. This makes it easy to correlate your application logic with what you see in the flame chart.

Common Trap

DevTools records with CPU throttling disabled by default. Your development machine is likely much faster than your users' devices. Enable CPU 4x slowdown in the Performance panel settings (gear icon) to simulate a mid-range mobile device. A function that takes 50ms on your MacBook Pro takes 200ms on a budget Android phone. Always profile with throttling enabled.

The Timings lane and Web Vitals markers

Chrome DevTools automatically marks key Web Vitals events in the Timings lane: First Paint (FP), First Contentful Paint (FCP), Largest Contentful Paint (LCP), and layout shift events. These are invaluable for understanding your Core Web Vitals scores. Click any of these markers to see the associated element and timing. If your LCP is 3 seconds, the flame chart shows exactly what was happening on the main thread during those 3 seconds — usually a long chain of blocking JavaScript or a slow network request holding up the render.

Key Rules
  1. 1Keep recordings short (5-15 seconds) and focused on a single interaction
  2. 2Start with the Bottom-Up view sorted by self time to find the actual bottleneck functions
  3. 3Self time is the true measure of a function's cost — total time includes children
  4. 4Long tasks (over 50ms) are marked with red triangles — these degrade INP and perceived responsiveness
  5. 5Enable CPU 4x throttling to simulate real-world devices — your dev machine is misleadingly fast
  6. 6Use performance.mark() and performance.measure() to correlate your code with the flame chart
What developers doWhat they should do
Optimizing functions with high total time but low self time
A function with 500ms total but 2ms self time is just a pass-through — the real cost is in its children
Focus on functions with high self time — they are the actual bottlenecks
Profiling without CPU throttling on a fast dev machine
Functions that feel instant on a 2024 MacBook take 200-800ms on a mid-range Android phone
Always enable 4x or 6x CPU throttling to approximate real user devices
Recording long Performance sessions (30+ seconds)
Long recordings are slow to process, hard to navigate, and the data is overwhelming
Record short bursts (5-15 seconds) focused on one interaction
Ignoring the purple and green bars (rendering phases)
Layout recalculations triggered at the wrong time can be more expensive than your JavaScript
Check for unexpected Layout bars — they often indicate layout thrashing