ForcePilot/backend/package/yuxi/channel/monitoring/diagnostic_phase.py
Kris 79c6e6b74f feat(channel/monitoring): 新增完整的频道监控模块
实现了包括事件循环监控、健康检查、状态聚合、诊断心跳、稳定性追踪在内的全套监控能力,提供指标采集、就绪检查、告警推送等功能
2026-05-21 10:27:34 +08:00

132 lines
3.8 KiB
Python

import logging
import math
import os
import time
from contextlib import asynccontextmanager
from dataclasses import dataclass
logger = logging.getLogger(__name__)
RECENT_PHASE_CAPACITY = 40
@dataclass
class PhaseSnapshot:
name: str
started_at: float
ended_at: float
duration_ms: float
cpu_user_ms: float
cpu_system_ms: float
cpu_total_ms: float
cpu_core_ratio: float
details: dict[str, str | int | float | bool] | None = None
_active_phase_stack: list[dict] = []
_recent_phases: list[PhaseSnapshot] = []
def _round_metric(value: float, digits: int = 1) -> float:
if not math.isfinite(value):
return 0.0
factor = 10**digits
return round(value * factor) / factor
def _push_recent(snapshot: PhaseSnapshot) -> None:
_recent_phases.append(snapshot)
if len(_recent_phases) > RECENT_PHASE_CAPACITY:
del _recent_phases[: len(_recent_phases) - RECENT_PHASE_CAPACITY]
def _record_phase(snapshot: PhaseSnapshot) -> None:
_push_recent(snapshot)
try:
from yuxi.channel.monitoring.diagnostic_stability import record_stability_event
record_stability_event(
"diagnostic.phase.completed",
name=snapshot.name,
started_at=snapshot.started_at,
ended_at=snapshot.ended_at,
duration_ms=snapshot.duration_ms,
cpu_user_ms=snapshot.cpu_user_ms,
cpu_system_ms=snapshot.cpu_system_ms,
cpu_total_ms=snapshot.cpu_total_ms,
cpu_core_ratio=snapshot.cpu_core_ratio,
)
except Exception:
pass
def get_current_phase() -> str | None:
if not _active_phase_stack:
return None
return _active_phase_stack[-1]["name"]
def get_recent_phases(limit: int = 8) -> list[PhaseSnapshot]:
return list(_recent_phases[-max(0, limit) :])
def record_diagnostic_phase(snapshot: PhaseSnapshot) -> None:
_record_phase(snapshot)
def reset_diagnostic_phases_for_test() -> None:
_active_phase_stack.clear()
_recent_phases.clear()
@asynccontextmanager
async def diagnostic_phase(name: str, details: dict[str, str | int | float | bool] | None = None):
cpu_started = os.times()
active: dict = {
"name": name,
"started_at": time.time(),
"started_wall": time.perf_counter(),
"cpu_started_user": cpu_started.user,
"cpu_started_system": cpu_started.system,
"details": details,
}
_active_phase_stack.append(active)
try:
yield
finally:
ended_at = time.time()
duration_ms = _round_metric((time.perf_counter() - active["started_wall"]) * 1000)
cpu_ended = os.times()
cpu_user_ms = _round_metric((cpu_ended.user - active["cpu_started_user"]) * 1000)
cpu_system_ms = _round_metric((cpu_ended.system - active["cpu_started_system"]) * 1000)
cpu_total_ms = _round_metric(cpu_user_ms + cpu_system_ms)
cpu_core_ratio = _round_metric(cpu_total_ms / max(1.0, duration_ms), 3)
_active_phase_stack[:] = [e for e in _active_phase_stack if e is not active]
snapshot = PhaseSnapshot(
name=name,
started_at=active["started_at"],
ended_at=ended_at,
duration_ms=duration_ms,
cpu_user_ms=cpu_user_ms,
cpu_system_ms=cpu_system_ms,
cpu_total_ms=cpu_total_ms,
cpu_core_ratio=cpu_core_ratio,
details=active["details"],
)
_record_phase(snapshot)
if cpu_core_ratio > 0.9:
logger.debug(
"DiagnosticPhase '%s': %sms, cpu_user=%.1fms, cpu_system=%.1fms, cpu_total=%.1fms, ratio=%.2f",
name,
duration_ms,
cpu_user_ms,
cpu_system_ms,
cpu_total_ms,
cpu_core_ratio,
)