L'application ne demarrait pas du tout : gui/app.py importait pyqtSignal, qui est le nom PyQt du signal, absent de PySide6. Trois autres defauts bloquaient juste derriere, une fois celui-ci leve : - MPLBACKEND etait fixe a 'module://backend_interagg', le backend interne de PyCharm, indisponible hors de cet IDE ; - gui/app.py et monitor/monitor.py utilisaient des imports relatifs remontant au-dela du paquet racine charge par main.py ; - core/ n'avait pas de __init__.py. Corrige aussi get_statistics(), dont le retour anticipe "historique vide" omettait connection_loss_count et uptime_percentage : le KeyError qui en resultait cassait silencieusement tout l'onglet Statistiques a chaque rafraichissement. Les deux chemins renvoient desormais les memes cles. Ajoute le build Windows (StabilityTester.spec + make build-windows), qui produit un .exe autonome depuis Linux via Wine. PySide6 y est epingle a 6.9.3 : a partir de 6.10, Qt6Core.dll depend de l'ICU systeme de Windows que Wine n'implemente pas. La sortie standard est reconfiguree en UTF-8 dans main.py, les emojis des messages faisant echouer cp1252 sous Windows. make test etait casse par la meme confusion de paquet et passe a nouveau. Makefile : libgl1-mesa-glx, retire des depots Debian/Ubuntu recents, remplace par libgl1 et les dependances Qt6 manquantes.
430 lines
14 KiB
Python
430 lines
14 KiB
Python
"""
|
|
Module de test réseau - Effectue divers tests de connectivité et de performance
|
|
"""
|
|
|
|
import subprocess
|
|
import time
|
|
import requests
|
|
import socket
|
|
import json
|
|
import threading
|
|
from dataclasses import dataclass
|
|
from typing import Optional, Dict, Any
|
|
from datetime import datetime
|
|
|
|
# Import optionnel de speedtest
|
|
try:
|
|
import speedtest
|
|
SPEEDTEST_AVAILABLE = True
|
|
except ImportError:
|
|
SPEEDTEST_AVAILABLE = False
|
|
speedtest = None
|
|
|
|
# Import optionnel de ping3
|
|
try:
|
|
import ping3
|
|
PING3_AVAILABLE = True
|
|
except ImportError:
|
|
PING3_AVAILABLE = False
|
|
ping3 = None
|
|
|
|
|
|
@dataclass
|
|
class TestResult:
|
|
"""Résultat d'un test réseau"""
|
|
timestamp: datetime
|
|
test_type: str
|
|
success: bool
|
|
latency_ms: Optional[float] = None
|
|
download_speed: Optional[float] = None # en Mbps
|
|
upload_speed: Optional[float] = None # en Mbps
|
|
packet_loss: Optional[float] = None # en %
|
|
jitter: Optional[float] = None # en ms
|
|
error_message: Optional[str] = None
|
|
server: Optional[str] = None
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return {
|
|
'timestamp': self.timestamp.isoformat(),
|
|
'test_type': self.test_type,
|
|
'success': self.success,
|
|
'latency_ms': self.latency_ms,
|
|
'download_speed': self.download_speed,
|
|
'upload_speed': self.upload_speed,
|
|
'packet_loss': self.packet_loss,
|
|
'jitter': self.jitter,
|
|
'error_message': self.error_message,
|
|
'server': self.server
|
|
}
|
|
|
|
|
|
class NetworkTester:
|
|
"""
|
|
Classe pour effectuer divers tests de connectivité et de performance réseau
|
|
"""
|
|
|
|
# Serveurs de test par défaut
|
|
PING_SERVERS = [
|
|
"8.8.8.8", # Google DNS
|
|
"1.1.1.1", # Cloudflare DNS
|
|
"9.9.9.9", # Quad9 DNS
|
|
"8.8.4.4", # Google DNS secondaire
|
|
"208.67.222.222" # OpenDNS
|
|
]
|
|
|
|
SPEEDTEST_SERVERS = [
|
|
{"name": "Paris", "id": 12345}, # ID à adapter selon votre région
|
|
{"name": "Lyon", "id": 67890},
|
|
]
|
|
|
|
# URL pour tester le téléchargement
|
|
DOWNLOAD_TEST_URL = "https://speedtest.ftp.otenet.gr/files/test1Mb.db"
|
|
UPLOAD_TEST_URL = "https://httpbin.org/post"
|
|
|
|
def __init__(self, timeout: int = 5):
|
|
"""
|
|
Initialisation du testeur réseau
|
|
|
|
Args:
|
|
timeout: Timeout en secondes pour les tests
|
|
"""
|
|
self.timeout = timeout
|
|
self.last_test_time = None
|
|
|
|
def ping_test(self, server: str = None, count: int = 4) -> TestResult:
|
|
"""
|
|
Effectue un test de ping vers un serveur
|
|
|
|
Args:
|
|
server: Adresse IP ou hostname à pinger (par défaut, utilise le premier serveur)
|
|
count: Nombre de paquets à envoyer
|
|
|
|
Returns:
|
|
TestResult: Résultat du test
|
|
"""
|
|
if server is None:
|
|
server = self.PING_SERVERS[0]
|
|
|
|
if not PING3_AVAILABLE:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="ping",
|
|
success=False,
|
|
error_message="ping3 non installé. Installez avec: pip install ping3"
|
|
)
|
|
|
|
try:
|
|
# Utilisation de ping3 pour plus de fiabilité
|
|
start_time = time.time()
|
|
|
|
# Effectuer le ping
|
|
latency_ms = ping3.ping(server, unit='ms', timeout=self.timeout)
|
|
|
|
if latency_ms is None or latency_ms is False:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="ping",
|
|
success=False,
|
|
error_message=f"Échec du ping vers {server}",
|
|
server=server
|
|
)
|
|
|
|
# Calcul du jitter (variation de latence)
|
|
latencies = []
|
|
for _ in range(count):
|
|
lat = ping3.ping(server, unit='ms', timeout=self.timeout)
|
|
if lat is not None and lat is not False:
|
|
latencies.append(lat)
|
|
|
|
jitter = max(latencies) - min(latencies) if latencies else 0
|
|
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="ping",
|
|
success=True,
|
|
latency_ms=latency_ms,
|
|
jitter=jitter,
|
|
server=server
|
|
)
|
|
|
|
except Exception as e:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="ping",
|
|
success=False,
|
|
error_message=f"Erreur lors du ping: {str(e)}",
|
|
server=server
|
|
)
|
|
|
|
def ping_all_servers(self) -> list:
|
|
"""
|
|
Ping tous les serveurs configurés
|
|
|
|
Returns:
|
|
Liste de TestResult pour chaque serveur
|
|
"""
|
|
results = []
|
|
for server in self.PING_SERVERS:
|
|
result = self.ping_test(server, count=2)
|
|
results.append(result)
|
|
time.sleep(0.5) # Petite pause entre les tests
|
|
return results
|
|
|
|
def connection_test(self, url: str = "https://www.google.com") -> TestResult:
|
|
"""
|
|
Teste la connectivité HTTP vers une URL
|
|
|
|
Args:
|
|
url: URL à tester
|
|
|
|
Returns:
|
|
TestResult: Résultat du test
|
|
"""
|
|
try:
|
|
start_time = time.time()
|
|
response = requests.get(url, timeout=self.timeout)
|
|
latency = (time.time() - start_time) * 1000 # en ms
|
|
|
|
if response.status_code < 400:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="http_connection",
|
|
success=True,
|
|
latency_ms=latency,
|
|
server=url
|
|
)
|
|
else:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="http_connection",
|
|
success=False,
|
|
error_message=f"Code HTTP: {response.status_code}",
|
|
server=url
|
|
)
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="http_connection",
|
|
success=False,
|
|
error_message=f"Erreur de connexion: {str(e)}",
|
|
server=url
|
|
)
|
|
|
|
def dns_test(self, domain: str = "google.com") -> TestResult:
|
|
"""
|
|
Teste la résolution DNS
|
|
|
|
Args:
|
|
domain: Domaine à résoudre
|
|
|
|
Returns:
|
|
TestResult: Résultat du test
|
|
"""
|
|
try:
|
|
start_time = time.time()
|
|
# Résolution DNS
|
|
ip_addresses = socket.getaddrinfo(domain, None)
|
|
latency = (time.time() - start_time) * 1000
|
|
|
|
if ip_addresses:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="dns_resolution",
|
|
success=True,
|
|
latency_ms=latency,
|
|
server=domain
|
|
)
|
|
else:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="dns_resolution",
|
|
success=False,
|
|
error_message="Aucune adresse IP trouvée",
|
|
server=domain
|
|
)
|
|
|
|
except socket.gaierror as e:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="dns_resolution",
|
|
success=False,
|
|
error_message=f"Erreur DNS: {str(e)}",
|
|
server=domain
|
|
)
|
|
|
|
def download_speed_test(self, url: str = None, duration: int = 5) -> TestResult:
|
|
"""
|
|
Teste la vitesse de téléchargement
|
|
|
|
Args:
|
|
url: URL du fichier à télécharger
|
|
duration: Durée du test en secondes
|
|
|
|
Returns:
|
|
TestResult: Résultat du test
|
|
"""
|
|
if url is None:
|
|
url = self.DOWNLOAD_TEST_URL
|
|
|
|
try:
|
|
start_time = time.time()
|
|
response = requests.get(url, stream=True, timeout=self.timeout)
|
|
|
|
# Lire les données par blocs pour mesurer la vitesse
|
|
bytes_received = 0
|
|
chunk_size = 1024 * 1024 # 1 Mo
|
|
|
|
while time.time() - start_time < duration:
|
|
chunk = response.raw.read(chunk_size)
|
|
if not chunk:
|
|
break
|
|
bytes_received += len(chunk)
|
|
|
|
elapsed = time.time() - start_time
|
|
speed_mbps = (bytes_received * 8) / (1024 * 1024) / elapsed if elapsed > 0 else 0
|
|
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="download_speed",
|
|
success=True,
|
|
download_speed=round(speed_mbps, 2),
|
|
server=url
|
|
)
|
|
|
|
except Exception as e:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="download_speed",
|
|
success=False,
|
|
error_message=f"Erreur de téléchargement: {str(e)}",
|
|
server=url
|
|
)
|
|
|
|
def upload_speed_test(self, url: str = None, data_size: int = 1024 * 1024) -> TestResult:
|
|
"""
|
|
Teste la vitesse d'upload
|
|
|
|
Args:
|
|
url: URL pour le test d'upload
|
|
data_size: Taille des données à uploader en octets
|
|
|
|
Returns:
|
|
TestResult: Résultat du test
|
|
"""
|
|
if url is None:
|
|
url = self.UPLOAD_TEST_URL
|
|
|
|
try:
|
|
# Créer des données aléatoires
|
|
data = b'0' * data_size
|
|
|
|
start_time = time.time()
|
|
response = requests.post(url, data=data, timeout=self.timeout * 2)
|
|
elapsed = time.time() - start_time
|
|
|
|
speed_mbps = (data_size * 8) / (1024 * 1024) / elapsed if elapsed > 0 else 0
|
|
|
|
if response.status_code < 400:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="upload_speed",
|
|
success=True,
|
|
upload_speed=round(speed_mbps, 2),
|
|
server=url
|
|
)
|
|
else:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="upload_speed",
|
|
success=False,
|
|
error_message=f"Code HTTP: {response.status_code}",
|
|
server=url
|
|
)
|
|
|
|
except Exception as e:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="upload_speed",
|
|
success=False,
|
|
error_message=f"Erreur d'upload: {str(e)}",
|
|
server=url
|
|
)
|
|
|
|
def full_speed_test(self) -> TestResult:
|
|
"""
|
|
Effectue un test complet de vitesse (download + upload) avec speedtest-cli
|
|
|
|
Returns:
|
|
TestResult: Résultat du test
|
|
"""
|
|
if not SPEEDTEST_AVAILABLE:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="full_speed_test",
|
|
success=False,
|
|
error_message="speedtest-cli non installé. Installez avec: pip install speedtest-cli"
|
|
)
|
|
|
|
try:
|
|
st = speedtest.Speedtest()
|
|
|
|
# Sélection du meilleur serveur
|
|
st.get_best_server()
|
|
|
|
# Test de téléchargement
|
|
st.download()
|
|
download_speed = st.results.dict()["download"] / 1024 / 1024 # en Mbps
|
|
|
|
# Test d'upload
|
|
st.upload()
|
|
upload_speed = st.results.dict()["upload"] / 1024 / 1024 # en Mbps
|
|
|
|
# Latence (ping)
|
|
latency = st.results.dict()["ping"]
|
|
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="full_speed_test",
|
|
success=True,
|
|
latency_ms=latency,
|
|
download_speed=round(download_speed, 2),
|
|
upload_speed=round(upload_speed, 2),
|
|
server=st.results.dict()["server"]["name"]
|
|
)
|
|
|
|
except Exception as e:
|
|
return TestResult(
|
|
timestamp=datetime.now(),
|
|
test_type="full_speed_test",
|
|
success=False,
|
|
error_message=f"Erreur lors du speedtest: {str(e)}"
|
|
)
|
|
|
|
def comprehensive_test(self) -> Dict[str, TestResult]:
|
|
"""
|
|
Effectue une série complète de tests réseau
|
|
|
|
Returns:
|
|
Dictionnaire avec les résultats de tous les tests
|
|
"""
|
|
results = {}
|
|
|
|
# Test de connectivité
|
|
results['connection'] = self.connection_test()
|
|
|
|
# Test DNS
|
|
results['dns'] = self.dns_test()
|
|
|
|
# Tests de ping
|
|
results['ping_google'] = self.ping_test("8.8.8.8")
|
|
results['ping_cloudflare'] = self.ping_test("1.1.1.1")
|
|
|
|
# Test de vitesse de téléchargement (rapide)
|
|
results['download'] = self.download_speed_test(duration=3)
|
|
|
|
# Test de vitesse d'upload (rapide)
|
|
results['upload'] = self.upload_speed_test(data_size=512 * 1024)
|
|
|
|
return results
|