Skip to content

Show each machine's CPU, memory and disk load #527

Closed
5 of 5 issues completed
Closed

Description

@V3RON

Request: none

Problem

An operator cannot see how loaded a machine is. simlock status, simlock worker list and the console say what a machine is (OS, architecture, tool versions) and what Simlock runs on it (devices, leases, capacity). They do not say how busy its CPU is, how much memory it has left, or how full its disk is.

So when devices boot slowly or an agent's tests time out, the operator has to log in to each machine and open Activity Monitor or top to find the one that is struggling. With a gateway and several workers, that means several logins.

Who it is for

  • The operator of a fleet. They watch many workers from one console or one simlock worker list, and want to spot an overloaded machine without logging in to it.
  • The owner of a single machine. They run simlock status and want to know whether the machine itself is the reason things are slow.
  • Scripts and dashboards. They read --json or the HTTP API and want the same figures.

Outcome

Every place that shows a machine's host facts also shows its current load:

  • CPU: how busy it is, as a percentage, over the last 15 seconds, and on how many cores.
  • Memory: how much is available out of the total, and how much swap is in use. On macOS, also the memory pressure level the operating system reports.
  • Disk: free space out of the total, on the disk that holds Simlock's data folder.
  • How old the reading is.

This works on macOS and on Linux, including a Linux container. Where a Linux CPU or memory limit applies to Simlock (a container's limit, or one set on Simlock's own service), that figure is measured against the limit and marked limit. Without a limit, the figures are the whole machine's.

The readings are for people to look at. Nothing in Simlock acts on them.

How it works for the user

flowchart LR
  S[Worker reads its machine every 15 s] --> C[Latest reading]
  C --> ST["simlock status, --json, HTTP"]
  C -->|"gateway's usual refresh"| V[Gateway's view of the worker]
  V --> WL[simlock worker list]
  V --> UI["Console: worker card and detail page"]
  V -->|worker disconnected| X[No reading shown]
Loading

simlock status on a macOS worker. Before:

Daemon: running (worker)
Host: macOS 15.5 arm64; xcode 16.4 (16F6)
...

After:

Daemon: running (worker)
Host: macOS 15.5 arm64; xcode 16.4 (16F6)
Load: CPU 34% busy (10 cores) · memory 9.8 GiB of 32.0 GiB available · swap 1.2 GiB used · pressure normal · disk 112.4 GiB of 494.4 GiB free · read 4s ago
...

simlock status in a Linux container limited to 2 CPUs and 8 GiB:

Host: Linux 6.8.0 x64; emulator 35.4.9
Load: CPU 61% busy (2 cores, limit) · memory 3.1 GiB of 8.0 GiB available (limit) · swap 0 B used · disk 40.2 GiB of 120.0 GiB free · read 9s ago

simlock worker list on a gateway. Each connected worker gets its Load line under its Host line. A disconnected worker shows Load: no reading while disconnected.

The console. Each worker card on the Workers page gets a one-line summary (CPU 34% · memory 9.8 of 32.0 GiB · disk 112.4 GiB free). The worker's detail page shows the full readings and their age next to the host facts.

--json and the HTTP API. simlock status --json, GET /v1/status and GET /v1/workers carry the same readings as structured fields.

Examples

What the Load line says in each situation. Today there is no Load line in any of them.

Situation Today After
macOS, every figure read no Load line CPU, memory, swap, pressure normal, disk, age
macOS under heavy memory use no Load line pressure warn or pressure critical, as macOS reports it
Linux, not in a container no Load line CPU, memory, swap, disk, age; no pressure part
Linux container with CPU and memory limits no Load line cores and memory total are the limits, each marked limit; busy % and available memory are measured within them
Linux container with no limits no Load line the whole machine's figures, no limit marker
Linux container with a memory limit only no Load line memory marked limit; CPU is the whole machine's, no marker
Linux, not in a container, Simlock's service limited to 16 GiB no Load line memory ... of 16.0 GiB available (limit)
No swap configured no Load line swap 0 B used
Worker started less than 15 s ago no Load line CPU unavailable, the other figures shown
Worker still starting up no Load line Load line shown, as for a running worker
Memory could not be read no Load line memory unavailable, the other figures shown
Worker disconnected from the gateway no Load line no reading while disconnected
simlock status against a gateway no Load line the gateway machine's own load

What could go wrong

  • The figures differ a little from Activity Monitor or top, which measure over different time windows. Likely, and harmless.
  • A Linux machine or container on the older cgroup v1 system shows the whole machine's figures even when a limit is set, with no limit marker. Unlikely on current distributions and Docker.
  • If a worker's readings stop updating, the shown age keeps growing, so the operator can tell. A reading never looks fresher than it is.
  • Taking a reading every 15 s costs a little CPU on each worker. Expected to be too small to notice.

Words used

  • busy %: the share of the CPU time available that was spent working over the last 15 s: the whole machine's, or within the CPU limit when one applies. E.g. CPU 34% busy (10 cores).
  • available memory: memory that can be handed out without swapping. It is the figure Activity Monitor and Linux's MemAvailable use, not the smaller "free" figure. E.g. memory 9.8 GiB of 32.0 GiB available.
  • memory pressure: macOS's own judgement of how short of memory the machine is: normal, warn or critical. Linux has no equivalent, so it is not shown there.
  • limit: a cap on the CPU or memory Simlock and everything it starts may use, set through Linux cgroup v2 (cpu.max, memory.max). A container's limits are one case; a limit on Simlock's own service is another. E.g. 2 cores and 8 GiB. Shown as limit next to the figure it caps.
  • Simlock data folder: where Simlock keeps its devices and state, the folder the low-disk cleanup already watches.
  • reading age: how long ago the worker took the reading being shown, e.g. read 12s ago.

Non-goals

  • Routing on load. The gateway does not pick workers by these readings.
  • Acting on load. Capacity, the warm pool and cleanup do not read them.
  • New thresholds, warnings or events. No new config key, no "high load" alert, no new event. The existing low-disk cleanup is unchanged.
  • A command to start or stop sending readings, or a live stream of them. Readings travel only inside the status answers the gateway already asks for.
  • History or charts over time. Each place shows the latest reading only.
  • Load per device. How much CPU one simulator or emulator uses is not measured.
  • Disks other than the one holding Simlock's data folder, such as where iOS runtimes or Android system images are installed.
  • Linux cgroup v1 limits.
  • A Prometheus or other metrics export.

Completion conditions

  • On a macOS worker, simlock status prints a Load line with CPU busy %, cores, available and total memory, swap used, the pressure level, free and total disk, and the reading's age.
  • On a Linux worker that is not in a container, simlock status prints the same Load line without the pressure part.
  • Where cgroup v2 CPU and memory limits apply to Simlock, the cores and the memory total equal those limits and both are marked limit. Where none applies, the figures are the whole machine's and carry no marker.
  • simlock status --json and GET /v1/status carry the same readings as simlock status prints.
  • On a gateway, GET /v1/workers and simlock worker list carry each connected worker's latest readings with their age, and no readings for a disconnected worker.
  • The console shows a one-line load summary on each connected worker's card, and the full readings with their age on its detail page.
  • A figure that cannot be read shows as unavailable while the others still show, and simlock status still answers.
  • simlock status takes no longer with this feature than without it.
  • A worker that is still starting shows its Load line.
  • A gateway's own simlock status shows the gateway machine's load.
  • With the same devices, leases and requests, the gateway picks the same worker and the worker makes the same capacity and cleanup decisions as before this feature.
  • No new operation, HTTP route, event or config key is added.
  • The readings come from a module of their own, and the daemon behaves the same without it apart from showing no load.

Open questions

Decisions

Tasks

flowchart LR
  T1["#531 worker status shows load"] --> T2["#532 Linux cgroup limits"]
  T1 --> T3["#533 gateway carries it"]
  T3 --> T4["#534 console"]
Loading

These tasks run alongside #511's tasks (#519, #513, #514, #515) and #348 and #318 with no ordering, by the maintainer's choice: whichever PR merges second takes the base branch and bumps the protocol again.

Written by an agent.


Generated by Claude Code

Activity

  1. added
    feature:specSpec in progress, business and technical together.
    on Oct 13, 2026
  2. V3RON commented on Oct 13, 2026

    @V3RON
    ContributorAuthor

    Spec updated: Outcome, Examples, Words used and Completion conditions mark a cgroup limit instead of in container; Decisions links ADR 0026 (#529).

    Written by an agent.


    Generated by Claude Code

  3. V3RON commented on Oct 13, 2026

    @V3RON
    ContributorAuthor

    Spec updated: split into #531, #532, #533 and #534 (Tasks section added); ADR 0027 (#529) awaits acceptance.

    Written by an agent.


    Generated by Claude Code

  4. added
    feature:plannedSplit into tasks. Never picked up itself.
    and removed
    feature:specSpec in progress, business and technical together.
    on Oct 13, 2026
  5. V3RON commented on Oct 14, 2026

    @V3RON
    ContributorAuthor

    Delivery run

    Merged: none
    Parked: none
    Waiting: stack #564 (#529, #563, #565, #567, #568) — all four tasks passed review and sit in the stack; #567 and #568 wait on the maintainer's rebase and push of task/533 before the gate runs
    Decided: #532 — busy % under a CPU limit: usage ÷ (elapsed × cores) = 30.5%, not 61% → option taken, pending the maintainer (see the Decision taken comment on #565)
    Follow-up: #569 (task:draft) — gauges of the latest reading on the worker detail page, requested by the maintainer
    Asleep: 0 min

    Written by an agent.

  6. github-actions commented on Oct 15, 2026

    @github-actions

    All 5 sub-issues are closed. If the completion conditions hold on main, close this feature and flip its ADRs to Accepted.

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

    feature:plannedSplit into tasks. Never picked up itself.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions