Build Your Own CPU Monitor with Python (Step-by-Step)Monitoring CPU usage and temperature can help you troubleshoot performance issues, optimize resource usage, and learn more about how your system works. In this step-by-step guide you’ll build a simple but functional CPU monitor using Python. The project includes real-time data collection, a graphical display, logging, and optional alerts. It works on Windows, macOS, and Linux with minor platform-specific notes.
What you’ll learn
- How to read CPU usage, per-core stats, and temperatures from the system
- How to display real-time graphs using a GUI toolkit
- How to log data to a file and implement basic alerts
- How to package the monitor so it’s easy to run
Requirements
- Python 3.8+
- pip (Python package installer)
- Basic knowledge of Python (functions, threading, classes)
Required Python packages:
psutil matplotlib tkinter (usually included with Python) pynvml (optional, for NVIDIA GPU temps)
Install with pip:
pip install psutil matplotlib pynvml
On some systems, tkinter may need to be installed via the OS package manager (e.g., apt, brew, or winget).
Project overview
- Data acquisition — use psutil to sample CPU usage (overall and per-core) and, where available, read temperatures.
- Visualization — real-time plotting of usage and temperatures using matplotlib embedded in a Tkinter GUI.
- Logging — write timestamped samples to CSV.
- Alerts — simple threshold-based alerts (sound or popup).
- Packaging — create an executable with PyInstaller (optional).
Step 1 — Project structure
Create a directory:
cpu_monitor/ ├─ main.py ├─ monitor.py ├─ gui.py ├─ logger.py ├─ requirements.txt └─ assets/ └─ alert.wav
- main.py — app entry point
- monitor.py — data sampling and system abstraction
- gui.py — Tkinter + matplotlib UI
- logger.py — CSV logging and rotation
Step 2 — Data acquisition (monitor.py)
Use psutil for CPU percentages and temperatures. Example core functions:
# monitor.py import psutil import time from typing import Dict, List, Optional def sample_cpu(interval: float = 0.5) -> Dict: """ Return a snapshot including overall percent, per-core percents, and timestamps. """ per_core = psutil.cpu_percent(percpu=True, interval=interval) overall = psutil.cpu_percent(percpu=False, interval=None) timestamp = time.time() return {"timestamp": timestamp, "overall": overall, "per_core": per_core} def get_temperatures() -> Optional[Dict[str, List[float]]]: """ Returns sensor temperatures if available (platform dependent). """ try: temps = psutil.sensors_temperatures() return temps except Exception: return None
Notes:
- psutil.cpu_percent with interval blocks for that interval. Use a separate sampling thread to avoid blocking the UI.
- psutil.sensors_temperatures() availability varies by OS and permissions.
Step 3 — Logging (logger.py)
Write CSV rows with timestamp, overall, and per-core usage.
# logger.py import csv import os from datetime import datetime from typing import Dict class CSVLogger: def __init__(self, path="cpu_usage.csv"): self.path = path self._ensure_header() def _ensure_header(self): if not os.path.exists(self.path): with open(self.path, "w", newline="") as f: writer = csv.writer(f) writer.writerow(["timestamp", "iso", "overall", "per_core"]) def log(self, sample: Dict): with open(self.path, "a", newline="") as f: writer = csv.writer(f) iso = datetime.fromtimestamp(sample["timestamp"]).isoformat() writer.writerow([sample["timestamp"], iso, sample["overall"], sample["per_core"]])
Consider rotating logs daily or when they exceed a size limit.
Step 4 — GUI and real-time plotting (gui.py)
Embed matplotlib in Tkinter to show a live updating chart. Use a background thread or after() loop to poll samples.
# gui.py import threading import tkinter as tk from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib.pyplot as plt from monitor import sample_cpu, get_temperatures from logger import CSVLogger import collections import time class CPUMonitorApp: def __init__(self, root, max_points=120): self.root = root self.max_points = max_points self.samples = collections.deque(maxlen=max_points) self.timestamps = collections.deque(maxlen=max_points) self.logger = CSVLogger() self.running = True # Matplotlib figure self.fig, self.ax = plt.subplots() self.line, = self.ax.plot([], [], label="Overall CPU %") canvas = FigureCanvasTkAgg(self.fig, master=root) canvas.get_tk_widget().pack(fill=tk.BOTH, expand=1) self.canvas = canvas # Control buttons btn_frame = tk.Frame(root) btn_frame.pack(fill=tk.X) tk.Button(btn_frame, text="Start", command=self.start).pack(side=tk.LEFT) tk.Button(btn_frame, text="Stop", command=self.stop).pack(side=tk.LEFT) def start(self): if not self.running: self.running = True threading.Thread(target=self._sampling_loop, daemon=True).start() def stop(self): self.running = False def _sampling_loop(self): while self.running: s = sample_cpu(interval=0.5) self.logger.log(s) self.timestamps.append(s["timestamp"]) self.samples.append(s["overall"]) self._update_plot() time.sleep(0.1) def _update_plot(self): xs = list(range(-len(self.samples)+1, 1)) ys = list(self.samples) self.line.set_data(xs, ys) self.ax.relim() self.ax.autoscale_view() self.canvas.draw_idle()
Notes:
- Keep heavy work off the main thread. Use thread-safe queues if you prefer.
- For per-core plotting, add more lines and a legend; consider color selection and performance impacts.
Step 5 — Alerts and thresholds
Implement threshold checks in the sampling loop. Example: if overall > 90% or a core > 95%, trigger an alert.
# simple alert snippet import winsound # Windows-only; use playsound or tkinter bell for cross-platform def check_alerts(sample, threshold=90): if sample["overall"] > threshold or any(p > 95 for p in sample["per_core"]): try: winsound.MessageBeep() except Exception: print("ALERT: High CPU usage")
For cross-platform beeps, use tkinter.Tk().bell() or the playsound package to play an alert file from assets/.
Step 6 — Temperature & GPU (optional)
- For CPU temp: psutil.sensors_temperatures() or platform-specific tools.
- For NVIDIA GPU temps: use pynvml.
Example NVIDIA:
from pynvml import nvmlInit, nvmlDeviceGetCount, nvmlDeviceGetHandleByIndex, nvmlDeviceGetTemperature, NVML_TEMPERATURE_GPU nvmlInit() count = nvmlDeviceGetCount() temps = [] for i in range(count): handle = nvmlDeviceGetHandleByIndex(i) temps.append(nvmlDeviceGetTemperature(handle, NVML_TEMPERATURE_GPU))
Step 7 — Packaging (optional)
Use PyInstaller to create a single executable:
pip install pyinstaller pyinstaller --onefile main.py
Include assets and data files via –add-data.
Example main.py
# main.py import tkinter as tk from gui import CPUMonitorApp def main(): root = tk.Tk() root.title("CPU Monitor") app = CPUMonitorApp(root) # start sampling immediately app.start() root.mainloop() if __name__ == "__main__": main()
Tips & improvements
- Use a ring buffer and deque for efficient memory.
- Add process-level monitoring (psutil.process_iter()) to show top consumers.
- Add historical charts and export options.
- Use async frameworks (asyncio) if integrating with network features.
- Add system tray support (pystray) for background running.
This guide gives a practical, extendable CPU monitor you can build and customize. Implementing features step-by-step helps you learn system APIs, GUI embedding, and safe threading patterns.
Leave a Reply