""" 配置变更度量模块 跟踪配置保存后的首次调用成功率和重试次数 """ import json from pathlib import Path from datetime import datetime from typing import Optional, Dict, Any from dataclasses import dataclass, asdict @dataclass class ConfigChangeMetric: """配置变更度量记录""" timestamp: str config_changed: bool # 是否发生配置变更 first_call_success: Optional[bool] # 首次调用是否成功 retry_count: int # 重试次数 error_message: Optional[str] # 错误信息 connection_test_success: bool # 保存后连通性测试是否成功 time_to_success_ms: Optional[int] # 从配置变更到首次成功调用的时间(毫秒) class ConfigMetricsManager: """配置度量管理器""" def __init__(self, metrics_file: Path): self.metrics_file = metrics_file self.metrics_file.parent.mkdir(parents=True, exist_ok=True) # 当前配置变更状态 self._config_changed = False self._config_change_time: Optional[datetime] = None self._connection_test_success = False self._first_call_recorded = False self._retry_count = 0 def mark_config_changed(self, connection_test_success: bool) -> None: """标记配置已变更""" self._config_changed = True self._config_change_time = datetime.now() self._connection_test_success = connection_test_success self._first_call_recorded = False self._retry_count = 0 def record_first_call(self, success: bool, error_message: Optional[str] = None) -> None: """记录配置变更后的首次调用""" if not self._config_changed or self._first_call_recorded: return time_to_success_ms = None if self._config_change_time: delta = datetime.now() - self._config_change_time time_to_success_ms = int(delta.total_seconds() * 1000) metric = ConfigChangeMetric( timestamp=datetime.now().isoformat(), config_changed=True, first_call_success=success, retry_count=self._retry_count, error_message=error_message, connection_test_success=self._connection_test_success, time_to_success_ms=time_to_success_ms ) self._save_metric(metric) self._first_call_recorded = True # 如果成功,重置状态 if success: self._config_changed = False self._retry_count = 0 def increment_retry(self) -> None: """增加重试计数""" if self._config_changed: self._retry_count += 1 def record_retry_success(self, retry_count: int) -> None: """记录重试后成功的请求""" # 可以用于统计重试恢复率 pass def record_retry_failure(self, retry_count: int) -> None: """记录重试后仍失败的请求""" # 可以用于统计重试失败率 pass def _save_metric(self, metric: ConfigChangeMetric) -> None: """保存度量记录""" try: # 读取现有记录 metrics = [] if self.metrics_file.exists(): with open(self.metrics_file, 'r', encoding='utf-8') as f: metrics = json.load(f) # 添加新记录 metrics.append(asdict(metric)) # 只保留最近 100 条记录 if len(metrics) > 100: metrics = metrics[-100:] # 保存 with open(self.metrics_file, 'w', encoding='utf-8') as f: json.dump(metrics, f, ensure_ascii=False, indent=2) except Exception as e: print(f"保存配置度量失败: {e}") def get_statistics(self) -> Dict[str, Any]: """获取统计信息""" try: if not self.metrics_file.exists(): return { "total_config_changes": 0, "first_call_success_rate": 0.0, "avg_retry_count": 0.0, "connection_test_success_rate": 0.0 } with open(self.metrics_file, 'r', encoding='utf-8') as f: metrics = json.load(f) if not metrics: return { "total_config_changes": 0, "first_call_success_rate": 0.0, "avg_retry_count": 0.0, "connection_test_success_rate": 0.0 } total = len(metrics) success_count = sum(1 for m in metrics if m.get('first_call_success')) total_retries = sum(m.get('retry_count', 0) for m in metrics) connection_test_success = sum(1 for m in metrics if m.get('connection_test_success')) return { "total_config_changes": total, "first_call_success_rate": success_count / total if total > 0 else 0.0, "avg_retry_count": total_retries / total if total > 0 else 0.0, "connection_test_success_rate": connection_test_success / total if total > 0 else 0.0, "recent_metrics": metrics[-10:] # 最近 10 条记录 } except Exception as e: print(f"获取配置度量统计失败: {e}") return { "total_config_changes": 0, "first_call_success_rate": 0.0, "avg_retry_count": 0.0, "connection_test_success_rate": 0.0 } # 全局单例 _metrics_manager: Optional[ConfigMetricsManager] = None def get_config_metrics(workspace: Path) -> ConfigMetricsManager: """获取配置度量管理器单例""" global _metrics_manager if _metrics_manager is None: metrics_file = workspace / ".metrics" / "config_metrics.json" _metrics_manager = ConfigMetricsManager(metrics_file) return _metrics_manager