idlecpu: which web page burns CPU while nobody touches it¶
The problem¶
A page that keeps working after it loads drains batteries and slows the whole machine, while looking perfectly still. Load-time tools stop after the first seconds. Activity Monitor shows a renderer at 40% and says nothing about which script. A single reading on a busy machine shows a few percent for everything.
The method¶
- Measure the idle part only. Load, wait ten seconds, then measure for twenty.
- Ask the renderer, not the process list. The DevTools Protocol's
Performance.getMetricsgives the page's own main-thread task time, split into script, layout and style. Other programs on the machine do not leak into it. - A blank page first, as the floor every page is compared against.
- Blame the line. A script injected before the page's own wraps
setInterval,setTimeoutandrequestAnimationFrame, keys each callback by the stack frame that scheduled it and times it during the window. Endless CSS animations and playing media are listed by name.
window.setInterval = function (fn, ...rest) {
const site = new Error().stack.split('\n')[2]; // who scheduled it
return orig.call(this, function () {
const t = performance.now();
try { return fn.apply(this, arguments); }
finally { record(site, performance.now() - t); } // calls and ms per site
}, ...rest);
};
node idlecpu.mjs https://example.com/ https://example.org/news/
# 0.6% (blank page, the floor)
# 22.2% https://example.org/news/
# script 17.3% layout 0.0% style 0.0% other 4.9%
# setInterval at refresh (https://example.org/app.js:3:1): 28 calls (9.2/s), 482 ms
node idlecpu.mjs --over 5 https://staging.example.com/ # exit 1 when 5 points above the floor
Limits¶
- Headless Chromium paints fewer frames than a visible tab; compare pages with each other.
- Compositor-only animations cost GPU time, not counted; they are named.
- Cross-origin iframes (most ads) run in other processes and are not attributed to a line.
- "other" (parsing, garbage collection, decoding) is reported, not explained.
Code¶
methods/idlecpu/ in the methods repository: one Node file using Playwright, and a 27-check test. The hook and the arithmetic are tested without a browser; with Playwright present it measures a still page, a busy timer and an endless animation for real.