记录hfc统计信息

This commit is contained in:
SengokuCola
2025-06-22 17:18:28 +08:00
parent ce50f59c0a
commit fc2c138bc4
5 changed files with 807 additions and 0 deletions

View File

@@ -28,6 +28,8 @@ from src.chat.focus_chat.planners.planner_factory import PlannerFactory
from src.chat.focus_chat.planners.modify_actions import ActionModifier
from src.chat.focus_chat.planners.action_manager import ActionManager
from src.config.config import global_config
from src.chat.focus_chat.hfc_performance_logger import HFCPerformanceLogger
from src.chat.focus_chat.hfc_version_manager import get_hfc_version
install(extra_lines=3)
@@ -85,6 +87,7 @@ class HeartFChatting:
self,
chat_id: str,
on_stop_focus_chat: Optional[Callable[[], Awaitable[None]]] = None,
performance_version: str = None,
):
"""
HeartFChatting 初始化函数
@@ -92,6 +95,7 @@ class HeartFChatting:
参数:
chat_id: 聊天流唯一标识符(如stream_id)
on_stop_focus_chat: 当收到stop_focus_chat命令时调用的回调函数
performance_version: 性能记录版本号,用于区分不同启动版本
"""
# 基础属性
self.stream_id: str = chat_id # 聊天流ID
@@ -147,6 +151,11 @@ class HeartFChatting:
# 存储回调函数
self.on_stop_focus_chat = on_stop_focus_chat
# 初始化性能记录器
# 如果没有指定版本号,则使用全局版本管理器的版本号
actual_version = performance_version or get_hfc_version()
self.performance_logger = HFCPerformanceLogger(chat_id, actual_version)
def _register_observations(self):
"""注册所有观察器"""
@@ -398,6 +407,21 @@ class HeartFChatting:
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
+ processor_time_log
)
# 记录性能数据
try:
action_result = self._current_cycle_detail.loop_plan_info.get('action_result', {})
cycle_performance_data = {
"cycle_id": self._current_cycle_detail.cycle_id,
"action_type": action_result.get('action_type', 'unknown'),
"total_time": self._current_cycle_detail.end_time - self._current_cycle_detail.start_time,
"step_times": cycle_timers.copy(),
"reasoning": action_result.get('reasoning', ''),
"success": self._current_cycle_detail.loop_action_info.get('action_taken', False),
}
self.performance_logger.record_cycle(cycle_performance_data)
except Exception as perf_e:
logger.warning(f"{self.log_prefix} 记录性能数据失败: {perf_e}")
await asyncio.sleep(global_config.focus_chat.think_interval)
@@ -775,6 +799,13 @@ class HeartFChatting:
self._processing_lock.release()
logger.warning(f"{self.log_prefix} 已释放处理锁")
# 完成性能统计
try:
self.performance_logger.finalize_session()
logger.info(f"{self.log_prefix} 性能统计已完成")
except Exception as e:
logger.warning(f"{self.log_prefix} 完成性能统计时出错: {e}")
logger.info(f"{self.log_prefix} HeartFChatting关闭完成")
def get_cycle_history(self, last_n: Optional[int] = None) -> List[Dict[str, Any]]:

View File

@@ -0,0 +1,238 @@
import json
import os
import time
from datetime import datetime
from typing import Dict, List, Any, Optional
from pathlib import Path
from src.common.logger import get_logger
logger = get_logger("hfc_performance")
class HFCPerformanceLogger:
"""HFC性能记录管理器"""
# 版本号常量,可在启动时修改
INTERNAL_VERSION = "v1.0.0"
def __init__(self, chat_id: str, version: str = None):
self.chat_id = chat_id
self.version = version or self.INTERNAL_VERSION
self.log_dir = Path("log/hfc_loop")
self.data_dir = Path("data/hfc")
self.session_start_time = datetime.now()
# 确保目录存在
self.log_dir.mkdir(parents=True, exist_ok=True)
self.data_dir.mkdir(parents=True, exist_ok=True)
# 当前会话的日志文件,包含版本号
version_suffix = self.version.replace(".", "_")
self.session_file = self.log_dir / f"{chat_id}_{version_suffix}_{self.session_start_time.strftime('%Y%m%d_%H%M%S')}.json"
self.current_session_data = []
# 统计数据文件
self.stats_file = self.data_dir / "time.json"
# 初始化时计算历史统计数据
self._update_historical_stats()
def record_cycle(self, cycle_data: Dict[str, Any]):
"""记录单次循环数据"""
try:
# 构建记录数据
record = {
"timestamp": datetime.now().isoformat(),
"version": self.version,
"cycle_id": cycle_data.get("cycle_id"),
"chat_id": self.chat_id,
"action_type": cycle_data.get("action_type", "unknown"),
"total_time": cycle_data.get("total_time", 0),
"step_times": cycle_data.get("step_times", {}),
"reasoning": cycle_data.get("reasoning", ""),
"success": cycle_data.get("success", False)
}
# 添加到当前会话数据
self.current_session_data.append(record)
# 立即写入文件(防止数据丢失)
self._write_session_data()
logger.debug(f"记录HFC循环数据: cycle_id={record['cycle_id']}, action={record['action_type']}, time={record['total_time']:.2f}s")
except Exception as e:
logger.error(f"记录HFC循环数据失败: {e}")
def _write_session_data(self):
"""写入当前会话数据到文件"""
try:
with open(self.session_file, 'w', encoding='utf-8') as f:
json.dump(self.current_session_data, f, ensure_ascii=False, indent=2)
except Exception as e:
logger.error(f"写入会话数据失败: {e}")
def _update_historical_stats(self):
"""更新历史统计数据"""
try:
# 读取所有历史会话文件
all_records = []
# 读取当前chat_id的所有历史文件包括不同版本
for file_path in self.log_dir.glob(f"{self.chat_id}_*.json"):
if file_path == self.session_file:
continue # 跳过当前会话文件
try:
with open(file_path, 'r', encoding='utf-8') as f:
records = json.load(f)
if isinstance(records, list):
all_records.extend(records)
except Exception as e:
logger.warning(f"读取历史文件 {file_path} 失败: {e}")
if not all_records:
logger.info(f"没有找到 chat_id={self.chat_id} 的历史数据")
return
# 计算统计数据
stats = self._calculate_stats(all_records)
# 更新统计文件
self._update_stats_file(stats)
logger.info(f"更新了 chat_id={self.chat_id} 的历史统计数据,共 {len(all_records)} 条记录")
except Exception as e:
logger.error(f"更新历史统计数据失败: {e}")
def _calculate_stats(self, records: List[Dict[str, Any]]) -> Dict[str, Any]:
"""计算统计数据"""
if not records:
return {}
# 按动作类型分组
action_groups = {}
total_times = []
step_time_totals = {}
for record in records:
action_type = record.get("action_type", "unknown")
total_time = record.get("total_time", 0)
step_times = record.get("step_times", {})
if action_type not in action_groups:
action_groups[action_type] = {
"count": 0,
"total_times": [],
"step_times": {}
}
action_groups[action_type]["count"] += 1
action_groups[action_type]["total_times"].append(total_time)
total_times.append(total_time)
# 记录步骤时间
for step_name, step_time in step_times.items():
if step_name not in action_groups[action_type]["step_times"]:
action_groups[action_type]["step_times"][step_name] = []
action_groups[action_type]["step_times"][step_name].append(step_time)
if step_name not in step_time_totals:
step_time_totals[step_name] = []
step_time_totals[step_name].append(step_time)
# 计算各种平均值和比例
total_records = len(records)
# 整体统计
overall_stats = {
"total_records": total_records,
"avg_total_time": sum(total_times) / len(total_times) if total_times else 0,
"avg_step_times": {}
}
# 各步骤平均时间
for step_name, times in step_time_totals.items():
overall_stats["avg_step_times"][step_name] = sum(times) / len(times) if times else 0
# 按动作类型统计
action_stats = {}
for action_type, data in action_groups.items():
action_stats[action_type] = {
"count": data["count"],
"percentage": (data["count"] / total_records) * 100,
"avg_total_time": sum(data["total_times"]) / len(data["total_times"]) if data["total_times"] else 0,
"avg_step_times": {}
}
# 该动作各步骤平均时间
for step_name, times in data["step_times"].items():
action_stats[action_type]["avg_step_times"][step_name] = sum(times) / len(times) if times else 0
return {
"chat_id": self.chat_id,
"version": self.version,
"last_updated": datetime.now().isoformat(),
"overall": overall_stats,
"by_action": action_stats
}
def _update_stats_file(self, new_stats: Dict[str, Any]):
"""更新统计文件"""
try:
# 读取现有统计数据
existing_stats = {}
if self.stats_file.exists():
with open(self.stats_file, 'r', encoding='utf-8') as f:
existing_stats = json.load(f)
# 更新当前chat_id和版本的统计数据
stats_key = f"{self.chat_id}_{self.version}"
existing_stats[stats_key] = new_stats
# 写回文件
with open(self.stats_file, 'w', encoding='utf-8') as f:
json.dump(existing_stats, f, ensure_ascii=False, indent=2)
except Exception as e:
logger.error(f"更新统计文件失败: {e}")
def get_current_session_stats(self) -> Dict[str, Any]:
"""获取当前会话的统计数据"""
if not self.current_session_data:
return {}
return self._calculate_stats(self.current_session_data)
def finalize_session(self):
"""结束会话,进行最终统计"""
try:
if self.current_session_data:
# 计算当前会话统计数据
current_stats = self._calculate_stats(self.current_session_data)
# 合并历史数据重新计算总体统计
all_records = self.current_session_data[:]
# 读取历史数据
for file_path in self.log_dir.glob(f"{self.chat_id}_*.json"):
if file_path == self.session_file:
continue
try:
with open(file_path, 'r', encoding='utf-8') as f:
records = json.load(f)
if isinstance(records, list):
all_records.extend(records)
except Exception as e:
logger.warning(f"读取历史文件 {file_path} 失败: {e}")
# 重新计算总体统计
total_stats = self._calculate_stats(all_records)
self._update_stats_file(total_stats)
logger.info(f"完成会话统计,当前会话 {len(self.current_session_data)} 条记录,总共 {len(all_records)} 条记录")
except Exception as e:
logger.error(f"结束会话统计失败: {e}")

View File

@@ -0,0 +1,185 @@
"""
HFC性能记录版本号管理器
用于管理HFC性能记录的内部版本号支持
1. 默认版本号设置
2. 启动时版本号配置
3. 版本号验证和格式化
"""
import os
import re
from datetime import datetime
from typing import Optional
from src.common.logger import get_logger
logger = get_logger("hfc_version")
class HFCVersionManager:
"""HFC版本号管理器"""
# 默认版本号
DEFAULT_VERSION = "v1.0.0"
# 当前运行时版本号
_current_version: Optional[str] = None
@classmethod
def set_version(cls, version: str) -> bool:
"""
设置当前运行时版本号
参数:
version: 版本号字符串,格式如 v1.0.0 或 1.0.0
返回:
bool: 设置是否成功
"""
try:
validated_version = cls._validate_version(version)
if validated_version:
cls._current_version = validated_version
logger.info(f"HFC性能记录版本已设置为: {validated_version}")
return True
else:
logger.warning(f"无效的版本号格式: {version}")
return False
except Exception as e:
logger.error(f"设置版本号失败: {e}")
return False
@classmethod
def get_version(cls) -> str:
"""
获取当前版本号
返回:
str: 当前版本号
"""
if cls._current_version:
return cls._current_version
# 尝试从环境变量获取
env_version = os.getenv("HFC_PERFORMANCE_VERSION")
if env_version:
if cls.set_version(env_version):
return cls._current_version
# 返回默认版本号
return cls.DEFAULT_VERSION
@classmethod
def auto_generate_version(cls, base_version: str = None) -> str:
"""
自动生成版本号(基于时间戳)
参数:
base_version: 基础版本号,如果不提供则使用默认版本
返回:
str: 生成的版本号
"""
if not base_version:
base_version = cls.DEFAULT_VERSION
# 提取基础版本号的主要部分
base_match = re.match(r'v?(\d+\.\d+)', base_version)
if base_match:
base_part = base_match.group(1)
else:
base_part = "1.0"
# 添加时间戳
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
generated_version = f"v{base_part}.{timestamp}"
cls.set_version(generated_version)
logger.info(f"自动生成版本号: {generated_version}")
return generated_version
@classmethod
def _validate_version(cls, version: str) -> Optional[str]:
"""
验证版本号格式
参数:
version: 待验证的版本号
返回:
Optional[str]: 验证后的版本号失败返回None
"""
if not version or not isinstance(version, str):
return None
version = version.strip()
# 支持的格式:
# v1.0.0, 1.0.0, v1.0, 1.0, v1.0.0.20241222_1530 等
patterns = [
r'^v?(\d+\.\d+\.\d+)$', # v1.0.0 或 1.0.0
r'^v?(\d+\.\d+)$', # v1.0 或 1.0
r'^v?(\d+\.\d+\.\d+\.\w+)$', # v1.0.0.build 或 1.0.0.build
r'^v?(\d+\.\d+\.\w+)$', # v1.0.build 或 1.0.build
]
for pattern in patterns:
match = re.match(pattern, version)
if match:
# 确保版本号以v开头
if not version.startswith('v'):
version = 'v' + version
return version
return None
@classmethod
def reset_version(cls):
"""重置版本号为默认值"""
cls._current_version = None
logger.info("HFC版本号已重置为默认值")
@classmethod
def get_version_info(cls) -> dict:
"""
获取版本信息
返回:
dict: 版本相关信息
"""
current = cls.get_version()
return {
"current_version": current,
"default_version": cls.DEFAULT_VERSION,
"is_custom": current != cls.DEFAULT_VERSION,
"env_version": os.getenv("HFC_PERFORMANCE_VERSION"),
"timestamp": datetime.now().isoformat()
}
# 全局函数,方便使用
def set_hfc_version(version: str) -> bool:
"""设置HFC性能记录版本号"""
return HFCVersionManager.set_version(version)
def get_hfc_version() -> str:
"""获取当前HFC性能记录版本号"""
return HFCVersionManager.get_version()
def auto_generate_hfc_version(base_version: str = None) -> str:
"""自动生成HFC版本号"""
return HFCVersionManager.auto_generate_version(base_version)
def reset_hfc_version():
"""重置HFC版本号"""
HFCVersionManager.reset_version()
# 在模块加载时显示当前版本信息
if __name__ != "__main__":
current_version = HFCVersionManager.get_version()
logger.debug(f"HFC性能记录模块已加载当前版本: {current_version}")