chinajz 发表于 4 天前

测试workbuddy写硬件监控软件

本帖最后由 chinajz 于 2026-3-15 10:35 编辑

workbuddy写的硬件监控软件,测试结果,达到预期要求。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CPU/GPU 温度监控 — 图形界面 (gui.py)
依赖:tkinter(Python 内置)、matplotlib

启动方式:
python main.py --gui
python gui.py      (直接运行)
"""

import sys
import time
import threading
from datetime import datetime
from collections import deque

if sys.platform == "win32":
    try:
      sys.stdout.reconfigure(encoding="utf-8")
    except Exception:
      pass

try:
    import tkinter as tk
    from tkinter import ttk, font as tkfont
except ImportError:
    raise ImportError("tkinter 不可用,请确认 Python 安装时包含了 Tk 支持")

try:
    import matplotlib
    matplotlib.use("TkAgg")
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    HAS_MPL = True
except ImportError:
    HAS_MPL = False

# 导入数据采集模块(与 main.py 同目录)
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from main import (
    get_cpu_temperature, get_gpu_temperature,
    get_cpu_info, get_gpu_info,
    CPU_WARN_TEMP, CPU_CRIT_TEMP,
    GPU_WARN_TEMP, GPU_CRIT_TEMP,
    HAS_PYNVML, HAS_PSUTIL,
)

# ── 颜色主题 ─────────────────────────────────────────────────────────────────
COLORS = {
    "bg":       "#1e1e2e",
    "panel":    "#2a2a3e",
    "border":   "#44475a",
    "text":   "#cdd6f4",
    "subtext":"#6c7086",
    "green":    "#a6e3a1",
    "yellow":   "#f9e2af",
    "red":      "#f38ba8",
    "blue":   "#89b4fa",
    "mauve":    "#cba6f7",
    "cpu_line": "#89b4fa",
    "gpu_line": "#f38ba8",
}

REFRESH_MS= 2000   # 界面刷新间隔(毫秒)
HISTORY_LEN = 60   # 曲线历史长度


# ════════════════════════════════════════════════════════════════════════════
# 数据模型
# ════════════════════════════════════════════════════════════════════════════

class HardwareData:
    def __init__(self):
      self.cpu_temp_history = deque( * HISTORY_LEN, maxlen=HISTORY_LEN)
      self.gpu_temp_history = deque( * HISTORY_LEN, maxlen=HISTORY_LEN)
      self.lock = threading.Lock()

      # 最新一帧数据
      self.cpu_temps    = {}
      self.gpu_temps    = {}
      self.cpu_info   = {}
      self.gpu_list   = []
      self.timestamp    = ""
      self.fetching   = False

    def fetch(self):
      """在后台线程里采集数据"""
      self.fetching = True
      try:
            cpu_temps = get_cpu_temperature()
            gpu_temps = get_gpu_temperature()
            cpu_info= get_cpu_info()
            gpu_list= get_gpu_info()
            ts      = datetime.now().strftime("%H:%M:%S")

            # 取物理核心(过滤 Thread 和 Package/Tdie 聚合传感器)的最高温度作为曲线代表值
            _PKG_KW = ("package", "tdie", "tctl", "average", "max", "distance")
            phys_temps = {k: v for k, v in cpu_temps.items() if "Thread" not in k}
            src = phys_temps if phys_temps else cpu_temps
            core_vals = [v for k, v in src.items()
                         if not any(kw in k.lower() for kw in _PKG_KW)]
            cpu_peak = max(core_vals) if core_vals else (max(src.values()) if src else None)
            # 第一块 GPU 的温度
            gpu_peak = None
            if gpu_list:
                i    = gpu_list["index"]
                name = gpu_list["name"]
                gpu_peak = gpu_temps.get(f"GPU{i} {name}")
            elif gpu_temps:
                gpu_peak = list(gpu_temps.values())

            with self.lock:
                self.cpu_temps = cpu_temps
                self.gpu_temps = gpu_temps
                self.cpu_info= cpu_info
                self.gpu_list= gpu_list
                self.timestamp = ts
                self.cpu_temp_history.append(cpu_peak)
                self.gpu_temp_history.append(gpu_peak)
      except Exception as e:
            print(f"[数据采集异常] {e}")
      finally:
            self.fetching = False


# ════════════════════════════════════════════════════════════════════════════
# 主窗口
# ════════════════════════════════════════════════════════════════════════════

class MonitorApp(tk.Tk):
    def __init__(self):
      super().__init__()
      self.title("CPU / GPU 硬件监控")
      self.configure(bg=COLORS["bg"])
      self.resizable(True, True)
      self.geometry("900x640")

      self.data = HardwareData()

      self._build_ui()
      self._schedule_refresh()

    # ── UI 构建 ────────────────────────────────────────────────────────────

    def _build_ui(self):
      # 顶部标题栏
      title_frame = tk.Frame(self, bg=COLORS["bg"])
      title_frame.pack(fill="x", padx=16, pady=(12, 4))

      tk.Label(
            title_frame, text="🖥硬件温度监控",
            bg=COLORS["bg"], fg=COLORS["text"],
            font=("Segoe UI", 16, "bold"),
      ).pack(side="left")

      self.lbl_time = tk.Label(
            title_frame, text="",
            bg=COLORS["bg"], fg=COLORS["subtext"],
            font=("Segoe UI", 10),
      )
      self.lbl_time.pack(side="right")

      # 主体两列布局
      body = tk.Frame(self, bg=COLORS["bg"])
      body.pack(fill="both", expand=True, padx=16, pady=4)

      left= tk.Frame(body, bg=COLORS["bg"])
      right = tk.Frame(body, bg=COLORS["bg"])
      left.pack(side="left", fill="both", expand=True, padx=(0, 8))
      right.pack(side="right", fill="both", expand=True)

      # 左列:CPU 面板 + GPU 面板
      self._cpu_panel = self._make_panel(left, "📊CPU", expand=True)
      self._gpu_panel = self._make_panel(left, "🎮GPU", expand=True)

      # 右列:温度曲线图
      if HAS_MPL:
            chart_frame = self._make_panel(right, "📈温度趋势(近 60 次)", expand=True)
            self._build_chart(chart_frame)
      else:
            warn = self._make_panel(right, "⚠提示", expand=True)
            tk.Label(
                warn, text="安装 matplotlib 以显示温度趋势图\npip install matplotlib",
                bg=COLORS["panel"], fg=COLORS["yellow"],
                font=("Segoe UI", 10), justify="left",
            ).pack(padx=12, pady=12, anchor="w")

      # 底部状态栏
      bar = tk.Frame(self, bg=COLORS["border"], height=1)
      bar.pack(fill="x", padx=16)
      self.lbl_status = tk.Label(
            self, text="正在加载…",
            bg=COLORS["bg"], fg=COLORS["subtext"],
            font=("Segoe UI", 9),
      )
      self.lbl_status.pack(anchor="w", padx=16, pady=(2, 8))

    def _make_panel(self, parent, title: str, expand: bool = False) -> tk.Frame:
      """创建带标题的卡片面板,返回内容区 Frame"""
      outer = tk.Frame(parent, bg=COLORS["border"], bd=0)
      outer.pack(fill="both", expand=expand, pady=4)

      inner = tk.Frame(outer, bg=COLORS["panel"], bd=0)
      inner.pack(fill="both", expand=True, padx=1, pady=1)

      tk.Label(
            inner, text=title,
            bg=COLORS["panel"], fg=COLORS["blue"],
            font=("Segoe UI", 11, "bold"),
      ).pack(anchor="w", padx=12, pady=(10, 4))

      sep = tk.Frame(inner, bg=COLORS["border"], height=1)
      sep.pack(fill="x", padx=12)

      content = tk.Frame(inner, bg=COLORS["panel"])
      content.pack(fill="both", expand=True, padx=12, pady=(6, 10))
      return content

    def _build_chart(self, parent: tk.Frame):
      """在 parent 内嵌入 matplotlib 折线图"""
      fig = Figure(figsize=(4.5, 4), dpi=90, facecolor=COLORS["bg"])
      self._ax = fig.add_subplot(111)
      self._ax.set_facecolor(COLORS["panel"])
      fig.tight_layout(pad=1.5)

      self._canvas = FigureCanvasTkAgg(fig, master=parent)
      self._canvas.get_tk_widget().pack(fill="both", expand=True)
      self._fig = fig

      self._line_cpu, = self._ax.plot(
            [], [], color=COLORS["cpu_line"], linewidth=1.8, label="CPU"
      )
      self._line_gpu, = self._ax.plot(
            [], [], color=COLORS["gpu_line"], linewidth=1.8, label="GPU"
      )

      self._ax.axhline(CPU_WARN_TEMP, color=COLORS["yellow"], linewidth=0.8,
                         linestyle="--", alpha=0.6, label=f"警告 {CPU_WARN_TEMP}°C")
      self._ax.axhline(CPU_CRIT_TEMP, color=COLORS["red"], linewidth=0.8,
                         linestyle="--", alpha=0.6, label=f"危险 {CPU_CRIT_TEMP}°C")

      self._ax.set_ylabel("温度 (°C)", color=COLORS["text"], fontsize=9)
      self._ax.set_xlabel("历史记录", color=COLORS["text"], fontsize=9)
      self._ax.tick_params(colors=COLORS["subtext"], labelsize=8)
      for spine in self._ax.spines.values():
            spine.set_edgecolor(COLORS["border"])
      self._ax.legend(
            facecolor=COLORS["panel"], edgecolor=COLORS["border"],
            labelcolor=COLORS["text"], fontsize=8
      )
      self._ax.set_ylim(0, 110)
      self._ax.yaxis.label.set_color(COLORS["text"])
      self._ax.xaxis.label.set_color(COLORS["text"])

    # ── 数据刷新 ──────────────────────────────────────────────────────────

    def _schedule_refresh(self):
      """开始后台采集 + 定时 UI 刷新"""
      self._do_refresh()

    def _do_refresh(self):
      """启动后台采集,采集完毕后更新 UI"""
      def worker():
            self.data.fetch()
            self.after(0, self._update_ui)

      t = threading.Thread(target=worker, daemon=True)
      t.start()

    def _update_ui(self):
      """用最新数据更新所有 UI 控件"""
      with self.data.lock:
            cpu_temps = dict(self.data.cpu_temps)
            gpu_temps = dict(self.data.gpu_temps)
            cpu_info= dict(self.data.cpu_info)
            gpu_list= list(self.data.gpu_list)
            ts      = self.data.timestamp
            cpu_hist= list(self.data.cpu_temp_history)
            gpu_hist= list(self.data.gpu_temp_history)

      self.lbl_time.config(text=ts)

      self._refresh_cpu_panel(cpu_temps, cpu_info)
      self._refresh_gpu_panel(gpu_temps, gpu_list)
      if HAS_MPL:
            self._refresh_chart(cpu_hist, gpu_hist)

      self.lbl_status.config(text=f"上次更新:{ts}|刷新间隔:{REFRESH_MS // 1000}s")

      # 安排下次刷新
      self.after(REFRESH_MS, self._do_refresh)

    def _refresh_cpu_panel(self, cpu_temps: dict, cpu_info: dict):
      """清空并重绘 CPU 面板"""
      for w in self._cpu_panel.winfo_children():
            w.destroy()

      def row(label, value, val_color=COLORS["text"]):
            f = tk.Frame(self._cpu_panel, bg=COLORS["panel"])
            f.pack(fill="x", pady=1)
            tk.Label(f, text=label, bg=COLORS["panel"], fg=COLORS["subtext"],
                     font=("Segoe UI", 9), width=22, anchor="w").pack(side="left")
            tk.Label(f, text=value, bg=COLORS["panel"], fg=val_color,
                     font=("Segoe UI", 9, "bold"), anchor="w").pack(side="left")

      if cpu_info:
            physical = cpu_info.get("cpu_count_physical", "?")
            logical= cpu_info.get("cpu_count_logical", "?")
            row("核心数", f"{physical} 物理 / {logical} 逻辑")

            if "freq_current" in cpu_info:
                row("当前频率", f"{cpu_info['freq_current']:.0f} MHz")

            usage = cpu_info.get("usage_total")
            if usage is not None:
                color = _usage_color(usage)
                row("CPU 使用率", f"{usage:.1f}%", color)

                # 各核使用率——按物理核聚合(超线程取两逻辑核中的较大值)
                per_core= cpu_info.get("usage_per_core", [])
                phys_cnt= cpu_info.get("cpu_count_physical") or 0
                logi_cnt= cpu_info.get("cpu_count_logical")or len(per_core)
                if per_core:
                  if phys_cnt and logi_cnt == phys_cnt * 2:
                        phys_usage = [
                            max(per_core, per_core)
                            for i in range(phys_cnt)
                        ]
                  else:
                        phys_usage = per_core
                  cols = 3
                  for start in range(0, len(phys_usage), cols):
                        chunk = phys_usage
                        txt = "".join(
                            f"核{start + j + 1}: {v:>5.1f}%" for j, v in enumerate(chunk)
                        )
                        tk.Label(
                            self._cpu_panel, text=txt,
                            bg=COLORS["panel"], fg=COLORS["subtext"],
                            font=("Consolas", 8),
                        ).pack(anchor="w")

            if "mem_used_gb" in cpu_info:
                mem_color = _usage_color(cpu_info["mem_percent"])
                row("内存使用",
                  f"{cpu_info['mem_used_gb']:.1f} / {cpu_info['mem_total_gb']:.1f} GB"
                  f"({cpu_info['mem_percent']:.1f}%)", mem_color)

      # 温度——最高核心温度 + Package 温度
      if cpu_temps:
            tk.Frame(self._cpu_panel, bg=COLORS["border"], height=1).pack(fill="x", pady=4)
            phys = {k: v for k, v in cpu_temps.items() if "Thread" not in k}
            src= phys if phys else cpu_temps

            # 最高核心温度(排除 Package/Tdie/Tctl 聚合传感器)
            _PKG_KW = ("package", "tdie", "tctl", "average", "max", "distance")
            core_vals = [v for k, v in src.items()
                         if not any(kw in k.lower() for kw in _PKG_KW)]
            peak = max(core_vals) if core_vals else max(src.values())
            row("温度(最高核心)", f"{peak:.1f} °C",
                _temp_color(peak, CPU_WARN_TEMP, CPU_CRIT_TEMP))

            # Package 温度
            for k, v in src.items():
                if any(kw in k.lower() for kw in ("package", "tdie", "tctl")):
                  row("温度(Package)", f"{v:.1f} °C",
                        _temp_color(v, CPU_WARN_TEMP, CPU_CRIT_TEMP))
                  break
      else:
            tk.Label(
                self._cpu_panel,
                text="温度不可用(需 LibreHardwareMonitor)",
                bg=COLORS["panel"], fg=COLORS["yellow"],
                font=("Segoe UI", 9),
            ).pack(anchor="w", pady=4)

    def _refresh_gpu_panel(self, gpu_temps: dict, gpu_list: list):
      """清空并重绘 GPU 面板"""
      for w in self._gpu_panel.winfo_children():
            w.destroy()

      def row(label, value, val_color=COLORS["text"]):
            f = tk.Frame(self._gpu_panel, bg=COLORS["panel"])
            f.pack(fill="x", pady=1)
            tk.Label(f, text=label, bg=COLORS["panel"], fg=COLORS["subtext"],
                     font=("Segoe UI", 9), width=22, anchor="w").pack(side="left")
            tk.Label(f, text=value, bg=COLORS["panel"], fg=val_color,
                     font=("Segoe UI", 9, "bold"), anchor="w").pack(side="left")

      if not gpu_list and not gpu_temps:
            tk.Label(
                self._gpu_panel,
                text="未检测到 GPU(NVIDIA 需安装 nvidia-ml-py3)",
                bg=COLORS["panel"], fg=COLORS["yellow"],
                font=("Segoe UI", 9),
            ).pack(anchor="w", pady=4)
            return

      for gpu in gpu_list:
            i    = gpu["index"]
            name = gpu["name"]
            tk.Label(
                self._gpu_panel, text=f"[{i}] {name}",
                bg=COLORS["panel"], fg=COLORS["mauve"],
                font=("Segoe UI", 9, "bold"),
            ).pack(anchor="w", pady=(4, 1))

            temp_key = f"GPU{i} {name}"
            temp = gpu_temps.get(temp_key)
            if temp is not None:
                color = _temp_color(temp, GPU_WARN_TEMP, GPU_CRIT_TEMP)
                row("温度", f"{temp:.1f} °C", color)

            if "usage_gpu" in gpu:
                row("GPU 使用率", f"{gpu['usage_gpu']:.1f}%",
                  _usage_color(gpu["usage_gpu"]))
            if "mem_used_gb" in gpu:
                row("显存使用",
                  f"{gpu['mem_used_gb']:.1f} / {gpu['mem_total_gb']:.1f} GB"
                  f"({gpu['mem_percent']:.1f}%)",
                  _usage_color(gpu["mem_percent"]))
            if "power_w" in gpu:
                row("功耗", f"{gpu['power_w']:.1f} W / {gpu.get('power_limit_w', '?'):.1f} W")
            if "fan_percent" in gpu:
                row("风扇转速", f"{gpu['fan_percent']}%")
            if "clock_gpu_mhz" in gpu:
                row("核心频率", f"{gpu['clock_gpu_mhz']} MHz")

      # 其他 GPU(WMI)
      known = {f"GPU{g['index']} {g['name']}" for g in gpu_list}
      for name, temp in gpu_temps.items():
            if name not in known:
                color = _temp_color(temp, GPU_WARN_TEMP, GPU_CRIT_TEMP)
                row(f"{_shorten(name, 22)}", f"{temp:.1f} °C", color)

    def _refresh_chart(self, cpu_hist: list, gpu_hist: list):
      """更新 matplotlib 折线图"""
      xs = list(range(HISTORY_LEN))

      cpu_ys =
      gpu_ys =

      self._line_cpu.set_data(xs, cpu_ys)
      self._line_gpu.set_data(xs, gpu_ys)

      all_vals = # filter nan
      if all_vals:
            y_min = max(0, min(all_vals) - 10)
            y_max = min(120, max(all_vals) + 15)
            self._ax.set_ylim(y_min, y_max)

      self._ax.set_xlim(0, HISTORY_LEN - 1)
      self._canvas.draw_idle()


# ════════════════════════════════════════════════════════════════════════════
# 辅助函数
# ════════════════════════════════════════════════════════════════════════════

def _temp_color(temp: float, warn: float, crit: float) -> str:
    if temp >= crit:
      return COLORS["red"]
    elif temp >= warn:
      return COLORS["yellow"]
    return COLORS["green"]


def _usage_color(pct: float) -> str:
    if pct >= 90:
      return COLORS["red"]
    elif pct >= 70:
      return COLORS["yellow"]
    return COLORS["green"]


def _shorten(text: str, max_len: int) -> str:
    return text if len(text) <= max_len else text[:max_len - 1] + "…"


# ════════════════════════════════════════════════════════════════════════════
# 入口
# ════════════════════════════════════════════════════════════════════════════

def launch_gui():
    app = MonitorApp()
    app.mainloop()


if __name__ == "__main__":
    launch_gui()

FishC 发表于 4 天前

鱼油,您好!很高兴得知您测试的硬件监控软件达到预期要求。硬件监控软件通常用于实时监测计算机硬件的状态,如CPU、内存、磁盘和温度等,这对于系统维护和性能优化非常有用。

如果您想了解如何编写这样的软件,这里是一个简单的Python示例,使用psutil库来监控CPU和内存使用率:


import psutil

# 获取CPU使用率
cpu_usage = psutil.cpu_percent(interval=1)
print(f"当前CPU使用率: {cpu_usage}%")

# 获取内存信息
memory = psutil.virtual_memory()
print(f"内存总量: {memory.total / (1024**3):.2f} GB")
print(f"内存使用率: {memory.percent}%")


要运行此代码,您需要先安装psutil库,可以使用pip install psutil命令。

继续加油,如果您在开发或测试中遇到具体问题,欢迎随时提问交流!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

player-none 发表于 4 天前

看不懂但大为震撼{:10_257:}

不二如是 发表于 4 天前

项目超级厉害!
页: [1]
查看完整版本: 测试workbuddy写硬件监控软件