147 lines
4.7 KiB
Python
147 lines
4.7 KiB
Python
import argparse
|
|
import datetime
|
|
import subprocess
|
|
import re
|
|
import os
|
|
import json
|
|
import sys
|
|
|
|
# BERSAGLIO (Cloudflare è solitamente il più stabile per i ping)
|
|
TARGET_HOST = "1.1.1.1"
|
|
|
|
# SOGLIE DI ALLARME
|
|
LIMIT_LOSS = 5.0 # % di pacchetti persi (sopra il 5% è grave)
|
|
LIMIT_JITTER = 30.0 # ms di deviazione (sopra 30ms lagga la voce/gioco)
|
|
|
|
# File di stato
|
|
STATE_FILE = "/home/daniely/docker/telegram-bot/quality_state.json"
|
|
LOG_FILE = "/home/daniely/docker/telegram-bot/quality_log.txt"
|
|
|
|
|
|
def log_line(message: str) -> None:
|
|
ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
try:
|
|
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
|
f.write(f"{ts} {message}\n")
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def send_alert(title: str, body: str, severity: str = "warning") -> None:
|
|
"""Invia alert alla WebApp Loogle Casa."""
|
|
sys.path.insert(0, "/home/daniely/docker/shared")
|
|
from loogle_core.alert_dispatcher import dispatch_alert
|
|
|
|
dispatch_alert(title, body, category="net_quality", severity=severity)
|
|
|
|
|
|
def load_state():
|
|
if os.path.exists(STATE_FILE):
|
|
try:
|
|
with open(STATE_FILE, "r") as f:
|
|
return json.load(f)
|
|
except Exception:
|
|
pass
|
|
return {"alert_active": False}
|
|
|
|
|
|
def save_state(active):
|
|
try:
|
|
with open(STATE_FILE, "w") as f:
|
|
json.dump({"alert_active": active}, f)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def measure_quality(dry_run: bool = False):
|
|
print("--- Avvio Test Qualità Linea ---")
|
|
log_line("INFO Avvio Test Qualità Linea")
|
|
|
|
cmd = f"ping -c 50 -i 0.2 -q -w 15 {TARGET_HOST}"
|
|
|
|
try:
|
|
output = subprocess.check_output(cmd, shell=True).decode("utf-8")
|
|
except subprocess.CalledProcessError as e:
|
|
output = e.output.decode("utf-8") if e.output else ""
|
|
if not output:
|
|
print("Errore critico: Nessuna connessione.")
|
|
return
|
|
|
|
loss_match = re.search(r"([0-9]+(?:[\\.,][0-9]+)?)% packet loss", output)
|
|
if loss_match:
|
|
loss_raw = loss_match.group(1).replace(",", ".")
|
|
try:
|
|
loss = float(loss_raw)
|
|
except Exception:
|
|
loss = 100.0
|
|
else:
|
|
loss = 100.0
|
|
if loss < 0:
|
|
loss = 0.0
|
|
if loss > 100:
|
|
loss = 100.0
|
|
|
|
jitter = 0.0
|
|
rtt_match = re.search(
|
|
r"rtt min/avg/max/mdev = ([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+)", output
|
|
)
|
|
|
|
if rtt_match:
|
|
avg_ping = float(rtt_match.group(2))
|
|
jitter = float(rtt_match.group(4))
|
|
else:
|
|
avg_ping = 0.0
|
|
|
|
result_line = f"Risultati: Loss={loss:.2f}% | Jitter={jitter}ms | AvgPing={avg_ping}ms"
|
|
print(result_line)
|
|
log_line(f"INFO {result_line}")
|
|
|
|
# Ping senza risposte (es. iptables DROP icmp locale) → misura non affidabile.
|
|
if loss >= 99.0 and avg_ping == 0.0 and jitter == 0.0:
|
|
print("Misura non affidabile (ping senza risposte, possibile blocco ICMP locale).")
|
|
log_line("WARN Misura non affidabile: ping senza risposte")
|
|
return
|
|
|
|
state = load_state()
|
|
was_active = state.get("alert_active", False)
|
|
is_bad = (loss >= LIMIT_LOSS) or (jitter >= LIMIT_JITTER)
|
|
|
|
if is_bad:
|
|
if not was_active:
|
|
body_parts = ["Degrado qualità linea rilevato."]
|
|
if loss >= LIMIT_LOSS:
|
|
body_parts.append(f"Packet loss: {loss:.2f}% (soglia {LIMIT_LOSS}%)")
|
|
if jitter >= LIMIT_JITTER:
|
|
body_parts.append(f"Jitter: {jitter}ms (soglia {LIMIT_JITTER}ms)")
|
|
body_parts.append(f"Ping medio: {avg_ping}ms")
|
|
body = "\n".join(body_parts)
|
|
if not dry_run:
|
|
send_alert("Degrado qualità linea", body, severity="warning")
|
|
save_state(True)
|
|
print("Allarme inviato." if not dry_run else "Allarme (dry-run).")
|
|
log_line("WARN Allarme inviato")
|
|
else:
|
|
print("Qualità ancora scarsa (già notificato).")
|
|
log_line("WARN Qualità ancora scarsa (già notificato)")
|
|
|
|
elif was_active and not is_bad:
|
|
body = (
|
|
f"Qualità linea ripristinata.\n"
|
|
f"Ping: {avg_ping}ms | Jitter: {jitter}ms | Loss: {loss:.2f}%"
|
|
)
|
|
if not dry_run:
|
|
send_alert("Qualità linea ripristinata", body, severity="info")
|
|
save_state(False)
|
|
print("Recovery inviata." if not dry_run else "Recovery (dry-run).")
|
|
log_line("INFO Recovery inviata")
|
|
else:
|
|
print("Linea OK.")
|
|
log_line("INFO Linea OK")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Network quality monitor")
|
|
parser.add_argument("--dry-run", action="store_true", help="Esegui senza inviare notifiche")
|
|
args = parser.parse_args()
|
|
measure_quality(dry_run=args.dry_run)
|