用AI写一个Python实时硬件监控与日志可视化界面

最近玩游戏多开总是黑屏,怀疑是温度过高。

所以让AI写一个记录温度的脚本。 等 过温重启打开日志看看

1、效果

日志形式:

提示词:

你是电脑专家和pyhton专家,我需要一个python 可视化界面,可以实时看到电脑CPU温和CPU占用率,还有GPU温度和占用率,还有显存占用率,并记录日志。

大模型:

python 复制代码
import tkinter as tk
from tkinter import ttk
import threading
import os
import subprocess
import warnings
import logging
from logging.handlers import RotatingFileHandler
from datetime import datetime

warnings.filterwarnings("ignore", category=FutureWarning)

import psutil
import pynvml

try:
    import ctypes
    ctypes.windll.shcore.SetProcessDpiAwareness(2)
except Exception:
    pass


# ============ CPU Temperature Reader ============
def get_cpu_temperature():
    """Try multiple methods to read CPU temperature. Returns (temp, method_name)."""
    # Method 1: PowerShell WMI (Win32_PerfFormattedData_Counters_ThermalZoneInformation)
    try:
        ps_cmd = (
            "(Get-CimInstance -Namespace root/cimv2 "
            "-ClassName Win32_PerfFormattedData_Counters_ThermalZoneInformation)"
            ".HighPrecisionTemperature"
        )
        result = subprocess.run(
            ["powershell", "-Command", ps_cmd],
            capture_output=True, text=True, timeout=5
        )
        output = result.stdout.strip()
        if output and output.isdigit():
            temp_k = int(output) / 10.0
            temp_c = temp_k - 273.15
            return round(temp_c, 1), "WMI-CIM"
    except Exception:
        pass

    # Method 2: PowerShell WMI (MSAcpi_ThermalZoneTemperature)
    try:
        result = subprocess.run(
            ["powershell", "-Command",
             "(Get-CimInstance -Namespace root/wmi -ClassName MSAcpi_ThermalZoneTemperature).CurrentTemperature"],
            capture_output=True, text=True, timeout=5
        )
        output = result.stdout.strip()
        if output and output.replace("-", "").isdigit():
            temp_k = float(output) / 10.0
            temp_c = temp_k - 273.15
            return round(temp_c, 1), "WMI-ACPI"
    except Exception:
        pass

    # Method 3: Python wmi module
    try:
        import wmi
        w = wmi.WMI(namespace="root\\cimv2")
        sensors = w.Win32_PerfFormattedData_Counters_ThermalZoneInformation()
        if sensors:
            temp_k = float(sensors[0].HighPrecisionTemperature) / 10.0
            temp_c = temp_k - 273.15
            return round(temp_c, 1), "WMI-Py"
    except Exception:
        pass

    # Method 4: OpenHardwareMonitor API (via LibreHardwareMonitor)
    try:
        import json, urllib.request
        req = urllib.request.Request("http://localhost:8085/data.json",
                                     headers={"User-Agent": "Mozilla/5.0"})
        with urllib.request.urlopen(req, timeout=2) as resp:
            data = json.loads(resp.read())
            for child in data.get("Children", []):
                if "CPU" in child.get("Text", "").upper():
                    for hw in child.get("Children", []):
                        if "Temperatures" in hw.get("Text", ""):
                            for sensor in hw.get("Children", []):
                                txt = sensor.get("Text", "")
                                if "Package" in txt or "Core" in txt or "CPU" in txt:
                                    val = sensor.get("Value", "")
                                    if val:
                                        m = val.split()
                                        if m:
                                            return round(float(m[0]), 1), "OHM"
    except Exception:
        pass

    # Method 5: psutil (Linux/Mac)
    try:
        temps = psutil.sensors_temperatures()
        if temps:
            for name, entries in temps.items():
                if entries:
                    return round(entries[0].current, 1), "psutil"
    except Exception:
        pass

    return None, "N/A"


# ============ GUI Application ============
class SystemMonitor:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("系统监控 - CPU & GPU")
        self.root.geometry("560x580")
        self.root.resizable(True, True)
        self.root.configure(bg="#1e1e2e")

        self.style = ttk.Style()
        self.style.theme_use("clam")
        self._setup_styles()

        self.running = True
        self.update_interval = 1000  # ms
        self.log_file = "monitor.log"
        self._setup_logger()

        # Initialize NVML
        self.gpu_available = False
        try:
            pynvml.nvmlInit()
            self.gpu_handle = pynvml.nvmlDeviceGetHandleByIndex(0)
            self.gpu_name = pynvml.nvmlDeviceGetName(self.gpu_handle)
            if isinstance(self.gpu_name, bytes):
                self.gpu_name = self.gpu_name.decode()
            self.gpu_available = True
        except Exception:
            self.gpu_name = "N/A"
            self.gpu_handle = None

        self._build_ui()
        self._update()
        self.root.mainloop()

    def _setup_styles(self):
        self.style.configure("red.Horizontal.TProgressbar", troughcolor="#313244",
                             background="#f38ba8")
        self.style.configure("blue.Horizontal.TProgressbar", troughcolor="#313244",
                             background="#89b4fa")
        self.style.configure("green.Horizontal.TProgressbar", troughcolor="#313244",
                             background="#a6e3a1")
        self.style.configure("yellow.Horizontal.TProgressbar", troughcolor="#313244",
                             background="#f9e2af")

    def _build_ui(self):
        main_frame = tk.Frame(self.root, bg="#1e1e2e")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=15)

        # ── Title ──
        title = tk.Label(main_frame, text="系统实时监控", font=("Microsoft YaHei", 16, "bold"),
                         fg="#cdd6f4", bg="#1e1e2e")
        title.pack(pady=(0, 15))

        # ── CPU Card ──
        cpu_card = tk.LabelFrame(main_frame, text="CPU", font=("Microsoft YaHei", 12, "bold"),
                                 fg="#89b4fa", bg="#181825", padx=10, pady=10)
        cpu_card.pack(fill=tk.X, pady=(0, 10))

        # CPU Temperature
        tk.Label(cpu_card, text="温度", font=("Microsoft YaHei", 10),
                 fg="#cdd6f4", bg="#181825").pack(anchor=tk.W)
        self.cpu_temp_bar = ttk.Progressbar(cpu_card, style="red.Horizontal.TProgressbar",
                                            orient=tk.HORIZONTAL, length=480, mode="determinate")
        self.cpu_temp_bar.pack(fill=tk.X, pady=(2, 2))
        self.cpu_temp_label = tk.Label(cpu_card, text="-- °C", font=("Microsoft YaHei", 10, "bold"),
                                       fg="#f38ba8", bg="#181825")
        self.cpu_temp_label.pack(anchor=tk.E)

        # CPU Usage
        tk.Label(cpu_card, text="占用率", font=("Microsoft YaHei", 10),
                 fg="#cdd6f4", bg="#181825").pack(anchor=tk.W, pady=(8, 0))
        self.cpu_usage_bar = ttk.Progressbar(cpu_card, style="blue.Horizontal.TProgressbar",
                                             orient=tk.HORIZONTAL, length=480, mode="determinate")
        self.cpu_usage_bar.pack(fill=tk.X, pady=(2, 2))
        self.cpu_usage_label = tk.Label(cpu_card, text="-- %", font=("Microsoft YaHei", 10, "bold"),
                                        fg="#89b4fa", bg="#181825")
        self.cpu_usage_label.pack(anchor=tk.E)

        # CPU source label
        self.cpu_source_label = tk.Label(cpu_card, text="", font=("Microsoft YaHei", 8),
                                         fg="#6c7086", bg="#181825")
        self.cpu_source_label.pack(anchor=tk.W, pady=(2, 0))

        # ── GPU Card ──
        gpu_card = tk.LabelFrame(main_frame, text=f"GPU --- {self.gpu_name}", font=("Microsoft YaHei", 12, "bold"),
                                 fg="#a6e3a1", bg="#181825", padx=10, pady=10)
        gpu_card.pack(fill=tk.X, pady=(0, 10))

        # GPU Temperature
        tk.Label(gpu_card, text="温度", font=("Microsoft YaHei", 10),
                 fg="#cdd6f4", bg="#181825").pack(anchor=tk.W)
        self.gpu_temp_bar = ttk.Progressbar(gpu_card, style="red.Horizontal.TProgressbar",
                                            orient=tk.HORIZONTAL, length=480, mode="determinate")
        self.gpu_temp_bar.pack(fill=tk.X, pady=(2, 2))
        self.gpu_temp_label = tk.Label(gpu_card, text="-- °C", font=("Microsoft YaHei", 10, "bold"),
                                       fg="#f38ba8", bg="#181825")
        self.gpu_temp_label.pack(anchor=tk.E)

        # GPU Usage
        tk.Label(gpu_card, text="占用率", font=("Microsoft YaHei", 10),
                 fg="#cdd6f4", bg="#181825").pack(anchor=tk.W, pady=(8, 0))
        self.gpu_usage_bar = ttk.Progressbar(gpu_card, style="green.Horizontal.TProgressbar",
                                             orient=tk.HORIZONTAL, length=480, mode="determinate")
        self.gpu_usage_bar.pack(fill=tk.X, pady=(2, 2))
        self.gpu_usage_label = tk.Label(gpu_card, text="-- %", font=("Microsoft YaHei", 10, "bold"),
                                        fg="#a6e3a1", bg="#181825")
        self.gpu_usage_label.pack(anchor=tk.E)

        # VRAM Usage
        tk.Label(gpu_card, text="显存", font=("Microsoft YaHei", 10),
                 fg="#cdd6f4", bg="#181825").pack(anchor=tk.W, pady=(8, 0))
        self.vram_bar = ttk.Progressbar(gpu_card, style="yellow.Horizontal.TProgressbar",
                                        orient=tk.HORIZONTAL, length=480, mode="determinate")
        self.vram_bar.pack(fill=tk.X, pady=(2, 2))
        self.vram_label = tk.Label(gpu_card, text="-- / -- MB", font=("Microsoft YaHei", 10, "bold"),
                                   fg="#f9e2af", bg="#181825")
        self.vram_label.pack(anchor=tk.E)

        # ── Status bar ──
        status_frame = tk.Frame(main_frame, bg="#181825", height=28)
        status_frame.pack(fill=tk.X, pady=(5, 0))
        self.status_label = tk.Label(status_frame, text="就绪", font=("Microsoft YaHei", 9),
                                     fg="#6c7086", bg="#181825")
        self.status_label.pack(side=tk.LEFT, padx=5)

        self.log_count_label = tk.Label(status_frame, text="", font=("Microsoft YaHei", 9),
                                        fg="#6c7086", bg="#181825")
        self.log_count_label.pack(side=tk.RIGHT, padx=5)

        self.root.protocol("WM_DELETE_WINDOW", self._on_close)

    def _setup_logger(self):
        self.logger = logging.getLogger("SystemMonitor")
        self.logger.setLevel(logging.INFO)
        handler = RotatingFileHandler(
            self.log_file, maxBytes=10 * 1024 * 1024, backupCount=20,
            encoding="utf-8"
        )
        handler.setFormatter(logging.Formatter("%(message)s"))
        self.logger.addHandler(handler)

    def _update(self):
        if not self.running:
            return

        t = threading.Thread(target=self._collect_data, daemon=True)
        t.start()
        self.root.after(self.update_interval, self._update)

    def _collect_data(self):
        data = {}

        # CPU Usage
        data["cpu_usage"] = round(psutil.cpu_percent(interval=0.3), 1)

        # CPU Temperature
        cpu_temp, cpu_method = get_cpu_temperature()
        data["cpu_temp"] = cpu_temp
        data["cpu_method"] = cpu_method

        # GPU data
        if self.gpu_available and self.gpu_handle:
            try:
                data["gpu_temp"] = pynvml.nvmlDeviceGetTemperature(
                    self.gpu_handle, pynvml.NVML_TEMPERATURE_GPU)
                data["gpu_usage"] = pynvml.nvmlDeviceGetUtilizationRates(self.gpu_handle).gpu
                mem = pynvml.nvmlDeviceGetMemoryInfo(self.gpu_handle)
                data["vram_used"] = mem.used // 1024 // 1024
                data["vram_total"] = mem.total // 1024 // 1024
            except Exception:
                data["gpu_temp"] = None
                data["gpu_usage"] = None
                data["vram_used"] = None
                data["vram_total"] = None
        else:
            data["gpu_temp"] = None
            data["gpu_usage"] = None
            data["vram_used"] = None
            data["vram_total"] = None

        # Update UI on main thread
        self.root.after(0, self._update_ui, data)

        # Log
        self._log(data)

    def _update_ui(self, data):
        # CPU Temp
        if data["cpu_temp"] is not None:
            self.cpu_temp_bar["value"] = min(data["cpu_temp"], 100)
            self.cpu_temp_label.config(text=f"{data['cpu_temp']} °C")
        else:
            self.cpu_temp_bar["value"] = 0
            self.cpu_temp_label.config(text="N/A °C")
        self.cpu_source_label.config(text=f"来源: {data['cpu_method']}" if data['cpu_temp'] else "来源: 未检测到")

        # CPU Usage
        self.cpu_usage_bar["value"] = data["cpu_usage"]
        self.cpu_usage_label.config(text=f"{data['cpu_usage']} %")

        # GPU Temp
        if data["gpu_temp"] is not None:
            self.gpu_temp_bar["value"] = min(data["gpu_temp"], 100)
            self.gpu_temp_label.config(text=f"{data['gpu_temp']} °C")
        else:
            self.gpu_temp_bar["value"] = 0
            self.gpu_temp_label.config(text="N/A °C")

        # GPU Usage
        if data["gpu_usage"] is not None:
            self.gpu_usage_bar["value"] = data["gpu_usage"]
            self.gpu_usage_label.config(text=f"{data['gpu_usage']} %")
        else:
            self.gpu_usage_bar["value"] = 0
            self.gpu_usage_label.config(text="N/A %")

        # VRAM
        if data["vram_used"] is not None:
            vram_pct = data["vram_used"] / data["vram_total"] * 100 if data["vram_total"] else 0
            self.vram_bar["value"] = vram_pct
            self.vram_label.config(text=f"{data['vram_used']} / {data['vram_total']} MB ({vram_pct:.1f}%)")
        else:
            self.vram_bar["value"] = 0
            self.vram_label.config(text="N/A")

        # Status
        ts = datetime.now().strftime("%H:%M:%S")
        cpu_part = f"CPU: {data['cpu_temp']}°C {data['cpu_usage']}%" if data['cpu_temp'] else f"CPU: {data['cpu_usage']}%"
        gpu_part = f" | GPU: {data['gpu_temp']}°C {data['gpu_usage']}%" if data['gpu_temp'] else ""
        self.status_label.config(text=f"[{ts}] {cpu_part}{gpu_part}")

    def _log(self, data):
        try:
            ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            cpu_temp = f"{data['cpu_temp']}°C" if data["cpu_temp"] is not None else "N/A"
            gpu_temp = f"{data['gpu_temp']}°C" if data["gpu_temp"] is not None else "N/A"
            gpu_usage = f"{data['gpu_usage']}%" if data["gpu_usage"] is not None else "N/A"
            vram_used = data["vram_used"] if data["vram_used"] is not None else "N/A"
            vram_total = data["vram_total"] if data["vram_total"] is not None else "N/A"

            line = (
                f"时间:{ts}, "
                f"CPU温度:{cpu_temp}, CPU占用:{data['cpu_usage']}%, CPU来源:{data.get('cpu_method', 'N/A')}, "
                f"GPU温度:{gpu_temp}, GPU占用:{gpu_usage}, "
                f"显存已用:{vram_used}MB, 显存总量:{vram_total}MB"
            )
            self.logger.info(line)
            self._update_log_count()
        except Exception:
            pass

    def _update_log_count(self):
        try:
            total = 0
            log_dir = os.path.dirname(self.log_file) or "."
            base = os.path.basename(self.log_file)
            for fname in os.listdir(log_dir):
                if fname == base or fname.startswith(base + "."):
                    fpath = os.path.join(log_dir, fname)
                    with open(fpath, "r", encoding="utf-8") as f:
                        total += sum(1 for _ in f)
            self.log_count_label.config(text=f"已记录: {total} 条")
        except Exception:
            pass

    def _on_close(self):
        self.running = False
        try:
            if self.gpu_available:
                pynvml.nvmlShutdown()
        except Exception:
            pass
        self.root.destroy()


if __name__ == "__main__":
    SystemMonitor()

下面是AI生成的。

Python 打造实时系统监控工具:CPU/GPU 温度、占用率、显存一网打尽

前言

在日常开发或游戏时,我们常常需要关注电脑的 CPU 和显卡状态------温度是不是过高?占用率是不是飙满了?显存还够不够用?

本文介绍一个用 **Python + Tkinter** 编写的轻量级系统监控工具,能**实时显示** CPU 温度、占用率和 GPU 温度、占用率、显存使用情况,并且**自动记录日志**,支持日志轮转。

> 成品预览:深色主题界面,进度条直观展示各项指标,每条数据自动写入日志文件。


一、功能一览

| 监控项 | 说明 |

| ------------ | -------------------------------- |

| CPU 温度 | 实时显示 CPU 封装温度 |

| CPU 占用率 | 整体 CPU 使用百分比 |

| GPU 温度 | NVIDIA GPU 核心温度 |

| GPU 占用率 | GPU 核心利用率 |

| 显存占用 | 已用 / 总量,含百分比 |

| 日志记录 | 每秒自动记录,带完整字段名 |

| 日志轮转 | 单文件最大 10MB,保留 20 个备份 |


二、技术栈

| 模块 | 用途 |

| --------- | ----------------------- |

| `tkinter` | GUI 界面 |

| `psutil` | CPU 占用率采集 |

| `pynvml` | NVIDIA GPU 数据采集 |

| `subprocess` | PowerShell WMI 获取 CPU 温度 |

| `logging` | 日志轮转记录 |


三、CPU 温度采集------多级回退策略

Windows 下获取 CPU 温度并不像 Linux 那样简单(`psutil.sensors_temperatures` 在 Windows 上不可用),本文采用**5 级回退**方案:

```

┌─────────────────────────────────────┐

│ 1. PowerShell WMI CIM 接口 │ ← 首选,无需额外依赖

│ 2. PowerShell WMI ACPI 接口 │

│ 3. Python wmi 模块 │

│ 4. OpenHardwareMonitor HTTP API │

│ 5. psutil sensors_temperatures │ ← Linux/macOS

└─────────────────────────────────────┘

```

核心代码:

python 复制代码
def get_cpu_temperature():
    # 方法1: PowerShell WMI CIM 接口
    try:
        ps_cmd = (
            "(Get-CimInstance -Namespace root/cimv2 "
            "-ClassName Win32_PerfFormattedData_Counters_ThermalZoneInformation)"
            ".HighPrecisionTemperature"
        )
        result = subprocess.run(
            ["powershell", "-Command", ps_cmd],
            capture_output=True, text=True, timeout=5
        )
        output = result.stdout.strip()
        if output and output.isdigit():
            temp_k = int(output) / 10.0        # 单位:开尔文×10
            temp_c = temp_k - 273.15            # 转摄氏度
            return round(temp_c, 1), "WMI-CIM"
    except Exception:
        pass

    # 方法2~5: 依次尝试其他方案 ...
    return None, "N/A"

> WMI 返回的温度单位是**十分之一开尔文**,需除以 10 再减去 273.15 转换为摄氏度。


四、GUI 界面实现

使用 `tkinter.ttk` 的 `Progressbar` 作为进度条,配合 Catppuccin 深色主题配色。

4.1 自定义样式

python 复制代码
def _setup_styles(self):
    self.style.theme_use("clam")  # 使用 clam 主题
    self.style.configure("red.Horizontal.TProgressbar",
                         troughcolor="#313244", background="#f38ba8")
    self.style.configure("blue.Horizontal.TProgressbar",
                         troughcolor="#313244", background="#89b4fa")
    self.style.configure("green.Horizontal.TProgressbar",
                         troughcolor="#313244", background="#a6e3a1")
    self.style.configure("yellow.Horizontal.TProgressbar",
                         troughcolor="#313244", background="#f9e2af")

> 关键点:自定义样式名必须从 `Horizontal.TProgressbar` 派生,否则会报 `Layout not found` 错误。

4.2 多线程数据采集

数据采集在**后台线程**执行,避免阻塞 UI:

python 复制代码
def _update(self):
    if not self.running:
        return
    t = threading.Thread(target=self._collect_data, daemon=True)
    t.start()
    self.root.after(self.update_interval, self._update)

UI 更新通过 `root.after(0, callback)` 回到主线程:

python 复制代码
def _collect_data(self):
    data = {}
    data["cpu_usage"] = round(psutil.cpu_percent(interval=0.3), 1)
    cpu_temp, cpu_method = get_cpu_temperature()
    data["cpu_temp"] = cpu_temp
    data["cpu_method"] = cpu_method
    # ... GPU 数据采集 ...
    self.root.after(0, self._update_ui, data)
    self._log(data)
```

### 4.3 GPU 数据采集(NVML)

```python
import pynvml

pynvml.nvmlInit()
handle = pynvml.nvmlDeviceGetHandleByIndex(0)

# 温度
temp = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
# 利用率
usage = pynvml.nvmlDeviceGetUtilizationRates(handle).gpu
# 显存
mem = pynvml.nvmlDeviceGetMemoryInfo(handle)
used_mb = mem.used // 1024 // 1024
total_mb = mem.total // 1024 // 1024

五、日志系统

使用 Python 标准库 `logging` + `RotatingFileHandler` 实现自动轮转:

python 复制代码
def _setup_logger(self):
    self.logger = logging.getLogger("SystemMonitor")
    self.logger.setLevel(logging.INFO)
    handler = RotatingFileHandler(
        self.log_file,
        maxBytes=10 * 1024 * 1024,   # 10MB
        backupCount=20,               # 保留20个备份
        encoding="utf-8"
    )
    handler.setFormatter(logging.Formatter("%(message)s"))
    self.logger.addHandler(handler)

日志格式示例

```

时间:2026-05-29 20:49:50, CPU温度:27.9°C, CPU占用:30.6%, CPU来源:WMI-CIM, GPU温度:48°C, GPU占用:54%, 显存已用:3611MB, 显存总量:8192MB

时间:2026-05-29 20:49:51, CPU温度:28.1°C, CPU占用:32.1%, CPU来源:WMI-CIM, GPU温度:48°C, GPU占用:52%, 显存已用:3615MB, 显存总量:8192MB

```

每条记录都带**字段名称**,方便直接阅读和后期分析。


六、完整代码

python 复制代码
import tkinter as tk
from tkinter import ttk
import threading
import os
import subprocess
import warnings
import logging
from logging.handlers import RotatingFileHandler
from datetime import datetime

warnings.filterwarnings("ignore", category=FutureWarning)

import psutil
import pynvml

try:
    import ctypes
    ctypes.windll.shcore.SetProcessDpiAwareness(2)
except Exception:
    pass


# ============ CPU Temperature Reader ============
def get_cpu_temperature():
    """Try multiple methods to read CPU temperature. Returns (temp, method_name)."""
    # Method 1: PowerShell WMI (Win32_PerfFormattedData_Counters_ThermalZoneInformation)
    try:
        ps_cmd = (
            "(Get-CimInstance -Namespace root/cimv2 "
            "-ClassName Win32_PerfFormattedData_Counters_ThermalZoneInformation)"
            ".HighPrecisionTemperature"
        )
        result = subprocess.run(
            ["powershell", "-Command", ps_cmd],
            capture_output=True, text=True, timeout=5
        )
        output = result.stdout.strip()
        if output and output.isdigit():
            temp_k = int(output) / 10.0
            temp_c = temp_k - 273.15
            return round(temp_c, 1), "WMI-CIM"
    except Exception:
        pass

    # Method 2: PowerShell WMI (MSAcpi_ThermalZoneTemperature)
    try:
        result = subprocess.run(
            ["powershell", "-Command",
             "(Get-CimInstance -Namespace root/wmi -ClassName MSAcpi_ThermalZoneTemperature).CurrentTemperature"],
            capture_output=True, text=True, timeout=5
        )
        output = result.stdout.strip()
        if output and output.replace("-", "").isdigit():
            temp_k = float(output) / 10.0
            temp_c = temp_k - 273.15
            return round(temp_c, 1), "WMI-ACPI"
    except Exception:
        pass

    # Method 3: Python wmi module
    try:
        import wmi
        w = wmi.WMI(namespace="root\\cimv2")
        sensors = w.Win32_PerfFormattedData_Counters_ThermalZoneInformation()
        if sensors:
            temp_k = float(sensors[0].HighPrecisionTemperature) / 10.0
            temp_c = temp_k - 273.15
            return round(temp_c, 1), "WMI-Py"
    except Exception:
        pass

    # Method 4: OpenHardwareMonitor API (via LibreHardwareMonitor)
    try:
        import json, urllib.request
        req = urllib.request.Request("http://localhost:8085/data.json",
                                     headers={"User-Agent": "Mozilla/5.0"})
        with urllib.request.urlopen(req, timeout=2) as resp:
            data = json.loads(resp.read())
            for child in data.get("Children", []):
                if "CPU" in child.get("Text", "").upper():
                    for hw in child.get("Children", []):
                        if "Temperatures" in hw.get("Text", ""):
                            for sensor in hw.get("Children", []):
                                txt = sensor.get("Text", "")
                                if "Package" in txt or "Core" in txt or "CPU" in txt:
                                    val = sensor.get("Value", "")
                                    if val:
                                        m = val.split()
                                        if m:
                                            return round(float(m[0]), 1), "OHM"
    except Exception:
        pass

    # Method 5: psutil (Linux/Mac)
    try:
        temps = psutil.sensors_temperatures()
        if temps:
            for name, entries in temps.items():
                if entries:
                    return round(entries[0].current, 1), "psutil"
    except Exception:
        pass

    return None, "N/A"


# ============ GUI Application ============
class SystemMonitor:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("系统监控 - CPU & GPU")
        self.root.geometry("560x580")
        self.root.resizable(True, True)
        self.root.configure(bg="#1e1e2e")

        self.style = ttk.Style()
        self.style.theme_use("clam")
        self._setup_styles()

        self.running = True
        self.update_interval = 1000  # ms
        self.log_file = "monitor.log"
        self._setup_logger()

        # Initialize NVML
        self.gpu_available = False
        try:
            pynvml.nvmlInit()
            self.gpu_handle = pynvml.nvmlDeviceGetHandleByIndex(0)
            self.gpu_name = pynvml.nvmlDeviceGetName(self.gpu_handle)
            if isinstance(self.gpu_name, bytes):
                self.gpu_name = self.gpu_name.decode()
            self.gpu_available = True
        except Exception:
            self.gpu_name = "N/A"
            self.gpu_handle = None

        self._build_ui()
        self._update()
        self.root.mainloop()

    def _setup_styles(self):
        self.style.configure("red.Horizontal.TProgressbar", troughcolor="#313244",
                             background="#f38ba8")
        self.style.configure("blue.Horizontal.TProgressbar", troughcolor="#313244",
                             background="#89b4fa")
        self.style.configure("green.Horizontal.TProgressbar", troughcolor="#313244",
                             background="#a6e3a1")
        self.style.configure("yellow.Horizontal.TProgressbar", troughcolor="#313244",
                             background="#f9e2af")

    def _build_ui(self):
        main_frame = tk.Frame(self.root, bg="#1e1e2e")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=15)

        # ── Title ──
        title = tk.Label(main_frame, text="系统实时监控", font=("Microsoft YaHei", 16, "bold"),
                         fg="#cdd6f4", bg="#1e1e2e")
        title.pack(pady=(0, 15))

        # ── CPU Card ──
        cpu_card = tk.LabelFrame(main_frame, text="CPU", font=("Microsoft YaHei", 12, "bold"),
                                 fg="#89b4fa", bg="#181825", padx=10, pady=10)
        cpu_card.pack(fill=tk.X, pady=(0, 10))

        # CPU Temperature
        tk.Label(cpu_card, text="温度", font=("Microsoft YaHei", 10),
                 fg="#cdd6f4", bg="#181825").pack(anchor=tk.W)
        self.cpu_temp_bar = ttk.Progressbar(cpu_card, style="red.Horizontal.TProgressbar",
                                            orient=tk.HORIZONTAL, length=480, mode="determinate")
        self.cpu_temp_bar.pack(fill=tk.X, pady=(2, 2))
        self.cpu_temp_label = tk.Label(cpu_card, text="-- °C", font=("Microsoft YaHei", 10, "bold"),
                                       fg="#f38ba8", bg="#181825")
        self.cpu_temp_label.pack(anchor=tk.E)

        # CPU Usage
        tk.Label(cpu_card, text="占用率", font=("Microsoft YaHei", 10),
                 fg="#cdd6f4", bg="#181825").pack(anchor=tk.W, pady=(8, 0))
        self.cpu_usage_bar = ttk.Progressbar(cpu_card, style="blue.Horizontal.TProgressbar",
                                             orient=tk.HORIZONTAL, length=480, mode="determinate")
        self.cpu_usage_bar.pack(fill=tk.X, pady=(2, 2))
        self.cpu_usage_label = tk.Label(cpu_card, text="-- %", font=("Microsoft YaHei", 10, "bold"),
                                        fg="#89b4fa", bg="#181825")
        self.cpu_usage_label.pack(anchor=tk.E)

        # CPU source label
        self.cpu_source_label = tk.Label(cpu_card, text="", font=("Microsoft YaHei", 8),
                                         fg="#6c7086", bg="#181825")
        self.cpu_source_label.pack(anchor=tk.W, pady=(2, 0))

        # ── GPU Card ──
        gpu_card = tk.LabelFrame(main_frame, text=f"GPU --- {self.gpu_name}", font=("Microsoft YaHei", 12, "bold"),
                                 fg="#a6e3a1", bg="#181825", padx=10, pady=10)
        gpu_card.pack(fill=tk.X, pady=(0, 10))

        # GPU Temperature
        tk.Label(gpu_card, text="温度", font=("Microsoft YaHei", 10),
                 fg="#cdd6f4", bg="#181825").pack(anchor=tk.W)
        self.gpu_temp_bar = ttk.Progressbar(gpu_card, style="red.Horizontal.TProgressbar",
                                            orient=tk.HORIZONTAL, length=480, mode="determinate")
        self.gpu_temp_bar.pack(fill=tk.X, pady=(2, 2))
        self.gpu_temp_label = tk.Label(gpu_card, text="-- °C", font=("Microsoft YaHei", 10, "bold"),
                                       fg="#f38ba8", bg="#181825")
        self.gpu_temp_label.pack(anchor=tk.E)

        # GPU Usage
        tk.Label(gpu_card, text="占用率", font=("Microsoft YaHei", 10),
                 fg="#cdd6f4", bg="#181825").pack(anchor=tk.W, pady=(8, 0))
        self.gpu_usage_bar = ttk.Progressbar(gpu_card, style="green.Horizontal.TProgressbar",
                                             orient=tk.HORIZONTAL, length=480, mode="determinate")
        self.gpu_usage_bar.pack(fill=tk.X, pady=(2, 2))
        self.gpu_usage_label = tk.Label(gpu_card, text="-- %", font=("Microsoft YaHei", 10, "bold"),
                                        fg="#a6e3a1", bg="#181825")
        self.gpu_usage_label.pack(anchor=tk.E)

        # VRAM Usage
        tk.Label(gpu_card, text="显存", font=("Microsoft YaHei", 10),
                 fg="#cdd6f4", bg="#181825").pack(anchor=tk.W, pady=(8, 0))
        self.vram_bar = ttk.Progressbar(gpu_card, style="yellow.Horizontal.TProgressbar",
                                        orient=tk.HORIZONTAL, length=480, mode="determinate")
        self.vram_bar.pack(fill=tk.X, pady=(2, 2))
        self.vram_label = tk.Label(gpu_card, text="-- / -- MB", font=("Microsoft YaHei", 10, "bold"),
                                   fg="#f9e2af", bg="#181825")
        self.vram_label.pack(anchor=tk.E)

        # ── Status bar ──
        status_frame = tk.Frame(main_frame, bg="#181825", height=28)
        status_frame.pack(fill=tk.X, pady=(5, 0))
        self.status_label = tk.Label(status_frame, text="就绪", font=("Microsoft YaHei", 9),
                                     fg="#6c7086", bg="#181825")
        self.status_label.pack(side=tk.LEFT, padx=5)

        self.log_count_label = tk.Label(status_frame, text="", font=("Microsoft YaHei", 9),
                                        fg="#6c7086", bg="#181825")
        self.log_count_label.pack(side=tk.RIGHT, padx=5)

        self.root.protocol("WM_DELETE_WINDOW", self._on_close)

    def _setup_logger(self):
        self.logger = logging.getLogger("SystemMonitor")
        self.logger.setLevel(logging.INFO)
        handler = RotatingFileHandler(
            self.log_file, maxBytes=10 * 1024 * 1024, backupCount=20,
            encoding="utf-8"
        )
        handler.setFormatter(logging.Formatter("%(message)s"))
        self.logger.addHandler(handler)

    def _update(self):
        if not self.running:
            return

        t = threading.Thread(target=self._collect_data, daemon=True)
        t.start()
        self.root.after(self.update_interval, self._update)

    def _collect_data(self):
        data = {}

        # CPU Usage
        data["cpu_usage"] = round(psutil.cpu_percent(interval=0.3), 1)

        # CPU Temperature
        cpu_temp, cpu_method = get_cpu_temperature()
        data["cpu_temp"] = cpu_temp
        data["cpu_method"] = cpu_method

        # GPU data
        if self.gpu_available and self.gpu_handle:
            try:
                data["gpu_temp"] = pynvml.nvmlDeviceGetTemperature(
                    self.gpu_handle, pynvml.NVML_TEMPERATURE_GPU)
                data["gpu_usage"] = pynvml.nvmlDeviceGetUtilizationRates(self.gpu_handle).gpu
                mem = pynvml.nvmlDeviceGetMemoryInfo(self.gpu_handle)
                data["vram_used"] = mem.used // 1024 // 1024
                data["vram_total"] = mem.total // 1024 // 1024
            except Exception:
                data["gpu_temp"] = None
                data["gpu_usage"] = None
                data["vram_used"] = None
                data["vram_total"] = None
        else:
            data["gpu_temp"] = None
            data["gpu_usage"] = None
            data["vram_used"] = None
            data["vram_total"] = None

        # Update UI on main thread
        self.root.after(0, self._update_ui, data)

        # Log
        self._log(data)

    def _update_ui(self, data):
        # CPU Temp
        if data["cpu_temp"] is not None:
            self.cpu_temp_bar["value"] = min(data["cpu_temp"], 100)
            self.cpu_temp_label.config(text=f"{data['cpu_temp']} °C")
        else:
            self.cpu_temp_bar["value"] = 0
            self.cpu_temp_label.config(text="N/A °C")
        self.cpu_source_label.config(text=f"来源: {data['cpu_method']}" if data['cpu_temp'] else "来源: 未检测到")

        # CPU Usage
        self.cpu_usage_bar["value"] = data["cpu_usage"]
        self.cpu_usage_label.config(text=f"{data['cpu_usage']} %")

        # GPU Temp
        if data["gpu_temp"] is not None:
            self.gpu_temp_bar["value"] = min(data["gpu_temp"], 100)
            self.gpu_temp_label.config(text=f"{data['gpu_temp']} °C")
        else:
            self.gpu_temp_bar["value"] = 0
            self.gpu_temp_label.config(text="N/A °C")

        # GPU Usage
        if data["gpu_usage"] is not None:
            self.gpu_usage_bar["value"] = data["gpu_usage"]
            self.gpu_usage_label.config(text=f"{data['gpu_usage']} %")
        else:
            self.gpu_usage_bar["value"] = 0
            self.gpu_usage_label.config(text="N/A %")

        # VRAM
        if data["vram_used"] is not None:
            vram_pct = data["vram_used"] / data["vram_total"] * 100 if data["vram_total"] else 0
            self.vram_bar["value"] = vram_pct
            self.vram_label.config(text=f"{data['vram_used']} / {data['vram_total']} MB ({vram_pct:.1f}%)")
        else:
            self.vram_bar["value"] = 0
            self.vram_label.config(text="N/A")

        # Status
        ts = datetime.now().strftime("%H:%M:%S")
        cpu_part = f"CPU: {data['cpu_temp']}°C {data['cpu_usage']}%" if data['cpu_temp'] else f"CPU: {data['cpu_usage']}%"
        gpu_part = f" | GPU: {data['gpu_temp']}°C {data['gpu_usage']}%" if data['gpu_temp'] else ""
        self.status_label.config(text=f"[{ts}] {cpu_part}{gpu_part}")

    def _log(self, data):
        try:
            ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            cpu_temp = f"{data['cpu_temp']}°C" if data["cpu_temp"] is not None else "N/A"
            gpu_temp = f"{data['gpu_temp']}°C" if data["gpu_temp"] is not None else "N/A"
            gpu_usage = f"{data['gpu_usage']}%" if data["gpu_usage"] is not None else "N/A"
            vram_used = data["vram_used"] if data["vram_used"] is not None else "N/A"
            vram_total = data["vram_total"] if data["vram_total"] is not None else "N/A"

            line = (
                f"时间:{ts}, "
                f"CPU温度:{cpu_temp}, CPU占用:{data['cpu_usage']}%, CPU来源:{data.get('cpu_method', 'N/A')}, "
                f"GPU温度:{gpu_temp}, GPU占用:{gpu_usage}, "
                f"显存已用:{vram_used}MB, 显存总量:{vram_total}MB"
            )
            self.logger.info(line)
            self._update_log_count()
        except Exception:
            pass

    def _update_log_count(self):
        try:
            total = 0
            log_dir = os.path.dirname(self.log_file) or "."
            base = os.path.basename(self.log_file)
            for fname in os.listdir(log_dir):
                if fname == base or fname.startswith(base + "."):
                    fpath = os.path.join(log_dir, fname)
                    with open(fpath, "r", encoding="utf-8") as f:
                        total += sum(1 for _ in f)
            self.log_count_label.config(text=f"已记录: {total} 条")
        except Exception:
            pass

    def _on_close(self):
        self.running = False
        try:
            if self.gpu_available:
                pynvml.nvmlShutdown()
        except Exception:
            pass
        self.root.destroy()


if __name__ == "__main__":
    SystemMonitor()

七、可配置参数

在 `system_monitor.py` 中可直接修改:

| 参数 | 行号 | 默认值 | 说明 |

| --------------- | ---- | -------------- | ----------------- |

| `update_interval` | 121 | `1000` | 刷新间隔 (ms) |

| `log_file` | 122 | `"monitor.log"` | 日志文件名 |

| `maxBytes` | 243 | `10485760` | 单文件上限 10MB |

| `backupCount` | 243 | `20` | 保留备份数 |

| 窗口大小 | 112 | `"560x580"` | 初始尺寸 |


九、常见问题

**Q: CPU 温度显示 N/A?**

可能原因:台式机主板未暴露 WMI 温度接口,可尝试安装 OpenHardwareMonitor(https://openhardwaremonitor.org/) 并启用 Web 服务器。

**Q: GPU 无数据?**

确认 NVIDIA 驱动已安装,命令行执行 `nvidia-smi` 测试。

**Q: 中文乱码?**

确保系统安装了 Microsoft YaHei 等中文字体,或修改代码中的 `font` 参数为系统可用字体。


十、总结

本文实现了一个基于 Python 的实时系统监控工具,核心要点:

  1. **CPU 温度**通过 PowerShell 调用 WMI 接口获取(Windows),多级回退确保兼容性

  2. **GPU 数据**通过 NVIDIA NVML 接口获取

  3. **GUI** 使用 Tkinter + ttk 进度条,多线程避免界面卡顿

  4. **日志**使用 `RotatingFileHandler` 自动轮转,无需手动维护

整个工具单文件不到 400 行,零外部 GUI 依赖,开箱即用。


> **完整项目地址**:https://gitee.com/galenyuan/cpu(可替换为你的仓库链接)

>

> 如果本文对你有帮助,欢迎点赞收藏,有问题评论区交流!

相关推荐
大圣编程4 小时前
Java 多维数组详解
java·开发语言
山东云弈创峰科技6 小时前
山东云弈创峰:基于多模态AI的跨境供应链数字化重构
人工智能·重构
智慧景区与市集主理人6 小时前
巨有科技乡村农文旅智慧建设|适配乡村短板,打造轻量长效数字业态
人工智能·科技
甲维斯6 小时前
马斯克Grok4.5太会了!狙击GPT5.6,贴脸Opus4.8!
人工智能·ai编程
殳翰6 小时前
下服务器端开发流程及相关工具介绍(C++)
开发语言·c++
雪碧聊技术6 小时前
Badge 应用场景与落地实践指南
大数据·人工智能
ai产品老杨7 小时前
NVIDIA GPU部署AI视频分析项目实战记录
人工智能·音视频
落寞的星星7 小时前
这个对象就包含了已经转换好的DFA和各种词法分析器运转所需要的参数。下一步,我们就可以用ScannerInfo对象创建出Scanner对象,请看下面的代码:
开发语言·c#
nothing&nowhere7 小时前
用 Python 做问卷数据清洗:无效样本检测与处理实战
开发语言·python·数据清洗·数据处理·问卷星·问卷星脚本·刷问卷
花酒锄作田7 小时前
如何发布自己的 Python 库到 PyPI
python