Monitoring process lists tells you what is happening right now, but it doesn't give you the full picture. To properly diagnose system performance spikes or track memory leaks over time, IT admins need a visual timeline showing resource historical usage trends.
Instead of introducing complex, heavy visualization packages like matplotlib, we can build a lightweight, native scrolling performance graph using Python's built-in tkinter canvas component. This keeps our file small, zero-dependency, and highly responsive.
Here is the complete HTML framework and Python script to add a real-time historical graph tracker to your helpdesk blog.
🐍 The Python Tkinter Script with Historical Graphing
This upgraded version maintains a rolling history of your last 50 CPU utilization measurements and plots them visually on an auto-refreshing tracking canvas grid. Save this script layout file as graph_task_manager.py:
import tkinter as tk
from tkinter import ttk, messagebox
import psutil
class GraphTaskManagerGUI:
def __init__(self, root):
self.root = root
self.root.title("Ayouli IT Tech: Advanced Performance Tracker")
self.root.geometry("700x550")
self.root.minsize(600, 450)
# Storage array for rolling historical CPU measurements
self.cpu_history = [0] * 50
# Top Control Frame for global usage readouts
self.stats_frame = ttk.LabelFrame(root, text=" Live Hardware Utilization ", padding=10)
self.stats_frame.pack(fill="x", padx=10, pady=5)
self.cpu_label = ttk.Label(self.stats_frame, text="CPU Usage: 0%", font=("Arial", 10, "bold"))
self.cpu_label.pack(side="left", padx=20)
self.ram_label = ttk.Label(self.stats_frame, text="RAM Usage: 0%", font=("Arial", 10, "bold"))
self.ram_label.pack(side="left", padx=20)
# Real-time Historical Graph Canvas Object
self.graph_frame = ttk.LabelFrame(root, text=" CPU Usage History (Rolling Timeline) ", padding=5)
self.graph_frame.pack(fill="x", padx=10, pady=5)
# Drawing board layout matrix
self.canvas = tk.Canvas(self.graph_frame, height=100, bg="#1e1e1e", highlightthickness=0)
self.canvas.pack(fill="x", padx=5, pady=5)
# Central Process List Treeview Grid Layout
self.list_frame = ttk.Frame(root, padding=10)
self.list_frame.pack(fill="both", expand=True)
columns = ("pid", "name", "cpu", "ram")
self.tree = ttk.Treeview(self.list_frame, columns=columns, show="headings", selectmode="browse")
self.tree.heading("pid", text="PID")
self.tree.heading("name", text="Process Name")
self.tree.heading("cpu", text="CPU %")
self.tree.heading("ram", text="RAM %")
self.tree.column("pid", width=70, anchor="center")
self.tree.column("name", width=250, anchor="w")
self.tree.column("cpu", width=80, anchor="center")
self.tree.column("ram", width=80, anchor="center")
scrollbar = ttk.Scrollbar(self.list_frame, orient="vertical", command=self.tree.yview)
self.tree.configure(yscrollcommand=scrollbar.set)
self.tree.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# Bottom Functional Action Row
self.action_frame = ttk.Frame(root, padding=10)
self.action_frame.pack(fill="x")
self.kill_button = ttk.Button(self.action_frame, text="End Task Process", command=self.kill_process)
self.kill_button.pack(side="right", padx=10)
# Kickoff structural application loops
self.update_metrics()
def draw_historical_graph(self):
"""Clears the drawing canvas and renders the line path graph."""
self.canvas.delete("all")
canvas_width = self.canvas.winfo_width()
if canvas_width < 10:
canvas_width = 650 # Fallback default width constraint values
points_count = len(self.cpu_history)
x_spacing = canvas_width / (points_count - 1)
# Build graphical node coordinate array mapping layouts
coordinates = []
for index, val in enumerate(self.cpu_history):
x = index * x_spacing
# Invert coordinates because Canvas 0 point is the top-left edge
y = 100 - val
coordinates.append((x, y))
# Draw smooth interconnecting path segments
for i in range(len(coordinates) - 1):
x1, y1 = coordinates[i]
x2, y2 = coordinates[i+1]
self.canvas.create_line(x1, y1, x2, y2, fill="#0078d4", width=2, smooth=True)
def update_metrics(self):
"""Fetches system process data and pushes tracking updates."""
cpu_percent = psutil.cpu_percent()
ram_percent = psutil.virtual_memory().percent
self.cpu_label.config(text=f"CPU Usage: {cpu_percent}%")
self.ram_label.config(text=f"RAM Usage: {ram_percent}%")
# Shift rolling layout variables over to log history data
self.cpu_history.pop(0)
self.cpu_history.append(cpu_percent)
# Redraw fresh timeline line chart paths
self.draw_historical_graph()
# Update process list view matching logic
selected_item = self.tree.selection()
selected_pid = None
if selected_item:
selected_pid = self.tree.item(selected_item)['values']
for item in self.tree.get_children():
self.tree.delete(item)
processes = []
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']):
try:
processes.append(proc.info)
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
sorted_processes = sorted(processes, key=lambda x: x['memory_percent'] or 0, reverse=True)
for p in sorted_processes[:20]:
item_id = self.tree.insert("", "end", values=(
p['pid'],
p['name'],
f"{p['cpu_percent']:.1f}",
f"{p['memory_percent']:.1f}"
))
if selected_pid and p['pid'] == selected_pid:
self.tree.selection_set(item_id)
# Loop script refresh execution sequence every 1 second (1000 milliseconds)
self.root.after(1000, self.update_metrics)
def kill_process(self):
"""Identifies target rows and initializes program execution drops."""
selected_item = self.tree.selection()
if not selected_item:
messagebox.showwarning("Selection Missing", "Please pick a live process trace row.")
return
pid, name, _, _ = self.tree.item(selected_item)['values']
confirm = messagebox.askyesno("Confirm", f"Kill process {name} (PID: {pid})?")
if confirm:
try:
psutil.Process(pid).terminate()
messagebox.showinfo("Success", f"Process {name} terminated.")
except psutil.AccessDenied:
messagebox.showerror("Access Error", "Admin context required to kill this program loop.")
except psutil.NoSuchProcess:
messagebox.showerror("Missing Process", "This tracking thread already changed execution paths.")
if __name__ == "__main__":
app_root = tk.Tk()
manager = GraphTaskManagerGUI(app_root)
app_root.mainloop()
🎯 Canvas Engineering Details for Tech Admins
- The Origin Trap: In graphical UI rendering engines, coordinate
(0,0)sits at the **top-left corner**. To prevent your graph from drawing upside down, our code explicitly maps performance using100 - val, flipping the line tracking properly. - Responsive Resizing: The
self.canvas.winfo_width()call dynamic calculates your exact application frame footprint. If you stretch the user interface window, the tracking line segments expand horizontally automatically.
No comments:
Post a Comment