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
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.
## 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)
CapacitySizing.cpu had Mutation-CRAP 33.17 (complexity 12, mutation score
0.47): its boundaries and the tight verdict were untested.
- cpuWaited: an upgrade when the load exceeded the threads in more than
5 % of the quarter hours; cpuBusy: downsize, tight or fits otherwise.
- CpuUsage carries threads, use, peak and days for both.
- Scenario#113.01-06 test the boundaries; cpu 6, cpuBusy 5.01,
cpuWaited 4.30. Gate 34 -> 32.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mi
changed title from WIP: CPU sizing advice tested and split (v1.34.10) to CPU sizing advice asks "did the load queue" and "how busy" separately, its thresholds tested at their boundaries (v1.34.10)2026-10-04 19:06:14 +02:00
mi
merged commit f2f23780a2 into main2026-10-04 19:11:52 +02:00
mi
deleted branch mihoe/capacity-sizing-cpu2026-10-04 19:11:52 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The Problem
After PR#112
CapacitySizing.cpuhad 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
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.
load1andcpuUsedcolumnsunknown, with the reasonno CPU historyVerified by
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.
Verified by
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.
Verified by
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.
Verified by
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.
Verified by
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.
load1, and one with onlycpuUsedVerified by
The Solution
cpuasks its two questions in two methods:cpuWaitedcpuBusyCpuUsagecarries what both need: the threads, the use per quarter hour, its peak, and the days of the basis.cpuitself keeps the guard for a missing history.Measured with PIT as the nightly build runs it:
CapacitySizing.cpuCapacitySizing.cpuBusyCapacitySizing.cpuWaitedcpuitself kills 8 of 8 mutants now, and none of the three isINCONCLUSIVE.The gate
maxMutationCrapfalls from 34 to 32: the highest score left isForgejoWorkflowExporter.settingNotesat 31.63.Additional Changes
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).🤖 Generated with Claude Code
WIP: CPU sizing advice tested and split (v1.34.10)to CPU sizing advice asks "did the load queue" and "how busy" separately, its thresholds tested at their boundaries (v1.34.10)