Skip to content

Actor resource telemetry: adapt to multi-actor workers #1640

Closed

Description

@baizhenyu

Context

Adapt the actor resource-telemetry stack (#550: GetWorkloadStats / GetActiveWorkloadStats on both ateom runtimes, the atelet stats sweep with template-level metrics and per-actor usage events) to multi-actor workers (#1266, closed).

The stack was built on the one-actor-per-worker state machine. Once workers host N actors, worker-pod-level metrics (metrics-server, kubectl ate top workers) stop being a usable proxy for any actor's usage. This pipeline is the only actor-granular source, so it must be multi-actor-clean. Both ateom binaries now default --max-actors to 1000; the gate is no longer "before capacity flips" but "before anyone relies on the numbers at capacity > 1".

Status

Piece State
Wire shape: repeated WorkloadStatsSample samples Done, #1644
gVisor cgroup leaves keyed by actor UID Done, #1667
Handler iteration on both runtimes Done, #1836: hostedActor registry, per-entry re-check, keyed read as uid lookup
micro-VM per-actor guest client Done, #1836: hostedActor.guest
micro-VM measurement concurrency Done, #1836: statsFanOut = 32, statsSweepBudget = 45s, unreached guests report pending
atelet: multi-entry fold tested, timeout rationale reconciled Open, the remaining work
Dead-guest visibility Deferred, see below

What #1836 settled

The handlers iterate a hostedActor registry guarded by its own actorsMu (RWMutex), not the lifecycle mutex, so a poll landing mid-boot or mid-checkpoint answers without waiting. Each entry is loaded, sampled, and re-checked by lookupActor(uid): an actor re-hosted underneath the read reports pending under its new attribution; one gone from the registry drops out of that response. The keyed GetWorkloadStats(actor_uid) is a registry lookup; its codes are unchanged.

On the micro-VM runtime each hostedActor owns its guest client. The discovery read fans out to at most 32 guests at once under a 45s sweep budget that stays inside atelet's 55s call deadline; a guest not reached in time reports as pending rather than failing the response. This replaces the ceil(N / cap) x 50s formula the earlier plan asked atelet to derive: the ateom bounds its own sweep, so atelet's deadline stays a constant.

Both runtimes have tests for two hosted actors, including one pending and one measured in the same response.

Remaining work

1. atelet: pin the multi-entry fold. collect iterates resp.GetSamples() and folds each entry: pending entries (source UNSPECIFIED) contribute nothing to aggregates, CPU baselines, or events; measured entries aggregate under their own template key and emit one event each. The code is correct by reading, but every test in statspoller_test.go uses single-entry responses. Add tests where one worker returns two measured actors (same template: summed; different templates: two keys; two lastCPU baselines), and one measured plus one pending (pending contributes nothing, sampled_actors counts one).

2. atelet: reconcile the timeout comments. minActorStatsPollInterval (50s) and statsRPCTimeout (55s) are still documented as derived from the single-actor worst case, maxActorContainers x statsCallTimeout. The values stay correct because the ateom now caps its own sweep at 45s regardless of N; the comments should say so, and cite statsSweepBudget as the contract atelet's 55s covers.

Both fit in one small atelet PR. Nothing else blocks calling the pipeline multi-actor-complete.

Deferred: dead-guest visibility

With one actor per worker, a dead guest fails readyz and is replaced. With N actors, one dead guest sits behind healthy siblings and shows only as a pending entry the poller skips. #1836 widened this slightly: a guest slower than the 45s sweep budget also reports pending, so "pending" now covers booting, restoring, slow, and dead.

The signal is still worth having: atelet tracks first-pending time per actor uid (same shape as lastCPU, pruned to actors seen) and emits a warning event or a long-pending-actors-per-template gauge past a threshold. It is lowered to a follow-up rather than a gate: today's occupancy is 1 everywhere, readyz still catches a dead lone guest, and the per-actor usage events already make a pending actor visible by its absence. Tracked separately when there is a consumer.

Out of scope

Related

#550 (parent) · #594 (phase 0: #667 #739 #832) · #896 (phase 1: #899 #961 #1206; open for the lifecycle-bracket follow-up, now #1748) · #1266 (the epic, closed: API #1283, runtime #1836, gVisor leaves #1667, uVM cgroup prep #1254) · #1644 (wire shape)

Metadata

Metadata

Labels

Type

No type

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions