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
A. F. Dudley 2026-03-10 01:00:36 +00:00
parent e597968708
commit cd36bfe5ee
1 changed files with 58 additions and 46 deletions

View File

@ -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