atelet: poll ateom workload stats into template-level metrics - #961
Draft
Tim Bai (baizhenyu) wants to merge 1 commit into
Draft
atelet: poll ateom workload stats into template-level metrics#961Tim Bai (baizhenyu) wants to merge 1 commit into
Tim Bai (baizhenyu) wants to merge 1 commit into
Conversation
The reader half of agent-substrate#896: a poller that discovers the node's ateoms from the filesystem and turns their GetActiveWorkloadStats samples into the TSDB half of agent-substrate#174's split -- per-ActorTemplate gauges with the bounded label set (template, sandbox class, stats source), actor and atespace identity never reaching a metric label. Discovery holds no state and never asks the control plane: every ateom registers itself on disk by creating its socket directory at boot, so a sweep is one readdir plus one probe per entry, and an atelet restart loses nothing. One tolerance rule covers the scan's noise -- any dial or call failure means "not a target this tick" -- which uniformly handles stale directories, half-born ateoms, and teardowns mid-sweep. The no-sample reasons are skips by the RPC's own contract. Attribution comes solely from the echoed identity, per the same contract. The interval is a flag (--actor-stats-poll-interval, default 1m, 0 disables) clamped to the worst-case micro-VM sweep so a low setting cannot pile overlapping polls onto one guest agent; within a sweep, distinct ateoms are probed concurrently (bounded), which stacks nothing on any one guest and keeps a node of stuck sockets from serializing into minutes. Three gauges: sampled actors, memory current, memory working set -- observable rather than synchronous, so a template whose actors leave the node disappears from the export instead of freezing at its last value. CPU is a counter, not a gauge: the raw cpu_usage_usec is cumulative per-epoch per actor, so the poller tracks each actor's last seen value and adds only the per-sweep INCREASE (a decrease is an epoch reset, charged from zero), which keeps rate() meaningful across actors joining, leaving, and resetting. Undercounts across atelet restarts and misses the tail before a checkpoint; the events channel carries per-actor precision. Samples are enriched with the owning WorkerPool (ate.workerpool.namespace/name) by resolving the node's worker pods -- one field- and label-selected list per sweep, joined on the pod UID the ateom directory is named for; an unresolved pod groups without pool labels rather than vanishing. Part of agent-substrate#896, toward agent-substrate#550.
Tim Bai (baizhenyu)
force-pushed
the
atelet-stats-reader
branch
from
August 14, 2026 21:37
6d74f59 to
9aa3096
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second piece of #896 (Phase 1 of #550): atelet polls every local ateom's
GetActiveWorkloadStatsand turns the samples into template-level metrics — the TSDB half of #174's cardinality split. After this PR, "how much CPU and memory is this template using" is a Cloud Monitoring query.The reader
A
statsPollerin atelet, driven by--actor-stats-poll-interval(default 1m;0disables; nonzero values are clamped to a 50s floor, the worst-case duration of sampling one micro-VM ateom — 25 containers × 2s per guest-agent call).ateoms/*(entry names are worker pod UIDs — the same sockets the lifecycle RPCs dial) and probes each with the parameterless discovery read. No worker-to-actor mapping, no control-plane dependency, nothing to recover after an atelet restart. Attribution comes solely from the identity echoed in each sample, per the RPC's contract.NO_WORKLOAD/NOT_MEASURABLE_YETare skips by the RPC's own contract.ate.dev/worker-pool, the label the pool controller stamps); unresolved pods group without pool labels rather than vanish. Chosen over an informer deliberately — negligible apiserver cost at this cadence, no cache-sync ordering, and the resolver sits behind a function seam if that trade ever changes. Needs one new Downward API env (NODE_NAME); the pods RBAC already existed.The metrics
Labels on every series:
ate.template.namespace/name,ate.sandbox.class,ate.stats.source,ate.workerpool.namespace/name— all bounded sets; actor and atespace identity never reach a metric label (they belong to the events channel, the next PR).ate.actor.stats.sampled_actors,…memory_current_bytes,…memory_working_set_bytes— observable gauges over the latest tick's snapshot: each collection observes exactly the groups that currently exist, so a template whose actors leave a node disappears from the export. (Synchronous gauges would re-export their last value until process exit — stale memory for actors long gone.)ate.actor.stats.cpu_usage— Float64Counter in seconds (cAdvisor / OTel*.cpu.timeconvention; the wire stays µs). The rawcpu_usage_usecis cumulative per-epoch per actor, so the poller accumulates per-sweep increases against per-actor baselines: first sight establishes a baseline and charges nothing (atelet cannot tell a new actor from its own restart — re-charging epochs the previous process counted would spikerate()), a decrease is an epoch reset charged from the new value, and baselines are pruned to the actors seen. Bounded imprecision (≤1 interval per actor across restarts; the pre-checkpoint tail) is documented on the instrument; per-actor precision arrives with the lifecycle events.Validated live on ate-dev
Both source families, simultaneously, with one actor per class:
The same counter application reads 5-6× larger through the cgroup source (whole sandbox: sentry heap, netstack, gofers) than through the guest agent (workload containers only) — the concrete case for the
ate.stats.sourcelabel and its group-don't-sum rule. Also exercised live: pool labels resolved on every series, restart-without-spike on the CPU counter across a DaemonSet rollout, and OTLP delivery to the gke-managed-otel collector with zero export errors.Out of scope
k8s.node.nameresource attribute (one manifest line, any time).Part of #896, toward #550.