#!/usr/bin/env python3 """Engine-state store: canonical snapshot + writer/reader, shared schema. The vLLM scheduler patch (instrument_engine_state.py) inlines a faithful copy of `compute_snapshot` + the file/redis writer (engine process needs no repo import). The router (cache_aware_proxy) imports `StateReader` here to read the real per-engine state instead of its stale shadow counters. Schema (one record per engine, key = engine_id): ts, engine_id, num_running, num_waiting, gpu_blocks_total, gpu_blocks_free, gpu_kv_used_frac, pending_prefill_tokens, ongoing_decode_tokens, num_prefilling, max_prefill_remaining Transport URIs: file:///dev/shm/agentic_engine_state (default; atomic temp+rename) redis://host:port/0 (optional; needs redis-py) """ from __future__ import annotations import json import os import time def compute_snapshot(scheduler, engine_id: str) -> dict: """Cheap O(batch) read of routing-relevant real state from a live vLLM V1 Scheduler (duck-typed for testability).""" try: pool = scheduler.kv_cache_manager.block_pool total = int(pool.num_gpu_blocks) free = int(pool.get_num_free_blocks()) except Exception: total = free = -1 n_run = pend = dec = n_pref = max_pref = 0 try: for r in scheduler.running: n_run += 1 npr = int(getattr(r, "num_prompt_tokens", 0)) nct = int(getattr(r, "num_computed_tokens", 0)) if nct < npr: rem = npr - nct pend += rem n_pref += 1 max_pref = max(max_pref, rem) else: dec += int(getattr(r, "num_tokens", 0)) except Exception: pass n_wait = 0 try: n_wait = len(scheduler.waiting) + len(getattr(scheduler, "skipped_waiting", [])) for r in list(scheduler.waiting): pend += max(0, int(getattr(r, "num_prompt_tokens", 0)) - int(getattr(r, "num_computed_tokens", 0))) except Exception: pass used = ((total - free) / total) if (total and total > 0) else -1.0 return { "ts": time.time(), "engine_id": engine_id, "num_running": n_run, "num_waiting": int(n_wait), "gpu_blocks_total": total, "gpu_blocks_free": free, "gpu_kv_used_frac": used, "pending_prefill_tokens": int(pend), "ongoing_decode_tokens": int(dec), "num_prefilling": n_pref, "max_prefill_remaining": int(max_pref), } class StateWriter: def __init__(self, uri: str, engine_id: str): self.engine_id = engine_id self.kind = None if uri.startswith("file://"): self.kind = "file" self.dir = uri[len("file://"):] os.makedirs(self.dir, exist_ok=True) self.path = os.path.join(self.dir, f"{engine_id}.json") self.tmp = self.path + f".tmp.{os.getpid()}" elif uri.startswith("redis://"): self.kind = "redis" import redis self.r = redis.Redis.from_url(uri) self.key = f"engine_state:{engine_id}" else: raise ValueError(f"unsupported engine-state URI: {uri}") def publish(self, state: dict): if self.kind == "file": with open(self.tmp, "w") as f: f.write(json.dumps(state)) os.replace(self.tmp, self.path) elif self.kind == "redis": self.r.set(self.key, json.dumps(state), ex=5) class StateReader: """Router-side reader. read_all() returns {engine_id: state}, dropping records older than max_age_s (so a dead/hung engine is ignored).""" def __init__(self, uri: str, max_age_s: float = 2.0): self.uri = uri self.max_age_s = max_age_s self.kind = None if uri.startswith("file://"): self.kind = "file" self.dir = uri[len("file://"):] elif uri.startswith("redis://"): self.kind = "redis" import redis self.r = redis.Redis.from_url(uri) else: raise ValueError(f"unsupported engine-state URI: {uri}") def read_all(self) -> dict[str, dict]: now = time.time() out: dict[str, dict] = {} try: if self.kind == "file": import glob for p in glob.glob(os.path.join(self.dir, "*.json")): try: s = json.load(open(p)) except Exception: continue if now - s.get("ts", 0) <= self.max_age_s: out[s.get("engine_id", os.path.basename(p)[:-5])] = s elif self.kind == "redis": for k in self.r.scan_iter("engine_state:*"): v = self.r.get(k) if not v: continue s = json.loads(v) if now - s.get("ts", 0) <= self.max_age_s: out[s.get("engine_id")] = s except Exception: pass return out