fix: check-status.py smooth in-place redraw, remove comment bars
- Overwrite lines in place instead of clear+redraw (no flicker) - Pad lines to terminal width to clear stale characters - Blank leftover rows when output shrinks between frames - Hide cursor during watch mode - Remove section comment bars - Replace unicode checkmarks with +/x Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>fix/kind-mount-propagation
parent
e597968708
commit
cd36bfe5ee
|
|
@ -19,13 +19,12 @@ from __future__ import annotations
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
# -- Config -------------------------------------------------------------------
|
|
||||||
|
|
||||||
SSH_HOST = "biscayne.vaasl.io"
|
SSH_HOST = "biscayne.vaasl.io"
|
||||||
KUBECONFIG = "/home/rix/.kube/config"
|
KUBECONFIG = "/home/rix/.kube/config"
|
||||||
DEPLOYMENT_DIR = "/srv/deployments/agave"
|
DEPLOYMENT_DIR = "/srv/deployments/agave"
|
||||||
|
|
@ -33,7 +32,6 @@ SNAPSHOT_DIR = "/srv/kind/solana/snapshots"
|
||||||
RAMDISK = "/srv/kind/solana/ramdisk"
|
RAMDISK = "/srv/kind/solana/ramdisk"
|
||||||
MAINNET_RPC = "https://api.mainnet-beta.solana.com"
|
MAINNET_RPC = "https://api.mainnet-beta.solana.com"
|
||||||
|
|
||||||
# Derived from deployment.yml on first connect
|
|
||||||
CLUSTER_ID: str = ""
|
CLUSTER_ID: str = ""
|
||||||
NAMESPACE: str = ""
|
NAMESPACE: str = ""
|
||||||
DEPLOYMENT: str = ""
|
DEPLOYMENT: str = ""
|
||||||
|
|
@ -41,9 +39,6 @@ POD_LABEL: str = ""
|
||||||
KIND_CONTAINER: str = ""
|
KIND_CONTAINER: str = ""
|
||||||
|
|
||||||
|
|
||||||
# -- Discovery ----------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def discover() -> None:
|
def discover() -> None:
|
||||||
"""Read cluster-id from deployment.yml and derive all identifiers."""
|
"""Read cluster-id from deployment.yml and derive all identifiers."""
|
||||||
global CLUSTER_ID, NAMESPACE, DEPLOYMENT, POD_LABEL, KIND_CONTAINER
|
global CLUSTER_ID, NAMESPACE, DEPLOYMENT, POD_LABEL, KIND_CONTAINER
|
||||||
|
|
@ -61,9 +56,6 @@ def discover() -> None:
|
||||||
KIND_CONTAINER = f"{CLUSTER_ID}-control-plane"
|
KIND_CONTAINER = f"{CLUSTER_ID}-control-plane"
|
||||||
|
|
||||||
|
|
||||||
# -- Helpers ------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def ssh(cmd: str, timeout: int = 15) -> tuple[int, str]:
|
def ssh(cmd: str, timeout: int = 15) -> tuple[int, str]:
|
||||||
"""Run a command on biscayne via SSH. Returns (rc, stdout)."""
|
"""Run a command on biscayne via SSH. Returns (rc, stdout)."""
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
|
|
@ -96,9 +88,6 @@ def get_mainnet_slot() -> int | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# -- Checks -------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def check_pod() -> dict:
|
def check_pod() -> dict:
|
||||||
"""Get pod phase and container statuses."""
|
"""Get pod phase and container statuses."""
|
||||||
rc, out = kubectl(
|
rc, out = kubectl(
|
||||||
|
|
@ -187,38 +176,32 @@ def check_ramdisk() -> str:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
# -- Display ------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
prev_slot: int | None = None
|
prev_slot: int | None = None
|
||||||
prev_time: float | None = None
|
prev_time: float | None = None
|
||||||
|
|
||||||
|
|
||||||
def display(iteration: int = 0) -> None:
|
def render() -> list[str]:
|
||||||
"""Run all checks and print status."""
|
"""Gather all data and return lines to display."""
|
||||||
global prev_slot, prev_time
|
global prev_slot, prev_time
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
ts = time.strftime("%H:%M:%S")
|
ts = time.strftime("%H:%M:%S")
|
||||||
|
lines: list[str] = []
|
||||||
|
|
||||||
# Gather data
|
|
||||||
pod = check_pod()
|
pod = check_pod()
|
||||||
mainnet = get_mainnet_slot()
|
mainnet = get_mainnet_slot()
|
||||||
snapshots = check_snapshots()
|
snapshots = check_snapshots()
|
||||||
ramdisk = check_ramdisk()
|
ramdisk = check_ramdisk()
|
||||||
|
|
||||||
# Clear screen and home cursor for clean redraw in watch mode
|
lines.append(f" Biscayne Agave Status {ts}")
|
||||||
if iteration > 0:
|
lines.append("")
|
||||||
print("\033[2J\033[H", end="")
|
|
||||||
|
|
||||||
print(f"\n Biscayne Agave Status — {ts}\n")
|
|
||||||
|
|
||||||
# Pod
|
# Pod
|
||||||
print(f"\n Pod: {pod['phase']}")
|
lines.append(f" Pod: {pod['phase']}")
|
||||||
for name, cs in pod["containers"].items():
|
for name, cs in pod["containers"].items():
|
||||||
ready = "✓" if cs["ready"] else "✗"
|
ready = "+" if cs["ready"] else "x"
|
||||||
restarts = f" (restarts: {cs['restarts']})" if cs["restarts"] > 0 else ""
|
restarts = f" (restarts: {cs['restarts']})" if cs["restarts"] > 0 else ""
|
||||||
print(f" {ready} {name}: {cs['state']}{restarts}")
|
lines.append(f" {ready} {name}: {cs['state']}{restarts}")
|
||||||
|
|
||||||
# Validator slot
|
# Validator slot
|
||||||
validator_slot = None
|
validator_slot = None
|
||||||
|
|
@ -227,6 +210,7 @@ def display(iteration: int = 0) -> None:
|
||||||
if agave.get("ready"):
|
if agave.get("ready"):
|
||||||
validator_slot = check_validator_slot()
|
validator_slot = check_validator_slot()
|
||||||
|
|
||||||
|
lines.append("")
|
||||||
if validator_slot is not None and mainnet is not None:
|
if validator_slot is not None and mainnet is not None:
|
||||||
gap = mainnet - validator_slot
|
gap = mainnet - validator_slot
|
||||||
rate = ""
|
rate = ""
|
||||||
|
|
@ -234,7 +218,6 @@ def display(iteration: int = 0) -> None:
|
||||||
dt = now - prev_time
|
dt = now - prev_time
|
||||||
if dt > 0:
|
if dt > 0:
|
||||||
slots_gained = validator_slot - prev_slot
|
slots_gained = validator_slot - prev_slot
|
||||||
# Net rate = our replay rate minus chain production
|
|
||||||
net_rate = slots_gained / dt
|
net_rate = slots_gained / dt
|
||||||
if net_rate > 0:
|
if net_rate > 0:
|
||||||
eta_sec = gap / net_rate
|
eta_sec = gap / net_rate
|
||||||
|
|
@ -244,38 +227,58 @@ def display(iteration: int = 0) -> None:
|
||||||
rate = f" net {net_rate:+.1f} slots/s (falling behind)"
|
rate = f" net {net_rate:+.1f} slots/s (falling behind)"
|
||||||
prev_slot = validator_slot
|
prev_slot = validator_slot
|
||||||
prev_time = now
|
prev_time = now
|
||||||
print(f"\n Validator: slot {validator_slot:,}")
|
lines.append(f" Validator: slot {validator_slot:,}")
|
||||||
print(f" Mainnet: slot {mainnet:,}")
|
lines.append(f" Mainnet: slot {mainnet:,}")
|
||||||
print(f" Gap: {gap:,} slots{rate}")
|
lines.append(f" Gap: {gap:,} slots{rate}")
|
||||||
elif mainnet is not None:
|
elif mainnet is not None:
|
||||||
print(f"\n Validator: not responding (downloading or starting)")
|
lines.append(" Validator: not responding (downloading or starting)")
|
||||||
print(f" Mainnet: slot {mainnet:,}")
|
lines.append(f" Mainnet: slot {mainnet:,}")
|
||||||
else:
|
else:
|
||||||
print(f"\n Mainnet: unreachable")
|
lines.append(" Mainnet: unreachable")
|
||||||
|
|
||||||
# Snapshots
|
# Snapshots
|
||||||
|
lines.append("")
|
||||||
if snapshots:
|
if snapshots:
|
||||||
print(f"\n Snapshots:")
|
lines.append(" Snapshots:")
|
||||||
for s in snapshots:
|
for s in snapshots:
|
||||||
print(f" {s['size']:>6s} {s['name']}")
|
lines.append(f" {s['size']:>6s} {s['name']}")
|
||||||
else:
|
else:
|
||||||
print(f"\n Snapshots: none on disk")
|
lines.append(" Snapshots: none on disk")
|
||||||
|
|
||||||
# Ramdisk
|
# Ramdisk
|
||||||
print(f" Ramdisk: {ramdisk}")
|
lines.append(f" Ramdisk: {ramdisk}")
|
||||||
|
|
||||||
# Entrypoint logs (only if validator not yet responding)
|
# Entrypoint logs (only if validator not yet responding)
|
||||||
if validator_slot is None and pod["phase"] in ("Running", "Pending"):
|
if validator_slot is None and pod["phase"] in ("Running", "Pending"):
|
||||||
logs = check_entrypoint_logs(10)
|
logs = check_entrypoint_logs(10)
|
||||||
if logs and logs != "(no logs)":
|
if logs and logs != "(no logs)":
|
||||||
print(f"\n Entrypoint logs (last 10 lines):")
|
lines.append("")
|
||||||
|
lines.append(" Entrypoint logs (last 10 lines):")
|
||||||
for line in logs.splitlines():
|
for line in logs.splitlines():
|
||||||
print(f" {line}")
|
lines.append(f" {line}")
|
||||||
|
|
||||||
print()
|
return lines
|
||||||
|
|
||||||
|
|
||||||
# -- Main ---------------------------------------------------------------------
|
def display(watch: bool, prev_lines: int) -> int:
|
||||||
|
"""Render status and paint to terminal. Returns number of lines written."""
|
||||||
|
output = render()
|
||||||
|
cols = shutil.get_terminal_size().columns
|
||||||
|
|
||||||
|
if watch:
|
||||||
|
# Move cursor to top-left without clearing — overwrite in place
|
||||||
|
sys.stdout.write("\033[H")
|
||||||
|
|
||||||
|
for line in output:
|
||||||
|
# Pad to terminal width to overwrite stale characters from prior frame
|
||||||
|
sys.stdout.write(line.ljust(cols)[:cols] + "\n")
|
||||||
|
|
||||||
|
# If previous frame had more lines, blank the leftover rows
|
||||||
|
for _ in range(max(0, prev_lines - len(output))):
|
||||||
|
sys.stdout.write(" " * cols + "\n")
|
||||||
|
|
||||||
|
sys.stdout.flush()
|
||||||
|
return len(output)
|
||||||
|
|
||||||
|
|
||||||
def spawn_tmux_pane(interval: int) -> None:
|
def spawn_tmux_pane(interval: int) -> None:
|
||||||
|
|
@ -304,17 +307,26 @@ def main() -> int:
|
||||||
|
|
||||||
discover()
|
discover()
|
||||||
|
|
||||||
|
if args.watch:
|
||||||
|
# Hide cursor, clear screen once at start
|
||||||
|
sys.stdout.write("\033[?25l\033[2J\033[H")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
prev_lines = 0
|
||||||
if args.watch:
|
if args.watch:
|
||||||
i = 0
|
|
||||||
while True:
|
while True:
|
||||||
display(i)
|
prev_lines = display(watch=True, prev_lines=prev_lines)
|
||||||
i += 1
|
|
||||||
time.sleep(args.interval)
|
time.sleep(args.interval)
|
||||||
else:
|
else:
|
||||||
display()
|
display(watch=False, prev_lines=0)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print()
|
pass
|
||||||
|
finally:
|
||||||
|
if args.watch:
|
||||||
|
# Show cursor again
|
||||||
|
sys.stdout.write("\033[?25l\n")
|
||||||
|
sys.stdout.flush()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue