import os
import sys
import io
import socket
import time
import re
import threading
import tkinter as tk
import ttkbootstrap as tb
import psutil
import math
import random
import subprocess
import json
import queue
from ttkbootstrap.constants import *
from PIL import Image, ImageTk, ImageDraw
# ====================== 数据采集服务 ======================
class HardwareMonitorService:
def __init__(self):
self.config = self.read_config()
if self.config is None:
raise Exception("配置读取失败,无法启动服务")
self.server_ip = self.config.get('SERVER_IP')
self.server_port = int(self.config.get('SERVER_PORT'))
self.client_ip = self.config.get('CLIENT_IP')
self.client_port = int(self.config.get('CLIENT_PORT'))
self.running = True
self.sock = None
self.send_count = 1
self.setup_socket()
# 启动服务线程
self.service_thread = threading.Thread(target=self.run_service, daemon=True)
self.service_thread.start()
def read_config(self):
config = {}
try:
with open('config.txt', 'r') as config_file:
for line in config_file:
if '=' in line:
key, value = line.strip().split('=', 1)
config[key] = value
print("读取配置成功", flush=True)
return config
except FileNotFoundError:
print("无法打开配置文件!", flush=True)
return None
def is_command_available(self, command):
"""检查命令是否可用"""
if os.name == 'nt': # Windows 系统
result = os.system(f'where {command} >nul 2>&1')
else: # Linux 系统
result = os.system(f'which {command} >/dev/null 2>&1')
return result == 0
def get_hardware_info(self, send_count):
# 初始化硬件信息字典
hardware_data = {
"cpu_temp": 0.0,
"power": 0.0,
"memory_used": 0,
"memory_total": 0,
"bandwidth": 0.0,
"mlu_usage": 0.0,
"cpu_usage": 0.0
}
try:
# 获取温度信息
result_temp = subprocess.run(
['cnmon', 'info', '-e'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=1.0,
check=True
).stdout.decode('utf-8')
temp_match = re.search(r"Board\s*[::]?\s*([-+]?\d*\.?\d+)\s*[cC°]", result_temp)
if temp_match:
hardware_data["cpu_temp"] = float(temp_match.group(1))
# 获取功耗信息
result_power = subprocess.run(
['cnmon', 'info', '-p'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=1.0,
check=True
).stdout.decode('utf-8')
power_match = re.search(r"Usage\s+:\s+(\d+\.?\d*)\s*W", result_power)
if power_match:
hardware_data["power"] = float(power_match.group(1))
# 获取内存信息
result_memory = subprocess.run(
['cnmon', 'info', '-m'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=1.0,
check=True
).stdout.decode('utf-8')
mem_match = re.search(r"Used\s+:\s+(\d+)\s*MiB.*?Total\s+:\s+(\d+)\s*MiB",
result_memory, re.DOTALL)
if mem_match:
hardware_data["memory_used"] = int(mem_match.group(1))
hardware_data["memory_total"] = int(mem_match.group(2))
# 获取带宽信息
result_bandwidth = subprocess.run(
['cnmon', 'info', '-b'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=1.0,
check=True
).stdout.decode('utf-8')
bw_match = re.search(r"Bandwidth\s+:\s+([\d.]+)\s*GB/s", result_bandwidth)
if bw_match:
hardware_data["bandwidth"] = float(bw_match.group(1))
# 获取利用率信息
result_usage = subprocess.run(
['cnmon', 'info', '-u'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=1.0,
check=True
).stdout.decode('utf-8')
mlu_match = re.search(r"MLU Average\s+:\s+(\d+) %", result_usage)
if mlu_match:
hardware_data["mlu_usage"] = float(mlu_match.group(1))
cpu_match = re.search(r"Device CPU Chip\s+:\s+(\d+) %", result_usage)
if cpu_match:
hardware_data["cpu_usage"] = float(cpu_match.group(1))
except Exception as e:
print(f"硬件信息获取错误: {e}")
# 返回模拟数据
return self.get_fallback_data(send_count)
# 添加发送次数
hardware_data["send_count"] = send_count
return json.dumps(hardware_data)
def get_fallback_data(self, send_count):
"""返回全0数据"""
return json.dumps({
"cpu_temp":0.0,
"power": 0.0,
"memory_used": 0,
"memory_total": 0,
"bandwidth": 0.0,
"mlu_usage": 0.0,
"cpu_usage": 0.0,
"send_count": send_count
})
def setup_socket(self):
"""设置UDP套接字"""
try:
# 创建 UDP 套接字
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 端口号快速重用
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定服务器 IP 和端口
self.sock.bind((self.server_ip, self.server_port))
print(f"绑定到 {self.server_ip}:{self.server_port}", flush=True)
except OSError as e:
print(f"绑定错误: {e}", flush=True)
self.running = False
def run_service(self):
print("硬件监控服务已启动", flush=True)
while self.running:
try:
if not self.is_command_available('cnmon'):
# 模拟数据
info = self.get_fallback_data(self.send_count)
else:
# 获取真实数据
info = self.get_hardware_info(self.send_count)
# 发送JSON数据到客户端
self.sock.sendto(info.encode(), (self.client_ip, self.client_port))
self.send_count += 1
time.sleep(0.5)
except Exception as e:
print(f"服务错误: {e}", flush=True)
time.sleep(1)
def stop_service(self):
"""停止数据采集服务"""
self.running = False
if self.sock:
self.sock.close()
print("硬件监控服务已停止", flush=True)
# ====================== GUI 监控界面 ======================
class EnhancedCircularProgressBar:
def __init__(self, parent, size=200, thickness=20,
bg_color="#1a1a1a", fg_color="#4caf50",
text_color="#ffffff", font_size=16,
title="", unit="", max_value=100,
glow_effect=True):
self.parent = parent
self.size = size
self.thickness = thickness
self.bg_color = bg_color
self.fg_color = fg_color
self.text_color = text_color
self.font_size = font_size
self.title = title
self.unit = unit
self.max_value = max_value
self.glow_effect = glow_effect
# 创建Canvas
self.canvas = tk.Canvas(
parent,
width=size,
height=size,
bg="black",
highlightthickness=0,
bd=0
)
# 计算圆心和半径
self.center_x = size / 2
self.center_y = size / 2
self.radius = (size - thickness) / 2 - 5
# 创建渐变效果
self.create_gradient()
# 绘制背景圆环
self.draw_background()
# 创建进度弧
self.arc_id = self.canvas.create_arc(
self.center_x - self.radius,
self.center_y - self.radius,
self.center_x + self.radius,
self.center_y + self.radius,
start=90,
extent=0,
style=tk.ARC,
outline="",
width=thickness,
tags="progress"
)
# 创建发光效果
if self.glow_effect:
self.glow_id = self.canvas.create_oval(
self.center_x - self.radius - 5,
self.center_y - self.radius - 5,
self.center_x + self.radius + 5,
self.center_y + self.radius + 5,
outline="",
fill="",
tags="glow"
)
# 创建文本元素
self.create_text_elements()
# 动画控制变量
self.current_value = 0
self.target_value = 0
self.animation_running = False
self.animation_id = None
self.last_update_time = time.time()
# 性能优化
self.canvas.tag_raise("progress")
self.canvas.tag_raise("text")
def create_gradient(self):
"""创建渐变背景效果"""
self.gradient_img = Image.new("RGBA", (self.size, self.size), (0, 0, 0, 0))
draw = ImageDraw.Draw(self.gradient_img)
for r in range(int(self.radius), 0, -1):
alpha = int(150 * (1 - r/self.radius))
draw.ellipse([
self.center_x - r,
self.center_y - r,
self.center_x + r,
self.center_y + r
], outline=(40, 40, 40, alpha))
self.gradient_photo = ImageTk.PhotoImage(self.gradient_img)
self.canvas.create_image(
self.center_x,
self.center_y,
image=self.gradient_photo,
tags="background"
)
def draw_background(self):
"""绘制背景圆环"""
self.bg_arc_id = self.canvas.create_arc(
self.center_x - self.radius,
self.center_y - self.radius,
self.center_x + self.radius,
self.center_y + self.radius,
start=0,
extent=359.9,
style=tk.ARC,
outline=self.bg_color,
width=self.thickness,
tags="background"
)
def create_text_elements(self):
"""创建所有文本元素"""
# 标题文本
self.title_id = self.canvas.create_text(
self.center_x,
self.center_y - self.radius * 0.5,
text=self.title,
fill=self.text_color,
font=("Arial", self.font_size, "bold"),
tags="text"
)
# 数值文本
self.value_id = self.canvas.create_text(
self.center_x,
self.center_y,
text="0",
fill=self.text_color,
font=("Arial", int(self.font_size * 1.8), "bold"),
tags="text"
)
# 单位文本
self.unit_id = self.canvas.create_text(
self.center_x,
self.center_y + self.radius * 0.3,
text=self.unit,
fill=self.text_color,
font=("Arial", self.font_size - 2),
tags="text"
)
def calculate_color(self, value):
"""实现绿→黄→橙→红颜色过渡"""
ratio = value / self.max_value
if ratio <= 0.5:
# 绿(0,255,0) → 黄(255,255,0)
r = int(510 * ratio)
g = 255
b = 0
elif ratio <= 0.75:
# 黄(255,255,0) → 橙(255,165,0)
r = 255
g = int(255 - 360 * (ratio - 0.5)) # 255 → 165
b = 0
else:
# 橙(255,165,0) → 红(255,0,0)
r = 255
g = int(165 - 660 * (ratio - 0.75)) # 165 → 0
b = 0
return f"#{r:02x}{g:02x}{b:02x}"
def set_value(self, value):
"""设置目标值"""
self.target_value = max(0, min(self.max_value, value))
# 更新数值显示
self.canvas.itemconfig(self.value_id, text=f"{self.target_value:.1f}")
# 启动动画
if not self.animation_running:
self.animate()
def animate(self):
"""平滑动画更新进度"""
self.animation_running = True
# 计算插值(使用缓动函数)
delta = self.target_value - self.current_value
speed_factor = 0.2 # 控制动画速度
if abs(delta) > 0.1:
self.current_value += delta * speed_factor
else:
self.current_value = self.target_value
# 计算弧的角度
angle = 360 * (self.current_value / self.max_value)
# 更新弧
self.canvas.itemconfig(self.arc_id, extent=-angle)
# 更新颜色
color = self.calculate_color(self.current_value)
self.canvas.itemconfig(self.arc_id, outline=color)
# 更新发光效果
if self.glow_effect and time.time() - self.last_update_time > 0.1:
self.update_glow_effect(color)
self.last_update_time = time.time()
# 继续动画或停止
if abs(self.current_value - self.target_value) > 0.5:
self.animation_id = self.canvas.after(16, self.animate)
else:
self.current_value = self.target_value
self.animation_running = False
self.animation_id = None
def update_glow_effect(self, color):
"""更新发光效果"""
if not self.glow_effect:
return
# 创建新的发光图像
glow_img = Image.new("RGBA", (self.size, self.size), (0, 0, 0, 0))
draw = ImageDraw.Draw(glow_img)
# 解析颜色
r = int(color[1:3], 16)
g = int(color[3:5], 16)
b = int(color[5:7], 16)
# 绘制发光效果
for i in range(1, 6):
alpha = int(50 * (1 - i/6))
radius = self.radius + i
draw.ellipse([
self.center_x - radius,
self.center_y - radius,
self.center_x + radius,
self.center_y + radius
], outline=(r, g, b, alpha), width=1)
self.glow_photo = ImageTk.PhotoImage(glow_img)
self.canvas.itemconfig(self.glow_id, image=self.glow_photo)
def reset(self):
"""重置进度条"""
if self.animation_id:
self.canvas.after_cancel(self.animation_id)
self.current_value = 0
self.target_value = 0
self.canvas.itemconfig(self.arc_id, extent=0)
self.canvas.itemconfig(self.value_id, text="0")
color = self.calculate_color(0)
self.canvas.itemconfig(self.arc_id, outline=color)
class SystemMonitorApp:
def __init__(self):
# 启动数据采集服务
try:
self.monitor_service = HardwareMonitorService()
client_ip = self.monitor_service.client_ip
client_port = self.monitor_service.client_port
except Exception as e:
print(f"无法启动数据采集服务: {e}")
self.monitor_service = None
client_ip = "127.0.0.1"
client_port = 9999
# 创建主窗口
self.root = tb.Window(themename="darkly", title="系统资源监控", size=(1200, 800))
self.root.iconbitmap("")
self.root.minsize(1000, 700)
# 设置样式
style = tb.Style()
style.configure("TFrame", background="#121212")
style.configure("Title.TLabel", background="#121212", foreground="#e0e0e0", font=("Arial", 16, "bold"))
# 创建UDP接收套接字
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
self.udp_socket.bind((client_ip, client_port))
print(f"GUI绑定到 {client_ip}:{client_port} 接收数据")
except Exception as e:
print(f"GUI绑定错误: {e}")
self.udp_socket.settimeout(0.1) # 设置超时避免阻塞
# 存储真实数据的变量
self.real_data = {
"cpu_temp": 0.0,
"power": 0.0,
"memory": 0.0, # 内存使用百分比
"bandwidth": 0.0, # 带宽,单位GB/s
"mlu_usage": 0.0,
"cpu_usage": 0.0
}
# 数据更新队列(用于线程安全)
self.data_queue = queue.Queue()
# 创建主框架
main_frame = tb.Frame(self.root, padding=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 标题
tb.Label(
main_frame,
text="系统资源实时监控",
style="Title.TLabel"
).pack(pady=(0, 20))
# 创建进度条容器框架
progress_container = tb.Frame(main_frame)
progress_container.pack(fill=tk.BOTH, expand=True, pady=10)
# 创建监控指标配置
monitor_config = [
{"title": "CPU温度", "unit": "°C", "max_value": 100, "thickness": 18, "fg_color": "#ff5555"},
{"title": "功耗", "unit": "W", "max_value": 200, "thickness": 18, "fg_color": "#ffaa00"},
{"title": "内存使用", "unit": "%", "max_value": 100, "thickness": 18, "fg_color": "#55aaff"},
{"title": "网络带宽", "unit": "Mbps", "max_value": 1000, "thickness": 18, "fg_color": "#aa55ff"},
{"title": "MLU利用率", "unit": "%", "max_value": 100, "thickness": 18, "fg_color": "#00cc99"},
{"title": "CPU利用率", "unit": "%", "max_value": 100, "thickness": 18, "fg_color": "#ff55ff"}
]
# 使用网格布局排列进度条
self.progress_bars = []
for i, config in enumerate(monitor_config):
frame = tb.Frame(progress_container)
frame.grid(row=i//3, column=i%3, padx=20, pady=20, sticky="nsew")
# 创建增强型进度条
progress_bar = EnhancedCircularProgressBar(
frame,
size=220,
thickness=config["thickness"],
title=config["title"],
unit=config["unit"],
max_value=config["max_value"],
fg_color=config["fg_color"],
glow_effect=True
)
self.progress_bars.append(progress_bar)
progress_bar.canvas.pack(fill=tk.BOTH, expand=True)
# 设置网格列权重
for i in range(3):
progress_container.columnconfigure(i, weight=1)
for i in range(2):
progress_container.rowconfigure(i, weight=1)
# 控制面板
control_frame = tb.Frame(main_frame)
control_frame.pack(pady=20, fill=tk.X)
# 按钮框架
btn_frame = tb.Frame(control_frame)
btn_frame.pack(pady=10)
tb.Button(
btn_frame,
text="启动",
bootstyle=SUCCESS,
command=self.start_monitoring
).pack(side=tk.LEFT, padx=5)
tb.Button(
btn_frame,
text="暂停",
bootstyle=DANGER,
command=self.stop_monitoring
).pack(side=tk.LEFT, padx=5)
tb.Button(
btn_frame,
text="重置",
bootstyle=WARNING,
command=self.reset_all
).pack(side=tk.LEFT, padx=5)
# 主题选择器
theme_frame = tb.Frame(control_frame)
theme_frame.pack(fill=tk.X, pady=10)
tb.Label(theme_frame, text="选择主题:", bootstyle=PRIMARY).pack(anchor=tk.W)
self.theme_var = tk.StringVar(value="darkly")
themes = ["darkly", "cyborg", "solar"]
for theme in themes:
tb.Radiobutton(
theme_frame,
text=theme.capitalize(),
variable=self.theme_var,
value=theme,
bootstyle=PRIMARY,
command=self.change_theme
).pack(side=tk.LEFT, padx=5)
# 状态栏
self.status = tb.Label(
self.root,
text="系统准备就绪",
bootstyle=(SECONDARY, INVERSE),
anchor=tk.CENTER
)
self.status.pack(side=tk.BOTTOM, fill=tk.X)
# 监控控制变量
self.monitoring_active = False
self.monitoring_thread = None
# 启动初始监控
self.start_monitoring()
# 设置定时器处理数据队列
self.root.after(100, self.process_updates)
# 窗口关闭事件处理
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
self.root.mainloop()
def start_monitoring(self):
"""启动资源监控"""
if self.monitoring_active:
return
self.status.config(text="启动系统资源监控...")
self.monitoring_active = True
# 使用线程运行监控,避免阻塞UI
self.monitoring_thread = threading.Thread(target=self.monitor_resources, daemon=True)
self.monitoring_thread.start()
def stop_monitoring(self):
"""停止资源监控"""
self.monitoring_active = False
self.status.config(text="监控已暂停")
def reset_all(self):
"""重置所有监控指标"""
for bar in self.progress_bars:
bar.reset()
self.status.config(text="所有监控指标已重置")
def receive_real_data(self):
"""接收并解析真实硬件数据"""
try:
data, _ = self.udp_socket.recvfrom(4096)
data_str = data.decode('utf-8')
try:
# 解析JSON数据
data_dict = json.loads(data_str)
# 更新real_data
self.real_data["cpu_temp"] = data_dict.get("cpu_temp", 0.0)
self.real_data["power"] = data_dict.get("power", 0.0)
# 计算内存使用百分比
mem_used = data_dict.get("memory_used", 0)
mem_total = data_dict.get("memory_total", 1) # 避免除零错误
if mem_total > 0:
self.real_data["memory"] = (mem_used / mem_total) * 100
self.real_data["bandwidth"] = data_dict.get("bandwidth", 0.0)
self.real_data["mlu_usage"] = data_dict.get("mlu_usage", 0.0)
self.real_data["cpu_usage"] = data_dict.get("cpu_usage", 0.0)
except json.JSONDecodeError:
# 兼容旧版文本格式
self._legacy_parse(data_str)
except socket.timeout:
pass # 没有数据是正常的
except Exception as e:
print(f"接收数据错误: {e}")
def _legacy_parse(self, info_str):
"""兼容旧版文本格式解析"""
try:
# 解析CPU温度
temp_match = re.search(r"Board\s+:\s+(\d+\.?\d*)\s*C", info_str)
if temp_match:
self.real_data["cpu_temp"] = float(temp_match.group(1))
# 解析功耗
power_match = re.search(r"Usage\s+:\s+(\d+\.?\d*)\s*W", info_str)
if power_match:
self.real_data["power"] = float(power_match.group(1))
# 解析内存使用率
mem_match = re.search(r"Used\s+:\s+(\d+)\s*MiB.*?Total\s+:\s+(\d+)\s*MiB", info_str, re.DOTALL)
if mem_match:
used = float(mem_match.group(1))
total = float(mem_match.group(2))
if total > 0:
self.real_data["memory"] = (used / total) * 100
# 解析带宽
bw_match = re.search(r"Bandwidth\s+:\s+([\d.]+)\s*GB/s", info_str)
if bw_match:
self.real_data["bandwidth"] = float(bw_match.group(1))
# 解析MLU利用率
mlu_match = re.search(r"MLU Average:\s*(\d+\.?\d*)%", info_str)
if mlu_match:
self.real_data["mlu_usage"] = float(mlu_match.group(1))
# 解析CPU利用率
cpu_match = re.search(r"Device CPU Chip:\s*(\d+\.?\d*)%", info_str)
if cpu_match:
self.real_data["cpu_usage"] = float(cpu_match.group(1))
except Exception as e:
print(f"旧版解析错误: {e}")
def monitor_resources(self):
"""监控系统资源"""
while self.monitoring_active:
# 接收并解析真实数据
self.receive_real_data()
# 使用真实数据或模拟数据
cpu_temp = self.real_data["cpu_temp"] or self.get_cpu_temperature()
power = self.real_data["power"] or self.get_power_usage()
mem_usage = self.real_data["memory"] or self.get_memory_usage()
# 带宽单位转换:GB/s → Mbps (1 GB/s = 8000 Mbps)
bandwidth_gb = self.real_data["bandwidth"]
network = bandwidth_gb * 8000 # 转换为Mbps
if network <= 0: # 如果真实数据无效,使用模拟数据
network = self.get_network_usage()
mlu_usage = self.real_data["mlu_usage"] or self.get_mlu_usage()
cpu_usage = self.real_data["cpu_usage"] or psutil.cpu_percent()
# 将数据放入队列,由主线程更新UI
self.data_queue.put((cpu_temp, power, mem_usage, network, mlu_usage, cpu_usage))
time.sleep(1)
def process_updates(self):
"""从队列中取出数据并更新UI(在主线程中调用)"""
try:
while not self.data_queue.empty():
data = self.data_queue.get_nowait()
# 更新进度条
self.progress_bars[0].set_value(data[0])
self.progress_bars[1].set_value(data[1])
self.progress_bars[2].set_value(data[2])
self.progress_bars[3].set_value(data[3])
self.progress_bars[4].set_value(data[4])
self.progress_bars[5].set_value(data[5])
# 更新状态栏
status_text = (
f"CPU: {data[5]:.1f}% | "
f"温度: {data[0]:.1f}°C | "
f"内存: {data[2]:.1f}% | "
f"MLU: {data[4]:.1f}%"
)
self.status.config(text=status_text)
except queue.Empty:
pass
# 每100毫秒检查一次
self.root.after(100, self.process_updates)
def get_cpu_temperature(self):
"""获取CPU温度(模拟)"""
base_temp = 40.0
fluctuation = random.uniform(-2, 8)
load_factor = self.progress_bars[5].current_value / 100 * 10
return min(100, max(30, base_temp + fluctuation + load_factor))
def get_power_usage(self):
"""获取功耗使用(模拟)"""
base_power = 80.0
fluctuation = random.uniform(-10, 15)
load_factor = (self.progress_bars[5].current_value +
self.progress_bars[4].current_value) / 200 * 50
return min(200, max(50, base_power + fluctuation + load_factor))
def get_memory_usage(self):
"""获取内存使用率"""
return psutil.virtual_memory().percent
def get_network_usage(self):
"""获取网络带宽使用(模拟)"""
base_usage = 300.0
fluctuation = random.uniform(-50, 100)
return min(1000, max(0, base_usage + fluctuation))
def get_mlu_usage(self):
"""获取MLU利用率(模拟)"""
base_usage = 30.0
fluctuation = random.uniform(-5, 15)
load_factor = random.uniform(0, 40)
return min(100, max(0, base_usage + fluctuation + load_factor))
def change_theme(self):
"""更改应用主题"""
theme = self.theme_var.get()
tb.Style(theme=theme)
self.status.config(text=f"主题已切换为: {theme.capitalize()}")
def on_close(self):
"""窗口关闭时清理资源"""
self.monitoring_active = False
if self.monitoring_thread and self.monitoring_thread.is_alive():
self.monitoring_thread.join(timeout=2.0)
try:
if self.monitor_service:
self.monitor_service.stop_service()
self.udp_socket.close()
except Exception as e:
print(f"关闭资源时出错: {e}")
self.root.destroy()
if __name__ == "__main__":
# 设置无缓冲输出
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', line_buffering=True)
# 启动GUI应用
SystemMonitorApp()
在改代码左侧增加下拉选项按钮内容待定,合理布局,写出完整代码
最新发布