Main-thread CPU (lab)
DevTools-style main-thread CPU split on the lab crawl: loading, scripting, rendering, painting.
At a glance the headline numbers for Main-thread CPU (lab)
DevTools-style main-thread CPU split on the lab crawl: loading, scripting, rendering, painting.
On the typical lab load, 434ms of main-thread time goes to JavaScript.
Where the milliseconds go the phases at the median, against the budget
At the median the whole stack uses 652ms. The biggest piece is Scripting at 434ms, 67% of the total. computed
Passing CLS per phase the share of sites passing, by how long each phase takes
Each line is one phase: left = sites where it is quick, right = sites where it drags (buckets, short to long). The steeper the fall, the more that phase decides CLS.
Scripting falls hardest: from its fastest to its slowest bucket the CLS pass rate drops 9 points, down to 83%. computed
The 3 distributions how each phase spreads across sites - color is the share passing in that range
Scripting stays green across its whole range: it never decides CLS. Scripting turns red in its tail - the failing sites live there. computed
Why this matters for the Core Web Vitals, and where to start fixing it
The main thread's time on the lab load, split four ways. Loading is parsing bytes. Scripting is JavaScript. Rendering is style and layout. Painting is pixels. The shape tells you what kind of page it is: a script-dominated profile points at INP risk, a rendering-heavy profile points at DOM and style cost.
On most modern pages scripting is the biggest slice by far. That is the same thread the visitor's first click has to share.
How does this affect the Core Web Vitals?
Main-thread CPU (lab) correlates with the CLS. Of the 4 parts, scripting time separates passing sites from failing sites the most. Where the scripting time is low, 92% of sites pass the CLS. Where it is high, 83% do. The decline is gradual. There is no point where sites suddenly start failing.
Chrome field data from 189,915 sites, representing millions of real page loads. How we measured.
Live queries (8) — admin only
SELECT COUNT(*) AS count,
quantile_disc(m.synthetic.cpu.loading, 0.10) AS p10,
quantile_disc(m.synthetic.cpu.loading, 0.25) AS p25,
quantile_disc(m.synthetic.cpu.loading, 0.50) AS p50,
quantile_disc(m.synthetic.cpu.loading, 0.75) AS p75,
quantile_disc(m.synthetic.cpu.loading, 0.90) AS p90,
quantile_disc(m.synthetic.cpu.loading, 0.99) AS p99
FROM sites WHERE m.synthetic.cpu.loading IS NOT NULL;
SELECT CASE WHEN m.synthetic.cpu.loading >= 0 AND m.synthetic.cpu.loading < 100 THEN 0 WHEN m.synthetic.cpu.loading >= 100 THEN 1 END AS bucket_idx, COUNT(*) AS n,
quantile_disc(crux."phone".cls, 0.5) AS median,
COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL AND crux."phone".cls <= 0.1) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL), 0) AS good_pct
FROM sites WHERE m.synthetic.cpu.loading IS NOT NULL GROUP BY bucket_idx;
SELECT COUNT(*) AS count,
quantile_disc(m.synthetic.cpu.scripting, 0.10) AS p10,
quantile_disc(m.synthetic.cpu.scripting, 0.25) AS p25,
quantile_disc(m.synthetic.cpu.scripting, 0.50) AS p50,
quantile_disc(m.synthetic.cpu.scripting, 0.75) AS p75,
quantile_disc(m.synthetic.cpu.scripting, 0.90) AS p90,
quantile_disc(m.synthetic.cpu.scripting, 0.99) AS p99
FROM sites WHERE m.synthetic.cpu.scripting IS NOT NULL;
SELECT CASE WHEN m.synthetic.cpu.scripting >= 0 AND m.synthetic.cpu.scripting < 500 THEN 0 WHEN m.synthetic.cpu.scripting >= 500 AND m.synthetic.cpu.scripting < 1000 THEN 1 WHEN m.synthetic.cpu.scripting >= 1000 AND m.synthetic.cpu.scripting < 1500 THEN 2 WHEN m.synthetic.cpu.scripting >= 1500 AND m.synthetic.cpu.scripting < 2000 THEN 3 WHEN m.synthetic.cpu.scripting >= 2000 AND m.synthetic.cpu.scripting < 2500 THEN 4 WHEN m.synthetic.cpu.scripting >= 2500 THEN 5 END AS bucket_idx, COUNT(*) AS n,
quantile_disc(crux."phone".cls, 0.5) AS median,
COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL AND crux."phone".cls <= 0.1) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL), 0) AS good_pct
FROM sites WHERE m.synthetic.cpu.scripting IS NOT NULL GROUP BY bucket_idx;
SELECT COUNT(*) AS count,
quantile_disc(m.synthetic.cpu.rendering, 0.10) AS p10,
quantile_disc(m.synthetic.cpu.rendering, 0.25) AS p25,
quantile_disc(m.synthetic.cpu.rendering, 0.50) AS p50,
quantile_disc(m.synthetic.cpu.rendering, 0.75) AS p75,
quantile_disc(m.synthetic.cpu.rendering, 0.90) AS p90,
quantile_disc(m.synthetic.cpu.rendering, 0.99) AS p99
FROM sites WHERE m.synthetic.cpu.rendering IS NOT NULL;
SELECT CASE WHEN m.synthetic.cpu.rendering >= 0 AND m.synthetic.cpu.rendering < 100 THEN 0 WHEN m.synthetic.cpu.rendering >= 100 AND m.synthetic.cpu.rendering < 200 THEN 1 WHEN m.synthetic.cpu.rendering >= 200 AND m.synthetic.cpu.rendering < 300 THEN 2 WHEN m.synthetic.cpu.rendering >= 300 AND m.synthetic.cpu.rendering < 400 THEN 3 WHEN m.synthetic.cpu.rendering >= 400 AND m.synthetic.cpu.rendering < 500 THEN 4 WHEN m.synthetic.cpu.rendering >= 500 AND m.synthetic.cpu.rendering < 600 THEN 5 WHEN m.synthetic.cpu.rendering >= 600 AND m.synthetic.cpu.rendering < 700 THEN 6 WHEN m.synthetic.cpu.rendering >= 700 AND m.synthetic.cpu.rendering < 800 THEN 7 WHEN m.synthetic.cpu.rendering >= 800 AND m.synthetic.cpu.rendering < 900 THEN 8 WHEN m.synthetic.cpu.rendering >= 900 AND m.synthetic.cpu.rendering < 1000 THEN 9 WHEN m.synthetic.cpu.rendering >= 1000 THEN 10 END AS bucket_idx, COUNT(*) AS n,
quantile_disc(crux."phone".cls, 0.5) AS median,
COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL AND crux."phone".cls <= 0.1) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL), 0) AS good_pct
FROM sites WHERE m.synthetic.cpu.rendering IS NOT NULL GROUP BY bucket_idx;
SELECT COUNT(*) AS count,
quantile_disc(m.synthetic.cpu.painting, 0.10) AS p10,
quantile_disc(m.synthetic.cpu.painting, 0.25) AS p25,
quantile_disc(m.synthetic.cpu.painting, 0.50) AS p50,
quantile_disc(m.synthetic.cpu.painting, 0.75) AS p75,
quantile_disc(m.synthetic.cpu.painting, 0.90) AS p90,
quantile_disc(m.synthetic.cpu.painting, 0.99) AS p99
FROM sites WHERE m.synthetic.cpu.painting IS NOT NULL;
SELECT CASE WHEN m.synthetic.cpu.painting >= 0 AND m.synthetic.cpu.painting < 100 THEN 0 WHEN m.synthetic.cpu.painting >= 100 AND m.synthetic.cpu.painting < 200 THEN 1 WHEN m.synthetic.cpu.painting >= 200 AND m.synthetic.cpu.painting < 300 THEN 2 WHEN m.synthetic.cpu.painting >= 300 THEN 3 END AS bucket_idx, COUNT(*) AS n,
quantile_disc(crux."phone".cls, 0.5) AS median,
COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL AND crux."phone".cls <= 0.1) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL), 0) AS good_pct
FROM sites WHERE m.synthetic.cpu.painting IS NOT NULL GROUP BY bucket_idx;