CreaRack-SL

Disponibilidad: cálculo desde packet loss en vez de ping_reachable (v1.66.5)

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 None en vez de 0.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_reachable sí 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étricas

    • test_toda_metrica_leida_la_escribe_alguien(): ninguna métrica leída puede estar huérfana
    • test_el_lector_consulta_algo(): guarda anti-fallo-mudo en la extracción de regex
    • test_disponibilidad_sale_de_una_metrica_viva(): parametrizado para ping_packet_loss_percent
  • Verificación PROD: antes de tocar nada se comprobó en producción:

    • ping_latency_ms: 131 series
    • ping_packet_loss_percent: 131 series
    • ping_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]]