# -*- coding: utf-8 -*- """采集健康度统计 —— 丢失率 / P99 延迟 / 可用性(PRD 9 章 NFR 落点)。 指标口径(对齐 PRD 5.1 验收): - 丢失率:未读到(含超时/失败)样本数 / 应采集样本总数,目标 ≤ 0.02%; - P99 延迟:单轮采集批处理耗时(入队到读取完成)的 P99,目标 ≤ 1.8s; - 可用性:成功轮次 / 总轮次(连续两轮成功间不间断视为可用),目标 ≥ 99.8%。 """ from __future__ import annotations import threading import time from typing import List, Optional class HealthMetrics: """线程安全的采集健康度统计器。""" def __init__(self, window_size: int = 4096): self._lock = threading.Lock() # 轮次耗时(秒),用于 P99 分位计算 self._round_latencies: List[float] = [] self._window_size = max(64, window_size) # 样本计数 self.total_samples = 0 # 应采集样本总数 self.lost_samples = 0 # 未读到样本数 self.failed_rounds = 0 # 失败轮次(驱动异常/超时) self.total_rounds = 0 # 总轮次数 def record_round(self, latency_sec: float, expected: int, got: int, failed: bool = False) -> None: """记录一轮采集结果。 Args: latency_sec: 本轮批处理耗时(秒); expected: 本轮应采集样本数; got: 本轮实际读到样本数; failed: 本轮是否整体失败(驱动异常等)。 """ with self._lock: self.total_rounds += 1 self.total_samples += expected self.lost_samples += max(0, expected - got) if failed: self.failed_rounds += 1 self._round_latencies.append(latency_sec) if len(self._round_latencies) > self._window_size: # 只保留最近窗口,避免无限增长 self._round_latencies = self._round_latencies[-self._window_size:] # ------------------------------------------------------------------ @property def loss_rate(self) -> float: """丢失率(0~1 区间的小数,如 0.0002 表示 0.02%)。""" with self._lock: if self.total_samples == 0: return 0.0 return self.lost_samples / self.total_samples @property def availability(self) -> float: """可用性(0~1 区间小数,如 0.998 表示 99.8%)。""" with self._lock: if self.total_rounds == 0: return 1.0 return 1.0 - self.failed_rounds / self.total_rounds def p99_latency(self) -> float: """最近窗口内采集批处理耗时的 P99(秒)。""" with self._lock: if not self._round_latencies: return 0.0 ordered = sorted(self._round_latencies) idx = max(0, min(len(ordered) - 1, int(len(ordered) * 0.99))) return ordered[idx] def snapshot(self) -> dict: """一次性导出全部健康度指标(供驾驶舱/日志上报)。""" return { "total_rounds": self.total_rounds, "total_samples": self.total_samples, "lost_samples": self.lost_samples, "loss_rate": round(self.loss_rate, 6), "p99_latency_sec": round(self.p99_latency(), 4), "availability": round(self.availability, 6), "failed_rounds": self.failed_rounds, "window_size": self._window_size, } def meets_sla(self) -> bool: """是否满足 PRD 验收基线(P99≤1.8s / 丢失率≤0.02% / 可用性≥99.8%)。""" return ( self.p99_latency() <= 1.8 and self.loss_rate <= 0.0002 and self.availability >= 0.998 )