Problema
MetricsReader.get_uptime promedia solo las muestras que existen en la ventana. Si el Agente que sondea la red se queda mudo unas horas, esas horas simplemente no cuentan — ni a favor ni en contra. En PROD, el equipo 1253 tuvo 7 de 24 horas sin ninguna muestra y el endpoint devolvía 100% de disponibilidad: el silencio se leía como “estuvo arriba”.
Es la misma familia de fallo que [[feature—monitoring—uptime-calculo-desde-packet-loss]] (v1.66.5) ya corrigió una vez para el caso “cero muestras en toda la ventana” — aquí el hueco es parcial, no total, y por eso no lo cazaba aquel fix.
Solución
Nuevo método MetricsReader.get_uptime_coverage(tenant_id, target_id, hours): calcula la fracción de horas de la ventana que tienen al menos una muestra (de ping_reachable o ping_packet_loss_percent), con el mismo criterio que ya usa el mapa de calor de disponibilidad — nunca una tasa teórica derivada de interval_seconds, porque la cadencia real medida (~107 muestras/hora) no cuadra con el intervalo declarado.
# monitoring/services/metrics_reader.py
UPTIME_MIN_COVERAGE = 0.8 # por debajo, no se afirma una disponibilidad
per_hour = f"(count_over_time(ping_reachable{sel}[1h]) or count_over_time(ping_packet_loss_percent{sel}[1h]))"
hours_with_data = await cls._query_instant(f"count_over_time({per_hour}[{dur}:1h])")
return round(max(0.0, min(1.0, hours_with_data / slots)), 3)
Ventanas de menos de 2 horas no miden cobertura (devuelve None: contar por horas no dice nada a esa escala). Si la consulta a VictoriaMetrics falla, también None — para que “sin cobertura” y “no supe medir” no se confundan.
Contrato del endpoint
GET /api/monitoring/targets/{id}/vm/stats (monitoring/api/metrics.py :: get_vm_target_stats) añade el campo uptime_coverage_pct y cambia uptime_pct a null cuando la cobertura baja del 80%:
// cobertura 70.8% → no se afirma disponibilidad
{"uptime_pct": null, "uptime_coverage_pct": 70.8}
// cobertura completa → el valor se mantiene
{"uptime_pct": 97.5, "uptime_coverage_pct": 100.0}
// cobertura desconocida (falló la consulta) → no oculta el valor que sí se tiene
{"uptime_pct": 97.5, "uptime_coverage_pct": null}
get_uptime en sí no cambia de contrato — sigue devolviendo lo que ya devolvía; el filtrado por cobertura vive en el endpoint, comparando coverage contra MetricsReader.UPTIME_MIN_COVERAGE.
Límite honesto
Sin víctimas hoy: uptime_pct no lo consume ningún JavaScript del producto (grep -r uptime_pct src/ en .js/.html no devuelve nada) — se corrige antes de que una pantalla o informe futuro lo enchufe y muestre un porcentaje inventado. uptime_coverage_pct tampoco tiene consumidor todavía; viaja preparado para que una pantalla diga “datos insuficientes” en vez de omitir el dato sin más.
Testing
tests/racks/test_ronda_0906_tanda4.py (clases TestUptimeCoverage y TestStatsEndpointHonestUptime):
- Cuenta horas-con-dato sobre la ventana declarada, con subconsulta
[24h:1h]. Nonesi la consulta falla o si la ventana es menor a 2 horas (no mide).- El endpoint devuelve
uptime_pct: nullcon cobertura baja (70.8%) reproduciendo el caso real del equipo 1253; mantiene el valor con cobertura completa; y no oculta unuptime_pctconocido cuando la cobertura esNone(desconocida ≠ baja).
Véase también
- [[feature—monitoring—uptime-calculo-desde-packet-loss]]
- [[entity—monitoring—test—metrics-read-write-contract]]
- [[entity—monitoring—model—monitoringtarget]]
- [[entity—core—model—organization]]
- [[concept—saas—multi-tenancy]]