Performance Flame Charts
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.
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
- Open Chrome DevTools → Performance tab
- Click the Record button (circle icon) or press
Ctrl+E/Cmd+E - Perform the interaction you want to analyze
- 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.
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:
| Color | Activity | What It Means |
|---|---|---|
| Yellow | JavaScript execution (Scripting) | Your JS code running — functions, event handlers, timers |
| Purple | Style and Layout (Rendering) | CSS recalculation, layout computation, tree building |
| Green | Paint and Composite (Painting) | Pixel drawing, layer composition, GPU operations |
| Grey | Idle | Main thread is free — this is good |
| Red/Orange | System | Browser 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:
onClickis the entry point (widest bar — total time)- It calls
processDataandupdateUI processDatacallsparseandsortsortis wider thanparse— it takes more time
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.
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.
| View | Best For | Sort By |
|---|---|---|
| Bottom-Up | Finding which functions are slowest | Self Time — highest self time = biggest optimization target |
| Call Tree | Understanding what triggers expensive work | Total Time — shows the full execution path |
| Event Log | Finding exact timing and event sequences | Timestamp — 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) ││
│ │ └───────────────────────────────────────────┘│
│ └──────────────────────────────────────────────┘│
└─────────────────────────────────────────────────┘
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.
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.
- 1Keep recordings short (5-15 seconds) and focused on a single interaction
- 2Start with the Bottom-Up view sorted by self time to find the actual bottleneck functions
- 3Self time is the true measure of a function's cost — total time includes children
- 4Long tasks (over 50ms) are marked with red triangles — these degrade INP and perceived responsiveness
- 5Enable CPU 4x throttling to simulate real-world devices — your dev machine is misleadingly fast
- 6Use performance.mark() and performance.measure() to correlate your code with the flame chart
| What developers do | What 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 |