python-resource-monitor/RESOURCE_MONITOR_README.md
2025-11-26 08:29:04 +01:00

11 KiB

ResourceMonitor - Modulo di Monitoring Risorse

Modulo Python standalone e riutilizzabile per il monitoring in tempo reale di CPU, RAM e Thread di un processo. Estratto dal progetto Target Simulator e reso completamente indipendente per essere integrato facilmente in altre applicazioni.

🎯 Caratteristiche

  • Monitoring in background tramite thread daemon
  • Aggiornamenti periodici configurabili (polling interval personalizzabile)
  • Callback personalizzati per gestire i dati come preferisci
  • Integrazione Tkinter con classe specializzata TkinterResourceMonitor
  • Thread-safe per uso in applicazioni multi-thread
  • Gestione errori robusta - non crasha mai l'applicazione
  • Metriche accurate:
    • CPU normalizzata per sistemi multi-core (0-100%)
    • Memoria USS (Unique Set Size) quando disponibile, fallback a RSS
    • Conteggio thread attivi
  • Zero dipendenze obbligatorie - graceful degradation se psutil non disponibile

📦 Installazione

Dipendenze

Il modulo funziona senza dipendenze obbligatorie, ma richiede psutil per il monitoring effettivo:

pip install psutil

Integrazione nel tuo progetto

Hai due opzioni:

Opzione 1: Copia diretta del modulo

# Copia il file nella tua applicazione
cp target_simulator/utils/resource_monitor.py <tuo_progetto>/

Opzione 2: Import dal progetto Target Simulator

from target_simulator.utils.resource_monitor import ResourceMonitor

🚀 Quick Start

Esempio 1: Console Semplice

from target_simulator.utils.resource_monitor import ResourceMonitor

def print_stats(stats_string):
    print(f"\r{stats_string}", end="", flush=True)

monitor = ResourceMonitor(
    update_callback=print_stats,
    poll_interval=1.0  # Aggiorna ogni secondo
)

monitor.start()

# ... la tua applicazione gira ...

monitor.stop()  # Quando hai finito

Esempio 2: Tkinter GUI

import tkinter as tk
from target_simulator.utils.resource_monitor import TkinterResourceMonitor

root = tk.Tk()

# StringVar per visualizzare le statistiche
status_var = tk.StringVar()
tk.Label(root, textvariable=status_var, font=("Courier", 10)).pack()

# Crea e avvia il monitor
monitor = TkinterResourceMonitor(
    tk_widget=root,
    string_var=status_var,
    poll_interval=1.0
)
monitor.start()

# Alla chiusura
def on_closing():
    monitor.stop()
    root.destroy()

root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()

Esempio 3: Lettura Sincrona (Senza Thread)

from target_simulator.utils.resource_monitor import ResourceMonitor

# Callback dummy se non serve monitoring continuo
monitor = ResourceMonitor(update_callback=lambda s: None)

# Ottieni statistiche on-demand
stats = monitor.get_current_stats()

if stats:
    print(f"CPU: {stats['cpu_percent']:.1f}%")
    print(f"RAM: {stats['memory_mb']:.1f} MB ({stats['memory_percent']:.1f}%)")
    print(f"Thread: {stats['thread_count']}")

📚 Documentazione API

Classe ResourceMonitor

Monitor delle risorse di sistema che gira in background.

Constructor

ResourceMonitor(
    update_callback: Callable[[str], None],
    poll_interval: float = 1.0,
    process_pid: Optional[int] = None
)

Parametri:

  • update_callback: Funzione chiamata ad ogni aggiornamento con la stringa formattata delle statistiche
    • Esempio output: "CPU 5% · MEM 120MB (2.5%) [USS] · Thr 12"
  • poll_interval: Intervallo in secondi tra le letture (default: 1.0)
  • process_pid: PID del processo da monitorare (default: processo corrente)

Metodi Principali

start() -> bool

Avvia il monitoring in background.

Returns: True se avviato con successo, False se psutil non disponibile o già in esecuzione

stop() -> None

Ferma il monitoring. Thread-safe, può essere chiamato più volte.

get_current_stats() -> Optional[Dict[str, Any]]

Ottiene statistiche correnti in modo sincrono (senza avviare il thread).

Returns: Dizionario con chiavi:

  • cpu_percent (float): Percentuale CPU normalizzata (0-100)
  • memory_mb (float): Memoria in MiB
  • memory_percent (float): Percentuale memoria del sistema
  • memory_type (str): "USS" o "RSS"
  • thread_count (int): Numero di thread

Oppure None se psutil non disponibile o errore.

Proprietà

  • is_running (bool): True se il monitor è attivo
  • poll_interval (float): Intervallo di polling corrente

Classe TkinterResourceMonitor

Specializzazione di ResourceMonitor per GUI Tkinter con aggiornamento thread-safe automatico.

Constructor

TkinterResourceMonitor(
    tk_widget,              # Any widget with .after() method
    string_var,             # tk.StringVar to update
    poll_interval: float = 1.0,
    process_pid: Optional[int] = None
)

Parametri:

  • tk_widget: Qualsiasi widget Tkinter (root, Frame, etc.) con metodo .after()
  • string_var: tk.StringVar da aggiornare automaticamente
  • Altri parametri come ResourceMonitor

Esempio:

import tkinter as tk
from target_simulator.utils.resource_monitor import TkinterResourceMonitor

root = tk.Tk()
stats_var = tk.StringVar()

monitor = TkinterResourceMonitor(root, stats_var)
monitor.start()

Funzioni Utility

is_psutil_available() -> bool

Verifica se psutil è disponibile nel sistema.

from target_simulator.utils.resource_monitor import is_psutil_available

if is_psutil_available():
    print("psutil OK")
else:
    print("Installa psutil: pip install psutil")

🧪 Testing & Esempi

Esecuzione Test Standalone

Il modulo può essere eseguito direttamente per un test rapido:

$env:PYTHONPATH='C:\src\____GitProjects\target_simulator'
python -m target_simulator.utils.resource_monitor

Output esempio:

=== Test ResourceMonitor ===
Monitoraggio risorse per 10 secondi...
Premi Ctrl+C per interrompere

CPU 8%  ·  MEM 145MB (1.8%) [USS]  ·  Thr 15

Monitor fermato.

Statistiche finali:
  CPU: 8.2%
  Memoria: 145.3 MB (1.8%)
  Tipo memoria: USS
  Thread: 15

Esempi Completi

Esegui il file di esempi interattivo:

python target_simulator/utils/examples/resource_monitor_example.py

Il file contiene 5 esempi:

  1. Console semplice - Stampa su terminale
  2. Lettura sincrona - Lettura singola senza thread
  3. Callback personalizzato - Analisi e logging custom
  4. Tkinter GUI - Integrazione completa con finestra
  5. Monitoring altro processo - Specifica PID esplicito

🔧 Integrazione nel Tuo Progetto

Scenario 1: Applicazione Console

from target_simulator.utils.resource_monitor import ResourceMonitor
import logging

logger = logging.getLogger(__name__)

def log_resources(stats: str):
    logger.info(f"Resources: {stats}")

# Avvia monitoring all'inizio dell'app
monitor = ResourceMonitor(log_resources, poll_interval=5.0)
monitor.start()

# ... la tua applicazione ...

# Cleanup alla fine
monitor.stop()

Scenario 2: Web Server (Flask/FastAPI)

from target_simulator.utils.resource_monitor import ResourceMonitor
from flask import Flask

app = Flask(__name__)
current_stats = {"data": "Not started"}

def update_stats(stats: str):
    current_stats["data"] = stats

monitor = ResourceMonitor(update_stats)
monitor.start()

@app.route('/health')
def health():
    return {"status": "ok", "resources": current_stats["data"]}

if __name__ == '__main__':
    try:
        app.run()
    finally:
        monitor.stop()

Scenario 3: GUI Tkinter (Application Completa)

import tkinter as tk
from tkinter import ttk
from target_simulator.utils.resource_monitor import TkinterResourceMonitor

class MyApplication(tk.Tk):
    def __init__(self):
        super().__init__()
        
        # UI
        self.title("My App")
        self.stats_var = tk.StringVar(value="Inizializzazione...")
        
        # Status bar con resource monitor
        status_bar = ttk.Frame(self, relief=tk.SUNKEN)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        ttk.Label(status_bar, textvariable=self.stats_var).pack(
            side=tk.RIGHT, padx=5
        )
        
        # Avvia monitor
        self.monitor = TkinterResourceMonitor(
            tk_widget=self,
            string_var=self.stats_var,
            poll_interval=1.0
        )
        self.monitor.start()
        
        self.protocol("WM_DELETE_WINDOW", self.on_closing)
    
    def on_closing(self):
        self.monitor.stop()
        self.destroy()

if __name__ == '__main__':
    app = MyApplication()
    app.mainloop()

⚙️ Dettagli Tecnici

Normalizzazione CPU Multi-Core

Su sistemi multi-core, psutil.Process.cpu_percent() può ritornare valori >100% perché misura l'utilizzo totale su tutti i core. Il ResourceMonitor normalizza automaticamente questo valore dividendo per il numero di CPU logiche, rendendo il risultato comparabile con Task Manager (scala 0-100%).

cpu_proc = proc.cpu_percent(None)  # Può essere >100%
ncpu = psutil.cpu_count(logical=True) or 1
cpu = cpu_proc / ncpu  # Normalizzato 0-100%

Memoria: USS vs RSS

Il monitor preferisce USS (Unique Set Size) quando disponibile:

  • USS: Memoria unica del processo (più accurata, esclude shared memory)
  • RSS: Resident Set Size (fallback, include shared memory)

USS è più vicino al "Private Working Set" mostrato da Task Manager.

Thread Safety

Il monitoring avviene in un thread daemon separato. Gli aggiornamenti UI (Tkinter) sono schedulati sul main thread usando widget.after(0, ...) per garantire thread-safety.

Gestione Errori

Il modulo è progettato per mai crashare l'applicazione host:

  • Se psutil non disponibile → graceful degradation
  • Errori nel callback → ignorati, monitoring continua
  • Widget Tkinter distrutto → errori soppressi
  • Processo terminato → thread si ferma automaticamente

🐛 Troubleshooting

"psutil non disponibile"

# Installa psutil
pip install psutil

# Verifica installazione
python -c "import psutil; print(psutil.__version__)"

Valori CPU strani (molto alti o molto bassi)

La prima lettura CPU può essere imprecisa. Il monitor fa un "priming" iniziale (proc.cpu_percent(None)) per risolvere questo problema.

Memory leak con Tkinter

Assicurati di chiamare sempre monitor.stop() prima di chiudere l'applicazione:

def on_closing():
    monitor.stop()  # IMPORTANTE!
    root.destroy()

root.protocol("WM_DELETE_WINDOW", on_closing)

Thread non si ferma

Il thread è daemon, quindi termina automaticamente quando l'app termina. stop() ferma il polling ma se serve attendere la terminazione:

monitor.stop()
if monitor._thread:
    monitor._thread.join(timeout=2.0)

📄 Licenza

Stesso del progetto parent (Target Simulator).

🤝 Contributi

Questo modulo è estratto da un progetto più grande. Per bug o miglioramenti, aggiorna il file originale in target_simulator/utils/resource_monitor.py.

📞 Supporto

Per domande o problemi:

  1. Controlla gli esempi in examples/resource_monitor_example.py
  2. Verifica che psutil sia installato correttamente
  3. Prova il test standalone del modulo

Versione: 1.0
Estratto da: Target Simulator Project
Data: Novembre 2025