Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 30 additions & 15 deletions backend/protocol_rpc/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class HealthCache:
_background_task: Optional[asyncio.Task] = None
_rpc_router_ref: Optional[FastAPIRPCRouter] = None
_usage_metrics_service_ref: Optional[Any] = None
_metrics_send_counter: int = 0
_metrics_last_send_monotonic: Optional[float] = None

# =============================================================================
# Permit-aware readiness state (in-process)
Expand Down Expand Up @@ -212,8 +212,6 @@ def _evaluate_permit_readiness(
return True, "permits_recovering"


# Send system health metrics every 6 health checks (6 × 10s = 60s = 1 minute)
METRICS_SEND_INTERVAL = 6
_no_progress_scan_suppressed_until: float = 0.0

# Statuses where the consensus state machine is actively working.
Expand Down Expand Up @@ -242,6 +240,20 @@ def get_health_check_interval() -> float:
return float(os.getenv("HEALTH_CHECK_INTERVAL_SECONDS", "10"))


def get_metrics_send_interval_seconds() -> float:
"""Get wall-clock interval for external health metrics sends."""
return float(os.getenv("HEALTH_METRICS_SEND_INTERVAL_SECONDS", "60"))


def _should_send_health_metrics(now_monotonic: float) -> bool:
if _metrics_last_send_monotonic is None:
return True
return (
now_monotonic - _metrics_last_send_monotonic
>= get_metrics_send_interval_seconds()
)


def get_no_progress_scan_error_cooldown_seconds() -> float:
"""Cooldown after the expensive no-progress scan times out."""
return float(os.getenv("HEALTH_NO_PROGRESS_SCAN_ERROR_COOLDOWN_SECONDS", "300"))
Expand Down Expand Up @@ -459,7 +471,7 @@ async def _run_health_checks() -> None:

async def _background_health_loop() -> None:
"""Background loop that periodically runs health checks."""
global _metrics_send_counter
global _metrics_last_send_monotonic

interval = get_health_check_interval()
logger.info(f"Starting background health checker (interval={interval}s)")
Expand All @@ -468,17 +480,20 @@ async def _background_health_loop() -> None:
try:
await _run_health_checks()

# Send system health metrics every 1 minute (every 6th iteration)
_metrics_send_counter += 1
if _metrics_send_counter >= METRICS_SEND_INTERVAL:
_metrics_send_counter = 0
if _usage_metrics_service_ref and _usage_metrics_service_ref.enabled:
try:
await _usage_metrics_service_ref.send_system_health_metrics(
_health_cache
)
except Exception as e:
logger.warning(f"Failed to send system health metrics: {e}")
now_monotonic = time.monotonic()
if (
_usage_metrics_service_ref
and _usage_metrics_service_ref.enabled
and _should_send_health_metrics(now_monotonic)
):
try:
await _usage_metrics_service_ref.send_system_health_metrics(
_health_cache
)
except Exception as e:
logger.warning(f"Failed to send system health metrics: {e}")
finally:
_metrics_last_send_monotonic = now_monotonic

except asyncio.CancelledError:
logger.info("Background health checker cancelled")
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/test_rpc_health_genvm_tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,16 @@ async def fake_memory_health():
assert health_module._health_cache.genvm_available_permits == 4
assert health_module._health_cache.genvm_active_executions == 1

def test_health_metrics_reporting_uses_wall_clock_interval(self, monkeypatch):
monkeypatch.setenv("HEALTH_METRICS_SEND_INTERVAL_SECONDS", "60")
health_module._metrics_last_send_monotonic = None

assert health_module._should_send_health_metrics(100.0) is True

health_module._metrics_last_send_monotonic = 100.0
assert health_module._should_send_health_metrics(159.9) is False
assert health_module._should_send_health_metrics(160.0) is True

@pytest.mark.asyncio
async def test_consensus_health_includes_max_recovery_exhaustion_events(
self, monkeypatch
Expand Down
Loading