diff --git a/src/chat/focus_chat/hfc_performance_logger.py b/src/chat/focus_chat/hfc_performance_logger.py
index c7b9d7ab5..16f222e80 100644
--- a/src/chat/focus_chat/hfc_performance_logger.py
+++ b/src/chat/focus_chat/hfc_performance_logger.py
@@ -29,10 +29,6 @@ class HFCPerformanceLogger:
)
self.current_session_data = []
-
-
-
-
def record_cycle(self, cycle_data: Dict[str, Any]):
"""记录单次循环数据"""
try:
@@ -80,7 +76,7 @@ class HFCPerformanceLogger:
"version": self.version,
"session_file": str(self.session_file),
"record_count": len(self.current_session_data),
- "start_time": self.session_start_time.isoformat()
+ "start_time": self.session_start_time.isoformat(),
}
def finalize_session(self):
@@ -95,7 +91,7 @@ class HFCPerformanceLogger:
def cleanup_old_logs(cls, max_size_mb: float = 50.0):
"""
清理旧的HFC日志文件,保持目录大小在指定限制内
-
+
Args:
max_size_mb: 最大目录大小限制(MB)
"""
@@ -103,62 +99,58 @@ class HFCPerformanceLogger:
if not log_dir.exists():
logger.info("HFC日志目录不存在,跳过日志清理")
return
-
+
# 获取所有日志文件及其信息
log_files = []
total_size = 0
-
+
for log_file in log_dir.glob("*.json"):
try:
file_stat = log_file.stat()
- log_files.append({
- 'path': log_file,
- 'size': file_stat.st_size,
- 'mtime': file_stat.st_mtime
- })
+ log_files.append({"path": log_file, "size": file_stat.st_size, "mtime": file_stat.st_mtime})
total_size += file_stat.st_size
except Exception as e:
logger.warning(f"无法获取文件信息 {log_file}: {e}")
-
+
if not log_files:
logger.info("没有找到HFC日志文件")
return
-
+
max_size_bytes = max_size_mb * 1024 * 1024
current_size_mb = total_size / (1024 * 1024)
-
+
logger.info(f"HFC日志目录当前大小: {current_size_mb:.2f}MB,限制: {max_size_mb}MB")
-
+
if total_size <= max_size_bytes:
logger.info("HFC日志目录大小在限制范围内,无需清理")
return
-
+
# 按修改时间排序(最早的在前面)
- log_files.sort(key=lambda x: x['mtime'])
-
+ log_files.sort(key=lambda x: x["mtime"])
+
deleted_count = 0
deleted_size = 0
-
+
for file_info in log_files:
if total_size <= max_size_bytes:
break
-
+
try:
- file_size = file_info['size']
- file_path = file_info['path']
-
+ file_size = file_info["size"]
+ file_path = file_info["path"]
+
file_path.unlink()
total_size -= file_size
deleted_size += file_size
deleted_count += 1
-
+
logger.info(f"删除旧日志文件: {file_path.name} ({file_size / 1024:.1f}KB)")
-
+
except Exception as e:
logger.error(f"删除日志文件失败 {file_info['path']}: {e}")
-
+
final_size_mb = total_size / (1024 * 1024)
deleted_size_mb = deleted_size / (1024 * 1024)
-
+
logger.info(f"HFC日志清理完成: 删除了{deleted_count}个文件,释放{deleted_size_mb:.2f}MB空间")
logger.info(f"清理后目录大小: {final_size_mb:.2f}MB")
diff --git a/src/chat/utils/statistic.py b/src/chat/utils/statistic.py
index 052324626..bb3f53a1a 100644
--- a/src/chat/utils/statistic.py
+++ b/src/chat/utils/statistic.py
@@ -512,16 +512,16 @@ class StatisticOutputTask(AsyncTask):
try:
# 从文件名解析时间戳 (格式: hash_version_date_time.json)
filename = os.path.basename(json_file)
- name_parts = filename.replace('.json', '').split('_')
+ name_parts = filename.replace(".json", "").split("_")
if len(name_parts) >= 4:
date_str = name_parts[-2] # YYYYMMDD
time_str = name_parts[-1] # HHMMSS
file_time_str = f"{date_str}_{time_str}"
file_time = datetime.strptime(file_time_str, "%Y%m%d_%H%M%S")
-
+
# 如果文件时间在查询范围内,则处理该文件
if file_time >= query_start_time:
- with open(json_file, 'r', encoding='utf-8') as f:
+ with open(json_file, "r", encoding="utf-8") as f:
cycles_data = json.load(f)
self._process_focus_file_data(cycles_data, stats, collect_period, file_time)
except Exception as e:
@@ -532,8 +532,13 @@ class StatisticOutputTask(AsyncTask):
self._calculate_focus_averages(stats)
return stats
- def _process_focus_file_data(self, cycles_data: List[Dict], stats: Dict[str, Any],
- collect_period: List[Tuple[str, datetime]], file_time: datetime):
+ def _process_focus_file_data(
+ self,
+ cycles_data: List[Dict],
+ stats: Dict[str, Any],
+ collect_period: List[Tuple[str, datetime]],
+ file_time: datetime,
+ ):
"""
处理单个focus文件的数据
"""
@@ -542,7 +547,7 @@ class StatisticOutputTask(AsyncTask):
# 解析时间戳
timestamp_str = cycle_data.get("timestamp", "")
if timestamp_str:
- cycle_time = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
+ cycle_time = datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
else:
cycle_time = file_time # 使用文件时间作为后备
@@ -563,7 +568,7 @@ class StatisticOutputTask(AsyncTask):
if cycle_time >= period_start:
for period_key, _ in collect_period[idx:]:
stat = stats[period_key]
-
+
# 基础统计
stat[FOCUS_TOTAL_CYCLES] += 1
stat[FOCUS_ACTION_RATIOS][action_type] += 1
@@ -572,7 +577,7 @@ class StatisticOutputTask(AsyncTask):
stat["focus_action_ratios_by_chat"][chat_id][action_type] += 1
stat[FOCUS_TOTAL_TIME_BY_CHAT][chat_id] += total_time
stat[FOCUS_TOTAL_TIME_BY_ACTION][action_type] += total_time
-
+
# 版本统计
stat[FOCUS_CYCLE_CNT_BY_VERSION][version] += 1
stat[FOCUS_ACTION_RATIOS_BY_VERSION][version][action_type] += 1
@@ -583,7 +588,7 @@ class StatisticOutputTask(AsyncTask):
stat[FOCUS_AVG_TIMES_BY_CHAT_ACTION][chat_id][stage].append(time_val)
stat[FOCUS_AVG_TIMES_BY_ACTION][action_type][stage].append(time_val)
stat[FOCUS_AVG_TIMES_BY_VERSION][version][stage].append(time_val)
-
+
# 专门收集执行动作阶段的时间,按聊天流和action类型分组
if stage == "执行动作":
stat["focus_exec_times_by_chat_action"][chat_id][action_type].append(time_val)
@@ -646,8 +651,6 @@ class StatisticOutputTask(AsyncTask):
else:
stat["focus_exec_times_by_version_action"][version][action_type] = 0.0
-
-
def _collect_all_statistics(self, now: datetime) -> Dict[str, Dict[str, Any]]:
"""
收集各时间段的统计数据
@@ -803,12 +806,8 @@ class StatisticOutputTask(AsyncTask):
"""
if stats[FOCUS_TOTAL_CYCLES] <= 0:
return ""
-
- output = [
- "Focus系统统计:",
- f"总循环数: {stats[FOCUS_TOTAL_CYCLES]}",
- ""
- ]
+
+ output = ["Focus系统统计:", f"总循环数: {stats[FOCUS_TOTAL_CYCLES]}", ""]
# 全局阶段平均时间
if stats[FOCUS_AVG_TIMES_BY_STAGE]:
@@ -842,23 +841,24 @@ class StatisticOutputTask(AsyncTask):
try:
# 首先尝试从chat_stream获取真实群组名称
from src.chat.message_receive.chat_stream import get_chat_manager
+
chat_manager = get_chat_manager()
-
+
if chat_id in chat_manager.streams:
stream = chat_manager.streams[chat_id]
- if stream.group_info and hasattr(stream.group_info, 'group_name'):
+ if stream.group_info and hasattr(stream.group_info, "group_name"):
group_name = stream.group_info.group_name
if group_name and group_name.strip():
return group_name.strip()
- elif stream.user_info and hasattr(stream.user_info, 'user_nickname'):
+ elif stream.user_info and hasattr(stream.user_info, "user_nickname"):
user_name = stream.user_info.user_nickname
if user_name and user_name.strip():
return user_name.strip()
-
+
# 如果从chat_stream获取失败,尝试解析chat_id格式
- if chat_id.startswith('g'):
+ if chat_id.startswith("g"):
return f"群聊{chat_id[1:]}"
- elif chat_id.startswith('u'):
+ elif chat_id.startswith("u"):
return f"用户{chat_id[1:]}"
else:
return chat_id
@@ -942,43 +942,53 @@ class StatisticOutputTask(AsyncTask):
for chat_id, count in sorted(stat_data[MSG_CNT_BY_CHAT].items())
]
)
-
+
# Focus统计数据
# focus_action_rows = ""
# focus_chat_rows = ""
# focus_stage_rows = ""
# focus_action_stage_rows = ""
-
+
if stat_data.get(FOCUS_TOTAL_CYCLES, 0) > 0:
# Action类型统计
total_actions = sum(stat_data[FOCUS_ACTION_RATIOS].values()) if stat_data[FOCUS_ACTION_RATIOS] else 0
- _focus_action_rows = "\n".join([
- f"
| {action_type} | {count} | {(count/total_actions*100):.1f}% |
"
- for action_type, count in sorted(stat_data[FOCUS_ACTION_RATIOS].items())
- ])
-
+ _focus_action_rows = "\n".join(
+ [
+ f"| {action_type} | {count} | {(count / total_actions * 100):.1f}% |
"
+ for action_type, count in sorted(stat_data[FOCUS_ACTION_RATIOS].items())
+ ]
+ )
+
# 按聊天流统计
- _focus_chat_rows = "\n".join([
- f"| {self.name_mapping.get(chat_id, (chat_id, 0))[0]} | {count} | {stat_data[FOCUS_TOTAL_TIME_BY_CHAT].get(chat_id, 0):.2f}秒 |
"
- for chat_id, count in sorted(stat_data[FOCUS_CYCLE_CNT_BY_CHAT].items(), key=lambda x: x[1], reverse=True)
- ])
-
+ _focus_chat_rows = "\n".join(
+ [
+ f"| {self.name_mapping.get(chat_id, (chat_id, 0))[0]} | {count} | {stat_data[FOCUS_TOTAL_TIME_BY_CHAT].get(chat_id, 0):.2f}秒 |
"
+ for chat_id, count in sorted(
+ stat_data[FOCUS_CYCLE_CNT_BY_CHAT].items(), key=lambda x: x[1], reverse=True
+ )
+ ]
+ )
+
# 全局阶段时间统计
- _focus_stage_rows = "\n".join([
- f"| {stage} | {avg_time:.3f}秒 |
"
- for stage, avg_time in sorted(stat_data[FOCUS_AVG_TIMES_BY_STAGE].items())
- ])
-
+ _focus_stage_rows = "\n".join(
+ [
+ f"| {stage} | {avg_time:.3f}秒 |
"
+ for stage, avg_time in sorted(stat_data[FOCUS_AVG_TIMES_BY_STAGE].items())
+ ]
+ )
+
# 按Action类型的阶段时间统计
focus_action_stage_items = []
for action_type, stage_times in stat_data[FOCUS_AVG_TIMES_BY_ACTION].items():
for stage, avg_time in stage_times.items():
focus_action_stage_items.append((action_type, stage, avg_time))
-
- _focus_action_stage_rows = "\n".join([
- f"| {action_type} | {stage} | {avg_time:.3f}秒 |
"
- for action_type, stage, avg_time in sorted(focus_action_stage_items)
- ])
+
+ _focus_action_stage_rows = "\n".join(
+ [
+ f"| {action_type} | {stage} | {avg_time:.3f}秒 |
"
+ for action_type, stage, avg_time in sorted(focus_action_stage_items)
+ ]
+ )
# 生成HTML
return f"""
@@ -1046,11 +1056,11 @@ class StatisticOutputTask(AsyncTask):
# 添加Focus统计内容
focus_tab = self._generate_focus_tab(stat)
tab_content_list.append(focus_tab)
-
+
# 添加版本对比内容
versions_tab = self._generate_versions_tab(stat)
tab_content_list.append(versions_tab)
-
+
# 添加图表内容
chart_data = self._generate_chart_data(stat)
tab_content_list.append(self._generate_chart_tab(chart_data))
@@ -1203,30 +1213,32 @@ class StatisticOutputTask(AsyncTask):
def _generate_focus_tab(self, stat: dict[str, Any]) -> str:
"""生成Focus统计独立分页的HTML内容"""
-
+
# 为每个时间段准备Focus数据
focus_sections = []
-
+
for period_name, period_delta, period_desc in self.stat_period:
stat_data = stat.get(period_name, {})
-
+
if stat_data.get(FOCUS_TOTAL_CYCLES, 0) <= 0:
continue
-
+
# 生成Focus统计数据行
focus_action_rows = ""
focus_chat_rows = ""
focus_stage_rows = ""
focus_action_stage_rows = ""
-
+
# Action类型统计
total_actions = sum(stat_data[FOCUS_ACTION_RATIOS].values()) if stat_data[FOCUS_ACTION_RATIOS] else 0
if total_actions > 0:
- focus_action_rows = "\n".join([
- f"
| {action_type} | {count} | {(count/total_actions*100):.1f}% |
"
- for action_type, count in sorted(stat_data[FOCUS_ACTION_RATIOS].items())
- ])
-
+ focus_action_rows = "\n".join(
+ [
+ f"
| {action_type} | {count} | {(count / total_actions * 100):.1f}% |
"
+ for action_type, count in sorted(stat_data[FOCUS_ACTION_RATIOS].items())
+ ]
+ )
+
# 按聊天流统计(横向表格,显示各阶段时间差异和不同action的平均时间)
focus_chat_rows = ""
if stat_data[FOCUS_AVG_TIMES_BY_CHAT_ACTION]:
@@ -1242,72 +1254,84 @@ class StatisticOutputTask(AsyncTask):
break
if stage_exists:
existing_basic_stages.append(stage)
-
+
# 获取所有action类型(按出现频率排序)
- all_action_types = sorted(stat_data[FOCUS_ACTION_RATIOS].keys(),
- key=lambda x: stat_data[FOCUS_ACTION_RATIOS][x], reverse=True)
-
+ all_action_types = sorted(
+ stat_data[FOCUS_ACTION_RATIOS].keys(), key=lambda x: stat_data[FOCUS_ACTION_RATIOS][x], reverse=True
+ )
+
# 为每个聊天流生成一行
chat_rows = []
- for chat_id in sorted(stat_data[FOCUS_CYCLE_CNT_BY_CHAT].keys(),
- key=lambda x: stat_data[FOCUS_CYCLE_CNT_BY_CHAT][x], reverse=True):
+ for chat_id in sorted(
+ stat_data[FOCUS_CYCLE_CNT_BY_CHAT].keys(),
+ key=lambda x: stat_data[FOCUS_CYCLE_CNT_BY_CHAT][x],
+ reverse=True,
+ ):
chat_name = self.name_mapping.get(chat_id, (chat_id, 0))[0]
cycle_count = stat_data[FOCUS_CYCLE_CNT_BY_CHAT][chat_id]
-
+
# 获取该聊天流的各阶段平均时间
stage_times = stat_data[FOCUS_AVG_TIMES_BY_CHAT_ACTION].get(chat_id, {})
-
+
row_cells = [f"
{chat_name} ({cycle_count}次循环) | "]
-
+
# 添加基础阶段时间
for stage in existing_basic_stages:
time_val = stage_times.get(stage, 0.0)
row_cells.append(f"
{time_val:.3f}秒 | ")
-
+
# 添加每个action类型的平均执行时间
for action_type in all_action_types:
# 使用真实的按聊天流+action类型分组的执行时间数据
exec_times_by_chat_action = stat_data.get("focus_exec_times_by_chat_action", {})
chat_action_times = exec_times_by_chat_action.get(chat_id, {})
avg_exec_time = chat_action_times.get(action_type, 0.0)
-
+
if avg_exec_time > 0:
row_cells.append(f"
{avg_exec_time:.3f}秒 | ")
else:
row_cells.append("
- | ")
-
+
chat_rows.append(f"
{''.join(row_cells)}
")
-
+
# 生成表头
stage_headers = "".join([f"
{stage} | " for stage in existing_basic_stages])
- action_headers = "".join([f"
{action_type} (执行) | " for action_type in all_action_types])
+ action_headers = "".join(
+ [f"
{action_type} (执行) | " for action_type in all_action_types]
+ )
focus_chat_table_header = f"
| 聊天流 | {stage_headers}{action_headers}
|---|
"
focus_chat_rows = focus_chat_table_header + "\n" + "\n".join(chat_rows)
-
+
# 全局阶段时间统计
- focus_stage_rows = "\n".join([
- f"
| {stage} | {avg_time:.3f}秒 |
"
- for stage, avg_time in sorted(stat_data[FOCUS_AVG_TIMES_BY_STAGE].items())
- ])
-
+ focus_stage_rows = "\n".join(
+ [
+ f"
| {stage} | {avg_time:.3f}秒 |
"
+ for stage, avg_time in sorted(stat_data[FOCUS_AVG_TIMES_BY_STAGE].items())
+ ]
+ )
+
# 聊天流Action选择比例对比表(横向表格)
focus_chat_action_ratios_rows = ""
if stat_data.get("focus_action_ratios_by_chat"):
# 获取所有action类型(按全局频率排序)
- all_action_types_for_ratio = sorted(stat_data[FOCUS_ACTION_RATIOS].keys(),
- key=lambda x: stat_data[FOCUS_ACTION_RATIOS][x], reverse=True)
-
+ all_action_types_for_ratio = sorted(
+ stat_data[FOCUS_ACTION_RATIOS].keys(), key=lambda x: stat_data[FOCUS_ACTION_RATIOS][x], reverse=True
+ )
+
if all_action_types_for_ratio:
# 为每个聊天流生成数据行(按循环数排序)
chat_ratio_rows = []
- for chat_id in sorted(stat_data[FOCUS_CYCLE_CNT_BY_CHAT].keys(),
- key=lambda x: stat_data[FOCUS_CYCLE_CNT_BY_CHAT][x], reverse=True):
+ for chat_id in sorted(
+ stat_data[FOCUS_CYCLE_CNT_BY_CHAT].keys(),
+ key=lambda x: stat_data[FOCUS_CYCLE_CNT_BY_CHAT][x],
+ reverse=True,
+ ):
chat_name = self.name_mapping.get(chat_id, (chat_id, 0))[0]
total_cycles = stat_data[FOCUS_CYCLE_CNT_BY_CHAT][chat_id]
chat_action_counts = stat_data["focus_action_ratios_by_chat"].get(chat_id, {})
-
+
row_cells = [f"
{chat_name} ({total_cycles}次循环) | "]
-
+
# 添加每个action类型的数量和百分比
for action_type in all_action_types_for_ratio:
count = chat_action_counts.get(action_type, 0)
@@ -1316,9 +1340,9 @@ class StatisticOutputTask(AsyncTask):
row_cells.append(f"
{count} ({ratio:.1f}%) | ")
else:
row_cells.append("
- (0%) | ")
-
+
chat_ratio_rows.append(f"
{''.join(row_cells)}
")
-
+
# 生成表头
action_headers = "".join([f"
{action_type} | " for action_type in all_action_types_for_ratio])
chat_action_ratio_table_header = f"
| 聊天流 | {action_headers}
|---|
"
@@ -1333,33 +1357,38 @@ class StatisticOutputTask(AsyncTask):
for stage in stage_order:
if any(stage in stage_times for stage_times in stat_data[FOCUS_AVG_TIMES_BY_ACTION].values()):
all_stages.append(stage)
-
+
# 为每个Action类型生成一行
action_rows = []
for action_type in sorted(stat_data[FOCUS_AVG_TIMES_BY_ACTION].keys()):
stage_times = stat_data[FOCUS_AVG_TIMES_BY_ACTION][action_type]
row_cells = [f"
{action_type} | "]
-
+
for stage in all_stages:
time_val = stage_times.get(stage, 0.0)
row_cells.append(f"
{time_val:.3f}秒 | ")
-
+
action_rows.append(f"
{''.join(row_cells)}
")
-
+
# 生成表头
stage_headers = "".join([f"
{stage} | " for stage in all_stages])
focus_action_stage_table_header = f"
| Action类型 | {stage_headers}
|---|
"
focus_action_stage_rows = focus_action_stage_table_header + "\n" + "\n".join(action_rows)
-
+
# 计算时间范围
if period_name == "all_time":
from src.manager.local_store_manager import local_storage
+
start_time = datetime.fromtimestamp(local_storage["deploy_time"])
- time_range = f"{start_time.strftime('%Y-%m-%d %H:%M:%S')} ~ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
+ time_range = (
+ f"{start_time.strftime('%Y-%m-%d %H:%M:%S')} ~ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
+ )
else:
start_time = datetime.now() - period_delta
- time_range = f"{start_time.strftime('%Y-%m-%d %H:%M:%S')} ~ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
-
+ time_range = (
+ f"{start_time.strftime('%Y-%m-%d %H:%M:%S')} ~ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
+ )
+
# 生成该时间段的Focus统计HTML
section_html = f"""
@@ -1410,9 +1439,9 @@ class StatisticOutputTask(AsyncTask):
"""
-
+
focus_sections.append(section_html)
-
+
# 如果没有任何Focus数据
if not focus_sections:
focus_sections.append("""
@@ -1422,7 +1451,7 @@ class StatisticOutputTask(AsyncTask):
请确保 log/hfc_loop/ 目录下存在相应的JSON文件。
""")
-
+
return f"""
Focus系统详细统计
@@ -1431,7 +1460,7 @@ class StatisticOutputTask(AsyncTask):
统计内容: 各时间段的Focus循环性能分析
- {''.join(focus_sections)}
+ {"".join(focus_sections)}