Files
werkator/docs/prs/2026-09-08-PR#33-system-page-disks.md
2026-09-08 15:17:33 +02:00

7.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

The System page (PR#29, PR#32) measures one disk: the filesystem of the served repository's checkout. A Hostsharing Managed Webspace has two: the SSD home directory and the HDD tree under /home/storage/<pac>/users/<user>, each with its own group quota. The plan is to keep the artifacts, which grow, on the HDD and everything else on the SSD. Whether the HDD needs booking is then a question the page cannot answer, and the mount path is no title for a gauge.

A second problem surfaces with that plan: the measurement series live in system-metrics-state.json in the artifact root. Moving the artifacts to the HDD would move the history with them, onto the slow disk.

Non-Goals

  • Detecting SSD versus HDD; the labels come from the configuration.
  • Moving the artifacts, the werkdock image store, or anything else; this PR only measures.
  • Keeping the flat diskTotalGib/diskUsedGib/diskFreeGib/diskSource fields of GET /api/system; werkator.js was their only consumer.

The Scenarios

Feature: The System page shows every configured filesystem

Background

  • The instance configuration ~/.werkator.yml owns what is the host's, not a repository's (ADR 0009).
  • A monitored disk has a label (the gauge title), a path on the filesystem, and a key naming its series.
  • The reading of a disk is the tightest of the user quota, the group quota, and the volume of that filesystem (PR#16).

Scenario#33.01: metrics.disks is an instance section

So that a host's mounts are configured once for the instance, and a branch cannot reach them by construction.

  • Given ~/.werkator.yml declares metrics.disks with a label and a path per entry
  • When a repository's effective configuration is loaded
  • Then the section comes from the home file alone
    • and a copy in a repository's own files is ignored with one warning, like server
    • and the key of an entry defaults to its label lowercased (SSD → ssd), with an explicit key taking precedence.
Verified by

Scenario#33.02: every configured disk gets its own gauge, history, peak, health reason, and sizing chip

So that the SSD and the HDD are judged side by side, and the page says which of the two to book more of.

  • Given two entries, SSD on the home directory and HDD on the storage tree
  • When the collector samples
  • Then each entry is read against its own filesystem's quota lines
    • and its series are disk.<key>.usedGib and disk.<key>.freeGib in the state, the history, and GET /api/system/history
    • and GET /api/system carries disks[] with key, label, total, source, and the aggregates, plus capacity.disks[] with the advice per disk
    • and the page shows one gauge per disk in the configured order, one footer total each, and one chip each in the verdict panel
    • and the machine verdict names the disk by its label (HDD at 85 % (8.50 of 10.00 GiB)).
  • Given no metrics.disks section
  • Then the page shows the checkout's filesystem as before, labelled Disk, under the key disk.
Verified by

Scenario#33.03: the history recorded for the single disk continues as the first configured disk

So that switching a running instance to two disks keeps the weeks of history the sizing advice needs.

  • Given a state file written before this PR, with diskUsedGib/diskFreeGib series and a diskSourceKey
  • When the collector loads it under a configuration whose first disk has the key ssd
  • Then the series and the history columns continue as disk.ssd.usedGib/disk.ssd.freeGib
    • and the binding-source reset of Scenario#16.05 still fires only when that disk's source changes
    • and the other disks start their columns empty.
Verified by

Scenario#33.04: the measurement series stay in the instance's state home

So that moving the artifacts to a slow, large volume does not move the history with them.

  • Given a state file still in the served repository's artifact root
  • When the server starts with this PR
  • Then the file is moved once to ${XDG_STATE_HOME:-~/.local/state}/werkator/system-metrics-state.json
    • and the series continue there, whatever artifacts.rootDir says.
Verified by

The Solution

metrics.disks is a new instance-level section next to server and executor: ConfigLoader reads it from the home file alone and drops it from repository files with the same warning, so a branch cannot reach it. Each entry becomes a MonitoredDisk (key, label, directory), resolved against the instance's home directory by MetricsConfiguration; without the section the served checkout is the one disk under the key disk.

SystemMetrics carries the disks as a list (DiskMetrics) instead of the flat fields. The collector reads the quota output once per sample and matches every disk's file store against it, so two filesystems with two group quotas are two budgets, and keeps the binding source per disk key for the reset of PR#16. The history tiers take their columns from what is sampled instead of a fixed key list, so a disk added later starts with nulls and one removed ages out. HistoryPeaks, SystemHealth, and CapacitySizing iterate the disks: a reason names the disk by its label, the sizing advice is capacity.disks[] with key and label beside the verdict.

The page renders the gauges, the footer totals, and the chips from those lists; werkator.js resolves a card or chip keyed disk.<key> against disks[] of the polled snapshot and draws its chart from the disk.<key>.usedGib column.

A state file from before this PR is migrated on load: diskUsedGib/diskFreeGib become the first configured disk's series in the aggregates and in all three tiers, and the old diskSourceKey becomes that disk's entry. The state file itself moves out of the artifact root into the instance's state home, once, because the series are the instance's and the artifacts are about to go elsewhere.

Open Questions

  • The default disk without configuration is keyed disk, so its series are disk.disk.usedGib; a nicer default key would only matter for an API consumer.
  • A bare ~ in YAML is null, not a path; the documentation and the template quote it.

Additional Changes

  • The .werkator.yml template of init carries the section commented out, and docs/configuration.md documents it under the instance file.

Prerequisite PRs

  • PR#29 (system page with history) and PR#32 (capacity advice).