## The Problem After PR#112 `CapacitySizing.cpu` had the highest Mutation-CRAP score of Werkator: 33.17 at a complexity of 12 and a mutation score of 0.47. Unlike the two before it, it failed mostly on its tests: three tests reached it, and none asked for its boundaries. Whether the load queued in exactly 5 % of the quarter hours, whether a peak of exactly 40 % lets a thread go, whether a sustained 80 % is tight — a changed comparison passed every test. The tight verdict had no test at all. Its complexity of 12 was also two questions in one body, asked one after the other: did the load queue for the threads, and if not, how busy were they. ## Non-Goals - No change in the advice: the thresholds, the sizes, and the reasons stay as they are. ## The Scenarios ### Feature: CPU sizing advice #### Scenario#113.01: Without threads or without any CPU history nothing is advised So that a machine whose CPU was never sampled is not told to shrink. - **Given** a snapshot with no threads, or a history without the `load1` and `cpuUsed` columns - **When** the advice is computed - **Then** the CPU verdict is `unknown`, with the reason `no CPU history` ##### Verified by - [CapacitySizingUnitTest](../../src/test/kotlin/de/hoennig/werkator/metrics/CapacitySizingUnitTest.kt) — Scenario#113.01 #### Scenario#113.02: A load queuing in exactly 5 % of the quarter hours, or only reaching the threads, buys no thread So that the waiting share is a limit to exceed, not to reach. - **Given** two threads, and a load above them in exactly 48 of 960 quarter hours, or a load of exactly 2.0 throughout - **When** the advice is computed - **Then** the CPU fits ##### Verified by - [CapacitySizingUnitTest](../../src/test/kotlin/de/hoennig/werkator/metrics/CapacitySizingUnitTest.kt) — Scenario#113.02 #### Scenario#113.03: An upgrade is sized to the load the quarter hours sustain So that the advice names the size that stops the queue, not merely one thread more. - **Given** two threads, and a load of 5.2 in 10 % of the quarter hours - **When** the advice is computed - **Then** the CPU is an upgrade to 6 threads, with the busiest use as its peak ##### Verified by - [CapacitySizingUnitTest](../../src/test/kotlin/de/hoennig/werkator/metrics/CapacitySizingUnitTest.kt) — Scenario#113.03 #### Scenario#113.04: Threads go only when the peak stays below 40 % and a smaller size still holds it So that a downsize is never advised at the boundary, and never to the size already booked. - **Given** four threads with a peak of 1.5, four threads with a peak of 1.6, and two threads with a peak of 0.75 - **When** the advice is computed - **Then** the first is a downsize to 2 with its reason, the second fits, the third is a downsize to 1 ##### Verified by - [CapacitySizingUnitTest](../../src/test/kotlin/de/hoennig/werkator/metrics/CapacitySizingUnitTest.kt) — Scenario#113.04 #### Scenario#113.05: A CPU that sustains 80 % of its threads is tight So that the watch band starts at 80 %, and its advice holds the sustained use with headroom. - **Given** two threads sustaining 1.6, 1.59, and 4.0 - **When** the advice is computed - **Then** 1.6 is tight with 3 threads advised, 1.59 fits, and 4.0 asks for 5 ##### Verified by - [CapacitySizingUnitTest](../../src/test/kotlin/de/hoennig/werkator/metrics/CapacitySizingUnitTest.kt) — Scenario#113.05 #### Scenario#113.06: One CPU column alone is enough to advise So that a history that lost one column still gets advice from the other. - **Given** a history with only `load1`, and one with only `cpuUsed` - **When** the advice is computed - **Then** the first is judged with a peak of zero, the second without a waiting share ##### Verified by - [CapacitySizingUnitTest](../../src/test/kotlin/de/hoennig/werkator/metrics/CapacitySizingUnitTest.kt) — Scenario#113.06 ## The Solution `cpu` asks its two questions in two methods: | Method | Question | | ----------- | ----------------------------------------------------------------------------------------------- | | `cpuWaited` | did the load exceed the threads in more than 5 % of the quarter hours? An upgrade, else nothing | | `cpuBusy` | with nothing queued, how busy were the threads? A downsize, tight, or fits | `CpuUsage` carries what both need: the threads, the use per quarter hour, its peak, and the days of the basis. `cpu` itself keeps the guard for a missing history. Measured with PIT as the nightly build runs it: | Method | Before | After | | -------------------------- | ------ | ----- | | `CapacitySizing.cpu` | 33.17 | 6 | | `CapacitySizing.cpuBusy` | — | 5.01 | | `CapacitySizing.cpuWaited` | — | 4.30 | `cpu` itself kills 8 of 8 mutants now, and none of the three is `INCONCLUSIVE`. The gate `maxMutationCrap` falls from 34 to 32: the highest score left is `ForgejoWorkflowExporter.settingNotes` at 31.63. ## Additional Changes - Version 1.34.10 with its release note. ## Follow-up PRs - `ForgejoWorkflowExporter.settingNotes` (31.63), `SystemHealth.assess$diskUtilization` (31.28), `GitService.authenticated` (30.12), `SystemMetricsView.from` (29.92), and `CapacitySizing.ram`/`column`/`disk` (26.87, 26.83, 21.19). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: mhoennig <michael@hoennig.de> Reviewed-on: #113
5.5 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.
The Problem
After PR#112 CapacitySizing.cpu had the highest Mutation-CRAP score of Werkator: 33.17 at a complexity of 12 and a mutation score of 0.47.
Unlike the two before it, it failed mostly on its tests: three tests reached it, and none asked for its boundaries.
Whether the load queued in exactly 5 % of the quarter hours, whether a peak of exactly 40 % lets a thread go, whether a sustained 80 % is tight — a changed comparison passed every test.
The tight verdict had no test at all.
Its complexity of 12 was also two questions in one body, asked one after the other: did the load queue for the threads, and if not, how busy were they.
Non-Goals
- No change in the advice: the thresholds, the sizes, and the reasons stay as they are.
The Scenarios
Feature: CPU sizing advice
Scenario#113.01: Without threads or without any CPU history nothing is advised
So that a machine whose CPU was never sampled is not told to shrink.
- Given a snapshot with no threads, or a history without the
load1andcpuUsedcolumns - When the advice is computed
- Then the CPU verdict is
unknown, with the reasonno CPU history
Verified by
- CapacitySizingUnitTest — Scenario#113.01
Scenario#113.02: A load queuing in exactly 5 % of the quarter hours, or only reaching the threads, buys no thread
So that the waiting share is a limit to exceed, not to reach.
- Given two threads, and a load above them in exactly 48 of 960 quarter hours, or a load of exactly 2.0 throughout
- When the advice is computed
- Then the CPU fits
Verified by
- CapacitySizingUnitTest — Scenario#113.02
Scenario#113.03: An upgrade is sized to the load the quarter hours sustain
So that the advice names the size that stops the queue, not merely one thread more.
- Given two threads, and a load of 5.2 in 10 % of the quarter hours
- When the advice is computed
- Then the CPU is an upgrade to 6 threads, with the busiest use as its peak
Verified by
- CapacitySizingUnitTest — Scenario#113.03
Scenario#113.04: Threads go only when the peak stays below 40 % and a smaller size still holds it
So that a downsize is never advised at the boundary, and never to the size already booked.
- Given four threads with a peak of 1.5, four threads with a peak of 1.6, and two threads with a peak of 0.75
- When the advice is computed
- Then the first is a downsize to 2 with its reason, the second fits, the third is a downsize to 1
Verified by
- CapacitySizingUnitTest — Scenario#113.04
Scenario#113.05: A CPU that sustains 80 % of its threads is tight
So that the watch band starts at 80 %, and its advice holds the sustained use with headroom.
- Given two threads sustaining 1.6, 1.59, and 4.0
- When the advice is computed
- Then 1.6 is tight with 3 threads advised, 1.59 fits, and 4.0 asks for 5
Verified by
- CapacitySizingUnitTest — Scenario#113.05
Scenario#113.06: One CPU column alone is enough to advise
So that a history that lost one column still gets advice from the other.
- Given a history with only
load1, and one with onlycpuUsed - When the advice is computed
- Then the first is judged with a peak of zero, the second without a waiting share
Verified by
- CapacitySizingUnitTest — Scenario#113.06
The Solution
cpu asks its two questions in two methods:
| Method | Question |
|---|---|
cpuWaited |
did the load exceed the threads in more than 5 % of the quarter hours? An upgrade, else nothing |
cpuBusy |
with nothing queued, how busy were the threads? A downsize, tight, or fits |
CpuUsage carries what both need: the threads, the use per quarter hour, its peak, and the days of the basis.
cpu itself keeps the guard for a missing history.
Measured with PIT as the nightly build runs it:
| Method | Before | After |
|---|---|---|
CapacitySizing.cpu |
33.17 | 6 |
CapacitySizing.cpuBusy |
— | 5.01 |
CapacitySizing.cpuWaited |
— | 4.30 |
cpu itself kills 8 of 8 mutants now, and none of the three is INCONCLUSIVE.
The gate maxMutationCrap falls from 34 to 32: the highest score left is ForgejoWorkflowExporter.settingNotes at 31.63.
Additional Changes
- Version 1.34.10 with its release note.
Follow-up PRs
ForgejoWorkflowExporter.settingNotes(31.63),SystemHealth.assess$diskUtilization(31.28),GitService.authenticated(30.12),SystemMetricsView.from(29.92), andCapacitySizing.ram/column/disk(26.87, 26.83, 21.19).