用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(可替换为你的仓库链接)

>

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

相关推荐
wangqiaowq1 小时前
FFmpeg的下载使用
人工智能
2601_959480151 小时前
Moneta Markets亿汇:“芯片巨头开拓处理器市场”
人工智能
Sharewinfo_BJ1 小时前
Power BI 5月重磅更新:8大新功能全面提升数据分析效率
大数据·人工智能·数据分析
weixin_459778721 小时前
当 AI 开始理解企业:金融复杂系统下的智能体实践
人工智能·ai·金融·ai编程·ai-native
知识浅谈1 小时前
人工智能日报 每日AI新闻(2026年5月29日):Claude Opus 4.8、AI 工作流收购与内容版权升温
人工智能
weixin_468466851 小时前
机器学习与深度学习新手区分指南
人工智能·python·深度学习·机器学习·计算机视觉·ai·机器视觉
AI算法沐枫1 小时前
基于YOLO26深度学习的【果园荔枝检测与计数】系统设计与实现【python源码+Pyqt5界面+数据集+训练代码】
开发语言·人工智能·python·深度学习·qt·学习·机器学习
AI服务老曹1 小时前
统一安防底座:基于 GB28181 与 RTSP 的边缘计算 AI 视频管理平台架构演进(附 Docker 部署与源码交付机制)
人工智能·音视频·边缘计算
逻辑君1 小时前
Foresight研究报告【20260010】
人工智能·算法·机器学习