from __future__ import annotations import threading import time from dataclasses import dataclass, field from typing import Dict, List from .config import DeviceConfig @dataclass class DeviceRuntimeState: cfg: DeviceConfig next_scrape_at: float consecutive_failures: int = 0 backoff_factor: int = 1 class DeviceRegistry: """维护设备配置与运行时调度状态.""" def __init__(self, global_scrape_interval: int = 60) -> None: self._lock = threading.RLock() self._states: Dict[str, DeviceRuntimeState] = {} self._global_scrape_interval = global_scrape_interval # --- 注册与删除 --- def register_static_device(self, cfg: DeviceConfig) -> None: self._register_device(cfg) def register_runtime_device(self, cfg: DeviceConfig) -> None: cfg.source = "runtime" self._register_device(cfg) def _register_device(self, cfg: DeviceConfig) -> None: now = time.time() with self._lock: interval = cfg.scrape_interval_seconds or self._global_scrape_interval state = DeviceRuntimeState( cfg=cfg, next_scrape_at=now, # 初始立即可采集 ) self._states[cfg.name] = state def delete_runtime_device(self, name: str) -> None: with self._lock: state = self._states.get(name) if state is not None and state.cfg.source == "runtime": del self._states[name] # --- 查询 --- def list_devices(self) -> List[DeviceConfig]: with self._lock: return [s.cfg for s in self._states.values()] def get_enabled_devices(self, now: float) -> List[DeviceRuntimeState]: with self._lock: return [ s for s in self._states.values() if s.cfg.enabled and s.next_scrape_at <= now ] # --- 调度更新 --- def update_after_scrape( self, device_name: str, success: bool, now: float, scrape_interval: int, failure_threshold: int, max_backoff_factor: int, ) -> None: """Scraper 在单次采集后调用,更新运行状态.""" with self._lock: state = self._states.get(device_name) if state is None: return interval = state.cfg.scrape_interval_seconds or scrape_interval if success: state.consecutive_failures = 0 state.backoff_factor = 1 state.next_scrape_at = now + interval else: state.consecutive_failures += 1 if state.consecutive_failures >= failure_threshold: state.backoff_factor = min( max_backoff_factor, max(2, state.backoff_factor * 2) ) state.next_scrape_at = now + interval * state.backoff_factor