feat: 军事科技每日资讯推送系统 - Docker部署 + 日志系统 + 数据目录重组
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
运行时监控模块
|
||||
统计各环节耗时、API调用量/token消耗、成功率,运行结束输出统计报表
|
||||
"""
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
from .logger import get_logger
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
STAGE_ORDER = [
|
||||
"rss_fetch", "time_filter", "deduplicate", "translate_titles",
|
||||
"keyword_filter", "translate_contents", "ai_analyze",
|
||||
"webzine_generate", "category_overview", "report_generate", "image_generate"
|
||||
]
|
||||
|
||||
STAGE_LABELS = {
|
||||
"rss_fetch": "RSS抓取",
|
||||
"time_filter": "时间过滤",
|
||||
"deduplicate": "去重",
|
||||
"translate_titles": "标题翻译",
|
||||
"keyword_filter": "关键词筛选",
|
||||
"translate_contents": "正文翻译",
|
||||
"ai_analyze": "AI评分分类",
|
||||
"webzine_generate": "网摘生成",
|
||||
"category_overview": "分类介绍",
|
||||
"report_generate": "报告生成",
|
||||
"image_generate": "图片生成",
|
||||
}
|
||||
|
||||
|
||||
class RunMonitor:
|
||||
def __init__(self):
|
||||
self.start_time = time.time()
|
||||
self.stage_times = {}
|
||||
self.stage_active = {}
|
||||
self.api_calls = []
|
||||
self.source_success = defaultdict(int)
|
||||
self.source_failure = defaultdict(int)
|
||||
self.articles_total = 0
|
||||
self.articles_cached = 0
|
||||
self.articles_processed = 0
|
||||
|
||||
@contextmanager
|
||||
def stage(self, name):
|
||||
"""上下文管理器,自动记录阶段耗时"""
|
||||
start = time.perf_counter()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
elapsed = time.perf_counter() - start
|
||||
self.stage_times[name] = elapsed
|
||||
|
||||
def record_api_call(self, model, purpose, max_tokens, success, retries=0):
|
||||
"""记录一次API调用"""
|
||||
self.api_calls.append({
|
||||
"model": model,
|
||||
"purpose": purpose,
|
||||
"max_tokens": max_tokens,
|
||||
"success": success,
|
||||
"retries": retries,
|
||||
})
|
||||
|
||||
def record_article_stats(self, total, cached, processed):
|
||||
self.articles_total = total
|
||||
self.articles_cached = cached
|
||||
self.articles_processed = processed
|
||||
|
||||
def record_source_result(self, source, success):
|
||||
if success:
|
||||
self.source_success[source] += 1
|
||||
else:
|
||||
self.source_failure[source] += 1
|
||||
|
||||
def report(self):
|
||||
total_elapsed = time.time() - self.start_time
|
||||
|
||||
lines = []
|
||||
lines.append("")
|
||||
lines.append("=" * 64)
|
||||
lines.append(" 📊 运行监控报告")
|
||||
lines.append("=" * 64)
|
||||
|
||||
lines.append("")
|
||||
lines.append("── ⏱ 阶段耗时 ──")
|
||||
stage_total = 0.0
|
||||
for key in STAGE_ORDER:
|
||||
if key in self.stage_times:
|
||||
t = self.stage_times[key]
|
||||
stage_total += t
|
||||
label = STAGE_LABELS.get(key, key)
|
||||
lines.append(f" {label: <10s} {t:6.1f}s")
|
||||
lines.append(f" {'─' * 20}")
|
||||
lines.append(f" {'合计': <10s} {stage_total:6.1f}s / 总运行 {total_elapsed:.0f}s")
|
||||
|
||||
lines.append("")
|
||||
lines.append("── 📡 文章统计 ──")
|
||||
lines.append(f" 总计: {self.articles_total} 缓存命中: {self.articles_cached} 本次处理: {self.articles_processed}")
|
||||
if self.articles_total > 0:
|
||||
cache_rate = self.articles_cached / self.articles_total * 100
|
||||
lines.append(f" 缓存命中率: {cache_rate:.0f}%")
|
||||
|
||||
lines.append("")
|
||||
lines.append("── 🤖 API调用统计 ──")
|
||||
if not self.api_calls:
|
||||
lines.append(" 无API调用(全部命中缓存)")
|
||||
else:
|
||||
by_purpose = defaultdict(lambda: {"count": 0, "tokens": 0, "retries": 0, "failed": 0})
|
||||
by_model = defaultdict(lambda: {"count": 0, "tokens": 0})
|
||||
for call in self.api_calls:
|
||||
p = call["purpose"]
|
||||
by_purpose[p]["count"] += 1
|
||||
by_purpose[p]["tokens"] += call["max_tokens"]
|
||||
by_purpose[p]["retries"] += call["retries"]
|
||||
if not call["success"]:
|
||||
by_purpose[p]["failed"] += 1
|
||||
|
||||
m = call["model"]
|
||||
by_model[m]["count"] += 1
|
||||
by_model[m]["tokens"] += call["max_tokens"]
|
||||
|
||||
lines.append(" [按用途]")
|
||||
for purpose, stats in by_purpose.items():
|
||||
fail_info = f" 失败{stats['failed']}" if stats['failed'] > 0 else ""
|
||||
lines.append(f" {purpose}: {stats['count']}次 / tokens≈{stats['tokens']} / 重试{stats['retries']}{fail_info}")
|
||||
|
||||
lines.append(" [按模型]")
|
||||
for model, stats in by_model.items():
|
||||
lines.append(f" {model}: {stats['count']}次 / tokens≈{stats['tokens']}")
|
||||
|
||||
lines.append("")
|
||||
lines.append("── 📰 源抓取统计 ──")
|
||||
total_sources = len(self.source_success) + len(self.source_failure)
|
||||
failed_names = [src for src in self.source_failure if self.source_failure[src] > 0]
|
||||
success_count = total_sources - len(failed_names)
|
||||
lines.append(f" 源总数: {total_sources} 成功: {success_count} 失败: {len(failed_names)}")
|
||||
if failed_names:
|
||||
lines.append(f" 失败源: {', '.join(failed_names)}")
|
||||
|
||||
lines.append("")
|
||||
lines.append("=" * 64)
|
||||
|
||||
report_text = "\n".join(lines)
|
||||
logger.info(report_text)
|
||||
return report_text
|
||||
|
||||
|
||||
_monitor = None
|
||||
|
||||
|
||||
def get_monitor():
|
||||
global _monitor
|
||||
if _monitor is None:
|
||||
_monitor = RunMonitor()
|
||||
return _monitor
|
||||
|
||||
|
||||
def reset_monitor():
|
||||
global _monitor
|
||||
_monitor = RunMonitor()
|
||||
Reference in New Issue
Block a user