Skip to content

0009. Performance budgets ​

  • Status: Accepted
  • Date: 2026-10-21

Context ​

A desktop wrapper is judged on how fast it opens and how much memory it holds while it sits in the background all day. Regressions creep in one dependency or feature at a time, and nobody notices until users do. CI runners are slower and noisier than a developer's machine, so one-off timings in PRs would flap.

Decision ​

  • What is measured. A Playwright benchmark (apps/desktop/e2e/bench/, pnpm test:bench) launches the development build eight times (one warm-up, then seven measured), each on a fresh profile against the local fake WhatsApp page, and takes the median of two numbers:

    • Cold start: from starting the process to WhatsApp's page being visible.
    • Idle memory: the sum of every YapYap process's working set (Electron getAppMetrics), ten seconds after the page appeared. Shared pages count once per process, so this overstates real use, but it is the same measure on every OS and moves when YapYap's own footprint moves.
  • What it measures against. The fake page is tiny, so the numbers are YapYap's own overhead, not WhatsApp's. One account is measured until multi-account (M3) adds a per-account figure.

  • Budgets live in apps/desktop/e2e/bench/budgets.json, per OS. Memory is steady on CI runners (within 1–2 MB across runs), so its budget sits 25% above the measured median. Cold start is not: on the same runner image one Linux run spread from 338 ms to 2619 ms. So the cold-start budget is about twice the higher of two calibration medians, which catches a real regression (a feature that doubles start-up) without failing on noise. Calibrated on 2026-10-21:

    RunnerCold start (two runs)Idle memoryBudget
    ubuntu-latest531 ms, 1106 ms555 MB2200 ms, 690 MB
    windows-latest936 ms, 771 ms313 MB2000 ms, 390 MB
    macos-latest1444 ms, 1252 ms411 MB3500 ms, 510 MB

    Recalibrated on 2026-10-29 (#428). The same code measured more idle memory than a few days earlier, on the same runner image and Electron: commit 98910fc used 386 MB on Windows on 2026-10-26 and 399 MB on 2026-10-29, and about 575 MB and 650 MB on Linux. The growth is the runners', not YapYap's. The idle-memory budgets now sit 10% above the medians of 2026-10-29, not 25%, because YapYap's own memory also grew (Windows went from 313 MB to 386 MB in its first week), and a 25% margin would let that much growth pass unnoticed again:

    RunnerIdle memoryBudget
    ubuntu-latest651 MB715 MB
    windows-latest399 MB440 MB
    macos-latest489 MB540 MB

    The benchmark reports each process's memory too, so the next overrun shows which process grew.

  • Accounts (#109). A second benchmark (bench/accounts.spec.ts) launches three times and takes the median of two more numbers:

    • Memory per account: what all of YapYap's processes hold more after Add Account, both measured once settled.
    • Hidden-account CPU: the first account's renderer while the second is in front, as a share of one core over ten seconds. It uses the cumulative CPU time, because the per-call percentage restarts whenever anything reads the metrics, which YapYap's memory watchdog does.

    Memory per account sits 25% above its median, like idle memory. A hidden account barely runs (at most 0.4% of a core), so its budget is 2%: room for noise, but a hidden page that keeps animating or polling would cross it. Calibrated on 2026-10-24:

    RunnerPer accountHidden CPUBudget
    ubuntu-latest213 MB0–0.2%270 MB, 2%
    windows-latest183 MB0.2%230 MB, 2%
    macos-latest199 MB0.2–0.4%250 MB, 2%
  • Active use (#109). A third benchmark (bench/activity.spec.ts) runs fifteen rounds of everyday use on two accounts: showing each account, a notification from each, and opening and closing Settings. Over three launches it takes the median of two numbers:

    • Memory growth: what all of YapYap's processes hold more after the rounds than before, both once settled. It levels off: on Linux, 45 rounds grew no more than 15, so a budget near twice the median still catches a leak of about 1.5 MB a round.
    • Main-process CPU: the main process's share of one core while the rounds run, which is mostly opening windows and rebuilding menus.

    Calibrated on 2026-10-25:

    RunnerGrowthMain CPUBudget
    ubuntu-latest17 MB46–48%40 MB, 70%
    windows-latest8–20 MB42–45%40 MB, 70%
    macos-latest15–22 MB42–45%40 MB, 70%
  • Warm-up. The first launch on a fresh runner takes 3–7 s while the disk cache fills. The benchmark makes one warm-up launch and leaves it out, then takes the median of seven.

  • When it runs. Nightly on Linux, Windows and macOS runners, and on any PR that changes the benchmark or its budgets. A median over budget fails the run. Results are kept as artifacts for 30 days.

  • Changing a budget needs a PR that says why: a deliberate feature cost, or new runner hardware.

Consequences ​

  • Regressions surface within a day, with the run that introduced them easy to find in the nightly history.
  • The benchmark is not a PR gate, so a regression can merge and be caught the next morning. That trades a day's delay for PRs that never fail on runner noise.
  • Budgets must be revisited when the measured setup changes, for example when the fake page grows.

Released under the MIT License. Development supported by Ensita. YapYap is unofficial and not affiliated with WhatsApp or Meta.