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)}