| import gradio as gr
|
| import signal
|
| import sys
|
| import time
|
| import threading
|
| import atexit
|
| from contextlib import contextmanager
|
| from collections import deque
|
| import psutil
|
| import pynvml
|
|
|
|
|
| try:
|
| pynvml.nvmlInit()
|
| nvml_initialized = True
|
| except pynvml.NVMLError:
|
| print("Warning: Could not initialize NVML. GPU stats will not be available.")
|
| nvml_initialized = False
|
|
|
| class SystemStatsApp:
|
| def __init__(self):
|
| self.running = False
|
| self.active_generators = []
|
| self.setup_signal_handlers()
|
|
|
| def setup_signal_handlers(self):
|
|
|
| signal.signal(signal.SIGINT, self.shutdown_handler)
|
| signal.signal(signal.SIGTERM, self.shutdown_handler)
|
| if hasattr(signal, 'SIGBREAK'):
|
| signal.signal(signal.SIGBREAK, self.shutdown_handler)
|
|
|
|
|
| atexit.register(self.cleanup)
|
|
|
| def shutdown_handler(self, signum, frame):
|
|
|
| self.cleanup()
|
| sys.exit(0)
|
|
|
| def cleanup(self):
|
| if not self.running:
|
| print("Cleaning up streaming connections...")
|
| self.running = False
|
|
|
| time.sleep(1)
|
|
|
| def get_system_stats(self, first = False, last_disk_io = psutil.disk_io_counters() ):
|
|
|
|
|
|
|
| MAX_SSD_SPEED_MB_S = 100.0
|
|
|
| if first :
|
| cpu_percent = psutil.cpu_percent(interval=.01)
|
| else:
|
| cpu_percent = psutil.cpu_percent(interval=1)
|
| memory_info = psutil.virtual_memory()
|
| ram_percent = memory_info.percent
|
| ram_used_gb = memory_info.used / (1024**3)
|
| ram_total_gb = memory_info.total / (1024**3)
|
|
|
|
|
| current_disk_io = psutil.disk_io_counters()
|
| read_mb_s = (current_disk_io.read_bytes - last_disk_io.read_bytes) / (1024**2)
|
| write_mb_s = (current_disk_io.write_bytes - last_disk_io.write_bytes) / (1024**2)
|
| total_disk_speed = read_mb_s + write_mb_s
|
|
|
|
|
| last_disk_io = current_disk_io
|
|
|
|
|
| ssd_bar_height = min(100.0, (total_disk_speed / MAX_SSD_SPEED_MB_S) * 100)
|
|
|
|
|
| if nvml_initialized:
|
| try:
|
| handle = pynvml.nvmlDeviceGetHandleByIndex(0)
|
| util = pynvml.nvmlDeviceGetUtilizationRates(handle)
|
| gpu_percent = util.gpu
|
| mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
|
| vram_percent = (mem_info.used / mem_info.total) * 100
|
| vram_used_gb = mem_info.used / (1024**3)
|
| vram_total_gb = mem_info.total / (1024**3)
|
| except pynvml.NVMLError:
|
|
|
| gpu_percent, vram_percent, vram_used_gb, vram_total_gb = 0, 0, 0, 0
|
| else:
|
|
|
| gpu_percent, vram_percent, vram_used_gb, vram_total_gb = 0, 0, 0, 0
|
|
|
| stats_html = f"""
|
| <style>
|
| .stats-container {{
|
| display: flex;
|
| justify-content: space-between;
|
| align-items: flex-start;
|
| padding: 0px 5px;
|
| height: 60px;
|
| width: 100%;
|
| box-sizing: border-box;
|
| }}
|
|
|
| .stats-block {{
|
| width: calc(18% - 5px);
|
| min-width: 100px;
|
| text-align: center;
|
| font-family: sans-serif;
|
| }}
|
|
|
| .stats-bar-background {{
|
| width: 90%;
|
| height: 30px;
|
| background-color: #e9ecef;
|
| border: 1px solid #dee2e6;
|
| border-radius: 8px;
|
| overflow: hidden;
|
| position: relative;
|
| margin: 0 auto;
|
| }}
|
|
|
| .stats-bar-fill {{
|
| position: absolute;
|
| bottom: 0;
|
| left: 0;
|
| height: 100%;
|
| background-color: #0d6efd;
|
| }}
|
|
|
| .stats-title {{
|
| margin-top: 5px;
|
| font-size: 11px;
|
| font-weight: bold;
|
| }}
|
|
|
| .stats-detail {{
|
| font-size: 10px;
|
| margin-top: -2px;
|
| }}
|
| </style>
|
|
|
| <div class="stats-container">
|
| <!-- CPU Stat Block -->
|
| <div class="stats-block">
|
| <div class="stats-bar-background">
|
| <div class="stats-bar-fill" style="width: {cpu_percent}%;"></div>
|
| </div>
|
| <div class="stats-title">CPU: {cpu_percent:.1f}%</div>
|
| </div>
|
|
|
| <!-- RAM Stat Block -->
|
| <div class="stats-block">
|
| <div class="stats-bar-background">
|
| <div class="stats-bar-fill" style="width: {ram_percent}%;"></div>
|
| </div>
|
| <div class="stats-title">RAM {ram_percent:.1f}%</div>
|
| <div class="stats-detail">{ram_used_gb:.1f} / {ram_total_gb:.1f} GB</div>
|
| </div>
|
|
|
| <!-- SSD Activity Stat Block -->
|
| <div class="stats-block">
|
| <div class="stats-bar-background">
|
| <div class="stats-bar-fill" style="width: {ssd_bar_height}%;"></div>
|
| </div>
|
| <div class="stats-title">SSD R/W</div>
|
| <div class="stats-detail">{read_mb_s:.1f} / {write_mb_s:.1f} MB/s</div>
|
| </div>
|
|
|
| <!-- GPU Stat Block -->
|
| <div class="stats-block">
|
| <div class="stats-bar-background">
|
| <div class="stats-bar-fill" style="width: {gpu_percent}%;"></div>
|
| </div>
|
| <div class="stats-title">GPU: {gpu_percent:.1f}%</div>
|
| </div>
|
|
|
| <!-- VRAM Stat Block -->
|
| <div class="stats-block">
|
| <div class="stats-bar-background">
|
| <div class="stats-bar-fill" style="width: {vram_percent}%;"></div>
|
| </div>
|
| <div class="stats-title">VRAM {vram_percent:.1f}%</div>
|
| <div class="stats-detail">{vram_used_gb:.1f} / {vram_total_gb:.1f} GB</div>
|
| </div>
|
| </div>
|
| """
|
| return stats_html, last_disk_io
|
|
|
| def streaming_html(self, state):
|
| if "stats_running" in state:
|
| return
|
| state["stats_running"] = True
|
|
|
| self.running = True
|
| last_disk_io = psutil.disk_io_counters()
|
| i = 0
|
| import time
|
| try:
|
| while self.running:
|
| i+= 1
|
|
|
|
|
| html_content, last_disk_io = self.get_system_stats(False, last_disk_io)
|
| yield html_content
|
|
|
|
|
| except GeneratorExit:
|
|
|
| return
|
| except Exception as e:
|
| print(f"Streaming error: {e}")
|
|
|
|
|
| final_html = """
|
| <DIV>
|
| <img src="x" onerror="
|
| setInterval(()=>{
|
| console.log('trying...');
|
| setTimeout(() => {
|
| try{
|
| const btn = document.getElementById('restart_stats');
|
| if(btn) {
|
| console.log('found button, clicking');
|
| btn.click();
|
| } else {
|
| console.log('button not found');
|
| }
|
| }catch(e){console.log('error: ' + e.message)}
|
| }, 100);
|
| }, 8000);" style="display:none;">
|
|
|
| <button onclick="document.getElementById('restart_stats').click()"
|
| style="background: #007bff; color: white; padding: 15px 30px;
|
| border: none; border-radius: 5px; font-size: 16px; cursor: pointer;">
|
| 🔄 Connection to Server Lost. Attempting Auto reconnect. Click Here to for Manual Connection
|
| </button>
|
| </DIV>
|
| """
|
| try:
|
| yield final_html
|
| except:
|
| pass
|
|
|
|
|
| def get_gradio_element(self):
|
| self.system_stats_display = gr.HTML(self.get_system_stats(True)[0])
|
| self.restart_btn = gr.Button("restart stats",elem_id="restart_stats", visible= False)
|
| return self.system_stats_display
|
|
|
| def setup_events(self, main, state):
|
| gr.on([main.load, self.restart_btn.click],
|
| fn=self.streaming_html,
|
| inputs = state,
|
| outputs=self.system_stats_display,
|
| show_progress=False
|
| )
|
|
|