USF00's picture
Initial commit
2b67076
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
# Initialize NVIDIA Management Library (NVML) for GPU monitoring
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):
# Handle different shutdown signals
signal.signal(signal.SIGINT, self.shutdown_handler)
signal.signal(signal.SIGTERM, self.shutdown_handler)
if hasattr(signal, 'SIGBREAK'): # Windows
signal.signal(signal.SIGBREAK, self.shutdown_handler)
# Also register atexit handler as backup
atexit.register(self.cleanup)
def shutdown_handler(self, signum, frame):
# print(f"\nReceived signal {signum}. Shutting down gracefully...")
self.cleanup()
sys.exit(0)
def cleanup(self):
if not self.running:
print("Cleaning up streaming connections...")
self.running = False
# Give a moment for generators to stop
time.sleep(1)
def get_system_stats(self, first = False, last_disk_io = psutil.disk_io_counters() ):
# Set a reasonable maximum speed for the bar graph display.
# 100 MB/s will represent a 100% full bar.
MAX_SSD_SPEED_MB_S = 100.0
# Get CPU and RAM stats
if first :
cpu_percent = psutil.cpu_percent(interval=.01)
else:
cpu_percent = psutil.cpu_percent(interval=1) # This provides our 1-second delay
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)
# Get new disk IO counters and calculate the read/write speed in MB/s
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
# Update the last counters for the next loop
last_disk_io = current_disk_io
# Calculate the bar height as a percentage of our defined max speed
ssd_bar_height = min(100.0, (total_disk_speed / MAX_SSD_SPEED_MB_S) * 100)
# Get GPU stats if the library was initialized successfully
if nvml_initialized:
try:
handle = pynvml.nvmlDeviceGetHandleByIndex(0) # Assuming GPU 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:
# Handle cases where GPU might be asleep or driver issues
gpu_percent, vram_percent, vram_used_gb, vram_total_gb = 0, 0, 0, 0
else:
# Set default values if NVML failed to load
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
# if i % 2 == 0:
# print(f"time:{time.time()}")
html_content, last_disk_io = self.get_system_stats(False, last_disk_io)
yield html_content
# time.sleep(1)
except GeneratorExit:
# print("Generator stopped gracefully")
return
except Exception as e:
print(f"Streaming error: {e}")
# finally:
# # Send final message indicating clean shutdown
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) # 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
)