add slot ttl output with effective timeout config
This commit is contained in:
parent
d8afde8b18
commit
8fdf2d5e5b
5 changed files with 211 additions and 4 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue