Skip to content

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.getMetrics gives 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, setTimeout and requestAnimationFrame, 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.