Skip to content

Performance watchdog: detect and attribute sustained main-thread work while idle #103

Description

@daiverd

Problem

When the client starts burning CPU, nothing notices. A recent investigation found a tab that had sustained ~18% of a core for three days — roughly 13.7 hours of CPU — with the user unaware until the machine was audited from outside the browser.

Worse, the condition stopped mid-investigation and the trigger was never identified. There was no record of what had been running, so three days of evidence evaporated. A watchdog would have caught the onset, sampled it while it was live, and named the culprit.

Proposal

A lightweight, always-on monitor that detects sustained main-thread work while the client is idle, and records what was active at the time.

Detection

  • PerformanceObserver on longtask — count and total duration per interval. Cheap and natively supported.
  • Event-loop lag sampler — a low-frequency timer (every ~500 ms is plenty) measuring its own scheduling delay. A healthy idle client sits near 1 ms; measured at 1 ms average, 7 ms max on a clean session.
  • Animation-frame rate — a self-perpetuating rAF loop is a common runaway shape. Instrument by wrapping requestAnimationFrame to count callers, rather than installing a rAF loop of your own, which would force 60 fps and mask the very thing being measured. (This mistake was made during the investigation and produced a false positive.)

Idle definition

Only fire when the user is not interacting: no keystrokes or pointer events for N seconds. Sustained main-thread work during active use is expected; during idle it is a bug.

Attribution

The signal is only useful with context. On trip, capture a snapshot of what is live:

  • Which subsystems are active: audio, MIDI, voice chat, haptics/gamepad, editors, file transfers
  • Inbound message rate and output lines/sec
  • React render counts for the hot components
  • Current room, AudioContext.state, connection state

Output

Write structured records into the diagnostics ring buffer from #100, and log a single console warning naming the top contributors. The goal is to turn a three-day archaeology dig into one line the user can copy and paste.

Cost

The watchdog must not become the thing it is measuring. A longtask observer plus a 500 ms sampler is negligible. Keep sampling coarse and avoid per-event instrumentation in the steady state.

Why the naive approaches do not work

Externally measured per-thread CPU can tell you that a renderer thread is hot but not what it is running — Chrome renderers expose no thread names or start addresses to outside tooling, and no URL on the command line. Chrome's own background throttling also distorts the picture: the same tab measured 24.1% in the foreground and 1.6–4.2% backgrounded, which makes external sampling easy to misread. In-page instrumentation is the only thing that can attribute the work.

Acceptance criteria

  • Sustained main-thread busy time while idle produces exactly one console warning, not a stream.
  • The warning names the top contributing subsystem(s), not just a percentage.
  • Records land in the diagnostics buffer (Diagnostics: opt-in debug capture with one-click copy from Preferences #100) and appear in its export.
  • The rAF counter measures callers and does not itself schedule frames.
  • Watchdog overhead is unmeasurable on an idle client.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions