Disponibilidad medida por alcanzabilidad, no pérdida de paquetes (v1.66.6 · Agent 2.19.0)
Síntesis
Desde v1.66.6 (Agent 2.19.0), el porcentaje de disponibilidad de un equipo se calcula a partir de una métrica nueva: ping_reachable (1 si respondió, 0 si no a cada sondeo). Antes se derivaba de “100 menos la pérdida de paquetes”, que mide calidad de red (ancho de banda, jitter), no tiempo arriba. Un equipo puede responder perfectamente perdiendo la mitad de los paquetes (red congestionada en una dirección) y estuvo disponible el 100% del tiempo.
Contexto: La deuda de v1.66.5
En v1.66.5 (task #213, 06-08-2026) se arregló un bug grave: la métrica ping_reachable no se escribía en ningún lado porque el ingest del Agent no la producía. El reader (get_uptime) buscaba el dato, no lo encontraba, y retornaba 0.0 — equipos perfectamente sanos salían con “disponibilidad 0%”.
Aquel PR dejó escrita su propia limitación en el código: “100 − pérdida no es idéntico a tiempo arriba” y preparó el servidor para este cambio. Esta PR es el cierre: la vía pura.
Qué cambia
Agent (v2.19.0 → terminal/agent/sentinel/ping.py)
El bucle _do_ping_batch ya leía host_result["reachable"] para decidir si insistir con un equipo caído (circuit breaker), pero descartaba el dato. Ahora lo emite:
# Antes: solo latencia + pérdida
# ping_latency_ms, ping_packet_loss_percent
# Ahora: + alcanzabilidad
ping_reachable # 1.0 o 0.0
Sin costo extra: el dato viaja en el mismo lote con latencia y pérdida, sin sondeo adicional.
Anotaciones en terminal/agent/config.py: las dos listas ALLOWED_METRIC_TYPES aceptan ahora la métrica, así la purga local por tipo la deja pasar.
Servidor: Ingest (terminal/api/sentinel_ingest.py)
El mapa dinámico de nombres (METRIC_TYPE_MAP) necesitaba la traducción de ping_reachable — el pase dinámico solo admite prefijos whitelistados (snmp_extras_*, snmp_fast_*) para evitar la explosión de índices en VictoriaMetrics. Sin la entrada, el ingest rechazaría la métrica.
Servidor: Lógica de lectura (monitoring/services/metrics_reader.py::get_uptime)
async def get_uptime(cls, tenant_id: int, target_id: int, hours: float = 24) -> float | None:
"""Prefiere ping_reachable; cae a ping_packet_loss_percent si no hay dato."""
Transición segura:
- Intenta leer
avg_over_time(ping_reachable[...])→ disponibilidad directa - Si falta (Agente < 2.19.0), cae a la pérdida de paquetes — red de seguridad
- Si ambas faltan, retorna
None(distinto de 0%, que significa “caído”) — evita confundir “sin dato” con “muerto”
Garantía: ningún equipo pierde el dato durante la transición. El fallback desaparece solo cuando toda la flota emite el dato directo.
Tests (4 nuevos en tests/monitoring/test_metrics_read_write_contract.py)
- ✅ Prefiere
ping_reachabley no lanza la segunda consulta (eficiencia) - ✅ Cae a
ping_packet_loss_percentsi no hay dato del Agente - ✅ Sin ningún dato: retorna
None, no 0% - ✅ Un equipo caído de verdad sigue dando 0% (ambas métricas reportan su estado correcto)
El test de contrato existente pasó a exigir que ambas métricas salgan del ingest. Si alguien retira el fallback antes de que toda la flota lo necesite, los equipos con Agente viejo se quedan sin dato → el test falla inmediatamente.
Impacto de usuario
Hoy: ninguno. La métrica no se muestra en ninguna pantalla (Observatory está en desarrollo). El dato queda correcto antes de que la UI la use.
Mañana: cuando Observatory inicie sesión, la disponibilidad será la verdadera — sin distorsiones por pérdida de paquetes en enlaces congestionados.
Límite que queda
Los equipos solo miden lo que su Agente sondea. Un equipo cuyo Agente esté parado no tiene disponibilidad, ni antes ni ahora. No hay forma de distinguir “no respondió” de “nadie preguntó” desde afuera.
Referencias técnicas
- Métrica nueva:
ping_reachable(gauge 0–1) — media en una ventana = fracción de sondeos en que respondió - Métricas heredadas:
ping_packet_loss_percent(gauge 0–100) — mide calidad, no tiempo arriba - Traducción ingest:
terminal/api/sentinel_ingest.py::METRIC_TYPE_MAP - Allowlist Agent:
terminal/agent/config.py::ALLOWED_METRIC_TYPES(ping, snmp_fast, snmp_extras) - Lógica:
monitoring/services/metrics_reader.py::MetricsReader.get_uptime
Actualización 23-08-2026 (v1.82.3, task #247) — “sin medida” deja de ser “up”
MonitoringTarget.resolve_reachability(ping_loss) (monitoring/models.py) es otro consumidor de disponibilidad, independiente del get_uptime de arriba: resuelve el estado up/down/unknown de un target a partir del último ping_loss conocido más, si aplica, la sonda TCP. Tenía el mismo bug de fondo que motivó todo este documento — confundir “nadie ha medido” con “está vivo”:
# Antes (bug): ping_loss is None se trataba como "up"
if ping_loss is None or ping_loss < 100:
return "up"
if self.tcp_enabled and self.last_tcp_up and self.last_tcp_check:
...
return "up"
return "down"
# Ahora: None ya no cuenta como "responde"
if ping_loss is not None and ping_loss < 100:
return "up"
if self.tcp_enabled and self.last_tcp_check:
# sonda TCP fresca = medida real, conecte o falle
...
return "up" if self.last_tcp_up else "down"
return "down" if ping_loss is not None else "unknown"
Con el código viejo, un target solo-TCP cuya sonda TCP estaba caída se resolvía "up" para siempre (el or ping_loss < 100 con ping_loss=None ya devolvía up antes de mirar el TCP). Ahora: si hay ping reciente decide el ping; si no, decide la sonda TCP fresca (conecte o falle, es una medida real); si no hay ninguna medida, el estado es "unknown" — ya declarado en STATUS_CHOICES como default del modelo, pero que resolve_reachability nunca devolvía. Mina latente desactivada antes de pisarla: 0 targets solo-TCP en PROD el día del fix.
El test de contrato (tests/monitoring/test_ronda_monitoring_fixes.py) fija resolve_reachability(None) → "unknown", reemplazando el assert viejo que fijaba None → "up" (ese assert era, literalmente, el bug convertido en test).
El guardián de contrato de métricas pasa de 1 a 9 lectores
El test que nació del fallo de ping_reachable (task #213, sección de arriba) solo escaneaba monitoring/services/metrics_reader.py en busca de claves de métrica no declaradas en el contrato. Cualquier otro lector de VictoriaMetrics (wireless, UPS, signage, grupos) podía reproducir exactamente el mismo fallo sin que el CI lo viera. Se amplió a los 9 ficheros que consultan VictoriaMetrics, reconociendo por prefijo las claves dinámicas (snmp_extras_*, snmp_fast_* son datos de BD, no literales — el guardián no puede validar esas, límite declarado en el propio test).
Véase también
- [[entity—monitoring—service—metrics-reader]]
- [[entity—terminal—agent—sentinel-ping]]
- [[entity—terminal—api—sentinel-ingest]]
- [[decision—20260806—disponibilidad-task-213]]
- [[concept—monitoring—observability]]