Disponibilidad: cálculo desde packet loss en vez de ping_reachable (v1.66.5)
Problema
El cálculo del porcentaje de disponibilidad (uptime_pct) consultaba la métrica ping_reachable (1 = arriba, 0 = abajo), pero esa métrica nunca llegaba a VictoriaMetrics.
Raíz: la vía viva de instrumentación para los equipos de la LAN es el ingest del Agente (terminal/api/sentinel_ingest.py) y su mapa de nombres (METRIC_TYPE_MAP) no traduce ping_reachable. El servidor sí la declara (vía MetricsWriter), pero en PROD no se escribe.
Consecuencia: la consulta devolvía vacía, el código ejecutaba return 0.0, y un equipo perfectamente sano (Xirrus de planta baja: 3,14 ms de latencia, 0% de pérdida) salía con uptime_pct: 0.0 — disponibilidad 0%.
Solución
El cálculo ahora sale de ping_packet_loss_percent — la MISMA métrica que usa el mapa de disponibilidad (get_availability_heatmap). Ambas cifras de disponibilidad del producto salen ahora de la misma fuente y no pueden contradecirse.
- Fórmula:
disponibilidad (%) = 100 - pérdida (%) - Sin dato: devuelve
Noneen vez de0.0(la diferencia entre “no hay dato” y “el equipo no responde”) - Rango: clamped a [0.0, 100.0] por seguridad
Cambios de código
Función: monitoring/services/metrics_reader.py :: MetricsReader.get_uptime
# Antes:
query = f'avg_over_time(ping_reachable{{tenant_id="{tenant_id}", target_id="{target_id}"}}[{dur}])'
result = await cls._query_instant(query)
if result is not None:
return round(result * 100, 2)
return 0.0 # ← BUG: presentaba falta de instrumentación como 0%
# Después:
query = f'avg_over_time(ping_packet_loss_percent{{tenant_id="{tenant_id}", target_id="{target_id}"}}[{dur}])'
loss = await cls._query_instant(query)
if loss is None:
return None # ← Distinto de 0%
return round(max(0.0, min(100.0, 100.0 - loss)), 2)
Wrapper síncrono: get_uptime_sync — actualizado para devolver float | None
Límite honesto
Este fix no habría salido de los tests tradicionales del CI (llevan meses en verde con el fallo dentro). El test nuevo (test_metrics_read_write_contract.py) es una red barata: verifica que ninguna consulta del lector pida una métrica que nadie escribe. Pero:
- ✅ Habría cazado un error de dedo en un nombre.
- ✅ Habría detectado una métrica retirada del escritor y olvidada en el lector.
- ❌ NO habría cazado este fallo:
ping_reachablesí la declara una vía (MetricsWriter); lo que fallaba es que la vía viva en PROD es otra (sentinel_ingest.METRIC_TYPE_MAP), y qué vía está viva en producción solo se ve mirando los datos reales.
Esta fue la primera ronda del equipo técnico de mantenimiento (/crearack:ronda, piloto 06-08-2026, task #213).
Impacto usuario
Visible hoy: NINGUNO. uptime_pct no lo consume ninguna pantalla en el producto (grep -r uptime_pct src/ en archivos .js, .html devuelve nada).
Potencial: cuando se use — en una vista nueva, informe de Integridad, o integración — habría mostrado 0% de equipos sanos. Ahora mostrará la cifra correcta (o null si no hay dato).
Testing
-
Nuevo:
tests/monitoring/test_metrics_read_write_contract.py— contrato de lectura/escritura de métricastest_toda_metrica_leida_la_escribe_alguien(): ninguna métrica leída puede estar huérfanatest_el_lector_consulta_algo(): guarda anti-fallo-mudo en la extracción de regextest_disponibilidad_sale_de_una_metrica_viva(): parametrizado paraping_packet_loss_percent
-
Verificación PROD: antes de tocar nada se comprobó en producción:
ping_latency_ms: 131 seriesping_packet_loss_percent: 131 seriesping_reachable: CERO series → confirmado: nunca llegaba
Véase también
- [[entity—monitoring—service—metrics-reader]]
- [[entity—monitoring—endpoint—availability-heatmap]]
- [[entity—terminal—service—sentinel-ingest]]
- [[entity—monitoring—test—metrics-read-write-contract]]
- [[concept—observability—metricas-victoria-metrics]]