diff --git a/cpu-monitor_1.0_all.deb b/cpu-monitor_1.0_all.deb new file mode 100644 index 0000000..89307d7 Binary files /dev/null and b/cpu-monitor_1.0_all.deb differ diff --git a/cpu_monitor.py b/cpu_monitor.py index 5b5180f..78290ef 100644 --- a/cpu_monitor.py +++ b/cpu_monitor.py @@ -13,14 +13,33 @@ def get_cpu_ram() -> tuple[float, float]: return cpu, ram +def get_disk_usage() -> float: + return psutil.disk_usage('/').percent + + +def get_net_io() -> tuple[int, int]: + counters = psutil.net_io_counters() + return counters.bytes_sent, counters.bytes_recv + + +def get_disk_io() -> tuple[int, int]: + counters = psutil.disk_io_counters() + if counters is None: + return 0, 0 + return counters.read_bytes, counters.write_bytes + + def format_line() -> str: now = datetime.now().strftime("%H:%M:%S") cpu, ram = get_cpu_ram() - return f"{now} | CPU: {cpu:5.1f}% | RAM: {ram:5.1f}%" + disk = get_disk_usage() + d_read, d_write = get_disk_io() + sent, recv = get_net_io() + return f"{now} | CPU: {cpu:5.1f}% | RAM: {ram:5.1f}% | Disk: {disk:5.1f}% | ↑{d_read:,} ↓{d_write:,} B | Net: ↑{sent:,} ↓{recv:,} B" def main() -> None: - print("Monitoring CPU/RAM. Ctrl+C pour arrêter.") + print("Monitoring CPU/RAM/Disk. Ctrl+C pour arrêter.") try: while True: print(format_line()) diff --git a/cpu_monitor_gui.desktop b/cpu_monitor_gui.desktop new file mode 100644 index 0000000..edec41a --- /dev/null +++ b/cpu_monitor_gui.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Exec=/usr/bin/cpu-monitor +Hidden=false +NoDisplay=false +X-GNOME-Autostart-enabled=true +Name=CPU Monitor GUI +Comment=CPU/RAM/Disk/Network monitor +Icon=utilities-system-monitor diff --git a/cpu_monitor_gui.py b/cpu_monitor_gui.py old mode 100644 new mode 100755 index 3b74824..3953d86 --- a/cpu_monitor_gui.py +++ b/cpu_monitor_gui.py @@ -14,37 +14,89 @@ MAX_POINTS = 60 UPDATE_INTERVAL = 1000 # ms -class CPUTracker: +class Tracker: def __init__(self): self.cpu_data = deque(maxlen=MAX_POINTS) self.ram_data = deque(maxlen=MAX_POINTS) - self.time_data = deque(maxlen=MAX_POINTS) + self.disk_data = deque(maxlen=MAX_POINTS) + self.disk_read_data = deque(maxlen=MAX_POINTS) + self.disk_write_data = deque(maxlen=MAX_POINTS) + self.net_sent_data = deque(maxlen=MAX_POINTS) + self.net_recv_data = deque(maxlen=MAX_POINTS) self._last_cpu = None + self._last_net_sent = None + self._last_net_recv = None + self._last_disk_read = None + self._last_disk_write = None now = time.time() + self.time_data = deque(maxlen=MAX_POINTS) self.time_data.append(now) + def _net_rate(self, curr, last): + delta = curr - last + rate = max(delta / 2.0, 0) + return max(int(rate), 0) + def update(self): cpu = psutil.cpu_percent(interval=None) ram = psutil.virtual_memory().percent + disk = psutil.disk_usage('/').percent self._last_cpu = cpu t = time.time() + self.time_data.append(t) self.cpu_data.append(cpu) self.ram_data.append(ram) - self.time_data.append(t) + self.disk_data.append(disk) + + d_io = psutil.disk_io_counters() + if d_io is not None: + d_read = d_io.read_bytes + d_write = d_io.write_bytes + if self._last_disk_read is not None: + self.disk_read_data.append(max(int((d_read - self._last_disk_read) / 2.0), 0)) + self.disk_write_data.append(max(int((d_write - self._last_disk_write) / 2.0), 0)) + self._last_disk_read = d_read + self._last_disk_write = d_write + + net = psutil.net_io_counters() + sent = net.bytes_sent + recv = net.bytes_recv + if self._last_net_sent is not None: + send_rate = self._net_rate(sent, self._last_net_sent) + recv_rate = self._net_rate(recv, self._last_net_recv) + self.net_sent_data.append(send_rate) + self.net_recv_data.append(recv_rate) + self._last_net_sent = sent + self._last_net_recv = recv def get_latest(self): if self._last_cpu is None: self.update() return self._last_cpu, self.ram_data[-1] + def get_disk(self): + if not self.disk_data: + return 0 + return self.disk_data[-1] + + def get_disk_rates(self): + if not self.disk_read_data or not self.disk_write_data: + return 0, 0 + return self.disk_read_data[-1], self.disk_write_data[-1] + + def get_net_rates(self): + if not self.net_sent_data or not self.net_recv_data: + return 0, 0 + return self.net_sent_data[-1], self.net_recv_data[-1] + class App: def __init__(self, root): self.root = root self.root.title("CPU / RAM Monitor") - self.tracker = CPUTracker() + self.tracker = Tracker() # --- Header card --- card_bg = "#23272f" @@ -70,25 +122,56 @@ class App: ) self.ram_label.pack(side=tk.LEFT, padx=20) + self.net_label = ttk.Label( + header_frame, + text=f"Net: ↓ --", + font=("Segoe UI", 20, "bold"), + foreground="#ffb74d", + ) + self.net_label.pack(side=tk.LEFT, padx=20) + + self.disk_label = ttk.Label( + header_frame, + text=f"Disk: --%", + font=("Segoe UI", 20, "bold"), + foreground="#ba68c8", + ) + self.disk_label.pack(side=tk.LEFT, padx=20) + # --- Plot --- - fig = Figure(figsize=(8, 3), facecolor=card_bg) - ax_cpu = fig.add_subplot(1, 2, 1, facecolor=card_bg) - ax_ram = fig.add_subplot(1, 2, 2, facecolor=card_bg) + fig = Figure(figsize=(14, 3), facecolor=card_bg) + ax_cpu = fig.add_subplot(1, 4, 1, facecolor=card_bg) + ax_ram = fig.add_subplot(1, 4, 2, facecolor=card_bg) + ax_disk = fig.add_subplot(1, 4, 3, facecolor=card_bg) + ax_net = fig.add_subplot(1, 4, 4, facecolor=card_bg) ax_cpu.set_title("CPU %", color=card_fg, fontsize=11) ax_ram.set_title("RAM %", color=card_fg, fontsize=11) + ax_disk.set_title("Disk %", color=card_fg, fontsize=11) + ax_net.set_title("Réseau (B/s)", color=card_fg, fontsize=11) ax_cpu.set_ylim(0, 100) ax_ram.set_ylim(0, 100) + ax_disk.set_ylim(0, 100) + ax_net.set_ylim(0, 1) ax_cpu.set_ylabel("%") ax_ram.set_ylabel("%") + ax_disk.set_ylabel("%") + ax_net.set_ylabel("B/s") ax_cpu.tick_params(colors="#aaa") ax_ram.tick_params(colors="#aaa") + ax_net.tick_params(colors="#aaa") for label in ax_cpu.get_yticklabels(): label.set_color("#aaa") + for label in ax_ram.get_yticklabels(): + label.set_color("#aaa") + for label in ax_net.get_yticklabels(): + label.set_color("#aaa") for line in ax_cpu.spines.values(): line.set_color("#3a3f4a") for line in ax_ram.spines.values(): line.set_color("#3a3f4a") + for line in ax_net.spines.values(): + line.set_color("#3a3f4a") self.cpu_line, = ax_cpu.plot( [], [], color="#ef5350", linewidth=1.5, marker="", markerfacecolor="#ef5350" @@ -96,14 +179,26 @@ class App: self.ram_line, = ax_ram.plot( [], [], color="#66bb6a", linewidth=1.5, marker="", markerfacecolor="#66bb6a" ) + self.disk_line, = ax_disk.plot( + [], [], color="#ba68c8", linewidth=1.5, marker="", markerfacecolor="#ba68c8" + ) + self.net_sent_line, = ax_net.plot( + [], [], color="#ffb74d", linewidth=1.5, marker="", markerfacecolor="#ffb74d" + ) + self.net_recv_line, = ax_net.plot( + [], [], color="#90caf9", linewidth=1.5, marker="", markerfacecolor="#90caf9" + ) ax_cpu.axhline(y=80, color="#ef5350", linestyle="--", alpha=0.4) ax_ram.axhline(y=80, color="#66bb6a", linestyle="--", alpha=0.4) + ax_disk.axhline(y=80, color="#ba68c8", linestyle="--", alpha=0.4) ax_cpu.text(0.02, 0.02, "80%", fontsize=8, color="#ef5350", alpha=0.6, transform=ax_cpu.transAxes) self.fig = fig self.ax_cpu = ax_cpu self.ax_ram = ax_ram + self.ax_disk = ax_disk + self.ax_net = ax_net canvas = FigureCanvasTkAgg(fig, master=root) canvas.draw() @@ -140,17 +235,35 @@ class App: return self.tracker.update() cpu, ram = self.tracker.get_latest() + disk = self.tracker.get_disk() + send_rate, recv_rate = self.tracker.get_net_rates() self.cpu_label.config(text=f"CPU: {cpu:5.1f}%") self.ram_label.config(text=f"RAM: {ram:5.1f}%") + self.disk_label.config(text=f"Disk: {disk:5.1f}%") + + mb_sent = send_rate / (1024 * 1024) + mb_recv = recv_rate / (1024 * 1024) + self.net_label.config(text=f"Net: ↑{mb_sent:.2f} ↓{mb_recv:.2f} MB/s") - ax = self.ax_cpu cpu_arr = list(self.tracker.cpu_data) ram_arr = list(self.tracker.ram_data) + disk_arr = list(self.tracker.disk_data) + sent_arr = list(self.tracker.net_sent_data) + recv_arr = list(self.tracker.net_recv_data) self.cpu_line.set_data(range(len(cpu_arr)), cpu_arr) self.ram_line.set_data(range(len(ram_arr)), ram_arr) + self.disk_line.set_data(range(len(disk_arr)), disk_arr) + self.net_sent_line.set_data(range(len(sent_arr)), sent_arr) + self.net_recv_line.set_data(range(len(recv_arr)), recv_arr) - ax.set_xlim(0, MAX_POINTS) + self.ax_cpu.set_xlim(0, MAX_POINTS) self.ax_ram.set_xlim(0, MAX_POINTS) + self.ax_disk.set_xlim(0, MAX_POINTS) + self.ax_net.set_xlim(0, MAX_POINTS) + + max_net = max(max(sent_arr) if sent_arr else 0, max(recv_arr) if recv_arr else 0) + if max_net > 0: + self.ax_net.set_ylim(0, max_net * 1.2) self.fig.canvas.draw_idle() self.root.after(UPDATE_INTERVAL, self._refresh) diff --git a/cpu_monitor_gui.sh b/cpu_monitor_gui.sh new file mode 100755 index 0000000..4a57bb9 --- /dev/null +++ b/cpu_monitor_gui.sh @@ -0,0 +1,4 @@ +#!/bin/bash +export DISPLAY=:1 +export GDK_BACKEND=wayland,x11 +/home/tmzf5455/projects/continue-lab/.venv/bin/python /home/tmzf5455/projects/continue-lab/cpu_monitor_gui.py diff --git a/cpu_monitor_native.py b/cpu_monitor_native.py new file mode 100644 index 0000000..744ccad --- /dev/null +++ b/cpu_monitor_native.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python3 +# Lancement: python cpu_monitor_native.py +# Intégrable dans GNOME via /etc/xdg/autostart/resource-monitor.desktop + +from collections import deque +import time + +from gi.repository import Gtk, GLib +import psutil +from matplotlib.figure import Figure +from matplotlib.backends.backend_gtk4agg import FigureCanvasGTK4Agg as FigureCanvas + +MAX_POINTS = 60 +UPDATE_INTERVAL = 1000 # ms + + +class Tracker: + def __init__(self): + self.cpu_data = deque(maxlen=MAX_POINTS) + self.ram_data = deque(maxlen=MAX_POINTS) + self.disk_data = deque(maxlen=MAX_POINTS) + self.disk_read_data = deque(maxlen=MAX_POINTS) + self.disk_write_data = deque(maxlen=MAX_POINTS) + self.net_sent_data = deque(maxlen=MAX_POINTS) + self.net_recv_data = deque(maxlen=MAX_POINTS) + self._last_cpu = None + self._last_net_sent = None + self._last_net_recv = None + self._last_disk_read = None + self._last_disk_write = None + self.time_data = deque(maxlen=MAX_POINTS) + self.time_data.append(time.time()) + + def _rate(self, curr, last): + delta = curr - last + return max(int(delta / 2.0), 0) + + def update(self): + cpu = psutil.cpu_percent(interval=None) + ram = psutil.virtual_memory().percent + disk = psutil.disk_usage('/').percent + self._last_cpu = cpu + t = time.time() + self.time_data.append(t) + + self.cpu_data.append(cpu) + self.ram_data.append(ram) + self.disk_data.append(disk) + + d_io = psutil.disk_io_counters() + if d_io is not None: + d_read = d_io.read_bytes + d_write = d_io.write_bytes + if self._last_disk_read is not None: + self.disk_read_data.append(self._rate(d_read, self._last_disk_read)) + self.disk_write_data.append(self._rate(d_write, self._last_disk_write)) + self._last_disk_read = d_read + self._last_disk_write = d_write + + net = psutil.net_io_counters() + sent = net.bytes_sent + recv = net.bytes_recv + if self._last_net_sent is not None: + self.net_sent_data.append(self._rate(sent, self._last_net_sent)) + self.net_recv_data.append(self._rate(recv, self._last_net_recv)) + self._last_net_sent = sent + self._last_net_recv = recv + + def get_latest(self): + if self._last_cpu is None: + self.update() + return self._last_cpu, self.ram_data[-1] + + def get_disk(self): + if not self.disk_data: + return 0 + return self.disk_data[-1] + + def get_net_rates(self): + if not self.net_sent_data or not self.net_recv_data: + return 0, 0 + return self.net_sent_data[-1], self.net_recv_data[-1] + + +class MonitorWindow(Gtk.ApplicationWindow): + def __init__(self, app): + super().__init__(application=app, title="Resource Monitor") + self.set_default_size(1100, 360) + self.set_icon_name("org.gnome.SystemMonitor") + + self.tracker = Tracker() + self._running = True + + # Dark palette + self.DARK_BG = "#1e1e2e" + self.DARK_SURFACE = "#313244" + self.DARK_BORDER = "#45475a" + self.TEXT = "#cdd6f4" + self.TEXT_DIM = "#a6adc8" + self.CPU_COLOR = "#f38ba8" + self.RAM_COLOR = "#a6e3a1" + self.DISK_COLOR = "#cba6f7" + self.NET_UP_COLOR = "#fab387" + self.NET_DOWN_COLOR = "#89b4fa" + self.ALPHA_40 = 0.4 + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + self.add(box) + + # --- Top bar with values --- + header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + header.set_css_classes(["sourcing-frame"]) + header.get_style_context().add_class("card") + header.add_to_css_styleclass("monitor-header") + + self.cpu_label = Gtk.Label() + self.cpu_label.set_css_classes(["stat-label"]) + header.append(self.cpu_label) + + self.ram_label = Gtk.Label() + self.ram_label.set_css_classes(["stat-label"]) + header.append(self.ram_label) + + self.disk_label = Gtk.Label() + self.disk_label.set_css_classes(["stat-label"]) + header.append(self.disk_label) + + self.net_label = Gtk.Label() + self.net_label.set_css_classes(["stat-label"]) + header.append(self.net_label) + + box.append(header) + + # --- Plots --- + fig = Figure(figsize=(13, 3.2), facecolor=self.DARK_BG) + fig.set_tight_layout({"pad": 8}) + + axes = [] + titles = ["CPU %", "RAM %", "Disk %", "Réseau (B/s)"] + for i, title in enumerate(titles): + ax = fig.add_subplot(1, 4, i + 1) + ax.set_facecolor(self.DARK_BG) + ax.set_title(title, color=self.TEXT, fontsize=11, fontweight="medium") + ax.set_ylim(0, 100) + ax.set_ylabel("%" if i < 3 else "B/s", color=self.TEXT_DIM, fontsize=9) + ax.tick_params(colors=self.TEXT_DIM) + ax.spines["bottom"].set_color(self.DARK_BORDER) + ax.spines["top"].set_color(self.DARK_BORDER) + ax.spines["left"].set_color(self.DARK_BORDER) + ax.spines["right"].set_color(self.DARK_BORDER) + axes.append(ax) + + self.cpu_line, = axes[0].plot([], [], color=self.CPU_COLOR, linewidth=1.5) + self.ram_line, = axes[1].plot([], [], color=self.RAM_COLOR, linewidth=1.5) + self.disk_line, = axes[2].plot([], [], color=self.DISK_COLOR, linewidth=1.5) + self.net_up_line, = axes[3].plot([], [], color=self.NET_UP_COLOR, linewidth=1.5, label="↑") + self.net_down_line, = axes[3].plot([], [], color=self.NET_DOWN_COLOR, linewidth=1.5, label="↓") + + for ax in axes[:3]: + ax.axhline(y=80, color=ax.get_lines()[0].get_color(), linestyle="--", alpha=self.ALPHA_40) + + canvas = FigureCanvas(fig) + canvas.set_css_classes(["plot-canvas"]) + box.append(canvas) + + # --- Footer --- + footer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + footer.set_valign(Gtk.Align.END) + footer.get_style_context().add_class("linked") + + self.status_label = Gtk.Label(label="Monitoring...") + self.status_label.set_css_classes(["dim-label"]) + footer.append(self.status_label) + + self.stop_btn = Gtk.Button(label="Arrêter") + self.stop_btn.set_css_classes(["destructive-action"]) + self.stop_btn.connect("clicked", lambda *_: self._stop()) + footer.append(self.stop_btn) + + box.append(footer) + + self.fig = fig + self.axes = axes + self.canvas = canvas + + GLib.timeout_add(UPDATE_INTERVAL, self._refresh) + + def _update_plot(self): + cpu_arr = list(self.tracker.cpu_data) + ram_arr = list(self.tracker.ram_data) + disk_arr = list(self.tracker.disk_data) + sent_arr = list(self.tracker.net_sent_data) + recv_arr = list(self.tracker.net_recv_data) + + self.cpu_line.set_data(range(len(cpu_arr)), cpu_arr) + self.ram_line.set_data(range(len(ram_arr)), ram_arr) + self.disk_line.set_data(range(len(disk_arr)), disk_arr) + self.net_up_line.set_data(range(len(sent_arr)), sent_arr) + self.net_down_line.set_data(range(len(recv_arr)), recv_arr) + + for i, ax in enumerate(self.axes[:3]): + ax.set_xlim(0, MAX_POINTS) + + max_net = max(max(sent_arr) if sent_arr else 0, max(recv_arr) if recv_arr else 0) + self.axes[3].set_xlim(0, MAX_POINTS) + if max_net > 0: + self.axes[3].set_ylim(0, max_net * 1.2) + + self.fig.canvas.draw() + + def _refresh(self): + if not self._running: + return GLib.SOURCE_REMOVE + + self.tracker.update() + cpu, ram = self.tracker.get_latest() + disk = self.tracker.get_disk() + send_rate, recv_rate = self.tracker.get_net_rates() + + self.cpu_label.set_label(f"CPU: {cpu:5.1f}%") + self.ram_label.set_label(f"RAM: {ram:5.1f}%") + self.disk_label.set_label(f"Disk: {disk:5.1f}%") + + mb_up = send_rate / (1024 * 1024) + mb_dn = recv_rate / (1024 * 1024) + self.net_label.set_label(f"Net: ↑{mb_up:.2f} ↓{mb_dn:.2f} MB/s") + + self._update_plot() + return GLib.SOURCE_CONTINUE + + def _stop(self, *_): + self._running = False + self.status_label.set_label("Arrêté.") + self.stop_btn.set_sensitive(False) + + +class MonitorApplication(Gtk.Application): + def __init__(self): + super().__init__(application_id="com.local.resource-monitor") + + def do_activate(self): + win = MonitorWindow(self) + win.present() + + +def main(): + app = MonitorApplication() + app.run() + + +if __name__ == "__main__": + main() diff --git a/debian/.debhelper/generated/cpu-monitor/dh_installchangelogs.dch.trimmed b/debian/.debhelper/generated/cpu-monitor/dh_installchangelogs.dch.trimmed new file mode 100644 index 0000000..241e004 --- /dev/null +++ b/debian/.debhelper/generated/cpu-monitor/dh_installchangelogs.dch.trimmed @@ -0,0 +1,5 @@ +cpu-monitor (1.0) stable; urgency=low + + * Initial release + + -- tmzf5455 Sat, 09 May 2026 10:00:00 +0000 diff --git a/debian/.debhelper/generated/cpu-monitor/installed-by-dh_installdocs b/debian/.debhelper/generated/cpu-monitor/installed-by-dh_installdocs new file mode 100644 index 0000000..e69de29 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..241e004 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +cpu-monitor (1.0) stable; urgency=low + + * Initial release + + -- tmzf5455 Sat, 09 May 2026 10:00:00 +0000 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..256ee2c --- /dev/null +++ b/debian/control @@ -0,0 +1,13 @@ +Source: cpu-monitor +Section: python +Priority: optional +Maintainer: tmzf5455 +Build-Depends: debhelper-compat (= 13) +Standards-Version: 4.1.4 + +Package: cpu-monitor +Maintainer: tmzf5455 +Architecture: all +Depends: python3, python3-psutil, python3-matplotlib, python3-tk, x11-utils, ${misc:Depends} +Description: CPU Monitor GUI + A Python GUI application for monitoring CPU usage in real-time. diff --git a/debian/cpu-monitor.substvars b/debian/cpu-monitor.substvars new file mode 100644 index 0000000..978fc8b --- /dev/null +++ b/debian/cpu-monitor.substvars @@ -0,0 +1,2 @@ +misc:Depends= +misc:Pre-Depends= diff --git a/debian/cpu-monitor/DEBIAN/control b/debian/cpu-monitor/DEBIAN/control new file mode 100644 index 0000000..76edf1f --- /dev/null +++ b/debian/cpu-monitor/DEBIAN/control @@ -0,0 +1,10 @@ +Package: cpu-monitor +Version: 1.0 +Architecture: all +Maintainer: tmzf5455 +Installed-Size: 20 +Depends: python3, python3-psutil, python3-matplotlib, python3-tk, x11-utils +Section: python +Priority: optional +Description: CPU Monitor GUI + A Python GUI application for monitoring CPU usage in real-time. diff --git a/debian/cpu-monitor/DEBIAN/md5sums b/debian/cpu-monitor/DEBIAN/md5sums new file mode 100644 index 0000000..a4791b9 --- /dev/null +++ b/debian/cpu-monitor/DEBIAN/md5sums @@ -0,0 +1,3 @@ +3bb2a78f10db731e82226d58d83c7d31 usr/bin/cpu-monitor +b40607068b416aad76a36519e068e13a usr/share/applications/cpu-monitor.desktop +417d73b9352097d7166b96829dc5a863 usr/share/doc/cpu-monitor/changelog.gz diff --git a/debian/cpu-monitor/usr/bin/cpu-monitor b/debian/cpu-monitor/usr/bin/cpu-monitor new file mode 100755 index 0000000..3953d86 --- /dev/null +++ b/debian/cpu-monitor/usr/bin/cpu-monitor @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 +# Lancement: python cpu_monitor_gui.py + +import tkinter as tk +from tkinter import ttk +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk +from matplotlib.figure import Figure +from collections import deque +import time + +import psutil + +MAX_POINTS = 60 +UPDATE_INTERVAL = 1000 # ms + + +class Tracker: + def __init__(self): + self.cpu_data = deque(maxlen=MAX_POINTS) + self.ram_data = deque(maxlen=MAX_POINTS) + self.disk_data = deque(maxlen=MAX_POINTS) + self.disk_read_data = deque(maxlen=MAX_POINTS) + self.disk_write_data = deque(maxlen=MAX_POINTS) + self.net_sent_data = deque(maxlen=MAX_POINTS) + self.net_recv_data = deque(maxlen=MAX_POINTS) + self._last_cpu = None + self._last_net_sent = None + self._last_net_recv = None + self._last_disk_read = None + self._last_disk_write = None + now = time.time() + self.time_data = deque(maxlen=MAX_POINTS) + self.time_data.append(now) + + def _net_rate(self, curr, last): + delta = curr - last + rate = max(delta / 2.0, 0) + return max(int(rate), 0) + + def update(self): + cpu = psutil.cpu_percent(interval=None) + ram = psutil.virtual_memory().percent + disk = psutil.disk_usage('/').percent + self._last_cpu = cpu + t = time.time() + self.time_data.append(t) + + self.cpu_data.append(cpu) + self.ram_data.append(ram) + self.disk_data.append(disk) + + d_io = psutil.disk_io_counters() + if d_io is not None: + d_read = d_io.read_bytes + d_write = d_io.write_bytes + if self._last_disk_read is not None: + self.disk_read_data.append(max(int((d_read - self._last_disk_read) / 2.0), 0)) + self.disk_write_data.append(max(int((d_write - self._last_disk_write) / 2.0), 0)) + self._last_disk_read = d_read + self._last_disk_write = d_write + + net = psutil.net_io_counters() + sent = net.bytes_sent + recv = net.bytes_recv + if self._last_net_sent is not None: + send_rate = self._net_rate(sent, self._last_net_sent) + recv_rate = self._net_rate(recv, self._last_net_recv) + self.net_sent_data.append(send_rate) + self.net_recv_data.append(recv_rate) + self._last_net_sent = sent + self._last_net_recv = recv + + def get_latest(self): + if self._last_cpu is None: + self.update() + return self._last_cpu, self.ram_data[-1] + + def get_disk(self): + if not self.disk_data: + return 0 + return self.disk_data[-1] + + def get_disk_rates(self): + if not self.disk_read_data or not self.disk_write_data: + return 0, 0 + return self.disk_read_data[-1], self.disk_write_data[-1] + + def get_net_rates(self): + if not self.net_sent_data or not self.net_recv_data: + return 0, 0 + return self.net_sent_data[-1], self.net_recv_data[-1] + + +class App: + def __init__(self, root): + self.root = root + self.root.title("CPU / RAM Monitor") + + self.tracker = Tracker() + + # --- Header card --- + card_bg = "#23272f" + card_fg = "#ffffff" + accent = "#4fc3f7" + + header_frame = ttk.Frame(root) + header_frame.pack(fill=tk.X, padx=10, pady=(10, 0)) + + self.cpu_label = ttk.Label( + header_frame, + text=f"CPU: --%", + font=("Segoe UI", 20, "bold"), + foreground=accent, + ) + self.cpu_label.pack(side=tk.LEFT, padx=20) + + self.ram_label = ttk.Label( + header_frame, + text=f"RAM: --%", + font=("Segoe UI", 20, "bold"), + foreground="#81c784", + ) + self.ram_label.pack(side=tk.LEFT, padx=20) + + self.net_label = ttk.Label( + header_frame, + text=f"Net: ↓ --", + font=("Segoe UI", 20, "bold"), + foreground="#ffb74d", + ) + self.net_label.pack(side=tk.LEFT, padx=20) + + self.disk_label = ttk.Label( + header_frame, + text=f"Disk: --%", + font=("Segoe UI", 20, "bold"), + foreground="#ba68c8", + ) + self.disk_label.pack(side=tk.LEFT, padx=20) + + # --- Plot --- + fig = Figure(figsize=(14, 3), facecolor=card_bg) + ax_cpu = fig.add_subplot(1, 4, 1, facecolor=card_bg) + ax_ram = fig.add_subplot(1, 4, 2, facecolor=card_bg) + ax_disk = fig.add_subplot(1, 4, 3, facecolor=card_bg) + ax_net = fig.add_subplot(1, 4, 4, facecolor=card_bg) + + ax_cpu.set_title("CPU %", color=card_fg, fontsize=11) + ax_ram.set_title("RAM %", color=card_fg, fontsize=11) + ax_disk.set_title("Disk %", color=card_fg, fontsize=11) + ax_net.set_title("Réseau (B/s)", color=card_fg, fontsize=11) + ax_cpu.set_ylim(0, 100) + ax_ram.set_ylim(0, 100) + ax_disk.set_ylim(0, 100) + ax_net.set_ylim(0, 1) + ax_cpu.set_ylabel("%") + ax_ram.set_ylabel("%") + ax_disk.set_ylabel("%") + ax_net.set_ylabel("B/s") + ax_cpu.tick_params(colors="#aaa") + ax_ram.tick_params(colors="#aaa") + ax_net.tick_params(colors="#aaa") + for label in ax_cpu.get_yticklabels(): + label.set_color("#aaa") + for label in ax_ram.get_yticklabels(): + label.set_color("#aaa") + for label in ax_net.get_yticklabels(): + label.set_color("#aaa") + for line in ax_cpu.spines.values(): + line.set_color("#3a3f4a") + for line in ax_ram.spines.values(): + line.set_color("#3a3f4a") + for line in ax_net.spines.values(): + line.set_color("#3a3f4a") + + self.cpu_line, = ax_cpu.plot( + [], [], color="#ef5350", linewidth=1.5, marker="", markerfacecolor="#ef5350" + ) + self.ram_line, = ax_ram.plot( + [], [], color="#66bb6a", linewidth=1.5, marker="", markerfacecolor="#66bb6a" + ) + self.disk_line, = ax_disk.plot( + [], [], color="#ba68c8", linewidth=1.5, marker="", markerfacecolor="#ba68c8" + ) + self.net_sent_line, = ax_net.plot( + [], [], color="#ffb74d", linewidth=1.5, marker="", markerfacecolor="#ffb74d" + ) + self.net_recv_line, = ax_net.plot( + [], [], color="#90caf9", linewidth=1.5, marker="", markerfacecolor="#90caf9" + ) + + ax_cpu.axhline(y=80, color="#ef5350", linestyle="--", alpha=0.4) + ax_ram.axhline(y=80, color="#66bb6a", linestyle="--", alpha=0.4) + ax_disk.axhline(y=80, color="#ba68c8", linestyle="--", alpha=0.4) + ax_cpu.text(0.02, 0.02, "80%", fontsize=8, color="#ef5350", alpha=0.6, transform=ax_cpu.transAxes) + + self.fig = fig + self.ax_cpu = ax_cpu + self.ax_ram = ax_ram + self.ax_disk = ax_disk + self.ax_net = ax_net + + canvas = FigureCanvasTkAgg(fig, master=root) + canvas.draw() + canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + toolbar = NavigationToolbar2Tk(canvas, root) + toolbar.update() + + # --- Footer status --- + status_frame = ttk.Frame(root) + status_frame.pack(fill=tk.X, padx=10, pady=(0, 10)) + + self.status_label = ttk.Label( + status_frame, + text="Monitoring...", + font=("Segoe UI", 9), + foreground="#888", + ) + self.status_label.pack(side=tk.LEFT) + + stop_btn = ttk.Button( + status_frame, + text="Stop", + command=self._stop, + width=10, + ) + stop_btn.pack(side=tk.RIGHT, padx=(10, 0)) + + self._running = True + self._refresh() + + def _refresh(self): + if not self._running: + return + self.tracker.update() + cpu, ram = self.tracker.get_latest() + disk = self.tracker.get_disk() + send_rate, recv_rate = self.tracker.get_net_rates() + self.cpu_label.config(text=f"CPU: {cpu:5.1f}%") + self.ram_label.config(text=f"RAM: {ram:5.1f}%") + self.disk_label.config(text=f"Disk: {disk:5.1f}%") + + mb_sent = send_rate / (1024 * 1024) + mb_recv = recv_rate / (1024 * 1024) + self.net_label.config(text=f"Net: ↑{mb_sent:.2f} ↓{mb_recv:.2f} MB/s") + + cpu_arr = list(self.tracker.cpu_data) + ram_arr = list(self.tracker.ram_data) + disk_arr = list(self.tracker.disk_data) + sent_arr = list(self.tracker.net_sent_data) + recv_arr = list(self.tracker.net_recv_data) + self.cpu_line.set_data(range(len(cpu_arr)), cpu_arr) + self.ram_line.set_data(range(len(ram_arr)), ram_arr) + self.disk_line.set_data(range(len(disk_arr)), disk_arr) + self.net_sent_line.set_data(range(len(sent_arr)), sent_arr) + self.net_recv_line.set_data(range(len(recv_arr)), recv_arr) + + self.ax_cpu.set_xlim(0, MAX_POINTS) + self.ax_ram.set_xlim(0, MAX_POINTS) + self.ax_disk.set_xlim(0, MAX_POINTS) + self.ax_net.set_xlim(0, MAX_POINTS) + + max_net = max(max(sent_arr) if sent_arr else 0, max(recv_arr) if recv_arr else 0) + if max_net > 0: + self.ax_net.set_ylim(0, max_net * 1.2) + + self.fig.canvas.draw_idle() + self.root.after(UPDATE_INTERVAL, self._refresh) + + def _stop(self): + self._running = False + self.status_label.config(text="Stopped.") + + +def main(): + root = tk.Tk() + app = App(root) + root.mainloop() + + +if __name__ == "__main__": + main() diff --git a/debian/cpu-monitor/usr/share/applications/cpu-monitor.desktop b/debian/cpu-monitor/usr/share/applications/cpu-monitor.desktop new file mode 100644 index 0000000..edec41a --- /dev/null +++ b/debian/cpu-monitor/usr/share/applications/cpu-monitor.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Exec=/usr/bin/cpu-monitor +Hidden=false +NoDisplay=false +X-GNOME-Autostart-enabled=true +Name=CPU Monitor GUI +Comment=CPU/RAM/Disk/Network monitor +Icon=utilities-system-monitor diff --git a/debian/cpu-monitor/usr/share/doc/cpu-monitor/changelog.gz b/debian/cpu-monitor/usr/share/doc/cpu-monitor/changelog.gz new file mode 100644 index 0000000..da8d441 Binary files /dev/null and b/debian/cpu-monitor/usr/share/doc/cpu-monitor/changelog.gz differ diff --git a/debian/debhelper-build-stamp b/debian/debhelper-build-stamp new file mode 100644 index 0000000..cdafdd2 --- /dev/null +++ b/debian/debhelper-build-stamp @@ -0,0 +1 @@ +cpu-monitor diff --git a/debian/files b/debian/files new file mode 100644 index 0000000..669bd29 --- /dev/null +++ b/debian/files @@ -0,0 +1,2 @@ +cpu-monitor_1.0_all.deb python optional +cpu-monitor_1.0_arm64.buildinfo python optional diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..d1fe4d9 --- /dev/null +++ b/debian/rules @@ -0,0 +1,8 @@ +#!/usr/bin/make -f + +%: + dh $@ + +override_dh_auto_install: + install -D -m 0755 cpu_monitor_gui.py $(CURDIR)/debian/cpu-monitor/usr/bin/cpu-monitor + install -D -m 0644 cpu_monitor_gui.desktop $(CURDIR)/debian/cpu-monitor/usr/share/applications/cpu-monitor.desktop diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native)