Skip to content

The console shows each worker's load on its card and detail page #534

Closed

Description

@V3RON

Part of #527.

Scope

The console shows each worker's load in two places:

  • A summary row on its card on the Workers page, which is also the Status panel at the top of its detail page.
  • A Load panel with every figure and its age on its detail page.

This works on a gateway and on a single host alike, since both answer GET /v1/workers.

In short

The console already polls GET /v1/workers every second. It now renders the load block each view carries.

flowchart LR
  A["GET /v1/workers: view.load"] --> M["load-model.ts (new)"]
  M --> C["WorkerFacts: Load row (changed)"]
  M --> D["worker detail: Load panel (new)"]
Loading

Card, before: Connection, Health, Protocol, Version, Devices, Capacity. After, a Load row right after Capacity, or right after Version on a starting worker, whose card leaves out Devices and Capacity:

Worker Load row
connected, every part read CPU 34% · memory 9.8 of 32.0 GiB · disk 112.4 GiB free
limited CPU and memory CPU 61% (limit) · memory 3.1 of 8.0 GiB (limit) · disk 40.2 GiB free
available memory under 1 GiB memory 300 MiB of 8.0 GiB
CPU absent CPU unavailable · memory 9.8 of 32.0 GiB · disk 112.4 GiB free
starting, sent load as connected
connected, sent no load (an older worker) Not reported, as for any other fact a worker did not send
disconnected No reading while disconnected
incompatible no row

The detail page gets a Load panel, "How busy the machine is right now". It comes after the Host and Catalog row on a running worker. On a starting worker it comes after Host and before the "Devices, leases and capacity appear once startup finishes." line.

Row Example
CPU 34% busy, 10 cores / 61% busy, 2 cores (limit) / 0.5 cores, 1 core, 0.01 cores
Memory 9.8 GiB of 32.0 GiB available / 3.1 GiB of 8.0 GiB available (limit)
Swap 1.2 GiB used
Pressure normal (ok tone), warn (warn tone), critical (error tone)
Disk 112.4 GiB of 494.4 GiB free
Read 12 s ago, ticking every second, rounded up so a reading never looks fresher than it is

The (limit) mark is plain muted text after the figure, like the for iOS note in the Host panel's tool list. It is not a status tone: a limited figure is not over its limit.

  • An absent CPU, memory or disk shows Unavailable in its row.
  • With memory absent, Swap and Pressure are left out, since both come from memory. Pressure is also left out whenever the view has none, as on Linux.
  • A view with no load shows Not reported. in the panel, or No reading while disconnected. for a disconnected worker.
  • An incompatible worker's page has no Load panel.

If this goes wrong, an operator sees a missing, frozen or wrong load on a card or page.

Technical spec

Modules touched

flowchart LR
  WF["views/worker-facts.tsx *"] --> LM["views/load-model.ts (new)"]
  WD["views/worker-detail.tsx *"] --> LM
  WD --> T["live/time.ts formatDuration"]
  WD --> ST["status.tsx * (pressureStatus)"]
  LM --> WM["views/workers-model.ts (WorkerView type)"]
Loading
  • ui/src/views/load-model.ts (new). Pure functions from a WorkerView to the card summary and the detail rows. Formats match the CLI's Load line (A worker's simlock status shows its CPU, memory and disk load #531):
    • Bytes: 0 B, whole MiB below 1 GiB, GiB with one decimal from 1 GiB up, each rounded half up. A value that rounds to 1024 MiB is GiB.
    • Busy %: whole, rounded half up.
    • Cores: up to one decimal, or two below 0.1, and <0.01 below 0.005, core singular at exactly 1.
    • The card writes memory as <available> of <total> GiB when both are GiB, and otherwise each with its own unit (300 MiB of 8.0 GiB).
  • WorkerFacts gains the Load row, placed as the table above says. It is the same component on the card and in the detail page's Status panel, so the row shows in both. That is intended. The Status panel's description becomes "The worker's connection, health, capacity and load."
  • worker-detail.tsx gains a LoadFacts panel, placed as above.
    • Its age uses the now prop WorkerDetail already receives: the daemon's time, which workers.tsx fills from useNow().server. It does not call useNow().
    • formatDuration rounds down, so the panel rounds the age up to a whole second first: formatDuration(Math.ceil((now - load.readAt) / 1000) * 1000). formatDuration already shows a negative age as 0 s.
  • ui/src/status.tsx gains pressureStatus, beside the other tone maps.
  • The existing test "says Not reported for a worker whose status never carried the block" in worker-detail.test.tsx checks the whole page for "Not reported.". The Load panel now prints that too, so the test is narrowed to the Warm pool panel's markup. Otherwise it would stay green with the warm pool broken.

Contract and event changes

None. The console reads the load block #533 adds to the worker view.

Other code on the same state

Rules in play

  • docs/internal/DESIGN.md: Status colours and Voice, for the new rows and words.
  • testing.md.

Tests

The seams are load-model.ts directly, WorkerFacts and WorkerDetail through renderToStaticMarkup with a now prop, and the browser lane. Model them on these:

  • ui/src/views/workers-model.test.ts
  • ui/src/views/worker-detail.test.tsx ("counts its devices and capacity, and says Not reported for what it has not read")
  • e2e/console/workers.spec.ts ("a worker's detail shows its host facts, catalog and installs in progress", and "a disconnected worker shows as disconnected within a second" for the fleet)

The test lines:

  • the card summary lists CPU, memory and disk as the table in this issue shows, with (limit) on a limited part, including memory under 1 GiB
  • the card summary says CPU unavailable, memory unavailable or disk unavailable for an absent part
  • bytes, busy % and cores format as the CLI's Load line does, including 0, 1023.6 MiB, 0.5 cores, 1 core and 0.001 cores (<0.01 cores)
  • a running worker's card shows the Load row right after Capacity, and a starting worker's right after Version
  • a worker card says Not reported for a connected worker with no load, and No reading while disconnected for a disconnected one
  • an incompatible worker's card and page have no Load row or panel
  • a running worker's detail shows the Load panel after Host and Catalog, with CPU, memory, swap, pressure, disk and the reading's age
  • a starting worker's detail shows the Load panel after Host and before the startup line
  • a worker's detail leaves out Pressure when the view has none, and leaves out Swap and Pressure when memory is absent
  • a worker's detail shows pressure normal, warn and critical in the ok, warn and error tones
  • a worker's detail says Unavailable in the row of an absent CPU, memory or disk
  • a worker's detail says Not reported. in the Load panel with no load, and No reading while disconnected. for a disconnected worker
  • a worker's detail ages the reading as now − readAt rounded up to a whole second (4.1 s shows 5 s ago), and shows a larger age for a later now
  • the Status panel's description reads "The worker's connection, health, capacity and load.", and the Load panel's "How busy the machine is right now"
  • a worker's detail writes (limit) as muted text after a limited CPU or memory figure
  • the warm pool's Not reported test checks the Warm pool panel alone
  • in a browser, a single host's card shows its Load row, and its detail shows the Load panel with an s ago age that changes when read again a few seconds later, as the device time-in-state test does (about :346)
  • in a browser, with a gateway and two workers, stopping one turns its card's Load row into No reading while disconnected within a second of the gateway marking it disconnected

Done when

  • Every test above is green.
  • On a single host, the Workers page card shows the Load row, and the worker's page shows the Load panel with an age that ticks.

Out of scope

  • Charts or history.
  • An attention item for high load.
  • Sorting or filtering workers by load.

Depends on

Approval

  • Approved for delivery

Written by an agent.


Generated by Claude Code

Activity

  1. added
    task:draftScope written; technical spec or its spec check missing.
    task:readySpec checked. An agent may implement it once its Depends on allow.
    and removed
    task:draftScope written; technical spec or its spec check missing.
    on Oct 13, 2026
  2. 3 remaining items

  3. added a commit that references this issue on Oct 14, 2026
    eb8c23a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    task:readySpec checked. An agent may implement it once its Depends on allow.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions