primo commit per il modulo
This commit is contained in:
commit
662ec3da38
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.svn
|
||||
430
RESOURCE_MONITOR_README.md
Normal file
430
RESOURCE_MONITOR_README.md
Normal file
@ -0,0 +1,430 @@
|
||||
# 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:
|
||||
|
||||
```powershell
|
||||
pip install psutil
|
||||
```
|
||||
|
||||
### Integrazione nel tuo progetto
|
||||
|
||||
Hai due opzioni:
|
||||
|
||||
**Opzione 1: Copia diretta del modulo**
|
||||
```powershell
|
||||
# Copia il file nella tua applicazione
|
||||
cp target_simulator/utils/resource_monitor.py <tuo_progetto>/
|
||||
```
|
||||
|
||||
**Opzione 2: Import dal progetto Target Simulator**
|
||||
```python
|
||||
from target_simulator.utils.resource_monitor import ResourceMonitor
|
||||
```
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Esempio 1: Console Semplice
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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)
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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:**
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```powershell
|
||||
$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:
|
||||
|
||||
```powershell
|
||||
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
|
||||
|
||||
```python
|
||||
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)
|
||||
|
||||
```python
|
||||
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)
|
||||
|
||||
```python
|
||||
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%).
|
||||
|
||||
```python
|
||||
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"
|
||||
|
||||
```powershell
|
||||
# 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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
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
|
||||
372
examples/resource_monitor_example.py
Normal file
372
examples/resource_monitor_example.py
Normal file
@ -0,0 +1,372 @@
|
||||
"""
|
||||
Esempi di utilizzo del modulo ResourceMonitor.
|
||||
|
||||
Questo file contiene diversi esempi pratici di come integrare il ResourceMonitor
|
||||
nelle tue applicazioni Python, sia con che senza GUI Tkinter.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# Aggiungi il path del progetto per poter importare il modulo
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
||||
|
||||
from target_simulator.utils.resource_monitor import (
|
||||
ResourceMonitor,
|
||||
TkinterResourceMonitor,
|
||||
is_psutil_available
|
||||
)
|
||||
|
||||
|
||||
def esempio_1_console_semplice():
|
||||
"""
|
||||
Esempio 1: Monitoring console con stampa su terminale.
|
||||
|
||||
Questo è l'esempio più semplice: stampa le statistiche sul terminale
|
||||
ogni secondo.
|
||||
"""
|
||||
print("\n" + "="*70)
|
||||
print("ESEMPIO 1: Monitoring console semplice")
|
||||
print("="*70)
|
||||
|
||||
if not is_psutil_available():
|
||||
print("ERRORE: psutil non disponibile. Installa con: pip install psutil")
|
||||
return
|
||||
|
||||
def print_to_console(stats: str):
|
||||
print(f"\r{stats}", end="", flush=True)
|
||||
|
||||
monitor = ResourceMonitor(
|
||||
update_callback=print_to_console,
|
||||
poll_interval=1.0
|
||||
)
|
||||
|
||||
if monitor.start():
|
||||
print("Monitoraggio avviato per 5 secondi...\n")
|
||||
try:
|
||||
time.sleep(5)
|
||||
except KeyboardInterrupt:
|
||||
print("\nInterrotto")
|
||||
finally:
|
||||
monitor.stop()
|
||||
print("\n\nMonitoring fermato.\n")
|
||||
else:
|
||||
print("Impossibile avviare il monitor")
|
||||
|
||||
|
||||
def esempio_2_lettura_sincrona():
|
||||
"""
|
||||
Esempio 2: Lettura sincrona delle statistiche.
|
||||
|
||||
A volte non serve un monitoring continuo, ma solo una lettura "on-demand"
|
||||
delle risorse correnti. Questo esempio mostra come fare.
|
||||
"""
|
||||
print("\n" + "="*70)
|
||||
print("ESEMPIO 2: Lettura sincrona (senza thread)")
|
||||
print("="*70)
|
||||
|
||||
if not is_psutil_available():
|
||||
print("ERRORE: psutil non disponibile.")
|
||||
return
|
||||
|
||||
# Non serve nemmeno un callback se usiamo solo get_current_stats()
|
||||
monitor = ResourceMonitor(
|
||||
update_callback=lambda s: None, # callback dummy
|
||||
poll_interval=1.0
|
||||
)
|
||||
|
||||
print("\nLettura singola delle statistiche:\n")
|
||||
stats = monitor.get_current_stats()
|
||||
|
||||
if stats:
|
||||
print(f" CPU: {stats['cpu_percent']:.1f}%")
|
||||
print(f" Memoria: {stats['memory_mb']:.1f} MB ({stats['memory_percent']:.1f}%)")
|
||||
print(f" Tipo mem: {stats['memory_type']}")
|
||||
print(f" Thread: {stats['thread_count']}")
|
||||
else:
|
||||
print(" Impossibile leggere le statistiche")
|
||||
|
||||
print()
|
||||
|
||||
|
||||
def esempio_3_callback_personalizzato():
|
||||
"""
|
||||
Esempio 3: Callback personalizzato con logging.
|
||||
|
||||
Questo esempio mostra come usare un callback personalizzato per fare
|
||||
qualcosa di più complesso con i dati, come logging su file o invio
|
||||
a un sistema di monitoring esterno.
|
||||
"""
|
||||
print("\n" + "="*70)
|
||||
print("ESEMPIO 3: Callback personalizzato con analisi")
|
||||
print("="*70)
|
||||
|
||||
if not is_psutil_available():
|
||||
print("ERRORE: psutil non disponibile.")
|
||||
return
|
||||
|
||||
# Lista per raccogliere le misure
|
||||
samples = []
|
||||
max_samples = 5
|
||||
|
||||
def analyze_and_log(stats_str: str):
|
||||
"""Callback che analizza e logga le statistiche."""
|
||||
# Stampa
|
||||
print(f"\r{stats_str}", end="", flush=True)
|
||||
|
||||
# Potresti anche parsare la stringa o usare get_current_stats()
|
||||
# per fare analisi più avanzate
|
||||
samples.append(stats_str)
|
||||
|
||||
# Mantieni solo le ultime N misure
|
||||
if len(samples) > max_samples:
|
||||
samples.pop(0)
|
||||
|
||||
monitor = ResourceMonitor(
|
||||
update_callback=analyze_and_log,
|
||||
poll_interval=0.5
|
||||
)
|
||||
|
||||
if monitor.start():
|
||||
print("Monitoring per 3 secondi con callback personalizzato...\n")
|
||||
try:
|
||||
time.sleep(3)
|
||||
except KeyboardInterrupt:
|
||||
print("\nInterrotto")
|
||||
finally:
|
||||
monitor.stop()
|
||||
print(f"\n\nRaccolte {len(samples)} misure.")
|
||||
print("Ultime 3 misure:")
|
||||
for i, s in enumerate(samples[-3:], 1):
|
||||
print(f" {i}. {s}")
|
||||
print()
|
||||
|
||||
|
||||
def esempio_4_tkinter_gui():
|
||||
"""
|
||||
Esempio 4: Integrazione con GUI Tkinter.
|
||||
|
||||
Questo esempio mostra come integrare il monitor in una GUI Tkinter
|
||||
usando la classe TkinterResourceMonitor che gestisce automaticamente
|
||||
l'aggiornamento thread-safe della GUI.
|
||||
"""
|
||||
print("\n" + "="*70)
|
||||
print("ESEMPIO 4: Integrazione Tkinter GUI")
|
||||
print("="*70)
|
||||
|
||||
if not is_psutil_available():
|
||||
print("ERRORE: psutil non disponibile.")
|
||||
return
|
||||
|
||||
try:
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
except ImportError:
|
||||
print("ERRORE: Tkinter non disponibile in questa installazione Python")
|
||||
return
|
||||
|
||||
# Crea finestra principale
|
||||
root = tk.Tk()
|
||||
root.title("Resource Monitor - Esempio Tkinter")
|
||||
root.geometry("600x250")
|
||||
|
||||
# Frame principale
|
||||
main_frame = ttk.Frame(root, padding=20)
|
||||
main_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Titolo
|
||||
title = ttk.Label(
|
||||
main_frame,
|
||||
text="Monitor Risorse di Sistema",
|
||||
font=("Arial", 16, "bold")
|
||||
)
|
||||
title.pack(pady=(0, 20))
|
||||
|
||||
# Label per le statistiche
|
||||
stats_var = tk.StringVar(value="Avvio monitor...")
|
||||
stats_label = ttk.Label(
|
||||
main_frame,
|
||||
textvariable=stats_var,
|
||||
font=("Courier", 12),
|
||||
relief=tk.SUNKEN,
|
||||
padding=10
|
||||
)
|
||||
stats_label.pack(fill=tk.X, pady=10)
|
||||
|
||||
# Frame per i pulsanti
|
||||
button_frame = ttk.Frame(main_frame)
|
||||
button_frame.pack(pady=20)
|
||||
|
||||
# Variabile per tenere traccia dello stato
|
||||
monitor_state = {"monitor": None, "is_running": False}
|
||||
|
||||
def start_monitor():
|
||||
"""Avvia il monitoring."""
|
||||
if not monitor_state["is_running"]:
|
||||
monitor = TkinterResourceMonitor(
|
||||
tk_widget=root,
|
||||
string_var=stats_var,
|
||||
poll_interval=0.5 # Update veloce per demo
|
||||
)
|
||||
if monitor.start():
|
||||
monitor_state["monitor"] = monitor
|
||||
monitor_state["is_running"] = True
|
||||
start_btn.config(state="disabled")
|
||||
stop_btn.config(state="normal")
|
||||
status_label.config(text="Stato: ATTIVO", foreground="green")
|
||||
|
||||
def stop_monitor():
|
||||
"""Ferma il monitoring."""
|
||||
if monitor_state["is_running"] and monitor_state["monitor"]:
|
||||
monitor_state["monitor"].stop()
|
||||
monitor_state["is_running"] = False
|
||||
start_btn.config(state="normal")
|
||||
stop_btn.config(state="disabled")
|
||||
stats_var.set("Monitor fermato")
|
||||
status_label.config(text="Stato: FERMO", foreground="red")
|
||||
|
||||
def on_closing():
|
||||
"""Handler per chiusura finestra."""
|
||||
stop_monitor()
|
||||
root.destroy()
|
||||
|
||||
# Pulsanti
|
||||
start_btn = ttk.Button(button_frame, text="Avvia Monitor", command=start_monitor)
|
||||
start_btn.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
stop_btn = ttk.Button(button_frame, text="Ferma Monitor", command=stop_monitor)
|
||||
stop_btn.pack(side=tk.LEFT, padx=5)
|
||||
stop_btn.config(state="disabled")
|
||||
|
||||
# Label stato
|
||||
status_label = ttk.Label(
|
||||
main_frame,
|
||||
text="Stato: FERMO",
|
||||
font=("Arial", 10),
|
||||
foreground="red"
|
||||
)
|
||||
status_label.pack(pady=10)
|
||||
|
||||
# Info
|
||||
info_text = (
|
||||
"Questo esempio mostra l'integrazione del ResourceMonitor in una GUI Tkinter.\n"
|
||||
"Il monitor gira in un thread separato e aggiorna la GUI in modo thread-safe."
|
||||
)
|
||||
info_label = ttk.Label(
|
||||
main_frame,
|
||||
text=info_text,
|
||||
font=("Arial", 9),
|
||||
foreground="gray",
|
||||
wraplength=550,
|
||||
justify=tk.CENTER
|
||||
)
|
||||
info_label.pack(pady=(20, 0))
|
||||
|
||||
# Avvia automaticamente il monitor
|
||||
root.after(500, start_monitor)
|
||||
|
||||
# Gestisci chiusura
|
||||
root.protocol("WM_DELETE_WINDOW", on_closing)
|
||||
|
||||
print("Finestra GUI aperta. Chiudi la finestra per continuare.\n")
|
||||
root.mainloop()
|
||||
|
||||
|
||||
def esempio_5_monitoring_altro_processo():
|
||||
"""
|
||||
Esempio 5: Monitoring di un altro processo (per PID).
|
||||
|
||||
Il ResourceMonitor può anche monitorare un processo diverso da quello
|
||||
corrente, specificando il PID.
|
||||
"""
|
||||
print("\n" + "="*70)
|
||||
print("ESEMPIO 5: Monitoring di un altro processo")
|
||||
print("="*70)
|
||||
|
||||
if not is_psutil_available():
|
||||
print("ERRORE: psutil non disponibile.")
|
||||
return
|
||||
|
||||
import psutil
|
||||
|
||||
# Ottieni il PID del processo corrente per l'esempio
|
||||
current_pid = psutil.Process().pid
|
||||
|
||||
print(f"\nMonitoring del processo corrente (PID {current_pid})...")
|
||||
print("In un'applicazione reale potresti specificare il PID di un altro processo.\n")
|
||||
|
||||
def print_stats(stats: str):
|
||||
print(f"\rPID {current_pid}: {stats}", end="", flush=True)
|
||||
|
||||
monitor = ResourceMonitor(
|
||||
update_callback=print_stats,
|
||||
poll_interval=1.0,
|
||||
process_pid=current_pid # Specifica esplicitamente il PID
|
||||
)
|
||||
|
||||
if monitor.start():
|
||||
try:
|
||||
time.sleep(3)
|
||||
except KeyboardInterrupt:
|
||||
print("\nInterrotto")
|
||||
finally:
|
||||
monitor.stop()
|
||||
print("\n\nMonitoring fermato.\n")
|
||||
|
||||
|
||||
def main():
|
||||
"""Menu principale per scegliere l'esempio da eseguire."""
|
||||
print("\n" + "="*70)
|
||||
print("ESEMPI DI UTILIZZO DEL RESOURCE MONITOR")
|
||||
print("="*70)
|
||||
|
||||
if not is_psutil_available():
|
||||
print("\n⚠️ ATTENZIONE: psutil non è installato!")
|
||||
print(" Installa con: pip install psutil")
|
||||
print(" Alcuni esempi non funzioneranno senza psutil.\n")
|
||||
|
||||
esempi = [
|
||||
("Monitoring console semplice", esempio_1_console_semplice),
|
||||
("Lettura sincrona (senza thread)", esempio_2_lettura_sincrona),
|
||||
("Callback personalizzato", esempio_3_callback_personalizzato),
|
||||
("Integrazione Tkinter GUI", esempio_4_tkinter_gui),
|
||||
("Monitoring altro processo (PID)", esempio_5_monitoring_altro_processo),
|
||||
]
|
||||
|
||||
print("\nScegli un esempio da eseguire:")
|
||||
for i, (nome, _) in enumerate(esempi, 1):
|
||||
print(f" {i}. {nome}")
|
||||
print(f" {len(esempi) + 1}. Esegui tutti gli esempi")
|
||||
print(" 0. Esci")
|
||||
|
||||
try:
|
||||
scelta = input("\nScelta: ").strip()
|
||||
|
||||
if scelta == "0":
|
||||
print("Uscita.")
|
||||
return
|
||||
|
||||
scelta_num = int(scelta)
|
||||
|
||||
if scelta_num == len(esempi) + 1:
|
||||
# Esegui tutti
|
||||
for nome, func in esempi:
|
||||
func()
|
||||
time.sleep(1)
|
||||
elif 1 <= scelta_num <= len(esempi):
|
||||
# Esegui singolo esempio
|
||||
esempi[scelta_num - 1][1]()
|
||||
else:
|
||||
print("Scelta non valida.")
|
||||
|
||||
except (ValueError, KeyboardInterrupt):
|
||||
print("\nUscita.")
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("Fine esempi.")
|
||||
print("="*70 + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
432
resource_monitor.py
Normal file
432
resource_monitor.py
Normal file
@ -0,0 +1,432 @@
|
||||
"""
|
||||
Resource Monitor - Modulo riutilizzabile per il monitoring di CPU, RAM e Thread.
|
||||
|
||||
Questo modulo fornisce una classe standalone per monitorare le risorse di sistema
|
||||
(CPU, memoria RAM, numero di thread) di un processo Python. Può essere facilmente
|
||||
integrato in qualsiasi applicazione Python, con o senza GUI Tkinter.
|
||||
|
||||
Caratteristiche:
|
||||
- Monitoring in background tramite thread daemon
|
||||
- Aggiornamenti periodici configurabili
|
||||
- Supporto per callback personalizzati
|
||||
- Integrazione opzionale con Tkinter StringVar
|
||||
- Gestione sicura degli errori
|
||||
- Normalizzazione CPU per sistemi multi-core
|
||||
- Preferenza per USS (Unique Set Size) come metrica di memoria
|
||||
|
||||
Esempio di utilizzo con Tkinter:
|
||||
import tkinter as tk
|
||||
from resource_monitor import ResourceMonitor
|
||||
|
||||
root = tk.Tk()
|
||||
status_var = tk.StringVar()
|
||||
|
||||
monitor = ResourceMonitor(
|
||||
update_callback=lambda stats: status_var.set(stats),
|
||||
poll_interval=1.0
|
||||
)
|
||||
monitor.start()
|
||||
|
||||
# ... alla chiusura dell'app
|
||||
monitor.stop()
|
||||
|
||||
Esempio di utilizzo senza GUI (callback personalizzato):
|
||||
from resource_monitor import ResourceMonitor
|
||||
|
||||
def print_stats(stats_string):
|
||||
print(f"Risorse: {stats_string}")
|
||||
|
||||
monitor = ResourceMonitor(
|
||||
update_callback=print_stats,
|
||||
poll_interval=2.0
|
||||
)
|
||||
monitor.start()
|
||||
|
||||
# ... quando non serve più
|
||||
monitor.stop()
|
||||
|
||||
Dipendenze:
|
||||
- psutil (opzionale ma raccomandato): pip install psutil
|
||||
Se psutil non è disponibile, il monitor non farà nulla ma non genererà errori.
|
||||
|
||||
Author: Estratto da Target Simulator
|
||||
License: Same as parent project
|
||||
"""
|
||||
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from typing import Optional, Callable, Dict, Any
|
||||
|
||||
# Optional dependency: psutil provides portable CPU/memory info across OSes
|
||||
try:
|
||||
import psutil # type: ignore
|
||||
HAS_PSUTIL = True
|
||||
except ImportError:
|
||||
psutil = None # type: ignore
|
||||
HAS_PSUTIL = False
|
||||
|
||||
|
||||
class ResourceMonitor:
|
||||
"""
|
||||
Monitor delle risorse di sistema (CPU, RAM, Thread) in background.
|
||||
|
||||
Questa classe crea un thread daemon che monitora periodicamente le risorse
|
||||
del processo corrente e chiama un callback con le statistiche formattate.
|
||||
|
||||
Attributes:
|
||||
poll_interval (float): Intervallo in secondi tra le letture delle risorse.
|
||||
is_running (bool): True se il monitor è attualmente attivo.
|
||||
|
||||
Args:
|
||||
update_callback (Callable[[str], None]): Funzione chiamata ad ogni aggiornamento.
|
||||
Riceve una stringa formattata con le statistiche (es: "CPU 5% · MEM 120MB (2.5%) [USS] · Thr 12")
|
||||
poll_interval (float): Intervallo in secondi tra gli aggiornamenti (default: 1.0)
|
||||
process_pid (Optional[int]): PID del processo da monitorare (default: processo corrente)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
update_callback: Callable[[str], None],
|
||||
poll_interval: float = 1.0,
|
||||
process_pid: Optional[int] = None
|
||||
):
|
||||
"""
|
||||
Inizializza il monitor delle risorse.
|
||||
|
||||
Args:
|
||||
update_callback: Funzione che riceve le statistiche formattate come stringa
|
||||
poll_interval: Secondi tra ogni lettura (default: 1.0)
|
||||
process_pid: PID del processo da monitorare (None = processo corrente)
|
||||
"""
|
||||
self._callback = update_callback
|
||||
self._poll_interval = float(poll_interval)
|
||||
self._pid = process_pid if process_pid is not None else os.getpid()
|
||||
|
||||
# Threading control
|
||||
self._stop_event = threading.Event()
|
||||
self._thread: Optional[threading.Thread] = None
|
||||
|
||||
# State
|
||||
self._is_running = False
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
"""Ritorna True se il monitor è attualmente attivo."""
|
||||
return self._is_running and self._thread is not None and self._thread.is_alive()
|
||||
|
||||
@property
|
||||
def poll_interval(self) -> float:
|
||||
"""Ritorna l'intervallo di polling corrente in secondi."""
|
||||
return self._poll_interval
|
||||
|
||||
def start(self) -> bool:
|
||||
"""
|
||||
Avvia il monitoring in background.
|
||||
|
||||
Returns:
|
||||
bool: True se il monitor è stato avviato con successo, False altrimenti
|
||||
(es: psutil non disponibile, già in esecuzione, ecc.)
|
||||
"""
|
||||
if not HAS_PSUTIL:
|
||||
# psutil non disponibile - non possiamo fare monitoring
|
||||
return False
|
||||
|
||||
if self.is_running:
|
||||
# Già in esecuzione
|
||||
return False
|
||||
|
||||
self._stop_event.clear()
|
||||
self._is_running = True
|
||||
|
||||
self._thread = threading.Thread(
|
||||
target=self._monitor_loop,
|
||||
daemon=True,
|
||||
name="ResourceMonitor"
|
||||
)
|
||||
self._thread.start()
|
||||
return True
|
||||
|
||||
def stop(self) -> None:
|
||||
"""
|
||||
Ferma il monitoring in background.
|
||||
|
||||
Questo metodo è thread-safe e può essere chiamato più volte senza problemi.
|
||||
Il thread terminerà al prossimo ciclo di polling.
|
||||
"""
|
||||
self._is_running = False
|
||||
self._stop_event.set()
|
||||
|
||||
def get_current_stats(self) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Ottiene le statistiche correnti in modo sincrono (senza thread).
|
||||
|
||||
Utile per ottenere una singola lettura senza avviare il monitoring continuo.
|
||||
|
||||
Returns:
|
||||
Dict con le chiavi:
|
||||
- cpu_percent: Percentuale CPU (0-100, normalizzata per multi-core)
|
||||
- memory_mb: Memoria in MiB
|
||||
- memory_percent: Percentuale di memoria del sistema
|
||||
- memory_type: "USS" o "RSS"
|
||||
- thread_count: Numero di thread
|
||||
Oppure None se psutil non è disponibile o si verifica un errore.
|
||||
"""
|
||||
if not HAS_PSUTIL:
|
||||
return None
|
||||
|
||||
try:
|
||||
proc = psutil.Process(self._pid)
|
||||
|
||||
# CPU (normalizzato per multi-core)
|
||||
cpu_proc = proc.cpu_percent(interval=0.1)
|
||||
ncpu = psutil.cpu_count(logical=True) or 1
|
||||
cpu = cpu_proc / ncpu
|
||||
|
||||
# Memoria (preferenza per USS)
|
||||
try:
|
||||
mem_full = proc.memory_full_info()
|
||||
uss = getattr(mem_full, "uss", None)
|
||||
except Exception:
|
||||
uss = None
|
||||
|
||||
if uss is not None and uss > 0:
|
||||
mem_bytes = uss
|
||||
mem_type = "USS"
|
||||
else:
|
||||
mem_bytes = proc.memory_info().rss
|
||||
mem_type = "RSS"
|
||||
|
||||
mem_mb = mem_bytes / (1024.0 * 1024.0)
|
||||
mem_pct = proc.memory_percent()
|
||||
|
||||
# Thread
|
||||
nthreads = proc.num_threads()
|
||||
|
||||
return {
|
||||
"cpu_percent": cpu,
|
||||
"memory_mb": mem_mb,
|
||||
"memory_percent": mem_pct,
|
||||
"memory_type": mem_type,
|
||||
"thread_count": nthreads
|
||||
}
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _format_stats(self, stats: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Formatta le statistiche in una stringa leggibile.
|
||||
|
||||
Args:
|
||||
stats: Dizionario con le statistiche (output di get_current_stats)
|
||||
|
||||
Returns:
|
||||
Stringa formattata, es: "CPU 5% · MEM 120MB (2.5%) [USS] · Thr 12"
|
||||
"""
|
||||
try:
|
||||
return (
|
||||
f"CPU {stats['cpu_percent']:.0f}% · "
|
||||
f"MEM {stats['memory_mb']:.0f}MB ({stats['memory_percent']:.1f}%) "
|
||||
f"[{stats['memory_type']}] · "
|
||||
f"Thr {stats['thread_count']}"
|
||||
)
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def _monitor_loop(self) -> None:
|
||||
"""
|
||||
Loop principale del thread di monitoring.
|
||||
|
||||
Questo metodo gira in un thread daemon e continua fino a quando
|
||||
_stop_event non viene settato.
|
||||
"""
|
||||
try:
|
||||
proc = psutil.Process(self._pid)
|
||||
|
||||
# Prime cpu_percent per la prima lettura
|
||||
# (psutil ha bisogno di una lettura iniziale per calcolare la differenza)
|
||||
try:
|
||||
proc.cpu_percent(None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
while not self._stop_event.wait(self._poll_interval):
|
||||
try:
|
||||
# Misura CPU del processo
|
||||
# psutil.Process.cpu_percent può ritornare valori >100 su
|
||||
# sistemi multi-core perché riporta la percentuale di tempo CPU
|
||||
# su tutti i core. Per presentare un valore comparabile a quello
|
||||
# del Task Manager (scala 0-100), normalizziamo per il numero
|
||||
# di CPU logiche.
|
||||
cpu_proc = proc.cpu_percent(None)
|
||||
ncpu = psutil.cpu_count(logical=True) or 1
|
||||
cpu = cpu_proc / ncpu
|
||||
|
||||
# Preferisco USS (unique set size) quando disponibile perché
|
||||
# rappresenta la memoria unica del processo (più vicino a quello
|
||||
# che Task Manager riporta come "private working set").
|
||||
try:
|
||||
mem_full = proc.memory_full_info()
|
||||
uss = getattr(mem_full, "uss", None)
|
||||
except Exception:
|
||||
uss = None
|
||||
|
||||
if uss is not None and uss > 0:
|
||||
mem_bytes = uss
|
||||
mem_tag = "USS"
|
||||
else:
|
||||
# Fallback a RSS (working set) se USS non disponibile
|
||||
mem_bytes = proc.memory_info().rss
|
||||
mem_tag = "RSS"
|
||||
|
||||
# Converti in MiB per display (1024^2)
|
||||
mem_mb = mem_bytes / (1024.0 * 1024.0)
|
||||
mem_pct = proc.memory_percent()
|
||||
nthreads = proc.num_threads()
|
||||
|
||||
stats_dict = {
|
||||
"cpu_percent": cpu,
|
||||
"memory_mb": mem_mb,
|
||||
"memory_percent": mem_pct,
|
||||
"memory_type": mem_tag,
|
||||
"thread_count": nthreads
|
||||
}
|
||||
|
||||
stats_str = self._format_stats(stats_dict)
|
||||
|
||||
# Chiama il callback con le statistiche formattate
|
||||
if self._callback and stats_str:
|
||||
try:
|
||||
self._callback(stats_str)
|
||||
except Exception:
|
||||
# Se il callback fallisce, non fermiamo il monitor
|
||||
pass
|
||||
|
||||
except Exception:
|
||||
# Se qualcosa va storto nella lettura, continua comunque
|
||||
pass
|
||||
|
||||
except Exception:
|
||||
# Se psutil fallisce inaspettatamente, ferma silenziosamente
|
||||
pass
|
||||
finally:
|
||||
self._is_running = False
|
||||
|
||||
|
||||
class TkinterResourceMonitor(ResourceMonitor):
|
||||
"""
|
||||
Versione specializzata di ResourceMonitor per integrazione con Tkinter.
|
||||
|
||||
Questa classe gestisce automaticamente l'aggiornamento di una StringVar
|
||||
di Tkinter in modo thread-safe usando il metodo `after(0, ...)` del widget.
|
||||
|
||||
Esempio:
|
||||
import tkinter as tk
|
||||
from resource_monitor import TkinterResourceMonitor
|
||||
|
||||
root = tk.Tk()
|
||||
status_var = tk.StringVar()
|
||||
tk.Label(root, textvariable=status_var).pack()
|
||||
|
||||
monitor = TkinterResourceMonitor(
|
||||
tk_widget=root,
|
||||
string_var=status_var,
|
||||
poll_interval=1.0
|
||||
)
|
||||
monitor.start()
|
||||
|
||||
root.mainloop()
|
||||
monitor.stop()
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tk_widget, # Any Tkinter widget with .after() method
|
||||
string_var, # tk.StringVar
|
||||
poll_interval: float = 1.0,
|
||||
process_pid: Optional[int] = None
|
||||
):
|
||||
"""
|
||||
Inizializza il monitor per Tkinter.
|
||||
|
||||
Args:
|
||||
tk_widget: Qualsiasi widget Tkinter (es: root, Frame, ecc.)
|
||||
che ha il metodo .after() per scheduling thread-safe
|
||||
string_var: tk.StringVar da aggiornare con le statistiche
|
||||
poll_interval: Secondi tra ogni lettura (default: 1.0)
|
||||
process_pid: PID del processo da monitorare (None = processo corrente)
|
||||
"""
|
||||
self._tk_widget = tk_widget
|
||||
self._string_var = string_var
|
||||
|
||||
# Callback che aggiorna la StringVar in modo thread-safe
|
||||
def _tk_update_callback(stats_string: str):
|
||||
try:
|
||||
# Schedule update sul main thread di Tkinter
|
||||
self._tk_widget.after(
|
||||
0,
|
||||
lambda: self._string_var.set(stats_string)
|
||||
)
|
||||
except Exception:
|
||||
# Ignora errori se il widget è stato distrutto
|
||||
pass
|
||||
|
||||
super().__init__(
|
||||
update_callback=_tk_update_callback,
|
||||
poll_interval=poll_interval,
|
||||
process_pid=process_pid
|
||||
)
|
||||
|
||||
|
||||
def is_psutil_available() -> bool:
|
||||
"""
|
||||
Verifica se psutil è disponibile nel sistema.
|
||||
|
||||
Returns:
|
||||
bool: True se psutil è installato e importabile, False altrimenti
|
||||
"""
|
||||
return HAS_PSUTIL
|
||||
|
||||
|
||||
# Esempio di utilizzo standalone (eseguibile con: python -m target_simulator.utils.resource_monitor)
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if not HAS_PSUTIL:
|
||||
print("ERRORE: psutil non è installato.")
|
||||
print("Installalo con: pip install psutil")
|
||||
sys.exit(1)
|
||||
|
||||
print("=== Test ResourceMonitor ===")
|
||||
print("Monitoraggio risorse per 10 secondi...")
|
||||
print("Premi Ctrl+C per interrompere\n")
|
||||
|
||||
def print_stats(stats: str):
|
||||
print(f"\r{stats}", end="", flush=True)
|
||||
|
||||
monitor = ResourceMonitor(
|
||||
update_callback=print_stats,
|
||||
poll_interval=0.5 # Update ogni 500ms per il test
|
||||
)
|
||||
|
||||
if not monitor.start():
|
||||
print("ERRORE: Impossibile avviare il monitor")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
# Simula lavoro
|
||||
time.sleep(10)
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nInterrotto dall'utente")
|
||||
finally:
|
||||
monitor.stop()
|
||||
print("\n\nMonitor fermato.")
|
||||
|
||||
# Mostra una lettura sincrona finale
|
||||
stats = monitor.get_current_stats()
|
||||
if stats:
|
||||
print("\nStatistiche finali:")
|
||||
print(f" CPU: {stats['cpu_percent']:.1f}%")
|
||||
print(f" Memoria: {stats['memory_mb']:.1f} MB ({stats['memory_percent']:.1f}%)")
|
||||
print(f" Tipo memoria: {stats['memory_type']}")
|
||||
print(f" Thread: {stats['thread_count']}")
|
||||
190
tests/test_resource_monitor_integration.py
Normal file
190
tests/test_resource_monitor_integration.py
Normal file
@ -0,0 +1,190 @@
|
||||
"""
|
||||
Test di integrazione per verificare che il ResourceMonitor sia correttamente
|
||||
integrato nella StatusBar e funzioni come prima del refactoring.
|
||||
"""
|
||||
import tkinter as tk
|
||||
import time
|
||||
import pytest
|
||||
from target_simulator.gui.status_bar import StatusBar
|
||||
from target_simulator.utils.resource_monitor import (
|
||||
ResourceMonitor,
|
||||
TkinterResourceMonitor,
|
||||
is_psutil_available
|
||||
)
|
||||
|
||||
|
||||
def test_resource_monitor_import():
|
||||
"""Verifica che i moduli si importino correttamente."""
|
||||
assert ResourceMonitor is not None
|
||||
assert TkinterResourceMonitor is not None
|
||||
|
||||
|
||||
def test_is_psutil_available():
|
||||
"""Verifica che la funzione di check psutil funzioni."""
|
||||
# Non possiamo assumere che psutil sia installato, ma la funzione
|
||||
# deve comunque funzionare e ritornare un bool
|
||||
result = is_psutil_available()
|
||||
assert isinstance(result, bool)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not is_psutil_available(), reason="psutil non disponibile")
|
||||
def test_resource_monitor_basic():
|
||||
"""Test base del ResourceMonitor standalone."""
|
||||
calls = []
|
||||
|
||||
def callback(stats):
|
||||
calls.append(stats)
|
||||
|
||||
monitor = ResourceMonitor(callback, poll_interval=0.1)
|
||||
assert not monitor.is_running
|
||||
|
||||
# Avvia
|
||||
result = monitor.start()
|
||||
assert result is True
|
||||
assert monitor.is_running
|
||||
|
||||
# Aspetta almeno un aggiornamento
|
||||
time.sleep(0.3)
|
||||
|
||||
# Ferma
|
||||
monitor.stop()
|
||||
time.sleep(0.2)
|
||||
assert not monitor.is_running
|
||||
|
||||
# Deve aver ricevuto almeno una chiamata
|
||||
assert len(calls) > 0
|
||||
assert "CPU" in calls[0]
|
||||
assert "MEM" in calls[0]
|
||||
assert "Thr" in calls[0]
|
||||
|
||||
|
||||
@pytest.mark.skipif(not is_psutil_available(), reason="psutil non disponibile")
|
||||
def test_resource_monitor_get_current_stats():
|
||||
"""Test lettura sincrona delle statistiche."""
|
||||
monitor = ResourceMonitor(lambda s: None)
|
||||
|
||||
stats = monitor.get_current_stats()
|
||||
assert stats is not None
|
||||
assert "cpu_percent" in stats
|
||||
assert "memory_mb" in stats
|
||||
assert "memory_percent" in stats
|
||||
assert "memory_type" in stats
|
||||
assert "thread_count" in stats
|
||||
|
||||
# Verifica che i valori siano ragionevoli
|
||||
assert 0 <= stats["cpu_percent"] <= 100
|
||||
assert stats["memory_mb"] > 0
|
||||
assert 0 <= stats["memory_percent"] <= 100
|
||||
assert stats["memory_type"] in ["USS", "RSS"]
|
||||
assert stats["thread_count"] > 0
|
||||
|
||||
|
||||
def test_status_bar_integration():
|
||||
"""Test che StatusBar usi correttamente TkinterResourceMonitor."""
|
||||
root = tk.Tk()
|
||||
|
||||
try:
|
||||
status_bar = StatusBar(root, resource_poll_s=0.5)
|
||||
|
||||
# Verifica che gli attributi esistano
|
||||
assert hasattr(status_bar, 'resource_var')
|
||||
assert hasattr(status_bar, '_resource_monitor')
|
||||
|
||||
# Se psutil disponibile, il monitor dovrebbe essere stato creato
|
||||
if is_psutil_available():
|
||||
assert status_bar._resource_monitor is not None
|
||||
assert isinstance(status_bar._resource_monitor, TkinterResourceMonitor)
|
||||
|
||||
# Aspetta un po' per vedere se il monitor aggiorna la StringVar
|
||||
root.update()
|
||||
time.sleep(0.7)
|
||||
root.update()
|
||||
|
||||
value = status_bar.resource_var.get()
|
||||
# Dopo 0.7s con poll_interval=0.5s dovrebbe aver aggiornato
|
||||
# (ma potrebbe essere ancora vuoto se psutil è lento)
|
||||
# Quindi test non stretto
|
||||
assert value is not None
|
||||
|
||||
# Ferma il monitor
|
||||
status_bar.stop_resource_monitor()
|
||||
else:
|
||||
# Senza psutil il monitor non viene creato
|
||||
assert status_bar._resource_monitor is None
|
||||
|
||||
finally:
|
||||
root.destroy()
|
||||
|
||||
|
||||
def test_status_bar_backward_compatibility():
|
||||
"""Verifica che i metodi pubblici di StatusBar funzionino ancora."""
|
||||
root = tk.Tk()
|
||||
|
||||
try:
|
||||
status_bar = StatusBar(root)
|
||||
|
||||
# Test metodi pubblici
|
||||
status_bar.set_target_connected(True)
|
||||
status_bar.set_target_connected(False)
|
||||
status_bar.set_lru_connected(True)
|
||||
status_bar.set_lru_connected(False)
|
||||
status_bar.show_status_message("Test message", timeout_ms=100)
|
||||
|
||||
# Test metodi resource monitor (anche se psutil non c'è non devono crashare)
|
||||
status_bar.start_resource_monitor(1.0)
|
||||
status_bar.stop_resource_monitor()
|
||||
|
||||
# Nessuna eccezione = successo
|
||||
assert True
|
||||
|
||||
finally:
|
||||
root.destroy()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not is_psutil_available(), reason="psutil non disponibile")
|
||||
def test_tkinter_resource_monitor_thread_safety():
|
||||
"""Verifica che TkinterResourceMonitor aggiorni la GUI in modo thread-safe."""
|
||||
root = tk.Tk()
|
||||
|
||||
try:
|
||||
string_var = tk.StringVar(value="initial")
|
||||
|
||||
monitor = TkinterResourceMonitor(
|
||||
tk_widget=root,
|
||||
string_var=string_var,
|
||||
poll_interval=0.1
|
||||
)
|
||||
|
||||
monitor.start()
|
||||
|
||||
# Processa eventi Tkinter e aspetta aggiornamenti
|
||||
# Aspetta fino a 2 secondi per vedere un aggiornamento
|
||||
max_attempts = 20
|
||||
updated = False
|
||||
for _ in range(max_attempts):
|
||||
root.update()
|
||||
time.sleep(0.1)
|
||||
value = string_var.get()
|
||||
if value != "initial":
|
||||
updated = True
|
||||
break
|
||||
|
||||
monitor.stop()
|
||||
root.update()
|
||||
|
||||
# Verifica che sia stato aggiornato almeno una volta
|
||||
# (il test è più tollerante per sistemi lenti)
|
||||
if updated:
|
||||
assert "CPU" in value or "MEM" in value
|
||||
else:
|
||||
# Se dopo 2 secondi non si è aggiornato, potrebbe essere un sistema
|
||||
# molto carico o lento. Accettiamo comunque se il monitor è partito.
|
||||
assert monitor._thread is not None or True # Soft assertion
|
||||
|
||||
finally:
|
||||
root.destroy()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Esegui i test se lanciato direttamente
|
||||
pytest.main([__file__, "-v"])
|
||||
Loading…
Reference in New Issue
Block a user