Skip to content

Count each device against the RAM budget by its mode, and report the budget in status #188

Description

@V3RON

Part of #174.

Scope

This task delivers all of #174. After this PR the resource strategy counts each device against the RAM budget by the mode it has once booted, and at the full size until then. The operator can set a smaller size for slim devices, and status reports the RAM budget.

A slim device uses full RAM until its slim pass runs. So a device that has not booted yet counts at its platform's full size, whatever its spec's mode. That covers a planned device, one still provisioning, and one quarantined before it ever booted. Booting a shut-down device needs the same full-size room.

{
  "capacity": {
    "strategy": "resource",
    "config": {
      "ramBudget": {
        "iosBytesPerDevice": 1610612736,
        "iosSlimBytesPerDevice": 966367641,   // optional; absent means the full size
        "androidBytesPerDevice": 4294967296,
        "androidSlimBytesPerDevice": 1610612736   // optional; no effect until Android slim lands
      }
    }
  }
}

Technical spec

File and line references are to main at 03c027f, before #177 and #178. Branch task/188 implements the previous version of this spec. Build on it.

Modules touched

  • src/core/capacity/strategy.ts: CapacityDevice gains required mode (slim or full). canProvision takes the planned device (platform and mode) instead of a platform. New canBoot(device, devices) decides whether a shut-down device may boot. New ramBudget(devices) returns { limitBytes, usedBytes, overLimit }, or nothing for a strategy with no RAM budget. Fix the comment at :47-49.
  • src/core/capacity/devices.ts (new, exported from the module's index): the only place a record or a spec becomes a capacity device. A planned device counts as full. A device still provisioning counts as full. Every other device counts by record.mode. A device quarantined from provisioning still carries the full placeholder registerDevice wrote, so it counts as full with no special case.
  • src/core/capacity/strategies/resource/index.ts: options gain optional iosSlimBytesPerDevice and androidSlimBytesPerDevice, validated like their siblings (a non-negative number, rejected naming the key). No default. One size lookup by platform and mode, falling back to the platform's configured full size. One used-bytes sum and one limit serve canProvision, canBoot and ramBudget(). The limit stays total RAM minus the 4 GiB reserve. canBoot refuses with ram-budget when the boot's extra size (the platform's full size minus the device's own size) does not fit. A device whose own size is already the full size is never refused.
  • src/core/capacity/strategies/fixed/index.ts: accepts the planned device and ignores its mode. canBoot always allows. ramBudget() returns nothing.
  • src/core/capacity/coordinator.ts: tryReserveProvisioning takes the planned device; the in-flight reservation counts at the full size (:42-61, :113-115). tryReserveRunning for a boot becomes a boot reservation that takes the shut-down device. It checks canBoot before the running limits. Until it is released, it keeps the boot's extra size counted in every decision. Running reservations stay per platform. Add ramBudget(devices), which leaves every in-flight reservation out.
  • src/core/acquisition-planner.ts (:82-106, :162-167): pass the planned device to provisioning and the shut-down device to the boot reservation. A boot refused with ram-budget evicts nothing and waits (or answers no capacity under --no-wait). Replace the local record mapper with the shared one.
  • src/core/warm-pool-coordinator.ts (:255-301): a reclaimed device that would need a boot to stay warm (keepReady with a shutdown reclaim) stays warm only when canBoot allows it. Otherwise it stays shut down. Replace the local mapper with the shared one.
  • src/core/lease-engine.ts (:358-363): use the shared mapper.
  • src/core/lease-ports.ts (CapacityReader, :35-39): expose the RAM budget.
  • src/daemon/dispatcher.ts (status.get, :247-266): add capacity.ramBudget when the strategy reports one. usedBytes is the sum over non-deleted devices without in-flight reservations, so it equals what the listed devices add up to by their reported mode.
  • src/contract/schemas.ts: statusCapacitySchema gains optional ramBudget; the resource config schema's ramBudget gains the two optional keys.
  • src/gateway/aggregate.ts (sumCapacity, :89-120): sum limitBytes and usedBytes over connected workers that report the block, overLimit when any is, and no block when none reports one.
  • src/cli/index.ts (formatStatus): one RAM budget line, with (over limit) when over, and none when the daemon reports no budget.
  • e2e/fake-driver/: a script key that makes a slim-spec device report mode: "full", if A lease request chooses slim or full; ios.defaultMode sets the default #178 did not add one.
  • Docs: docs/CONFIGURATION.md (both keys, the fallback, the full-size count before a device has booted, sizing together with the limits, the Android key having no effect yet), docs/CLI.md and docs/HTTP-API.md (capacity.ramBudget in status), docs/internal/ARCHITECTURE.md (capacity section). docs/internal/KNOWN-PITFALLS.md: remove the overcommit entry A lease request chooses slim or full; ios.defaultMode sets the default #178 adds. Add three entries: over budget clears on delete, not on release; a full request at the device limit can evict a slim device and still wait; a recovery reboot of a leased device is not checked against the budget and can leave it over its limit.

Contract and event changes

  • status.get and worker views gain optional capacity.ramBudget. config.get gains two optional keys. All additive; no protocol bump.
  • No event changes.

Rules in play

  • ADR 0007 §4, §6, §8 (the spec's mode is the planned mode; the record's mode is what the device has). Capacity counts the planned device at the full size because the device uses full RAM before its slim pass, not because the planned mode is unknown.
  • architecture.md rule 1 (mode is a core value; the strategy learns nothing about runtimes or drivers), rule 3 (the Android key ships now, so Android slim needs no capacity change), rule 10 (one mapper, one size lookup, one sum for every decision and for status), rule 13.
  • safety.md rule 2 (over budget stops and reclaims nothing) and rule 10 (a worker's reported bytes are validated before the gateway sums them).
  • testing.md rules 1 to 4.
  • documentation.md rules 2 and 3.

Tests

  • With a slim size set, the resource strategy admits more slim devices than full ones under one RAM budget that fits at least two full devices, and refuses the first device past it in either mode.
  • Each new device needs full-size room to be admitted, whatever its mode.
  • Slim and full devices that have booted use the sum of each device's own size, across both platforms.
  • With no slim size set, a slim device uses the platform's configured full size, including a full size the operator changed.
  • A planned device counts at the full size whatever its spec's mode, and a device still provisioning counts at the full size.
  • A device that has booted counts by the mode on its record, including when that differs from its spec's mode.
  • A device quarantined from provisioning counts at the full size.
  • A provisioning reservation holds the full size until it is released.
  • Booting a shut-down slim device is refused with ram-budget when the full size does not fit, and allowed when it does.
  • Booting a shut-down full device is never refused for RAM, even at the limit.
  • A boot reservation holds the boot's extra size until it is released.
  • The planner waits, and evicts nothing, when the boot of a matching shut-down slim device is refused for RAM.
  • A reclaimed slim device that needs a boot to stay warm stays shut down when the full size does not fit.
  • A full spec, which a slim request on a runtime that cannot be slimmed resolves to, is refused before it is provisioned, and stays full after it boots.
  • The resource strategy reports the limit as total RAM minus the reserve, use as the sum over non-deleted devices, and over only when use exceeds the limit.
  • While the budget is over its limit, no device is created in either mode, no shut-down slim device boots, and an idle device of the requested spec is still granted.
  • A device planned as slim whose driver reports full keeps its lease, triggers no shutdown, and counts at the full size.
  • A worker restarted with a larger size than its devices were admitted under reports the budget over its limit. After a device is deleted and use is back under the limit, a new device is created.
  • At the device limit, a full request evicts an idle slim device and then waits when the freed RAM is not enough.
  • The fixed strategy grants up to maxRunning whatever the modes, never refuses a boot, and reports no RAM budget.
  • Every strategy counts running slots without regard to mode (the shared contract suite).
  • status.get reports capacity.ramBudget.usedBytes equal to the sum of the listed devices' sizes by reported mode, including a slim-spec device still provisioning, and omits the block under fixed.
  • A gateway sums the RAM budget over connected workers that report one, is over when any worker is, and omits it when none reports one.
  • simlock status prints the RAM budget with (over limit) when over, and no RAM line when the daemon reports none.
  • A slim size loads from capacity.config.ramBudget and from the legacy top-level ramBudget, is absent by default, and a negative or non-number value is rejected naming the key.
  • The config schema keeps both slim keys when they are set.
  • E2E, fast lane, sizes derived from the reported limitBytes: more slim leases than full ones are granted; the crossing --no-wait request exits 11 in each mode; --mode slim on an OS that cannot be slimmed is refused at the full size; a failed slim pass keeps its lease and counts at the full size; a restart with larger sizes shows the budget over its limit in status.

Done when

  • Every test above passes and pnpm check is green.
  • With no slim key set, every existing capacity test passes unchanged apart from the mode each fixture device now carries.
  • The PR body walks the Completion conditions of Capacity counts slim and full devices separately #174.

Out of scope

Depends on

Approval

  • Approved for delivery

Written by an agent.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

task:readyAn agent may implement it.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions