#!/usr/bin/env python3 import sys import ast import time IP_TO_VM = { "172.22.156.3": "1", "172.22.158.2": "2", "172.22.94.2": "3", "172.22.156.4": "4", "172.22.158.3": "5", "172.22.94.3": "6", "172.22.156.5": "7", "172.22.158.4": "8", "172.22.94.4": "9", "172.22.156.6": "10", } # COLORS GREEN = "\033[92m" YELLOW = "\033[93m" RED = "\033[91m" CYAN = "\033[96m" WHITE = "\033[97m" GRAY = "\033[90m" BOLD = "\033[1m" RESET = "\033[0m" CLEAR_LINE = "\033[K" STATE_STYLE = { "ALIVE": (GREEN, "A"), "SUSPECT": (YELLOW, "S"), "FAILED": (RED, "F"), } def render(lines): # STOPPED / SSH ERROR for line in lines: if line.startswith("**STOPPED**"): reason = line.replace( "**STOPPED**", "" ).strip() return ( f"{CLEAR_LINE}" f"{BOLD}{RED}>>> STOPPED <<<{RESET}" f"{CLEAR_LINE}\n" f"{GRAY}{reason}{RESET}" f"{CLEAR_LINE}\n" ) # PARSE members = [] mode = "?" drop_rate = "?" now = time.time() for raw_line in lines: line = raw_line.strip() if not line: continue # ---------------------------------------------------- # MODE / DROP RATE LINE # ---------------------------------------------------- if line.startswith("Mode:"): try: pieces = line.split(",") mode = pieces[0].split(":", 1)[1].strip() drop_rate = pieces[1].split(":", 1)[1].strip() except Exception: pass continue # ---------------------------------------------------- # MEMBERSHIP ENTRY # ---------------------------------------------------- try: id_text, data_text = line.split(": ", 1) member_id = ast.literal_eval(id_text) member_data = ast.literal_eval(data_text) ip, port, timestamp = member_id local_last_seen = member_data.get( "local_last_seen", now ) members.append({ "vm": IP_TO_VM.get(ip, "?"), "hb": member_data.get( "heart_beat", 0 ), "state": member_data.get( "state", "?" ).upper(), "age": max( 0.0, now - local_last_seen ), "inc": member_data.get( "incarnation_counter", 0 ), }) except Exception: continue # HEADER out = [] mode_upper = str(mode).upper() if mode_upper == "SUSPECT": mode_color = YELLOW elif mode_upper == "NOSUSPECT": mode_color = GREEN else: mode_color = GRAY out.append( f"{BOLD}{CYAN}MODE:{RESET} " f"{mode_color}{BOLD}{mode_upper}{RESET} " f"{GRAY}|{RESET} " f"{BOLD}{CYAN}DROP:{RESET} " f"{WHITE}{drop_rate}%{RESET}" f"{CLEAR_LINE}" ) # NO DATA if not members: out.append( f"{BOLD}{YELLOW}" f"waiting for membership..." f"{RESET}" f"{CLEAR_LINE}" ) return "\n".join(out) + "\n" # SORT def vm_number(member): try: return int(member["vm"]) except Exception: return 999 members.sort( key=vm_number ) # TABLE HEADER out.append( f"{BOLD}{CYAN}" f"{'VM':<3} " f"{'S':<2} " f"{'HB':<6} " f"{'AGE':<5} " f"{'INC'}" f"{RESET}" f"{CLEAR_LINE}" ) # MEMBERS for member in members: color, code = STATE_STYLE.get( member["state"], (GRAY, "?") ) out.append( f"{WHITE}" f"{member['vm']:<3}" f"{RESET} " f"{color}{BOLD}" f"{code:<2}" f"{RESET} " f"{member['hb']:<6} " f"{member['age']:>4.1f} " f"{member['inc']}" f"{CLEAR_LINE}" ) return "\n".join(out) + "\n" print( render(list(sys.stdin)), end="", flush=True )