How to Choose the Right CPU Monitor: Features to Look For

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

  1. Data acquisition — use psutil to sample CPU usage (overall and per-core) and, where available, read temperatures.
  2. Visualization — real-time plotting of usage and temperatures using matplotlib embedded in a Tkinter GUI.
  3. Logging — write timestamped samples to CSV.
  4. Alerts — simple threshold-based alerts (sound or popup).
  5. 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.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *