68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
from time import monotonic
|
|
|
|
from .probes import probe_many
|
|
from .store import Store
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def scheduled_probe_loop(
|
|
store: Store,
|
|
*,
|
|
interval_seconds: int,
|
|
timeout_seconds: float,
|
|
retries: int,
|
|
concurrency: int,
|
|
retention_days: int,
|
|
) -> None:
|
|
next_due: dict[str, float] = {}
|
|
while True:
|
|
sleep_seconds = interval_seconds
|
|
try:
|
|
# The Explore demo is deliberately synthetic. Pausing scheduled
|
|
# probes while it is active keeps its seeded incident and history
|
|
# deterministic and avoids any dependency on external services.
|
|
if not getattr(store, "demo_active", lambda: False)():
|
|
services = store.list_services()
|
|
now = monotonic()
|
|
due = [
|
|
service
|
|
for service in services
|
|
if service.enabled
|
|
and service.monitoring_enabled
|
|
and now >= next_due.get(service.id, 0)
|
|
]
|
|
if due:
|
|
results = await probe_many(
|
|
due,
|
|
timeout_seconds=None,
|
|
retries=None,
|
|
concurrency=concurrency,
|
|
trigger="scheduled",
|
|
default_timeout_seconds=timeout_seconds,
|
|
default_retries=retries,
|
|
)
|
|
for result in results:
|
|
store.save_probe(result)
|
|
for service in due:
|
|
next_due[service.id] = now + (service.interval_seconds or interval_seconds)
|
|
next_intervals = [
|
|
service.interval_seconds or interval_seconds
|
|
for service in services
|
|
if service.enabled and service.monitoring_enabled
|
|
]
|
|
sleep_seconds = min([interval_seconds, *next_intervals]) if next_intervals else interval_seconds
|
|
else:
|
|
sleep_seconds = interval_seconds
|
|
store.prune_probes(retention_days)
|
|
except asyncio.CancelledError:
|
|
raise
|
|
except Exception: # pragma: no cover - defensive daemon boundary
|
|
logger.exception("Scheduled probe cycle failed")
|
|
|
|
await asyncio.sleep(max(1, sleep_seconds))
|