## The Problem After PR#110 `SystemMetricsCollector.sample` had the highest Mutation-CRAP score of Werkator: 35.36 at a complexity of 35. A score is never below the complexity, so no test could bring it under the limit of 15. The method did everything one sample of the System page needs in one body: it read the processors, the memory and swap, the run queue and the stall times, measured every disk against its budget and reset a disk's series when that budget changed, built the snapshot, recorded the history, and persisted the state. Most of its complexity was null handling: each of about twenty `cpu?.usedCores`, `ram?.usedGib`, `reading?.space?.usedBytes` asked again whether its source had been readable. ## Non-Goals - No change in behaviour, in the snapshot, in the history, or in the persisted state. ## The Scenarios ### Feature: One sample of the System page, taken in named steps #### Scenario#111.01: A sample still measures what it measured before So that the System page, the health verdict and the sizing see the same numbers after the refactoring. - **Given** the existing collector tests: CPU, RAM, swap, swap traffic, load, pressure, OOM kills, disks with quotas, the usage walk, aggregates, persistence, and unreadable sources - **When** they run against the split `sample` - **Then** all of them pass unchanged ##### Verified by - [SystemMetricsCollectorUnitTest](../../src/test/kotlin/de/hoennig/werkator/metrics/SystemMetricsCollectorUnitTest.kt) — all 31 tests, unchanged ## The Solution `sample` now reads as its domain steps: | Step | What it does | | --------------- | --------------------------------------------------------------------------------------------------------------- | | `readHost` | what the kernel says about the machine: processors, memory and swap, run queue, stalls, OOM kills, swap traffic | | `sampleStorage` | every monitored disk against its budget, and the repository size from the same usage walk | | `bindSource` | remembers what a disk was measured against, and restarts its series when that changed (Scenario#16.05) | | `diskMetricsOf` | the gauge of one disk; an unreadable disk keeps its key and label | | `snapshotOf` | the snapshot the UI and the health verdict read | | `historyValues` | the columns a host reading and a storage sample add to the history | A host reading that failed now holds its unread value (`CpuLoad.UNREAD`, `RamUsage.UNREAD`, `LoadAverage.UNREAD`) instead of `null`. A gauge asks for its number, and the question whether the source was readable is answered once, where it is read. `RunningAggregates` names the running min/max/avg per metric that `sample` kept as a local map and a local function. `DiskMetrics.unread` is the gauge of a disk not measured, shared with the empty snapshot before the first sample. Measured with PIT as the nightly build runs it: | Method | Before | After | | ------------------------------- | ------ | ----- | | `SystemMetricsCollector.sample` | 35.36 | 3.04 | | `diskMetricsOf` | — | 4 | | `RunningAggregates.record` | — | 4 | | `bindSource` | — | 3 | | `sampleStorage` | — | 3 | | `StorageSample.historyValues` | — | 2 | | `readHost` | — | 1.13 | | `snapshotOf` | — | 1 | | `HostReading.historyValues` | — | 1 | No new method of the collector scores above 4, and none is `INCONCLUSIVE`. The gate `maxMutationCrap` falls from 36 to 35: the highest score left is `BuildDefinition.applyTo` at 34. ## Additional Changes - Version 1.34.8 with its release note. ## Follow-up PRs - `BuildDefinition.applyTo` (34) and `CapacitySizing.cpu` (33.17), each in its own PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: mhoennig <michael@hoennig.de> Reviewed-on: #111
4.3 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#110 SystemMetricsCollector.sample had the highest Mutation-CRAP score of Werkator: 35.36 at a complexity of 35.
A score is never below the complexity, so no test could bring it under the limit of 15.
The method did everything one sample of the System page needs in one body:
it read the processors, the memory and swap, the run queue and the stall times,
measured every disk against its budget and reset a disk's series when that budget changed,
built the snapshot, recorded the history, and persisted the state.
Most of its complexity was null handling: each of about twenty cpu?.usedCores, ram?.usedGib, reading?.space?.usedBytes asked again whether its source had been readable.
Non-Goals
- No change in behaviour, in the snapshot, in the history, or in the persisted state.
The Scenarios
Feature: One sample of the System page, taken in named steps
Scenario#111.01: A sample still measures what it measured before
So that the System page, the health verdict and the sizing see the same numbers after the refactoring.
- Given the existing collector tests: CPU, RAM, swap, swap traffic, load, pressure, OOM kills, disks with quotas, the usage walk, aggregates, persistence, and unreadable sources
- When they run against the split
sample - Then all of them pass unchanged
Verified by
- SystemMetricsCollectorUnitTest — all 31 tests, unchanged
The Solution
sample now reads as its domain steps:
| Step | What it does |
|---|---|
readHost |
what the kernel says about the machine: processors, memory and swap, run queue, stalls, OOM kills, swap traffic |
sampleStorage |
every monitored disk against its budget, and the repository size from the same usage walk |
bindSource |
remembers what a disk was measured against, and restarts its series when that changed (Scenario#16.05) |
diskMetricsOf |
the gauge of one disk; an unreadable disk keeps its key and label |
snapshotOf |
the snapshot the UI and the health verdict read |
historyValues |
the columns a host reading and a storage sample add to the history |
A host reading that failed now holds its unread value (CpuLoad.UNREAD, RamUsage.UNREAD, LoadAverage.UNREAD) instead of null.
A gauge asks for its number, and the question whether the source was readable is answered once, where it is read.
RunningAggregates names the running min/max/avg per metric that sample kept as a local map and a local function.
DiskMetrics.unread is the gauge of a disk not measured, shared with the empty snapshot before the first sample.
Measured with PIT as the nightly build runs it:
| Method | Before | After |
|---|---|---|
SystemMetricsCollector.sample |
35.36 | 3.04 |
diskMetricsOf |
— | 4 |
RunningAggregates.record |
— | 4 |
bindSource |
— | 3 |
sampleStorage |
— | 3 |
StorageSample.historyValues |
— | 2 |
readHost |
— | 1.13 |
snapshotOf |
— | 1 |
HostReading.historyValues |
— | 1 |
No new method of the collector scores above 4, and none is INCONCLUSIVE.
The gate maxMutationCrap falls from 36 to 35: the highest score left is BuildDefinition.applyTo at 34.
Additional Changes
- Version 1.34.8 with its release note.
Follow-up PRs
BuildDefinition.applyTo(34) andCapacitySizing.cpu(33.17), each in its own PR.