最近玩游戏多开总是黑屏,怀疑是温度过高。
所以让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 的实时系统监控工具,核心要点:
-
**CPU 温度**通过 PowerShell 调用 WMI 接口获取(Windows),多级回退确保兼容性
-
**GPU 数据**通过 NVIDIA NVML 接口获取
-
**GUI** 使用 Tkinter + ttk 进度条,多线程避免界面卡顿
-
**日志**使用 `RotatingFileHandler` 自动轮转,无需手动维护
整个工具单文件不到 400 行,零外部 GUI 依赖,开箱即用。
> **完整项目地址**:https://gitee.com/galenyuan/cpu(可替换为你的仓库链接)
>
> 如果本文对你有帮助,欢迎点赞收藏,有问题评论区交流!