Problema
En entornos domésticos o de pequeña empresa que usan routers basados en Linux, la mayoría de los sistemas de monitoreo se reduce a umbrales estáticos: “RTT > 300 ms → alerta”. Ese enfoque funciona mientras la red es estable, pero falla en el momento en que un nodo empieza a degradarse lentamente. La latencia sube gradualmente, el jitter varía y el tráfico se redistribuye, pero ninguno de esos valores cruza el límite definido. El resultado es una interrupción inesperada que solo se detecta cuando el usuario ya está experimentando buffering o caídas de conexión. El patrón típico es: múltiples proxies o enlaces con métricas que cambian de forma correlacionada, pero sin que ninguna métrica individual sea “mala”. Necesitamos una forma de observar la estructura global del conjunto de nodos y detectar cuándo esa estructura se está deformando.
Causa
- Umbrales estáticos mal calibrados – Cada ruta tiene características diferentes (distancia geográfica, capacidad del ISP). Un único valor de RTT no refleja la salud real.
- Correlación implícita entre métricas – Latencia, jitter, uptime y tendencia forman un espacio de características. Cuando una dimensión se desvía, el resto compensa y el punto sigue dentro del rango aceptable.
- Cambios de ruta externos – Problemas en el backbone del ISP o en la ruta de tránsito afectan a varios nodos simultáneamente. Los monitores por nodo no los agrupan, por lo que la alerta se dispersa.
- Falta de historial adaptativo – Los sistemas que no aprenden de datos históricos no pueden distinguir “ruido” de una verdadera deriva estructural.
Solución
Utilizar Topological Data Analysis (TDA) para monitorizar la nube de puntos que representa cada nodo. En lugar de observar valores aislados, se calcula la persistent homology del conjunto y se extrae un landscape que describe cuán compacto o disperso está el espacio de características. Cambios significativos en ese landscape indican que la topología del sistema está variando, lo que suele preceder a una falla observable.
Paso a paso general
-
Recolectar métricas
- Latencia promedio, jitter, porcentaje de tiempo activo, dirección de la tendencia (↑/↓).
- Los datos pueden provenir de eBPF (por ejemplo, el caché interno de
daed), de SNMP, o de cualquier exporter que exponga los valores en Prometheus.
-
Normalizar y vectorizar
Cada nodo se convierte en un vectorv = [lat, jitter, uptime, trend]. Se aplica una normalización (z‑score) para que todas las dimensiones tengan peso comparable. -
Construir la nube de puntos
Se guarda un snapshot cada 30 min en una tabla SQLite o en un archivo Parquet. Cada snapshot contiene los vectores de todos los nodos. -
Calcular persistent homology
- Usar una biblioteca como
gudhioripseren Python. - Generar el persistence diagram y convertirlo a un persistence landscape (una función unidimensional).
- Usar una biblioteca como
-
Modelar el landscape
- Modelo Bayesiano: posterior sobre la amplitud del landscape usando un proceso Gaussiano (Matérn 2.5).
- Modelo de ocupación‑amplitud: estimación empírica del rango de valores que cubre el landscape en los últimos N snapshots.
-
Comparar coberturas
- Cada modelo produce un intervalo de confianza (por ejemplo, 95 %).
- El shadow monitor calcula la distancia entre los dos intervalos. Un aumento sostenido de esa distancia indica que los dos modelos divergen, señal de que la topología está cambiando.
-
Generar alerta
- Cuando la divergencia supera un umbral dinámico (p.ej. 2 σ por encima de la media histórica), se envía una notificación (Webhook, Discord, etc.).
- Opcional: disparar un script que re‑balancee automáticamente el pool de proxies.
Alternativas prácticas
- Sin GP: usar una media móvil del landscape y medir su varianza. Menos preciso pero sin dependencias pesadas.
- Sin TDA: aplicar clustering (DBSCAN) a los vectores y observar cambios en el número de clusters o en la distancia media intra‑cluster. Es más simple pero pierde la robustez topológica frente a ruido.
Cuándo aplicar esta solución
- Entornos con múltiples rutas o proxies donde cada nodo tiene métricas de latencia y jitter.
- Redes que requieren alta disponibilidad (hogar inteligente, oficina pequeña) y donde los reinicios de servicio son costosos.
- Escenarios donde los umbrales tradicionales generan falsos positivos o, al contrario, no detectan degradaciones lentas.
No aplicar si:
- Sólo hay uno o dos nodos y la complejidad de TDA supera el beneficio.
- No se dispone de una fuente fiable de métricas en tiempo real.
Código
# Instalar dependencias (Python 3.11+)
pip install numpy pandas gudhi scikit-learn sqlite3
# Script simplificado: generar landscape y comparar modelos
python - <<'PY'
import sqlite3, pandas as pd, numpy as np
from gudhi import RipsComplex
from gudhi.representations import PersistenceLandscape
# 1. Cargar último snapshot
conn = sqlite3.connect('/var/lib/topo/metrics.db')
df = pd.read_sql("SELECT * FROM snapshot ORDER BY ts DESC LIMIT 1", conn)
# 2. Normalizar
vec = (df[['lat','jitter','uptime','trend']]-df.mean())/df.std()
# 3. Persistent homology (dimension 0)
rips = RipsComplex(points=vec.values, max_edge_length=2.0)
simplex = rips.create_simplex_tree(max_dimension=1)
diag = simplex.persistence()
land = PersistenceLandscape(resolution=500)
land.fit(diag)
# 4. Modelo Bayesiano (muy simplificado)
mean_land = np.mean(land.landscape_, axis=1)
std_land = np.std(land.landscape_, axis=1)
# 5. Occupancy model: rango histórico (últimos 48 snapshots)
hist = pd.read_sql("SELECT * FROM snapshot ORDER BY ts DESC LIMIT 48", conn)
hist_vec = (hist[['lat','jitter','uptime','trend']]-hist.mean())/hist.std()
rips_hist = RipsComplex(points=hist_vec.values, max_edge_length=2.0)
simplex_hist = rips_hist.create_simplex_tree(max_dimension=1)
diag_hist = simplex_hist.persistence()
land_hist = PersistenceLandscape(resolution=500)
land_hist.fit(diag_hist)
min_hist = np.min(land_hist.landscape_, axis=1)
max_hist = np.max(land_hist.landscape_, axis=1)
# 6. Divergencia
gap = np.mean(np.maximum(0, mean_land - max_hist) + np.maximum(0, min_hist - mean_land))
if gap > 0.05: # umbral ajustable
print("⚠️ Divergencia topológica detectada")
PY
Verificación
- Prueba de regresión
- Ejecutar el script con datos de una red estable. El output debe ser silencioso (sin mensaje de divergencia).
- Inyección de ruido
- Modificar manualmente la latencia de un nodo en la base de datos (
UPDATE snapshot SET lat=lat+200 WHERE node='proxy‑3'). Ejecutar de nuevo; el script debe imprimir la alerta.
- Modificar manualmente la latencia de un nodo en la base de datos (
- Correlación con incidentes reales
- Revisar los logs del router cuando ocurre una caída de servicio. La marca de tiempo debe coincidir con la alerta generada por el shadow monitor.
Notas adicionales
- Persistencia de datos: 9 MB de SQLite por día es típico en un router con 20‑30 nodos; asegúrese de rotar la tabla cada 30 días para evitar crecimiento indefinido.
- Calibración del umbral: la primera semana el sistema aprende la variabilidad natural; use la desviación estándar de la divergencia como referencia antes de fijar el umbral definitivo.
- Impacto en CPU: el cálculo de persistent homology sobre ~30 puntos es trivial (<0.5 s) en CPUs modernas; programar la tarea cada 30 min evita sobrecarga.
- Extensibilidad: la misma pipeline puede aplicarse a métricas de dispositivos IoT, consumo energético o cualquier serie temporal que pueda representarse como un punto en un espacio de características.
- Automatización futura: una vez que la alerta sea fiable, conecte el webhook a un script que modifique la tabla de rutas de
nftableso actualice la lista de nodos activos endaed.
Con este enfoque, el router deja de ser un observador pasivo y pasa a anticipar cambios estructurales en la red, reduciendo el tiempo de inactividad y evitando sorpresas en momentos críticos.