diff --git a/src/chat/utils/report_generator.py b/src/chat/utils/report_generator.py deleted file mode 100644 index 5526781fa..000000000 --- a/src/chat/utils/report_generator.py +++ /dev/null @@ -1,446 +0,0 @@ -""" -该模块用于生成HTML格式的统计报告。 -""" -from datetime import datetime, timedelta -from typing import Any - -import aiofiles - -from .statistic_keys import * # noqa: F403 - - -def format_online_time(online_seconds: int) -> str: - """ - 格式化在线时间。 - - :param online_seconds: 在线时间(秒)。 - :return: 格式化后的在线时间字符串。 - """ - total_online_time = timedelta(seconds=online_seconds) - days = total_online_time.days - hours = total_online_time.seconds // 3600 - minutes = (total_online_time.seconds // 60) % 60 - seconds = total_online_time.seconds % 60 - if days > 0: - return f"{days}天{hours}小时{minutes}分钟{seconds}秒" - elif hours > 0: - return f"{hours}小时{minutes}分钟{seconds}秒" - else: - return f"{minutes}分钟{seconds}秒" - - -class HTMLReportGenerator: - """生成HTML统计报告""" - - def __init__( - self, - name_mapping: dict, - stat_period: list, - deploy_time: datetime, - ): - """ - 初始化报告生成器。 - - :param name_mapping: 聊天ID到名称的映射。 - :param stat_period: 统计时间段配置。 - :param deploy_time: 系统部署时间。 - """ - self.name_mapping = name_mapping - self.stat_period = stat_period - self.deploy_time = deploy_time - - def _format_stat_data_div(self, stat_data: dict[str, Any], div_id: str, start_time: datetime, now: datetime) -> str: - """ - 将单个时间段的统计数据格式化为HTML div块。 - - :param stat_data: 统计数据。 - :param div_id: div的ID。 - :param start_time: 统计时间段的开始时间。 - :param now: 当前时间。 - :return: HTML字符串。 - """ - # 按模型分类统计 - model_rows = "\n".join( - [ - f"" - f"{model_name}" - f"{count}" - f"{stat_data[AVG_TOK_BY_MODEL].get(model_name, 0)}" - f"{stat_data[TOTAL_TOK_BY_MODEL].get(model_name, 0)}" - f"{stat_data[TPS_BY_MODEL].get(model_name, 0):.2f}" - f"{stat_data[COST_PER_KTOK_BY_MODEL].get(model_name, 0):.4f} ¥" - f"{stat_data[COST_BY_MODEL].get(model_name, 0):.4f} ¥" - f"{stat_data[AVG_TIME_COST_BY_MODEL].get(model_name, 0):.3f} 秒" - f"" - for model_name, count in sorted(stat_data[REQ_CNT_BY_MODEL].items()) - ] - ) - # 按供应商分类统计 - provider_rows = "\n".join( - [ - f"" - f"{provider_name}" - f"{count}" - f"{stat_data[TOTAL_TOK_BY_PROVIDER].get(provider_name, 0)}" - f"{stat_data[TPS_BY_PROVIDER].get(provider_name, 0):.2f}" - f"{stat_data[COST_PER_KTOK_BY_PROVIDER].get(provider_name, 0):.4f} ¥" - f"{stat_data[COST_BY_PROVIDER].get(provider_name, 0):.4f} ¥" - f"" - for provider_name, count in sorted(stat_data[REQ_CNT_BY_PROVIDER].items()) - ] - ) - # 按请求类型分类统计 - type_rows = "\n".join( - [ - f"" - f"{req_type}" - f"{count}" - f"{stat_data[IN_TOK_BY_TYPE].get(req_type, 0)}" - f"{stat_data[OUT_TOK_BY_TYPE].get(req_type, 0)}" - f"{stat_data[TOTAL_TOK_BY_TYPE].get(req_type, 0)}" - f"{stat_data[COST_BY_TYPE].get(req_type, 0):.4f} ¥" - f"{stat_data[AVG_TIME_COST_BY_TYPE].get(req_type, 0):.3f} 秒" - f"{stat_data[STD_TIME_COST_BY_TYPE].get(req_type, 0):.3f} 秒" - f"" - for req_type, count in sorted(stat_data[REQ_CNT_BY_TYPE].items()) - ] - ) - # 按模块分类统计 - module_rows = "\n".join( - [ - f"" - f"{module_name}" - f"{count}" - f"{stat_data[IN_TOK_BY_MODULE].get(module_name, 0)}" - f"{stat_data[OUT_TOK_BY_MODULE].get(module_name, 0)}" - f"{stat_data[TOTAL_TOK_BY_MODULE].get(module_name, 0)}" - f"{stat_data[COST_BY_MODULE].get(module_name, 0):.4f} ¥" - f"{stat_data[AVG_TIME_COST_BY_MODULE].get(module_name, 0):.3f} 秒" - f"{stat_data[STD_TIME_COST_BY_MODULE].get(module_name, 0):.3f} 秒" - f"" - for module_name, count in sorted(stat_data[REQ_CNT_BY_MODULE].items()) - ] - ) - # 聊天消息统计 - chat_rows = "\n".join( - [ - f"{self.name_mapping.get(chat_id, ('未知', 0))[0]}{count}" - for chat_id, count in sorted(stat_data[MSG_CNT_BY_CHAT].items()) - ] - ) - summary_cards = f""" -
-
-

总花费

-

{stat_data.get(TOTAL_COST, 0):.4f} ¥

-
-
-

总请求数

-

{stat_data.get(TOTAL_REQ_CNT, 0)}

-
-
-

总Token数

-

{sum(stat_data.get(TOTAL_TOK_BY_MODEL, {}).values())}

-
-
-

总消息数

-

{stat_data.get(TOTAL_MSG_CNT, 0)}

-
-
-

总在线时间

-

{format_online_time(int(stat_data.get(ONLINE_TIME, 0)))}

-
-
- """ - - # 增加饼图和条形图 - static_charts = self._generate_static_charts_div(stat_data, div_id) # 该功能尚未实现 - return f""" -
-

- 统计时段: - {start_time.strftime("%Y-%m-%d %H:%M:%S")} ~ {now.strftime("%Y-%m-%d %H:%M:%S")} -

- {summary_cards} - {static_charts} - -

按模型分类统计

- - - {model_rows} -
模型名称调用次数平均Token数Token总量TPS每K Token成本累计花费平均耗时(秒)
- -

按供应商分类统计

- - - {provider_rows} -
供应商名称调用次数Token总量TPS每K Token成本累计花费
- -

按模块分类统计

- - - - - {module_rows} -
模块名称调用次数输入Token输出TokenToken总量累计花费平均耗时(秒)标准差(秒)
- -

按请求类型分类统计

- - - - - {type_rows} -
请求类型调用次数输入Token输出TokenToken总量累计花费平均耗时(秒)标准差(秒)
- -

聊天消息统计

- - - - - {chat_rows} -
联系人/群组名称消息数量
-
- """ - def _generate_chart_tab(self, chart_data: dict) -> str: - """生成图表选项卡的HTML内容。""" - return f""" -
-

数据图表

-
- - - - - -
-
-
-
-
-
-
- - -
- """ - - async def generate_report(self, stat: dict[str, Any], chart_data: dict, now: datetime, output_path: str): - """ - 生成并写入完整的HTML报告文件。 - - :param stat: 所有时间段的统计数据。 - :param chart_data: 用于图表的数据。 - :param now: 当前时间。 - :param output_path: 输出文件路径。 - """ - tab_list = [ - f'' - for period in self.stat_period - ] - tab_list.append('') - - tab_content_list = [ - self._format_stat_data_div(stat[period[0]], period[0], now - period[1], now) - for period in self.stat_period - if period[0] != "all_time" - ] - tab_content_list.append( - self._format_stat_data_div(stat["all_time"], "all_time", self.deploy_time, now) - ) - tab_content_list.append(self._generate_chart_tab(chart_data)) - - joined_tab_list = "\n".join(tab_list) - joined_tab_content = "\n".join(tab_content_list) - - html_template = f""" - - - - - - MoFox-Bot运行统计报告 - - - - -
-

MoFox-Bot运行统计报告

-

统计截止时间: {now.strftime("%Y-%m-%d %H:%M:%S")}

-
{joined_tab_list}
- {joined_tab_content} -
- - - - """ - async with aiofiles.open(output_path, "w", encoding="utf-8") as f: - await f.write(html_template) - def _generate_static_charts_div(self, stat_data: dict[str, Any], period_id: str) -> str: - """生成静态图表(饼图、条形图)的HTML和JS。""" - provider_cost_data = stat_data.get(PIE_CHART_COST_BY_PROVIDER, {}) - model_cost_data = stat_data.get(BAR_CHART_COST_BY_MODEL, {}) - - if not provider_cost_data and not model_cost_data: - return "

数据总览

当前时段暂无足够数据生成图表。

" - - provider_labels = provider_cost_data.get('labels', []) - provider_data = provider_cost_data.get('data', []) - model_labels = model_cost_data.get('labels', []) - model_data = model_cost_data.get('data', []) - - return f""" -

数据总览

-
-
- -
-
- -
-
- - """ diff --git a/src/chat/utils/statistic.py b/src/chat/utils/statistic.py index 08d2ed8be..8007f8f97 100644 --- a/src/chat/utils/statistic.py +++ b/src/chat/utils/statistic.py @@ -16,7 +16,6 @@ logger = get_logger("maibot_statistic") # 彻底异步化:删除原同步包装器 _sync_db_get,所有数据库访问统一使用 await db_get。 -from .report_generator import HTMLReportGenerator, format_online_time from .statistic_keys import * @@ -181,16 +180,6 @@ class StatisticOutputTask(AsyncTask): logger.info("统计数据收集完成") self._statistic_console_output(stats, now) - # 使用新的 HTMLReportGenerator 生成报告 - chart_data = await self._collect_chart_data(stats) - deploy_time = datetime.fromtimestamp(local_storage.get("deploy_time", now.timestamp())) - report_generator = HTMLReportGenerator( - name_mapping=self.name_mapping, - stat_period=self.stat_period, - deploy_time=deploy_time, - ) - await report_generator.generate_report(stats, chart_data, now, self.record_file_path) - logger.info("统计数据HTML报告输出完成") except Exception as e: logger.exception(f"输出统计数据过程中发生异常,错误信息:{e}") @@ -207,18 +196,6 @@ class StatisticOutputTask(AsyncTask): logger.info("(后台) 正在收集统计数据(异步)...") stats = await self._collect_all_statistics(now) self._statistic_console_output(stats, now) - - # 使用新的 HTMLReportGenerator 生成报告 - chart_data = await self._collect_chart_data(stats) - deploy_time = datetime.fromtimestamp(local_storage.get("deploy_time", now.timestamp())) - report_generator = HTMLReportGenerator( - name_mapping=self.name_mapping, - stat_period=self.stat_period, - deploy_time=deploy_time, - ) - await report_generator.generate_report(stats, chart_data, now, self.record_file_path) - - logger.info("统计数据后台输出完成") except Exception as e: logger.exception(f"后台统计数据输出过程中发生异常:{e}")