Files
werkator/docs/prs/2026-09-09-PR#42-sizing-corrections.md
2026-09-09 15:58:08 +02:00

13 KiB

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

  • PR#40 — brought the advice forward to one day of history, which is what made these three defects visible.

The Problem

The first day of sizing advice on the production host asked for roughly a hundred Euro a month of hardware that nobody needs.

CPU: five threads on a machine that copes with two. CapacitySizing read the history through one helper that prefers the per-bucket maxima over the averages. For RAM and disk that is right — a single moment of overflow is the failure being sized against. For CPU it made the number a peak of peaks: the highest one-minute run queue inside each quarter hour, and then the 95th percentile of those. A compiler that queues four tasks for a minute read as a standing demand for four threads. The same history, read as sustained load, asks for three.

RAM: 6.5 GiB, a size that cannot be ordered. The recommendation was the peak divided by a 70 % headroom target, rounded up to half a GiB, and expressed in visible memory. Memory is bought in whole GiB, and what /proc/meminfo reports is a little less than what was booked because the host keeps a slice — 4.80 GiB visible is 5 GiB bought. So the advice named neither a bookable size nor the size actually in use.

Disk: an order for 62 GiB of quota because a directory was unpacked once. The growth rate was the difference between the first and the last point of the window, divided by its length. The production host's SSD series is flat at 4.89 GiB, steps once to 6.29 when a build environment is unpacked, and is flat again — which that formula reports as "growing 1.21 GiB/day" and, extrapolated over thirty days, as a need for 62 GiB. The HDD told the same story from a 0.6 GiB step. Both were phrased as "upgrade to N GiB" without naming that the budget is a group quota, which reads as an order for hardware.

And nothing at all between 80 % and 90 %. A resource sitting at 81 % of its budget was reported as fits, in the same green as one at 20 %, until it crossed 90 % and turned red. The gauges have had a watch band at 80 % since the beginning; the advice had no equivalent, so the only thing it could say about a budget worth planning to raise was nothing.

And nothing to do with the answer. Where the advice is correct, acting on it means writing a mail to a service desk and transcribing four numbers by hand.

Non-Goals

  • No change to the thresholds themselves: 90 % is still an upgrade, 80 % is the watch band the gauges already use, below 40 % is still a downsize, and CPU still needs more than 5 % of the quarter hours waiting to be an upgrade.
  • No change to what is sampled or stored. All three corrections are in how the existing history is read.
  • No swap in the sizing. It never was in it, and the question whether swap pressure should raise the RAM recommendation is left open.
  • The page never sends a mail and never talks to a hosting API. It composes text and hands it to the user's own mail client.
  • Only one hosting is recognised, because only one is deployed to. There is no plugin point for others and no guessing: an unrecognised environment simply offers nothing.

The Scenarios

Feature: sizing CPU

Background

  • The quarter-hour tier keeps an average and a maximum per bucket; load1 is the one-minute run queue, cpuUsed the busy threads.

Scenario#42.01: CPU is sized from the load a quarter hour sustained

So that a brief queue during a compile does not buy a thread.

  • Given a history whose quarter-hour maxima of load1 reach 4.6 on a two-thread machine
    • and whose quarter-hour averages stay near 2.2
  • When the advice is computed
  • Then both the share of waiting quarter hours and the recommended size come from the averages
    • and the recommendation is three threads, not five
Verified by

Feature: sizing RAM

Scenario#42.02: RAM is advised in the size it is bought in

So that the recommendation names something that can be ordered.

  • Given 4.80 GiB of visible memory and a peak of 4.40 GiB
  • When the advice is computed
  • Then the current size is reported as 5 GiB — the visible total rounded up to whole GiB
    • and the difference, 0.20 GiB, is treated as the reserve the host keeps
    • and the recommendation is the peak plus 20 % headroom plus that reserve, rounded up to whole GiB: 6 GiB
    • and the verdict still compares the peak against the visible total, because that is what a process can allocate
Verified by

Feature: sizing a disk

Scenario#42.03: A one-off step is not growth

So that unpacking a build environment does not order a month of it.

  • Given a disk series that is flat, steps up once, and is flat again
  • When the advice is computed
  • Then the growth rate used is the smaller of the two half-window slopes, which the step leaves at zero
    • and the verdict falls back to the peak against the total
  • Given less than seven days of history
  • Then no trend is extrapolated at all, whatever the points suggest
  • Given a series that grows steadily across the whole window, over at least a week
  • Then the trend is used as before, and the reason names the budget — "90 % of the 8.00 GiB group quota in 12 days"
Verified by

Feature: the disk card

Scenario#42.04: The breakdown button sits where the average sits

So that the three gauge cards have one shape.

  • Given a disk card with a breakdown
  • Then its button is a pale pie-chart icon in the row of the current value, the slot where CPU and RAM show their average
    • and it takes the accent colour on hover and on keyboard focus

Feature: the watch band

Scenario#42.05: A budget in the watch band is a planned raise, not an alarm

So that a quota can be raised before it is urgent.

  • Given a disk whose peak is 81 % of its budget
  • When the advice is computed
  • Then the verdict is tight
    • and the recommended size leaves a third free: 10 GiB for a 6.5 GiB peak
    • and the chip reads "SSD: watch, raise to 10.00 GiB" in the same yellow the gauges use for watch
  • Given a resource in the watch band whose recommended size is the one already booked
  • Then the verdict stays fits, because there is nothing to raise
  • Given a CPU whose busiest single quarter hour was fully used but which sustains a fifth of that
  • Then the verdict stays fits: the watch band asks the 95th percentile of the sustained load, not the peak
Verified by

Background

  • A Hostsharing Managed Webspace user lives at /home/pacs/<package>/users/<user>; resources are ordered by mail from a service desk, not through an API.

Scenario#42.06: The hosting is recognised, or nothing is offered

So that a button that cannot work is never shown.

  • Given a home directory of the shape /home/pacs/mih09/users/werkator and a hostname h68.hostsharing.net
  • Then the hosting is hostsharing, the webspace mih09 and the machine h68 — the host's own name, not the domain it answers for
  • Given any other home directory, or a hostname that cannot be read
  • Then there is no hosting, GET /api/system reports null, and the page shows no order button

Scenario#42.08: A Container Server is not a Managed Webspace

So that the wrong product is never offered the wrong order.

  • Given an instance running as the account tallyman
  • Then there is no hosting, whatever its home directory looks like
    • because a Managed Webspace account is always the package itself or the package with a suffix — mih09, mih09-werkator — and tallyman can be neither
Verified by
Verified by

Scenario#42.07: The order is chosen, then written as a mail

So that the customer decides what to order and sees the text before it is sent.

  • Given an advice of "CPU 3 threads", "RAM 6 GB", "SSD quota 10 GiB" and "HDD quota 5 GiB"
  • When the order dialog is opened
  • Then each of the four is a checkbox in German
    • and the three raises are ticked, the shrink is not
  • When the ticked ones are written
  • Then the mail client opens with a mail to the service desk
    • and its subject is "Auftrag für mih09 auf h68"
    • and its body names the webspace and the machine and lists the ticked changes as bullets
    • and nothing is sent by the page itself
Verified by
  • the pure orderItems/orderMail in werkator.js — no JS test harness exists in this repository; verified in the browser against a seeded advice, light and dark

The Solution

averageColumn joins column in CapacitySizing: the same shape, but always the averages, and CPU is the only caller. The two are documented against each other, because which one a resource uses is the whole difference between sizing for a peak and sizing for a load.

RAM gets its own function instead of sharing byPeak with the disks. It rounds the visible total up to whole GiB to get both the booked size and the reserve, and returns the advice in booked GiB while judging the verdict on the visible total.

growthPerDay replaces the first-to-last difference with least squares over each half of the window, taking the smaller slope. A genuine trend shows in both halves; a step shows in one. disk() additionally refuses to extrapolate below RELIABLE_DAYS, and takes the budget's name so the reason can say what would have to be raised.

The headroom target moves from 70 % to 80 % for CPU and RAM, because every recommendation is now rounded up to a bookable size and the two compounded: 30 % headroom plus the rounding bought a whole extra step. Disks keep the 70 %, as DISK_TARGET_RATIO: memory pressure passes, a full filesystem does not, and what a disk holds only grows between prunings.

SizingVerdict.TIGHT fills the gap between "fits" and "upgrade" wherever a ratio drives the verdict — RAM and disk from their peak, CPU from the 95th percentile of its sustained utilisation, never from its busiest quarter hour. It is only reached when there is actually something to raise, so a resource whose recommended size is the one already booked stays fits. The chip renders in the amber the gauges use for watch, and UiFormats.adviceLabel and its JavaScript twin both say "watch, raise to N".

On the page the breakdown button moves into the value row as an icon.

HostingEnvironments.detect answers where the instance runs, and null everywhere it cannot act — which is everywhere but a Managed Webspace. Two conditions have to hold together: the home directory has the webspace shape, and the account name is the package found in that path, or that package with a suffix. The path alone is not proof, because Hostsharing's Container Server is a different product that grows differently and must not be offered a webspace order; its account is tallyman, which no webspace account can be. It is read once per process, because neither value changes while the server runs, and served as hosting in GET /api/system; CapacityView.orderable combines it with "is there anything to change" so the button renders correctly on the first paint rather than appearing on the first poll. orderItems and orderMail in werkator.js are pure: the first turns the polled advice into German lines with a default tick, the second turns the ticked ones into subject, body and a mailto: link. The mail is German because the desk that reads it is; the dialog around it stays in the page's language.

Open Questions

  • The half-window rule rejects a step, and it also halves the sensitivity to a trend that only started in the second half. With a week's minimum window that seemed the better trade.
  • Whether sustained swap use should raise the RAM recommendation. It does not today, and on the production host 2 GiB of swap are in use while RAM is advised from its own peak alone.

Additional Changes

  • A CPU is counted in threads throughout the UI, the API and the advice, not in cores: availableProcessors reports hardware threads, and a Managed Webspace is billed by them. CapacitySizing.CPU_UNIT is the one place the word lives.
  • Version 1.4.1 and its release-notes entry, since this is what gets deployed.
  • The buildenv slice of the breakdown donut gets a colour of its own; it and other were both grey and, being the two largest slices on the production host, sat next to each other indistinguishably.