2025-11-28 14:35:21 +08:00

97 lines
2.9 KiB
Python

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