add slot ttl output with effective timeout config

This commit is contained in:
Abel Luck 2026-02-27 16:43:52 +01:00
parent d8afde8b18
commit 8fdf2d5e5b
5 changed files with 211 additions and 4 deletions

View file

@ -7,6 +7,7 @@ import http.client
import json
import socket
from collections.abc import Sequence
from datetime import UTC, datetime
from typing import Any
@ -64,7 +65,81 @@ def _print_table(headers: Sequence[str], rows: Sequence[Sequence[str]]) -> None:
print(" ".join(cell.ljust(widths[idx]) for idx, cell in enumerate(row)))
def _print_slots(data: list[dict[str, Any]]) -> None:
def _format_duration(seconds: float) -> str:
total = int(max(0, round(seconds)))
hours, rem = divmod(total, 3600)
minutes, secs = divmod(rem, 60)
if hours > 0:
return f"{hours}h{minutes:02d}m"
if minutes > 0:
return f"{minutes}m{secs:02d}s"
return f"{secs}s"
def _format_timeout_ttl(timeout_seconds: float, age_seconds: float) -> str:
remaining = timeout_seconds - age_seconds
if remaining <= 0:
return "due"
return _format_duration(remaining)
def _slot_age_seconds(slot: dict[str, Any]) -> float | None:
raw = slot.get("last_state_change")
if not isinstance(raw, str):
return None
try:
dt = datetime.fromisoformat(raw)
except ValueError:
return None
if dt.tzinfo is None:
dt = dt.replace(tzinfo=UTC)
return (datetime.now(UTC) - dt).total_seconds()
def _slot_ttl(slot: dict[str, Any], policy: dict[str, Any] | None, active_slots: int) -> str:
if policy is None:
return "-"
capacity = policy.get("capacity")
scheduler = policy.get("scheduler")
if not isinstance(capacity, dict) or not isinstance(scheduler, dict):
return "-"
state = str(slot.get("state", ""))
lease_count = int(slot.get("lease_count", 0))
age_seconds = _slot_age_seconds(slot)
if state in {"empty", "error"}:
return "-"
if age_seconds is None:
return "?"
if state == "launching":
return _format_timeout_ttl(float(capacity.get("launch_timeout_seconds", 0)), age_seconds)
if state == "booting":
return _format_timeout_ttl(float(capacity.get("boot_timeout_seconds", 0)), age_seconds)
if state == "binding":
return _format_timeout_ttl(float(capacity.get("binding_timeout_seconds", 0)), age_seconds)
if state == "terminating":
return _format_timeout_ttl(
float(capacity.get("terminating_timeout_seconds", 0)), age_seconds
)
if state == "draining":
if lease_count == 0:
return f"<={_format_duration(float(scheduler.get('reconcile_seconds', 0)))}"
return _format_timeout_ttl(float(capacity.get("drain_timeout_seconds", 0)), age_seconds)
if state == "ready":
if lease_count > 0:
return "-"
min_slots = int(capacity.get("min_slots", 0))
if active_slots <= min_slots:
return "pinned"
return _format_timeout_ttl(float(capacity.get("idle_scale_down_seconds", 0)), age_seconds)
return "-"
def _print_slots(data: list[dict[str, Any]], policy: dict[str, Any] | None = None) -> None:
active_slots = sum(1 for slot in data if str(slot.get("state", "")) not in {"empty", "error"})
rows: list[list[str]] = []
for slot in data:
rows.append(
@ -74,9 +149,10 @@ def _print_slots(data: list[dict[str, Any]]) -> None:
str(slot.get("instance_id") or "-"),
str(slot.get("instance_ip") or "-"),
str(slot.get("lease_count", 0)),
_slot_ttl(slot, policy, active_slots),
]
)
_print_table(["slot_id", "state", "instance_id", "ip", "leases"], rows)
_print_table(["slot_id", "state", "instance_id", "ip", "leases", "ttl"], rows)
def _print_reservations(data: list[dict[str, Any]]) -> None:
@ -117,6 +193,18 @@ def _print_status_summary(data: dict[str, Any]) -> None:
_print_table(["metric", "value"], rows)
def _get_effective_config(socket_path: str) -> dict[str, Any] | None:
try:
status, data = _uds_request(socket_path, "GET", "/v1/config/effective")
except OSError:
return None
if status < 200 or status >= 300:
return None
if isinstance(data, dict):
return data
return None
def _bulk_slot_action(socket_path: str, action: str) -> dict[str, Any]:
if action == "drain":
eligible_states = {"ready"}
@ -307,7 +395,8 @@ def main() -> None:
print(json.dumps(data, indent=2))
elif args.command == "slots":
if isinstance(data, list):
_print_slots(data)
policy = _get_effective_config(args.socket)
_print_slots(data, policy)
else:
_print_error(data)
raise SystemExit(1)