feat(agent): Status shows smoothed jobs/min + downloads/min (replace jumpy gauges)
The buffer / on-GPU / downloader counts flip many times a second, so a 3s
status poll only ever samples noise — the tiles looked frozen (same value
twice) or random (wildly different), reading as "the Status section doesn't
update" when the backend was in fact live (operator-flagged 2026-07-01).
Replace the three instantaneous gauge tiles with two derived RATE tiles:
- jobs / min — GPU throughput, from the monotonic `processed` counter
- downloads / min — fetch throughput, from a new monotonic `downloaded`
counter (bumped when a job is decoded into the buffer)
Together they also show pipeline balance (dl/min > j/min ⇒ GPU-bound; the
reverse ⇒ GPU starved). Both are EWMA-smoothed over the poll deltas, clamped
at 0 (agent restart resets the counters), and skip a backgrounded-tab gap.
The still-useful instantaneous state is demoted, not lost: buffer stays as
the occupancy bar; downloaders/consumers/on-GPU move to the sub-line. `waited
out` (transient) gets promoted to a tile.
backend: worker.status() gains `downloaded`; `_bump(downloaded=)`.
frontend: retiled Status + rate math in applyStatus; VERSION → .7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -162,6 +162,10 @@ class Worker:
|
||||
self._held: set[int] = set()
|
||||
self._held_lock = threading.Lock()
|
||||
self.processed = 0
|
||||
self.downloaded = 0 # jobs fetched+decoded into the buffer (monotonic)
|
||||
# — the UI derives a smoothed downloads/min from it,
|
||||
# since the instantaneous buffer/active gauges move
|
||||
# faster than any sane poll can show.
|
||||
self.errors = 0
|
||||
self.transient = 0 # jobs handed back due to a server outage (NOT
|
||||
# failed) — the "waiting out curator" counter
|
||||
@@ -425,13 +429,15 @@ class Worker:
|
||||
"buffer_max": BUFFER_MAX,
|
||||
"active": self._active,
|
||||
"processed": self.processed,
|
||||
"downloaded": self.downloaded,
|
||||
"errors": self.errors,
|
||||
"transient": self.transient,
|
||||
}
|
||||
|
||||
def _bump(self, *, processed=0, errors=0, active=0, transient=0):
|
||||
def _bump(self, *, processed=0, downloaded=0, errors=0, active=0, transient=0):
|
||||
with self._lock:
|
||||
self.processed += processed
|
||||
self.downloaded += downloaded
|
||||
self.errors += errors
|
||||
self.transient += transient
|
||||
# Clamp at 0: a Stop resets _active to 0, so a consumer that was
|
||||
@@ -497,6 +503,7 @@ class Worker:
|
||||
continue
|
||||
# Blocks on a full buffer (backpressure) but wakes promptly on stop.
|
||||
if self._put((job, frames), stop_evt):
|
||||
self._bump(downloaded=1) # fetched+decoded into the buffer
|
||||
owned.remove(jid) # ownership handed to the buffer/consumer
|
||||
else:
|
||||
break # stopped while waiting for buffer space
|
||||
|
||||
Reference in New Issue
Block a user