281 lines
8.9 KiB
Python
281 lines
8.9 KiB
Python
"""
|
|
Cross 模式统计报告功能
|
|
|
|
提供事件记录和统计报告生成功能,支持 Cross 和 Normal 两种模式。
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import fcntl
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
# 统计数据文件路径
|
|
STATS_FILE = Path(__file__).parent / ".stats_data.json"
|
|
|
|
# 敏感字段列表(用于过滤)
|
|
SENSITIVE_FIELDS = {"password", "token", "secret", "api_key", "apikey", "credential"}
|
|
|
|
|
|
def _filter_sensitive_data(data: dict) -> dict:
|
|
"""过滤敏感数据
|
|
|
|
递归过滤字典中的敏感字段,将值替换为 [REDACTED]
|
|
"""
|
|
if not isinstance(data, dict):
|
|
return data
|
|
|
|
filtered = {}
|
|
for key, value in data.items():
|
|
key_lower = key.lower().replace("-", "_").replace(" ", "_")
|
|
|
|
# 检查是否为敏感字段
|
|
is_sensitive = any(s in key_lower for s in SENSITIVE_FIELDS)
|
|
|
|
if is_sensitive:
|
|
filtered[key] = "[REDACTED]"
|
|
elif isinstance(value, dict):
|
|
filtered[key] = _filter_sensitive_data(value)
|
|
else:
|
|
filtered[key] = value
|
|
|
|
return filtered
|
|
|
|
|
|
def _ensure_stats_file():
|
|
"""确保统计数据文件存在,并设置正确权限"""
|
|
if not STATS_FILE.exists():
|
|
STATS_FILE.write_text(json.dumps({"events": []}, ensure_ascii=False, indent=2))
|
|
os.chmod(STATS_FILE, 0o600)
|
|
|
|
|
|
def _load_stats() -> dict:
|
|
"""加载统计数据(带文件锁)"""
|
|
_ensure_stats_file()
|
|
|
|
with open(STATS_FILE, "r") as f:
|
|
fcntl.flock(f.fileno(), fcntl.LOCK_SH) # 共享锁
|
|
try:
|
|
data = json.load(f)
|
|
finally:
|
|
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
|
|
|
return data
|
|
|
|
|
|
def _save_stats(data: dict):
|
|
"""保存统计数据(带文件锁)"""
|
|
_ensure_stats_file()
|
|
|
|
with open(STATS_FILE, "w") as f:
|
|
fcntl.flock(f.fileno(), fcntl.LOCK_EX) # 排他锁
|
|
try:
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
finally:
|
|
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
|
|
|
# 确保文件权限为 600
|
|
os.chmod(STATS_FILE, 0o600)
|
|
|
|
|
|
def record_event(
|
|
event_type: str,
|
|
mode: str,
|
|
metadata: Optional[dict] = None,
|
|
status: str = "success"
|
|
) -> bool:
|
|
"""记录一个事件
|
|
|
|
Args:
|
|
event_type: 事件类型 (debate, review, audit)
|
|
mode: 运行模式 (cross, normal)
|
|
metadata: 事件元数据(可选)
|
|
status: 事件状态 (success, failed)
|
|
|
|
Returns:
|
|
bool: 记录是否成功
|
|
"""
|
|
# 验证参数
|
|
valid_types = {"debate", "review", "audit"}
|
|
valid_modes = {"cross", "normal"}
|
|
valid_status = {"success", "failed"}
|
|
|
|
if event_type not in valid_types:
|
|
print(f"错误: 无效的事件类型 '{event_type}',有效值: {valid_types}")
|
|
return False
|
|
|
|
if mode not in valid_modes:
|
|
print(f"错误: 无效的模式 '{mode}',有效值: {valid_modes}")
|
|
return False
|
|
|
|
if status not in valid_status:
|
|
print(f"错误: 无效的状态 '{status}',有效值: {valid_status}")
|
|
return False
|
|
|
|
# 过滤敏感数据
|
|
if metadata:
|
|
metadata = _filter_sensitive_data(metadata)
|
|
else:
|
|
metadata = {}
|
|
|
|
# 创建事件记录
|
|
event = {
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
"event_type": event_type,
|
|
"mode": mode,
|
|
"status": status,
|
|
"metadata": metadata
|
|
}
|
|
|
|
try:
|
|
data = _load_stats()
|
|
data["events"].append(event)
|
|
_save_stats(data)
|
|
return True
|
|
except Exception as e:
|
|
print(f"记录事件失败: {e}")
|
|
return False
|
|
|
|
|
|
def get_stats() -> dict:
|
|
"""获取统计汇总数据
|
|
|
|
Returns:
|
|
dict: 统计汇总数据
|
|
"""
|
|
data = _load_stats()
|
|
events = data.get("events", [])
|
|
|
|
# 初始化统计结构
|
|
stats = {
|
|
"total_events": len(events),
|
|
"by_type": {
|
|
"debate": {"total": 0, "success": 0, "failed": 0},
|
|
"review": {"total": 0, "success": 0, "failed": 0},
|
|
"audit": {"total": 0, "success": 0, "failed": 0}
|
|
},
|
|
"by_mode": {
|
|
"cross": {"total": 0, "success": 0, "failed": 0},
|
|
"normal": {"total": 0, "success": 0, "failed": 0}
|
|
},
|
|
"cross_mode_breakdown": {
|
|
"debate": {"total": 0, "success": 0, "failed": 0},
|
|
"review": {"total": 0, "success": 0, "failed": 0},
|
|
"audit": {"total": 0, "success": 0, "failed": 0}
|
|
},
|
|
"normal_mode_breakdown": {
|
|
"debate": {"total": 0, "success": 0, "failed": 0},
|
|
"review": {"total": 0, "success": 0, "failed": 0},
|
|
"audit": {"total": 0, "success": 0, "failed": 0}
|
|
},
|
|
"success_rate": 0.0,
|
|
"first_event": None,
|
|
"last_event": None
|
|
}
|
|
|
|
if not events:
|
|
return stats
|
|
|
|
# 统计各类数据
|
|
for event in events:
|
|
event_type = event["event_type"]
|
|
mode = event["mode"]
|
|
status = event["status"]
|
|
|
|
# 按类型统计
|
|
stats["by_type"][event_type]["total"] += 1
|
|
stats["by_type"][event_type][status] += 1
|
|
|
|
# 按模式统计
|
|
stats["by_mode"][mode]["total"] += 1
|
|
stats["by_mode"][mode][status] += 1
|
|
|
|
# 按模式+类型交叉统计
|
|
breakdown_key = f"{mode}_mode_breakdown"
|
|
stats[breakdown_key][event_type]["total"] += 1
|
|
stats[breakdown_key][event_type][status] += 1
|
|
|
|
# 计算成功率
|
|
total = stats["total_events"]
|
|
success_count = sum(t["success"] for t in stats["by_type"].values())
|
|
stats["success_rate"] = round(success_count / total * 100, 2) if total > 0 else 0.0
|
|
|
|
# 时间范围
|
|
stats["first_event"] = events[0]["timestamp"]
|
|
stats["last_event"] = events[-1]["timestamp"]
|
|
|
|
return stats
|
|
|
|
|
|
def format_stats_report(stats: dict) -> str:
|
|
"""格式化统计报告为可读字符串
|
|
|
|
Args:
|
|
stats: get_stats() 返回的统计数据
|
|
|
|
Returns:
|
|
str: 格式化的统计报告
|
|
"""
|
|
lines = []
|
|
lines.append("=" * 50)
|
|
lines.append(" Cross 模式统计报告")
|
|
lines.append("=" * 50)
|
|
|
|
# 总览
|
|
lines.append(f"\n📊 总览")
|
|
lines.append(f" 总事件数: {stats['total_events']}")
|
|
lines.append(f" 成功率: {stats['success_rate']}%")
|
|
if stats['first_event']:
|
|
lines.append(f" 时间范围: {stats['first_event']} ~ {stats['last_event']}")
|
|
|
|
# 按模式统计
|
|
lines.append(f"\n📈 按模式统计")
|
|
for mode, data in stats["by_mode"].items():
|
|
mode_name = "Cross 模式" if mode == "cross" else "Normal 模式"
|
|
rate = round(data["success"] / data["total"] * 100, 2) if data["total"] > 0 else 0
|
|
lines.append(f" {mode_name}:")
|
|
lines.append(f" 总数: {data['total']} | 成功: {data['success']} | 失败: {data['failed']} | 成功率: {rate}%")
|
|
|
|
# 按事件类型统计
|
|
lines.append(f"\n📋 按事件类型统计")
|
|
for event_type, data in stats["by_type"].items():
|
|
type_name = {"debate": "辩论", "review": "评审", "audit": "审计"}.get(event_type, event_type)
|
|
rate = round(data["success"] / data["total"] * 100, 2) if data["total"] > 0 else 0
|
|
lines.append(f" {type_name}:")
|
|
lines.append(f" 总数: {data['total']} | 成功: {data['success']} | 失败: {data['failed']} | 成功率: {rate}%")
|
|
|
|
# Cross 模式详细分解
|
|
lines.append(f"\n🔍 Cross 模式详细分解")
|
|
for event_type, data in stats["cross_mode_breakdown"].items():
|
|
type_name = {"debate": "辩论", "review": "评审", "audit": "审计"}.get(event_type, event_type)
|
|
rate = round(data["success"] / data["total"] * 100, 2) if data["total"] > 0 else 0
|
|
lines.append(f" {type_name}:")
|
|
lines.append(f" 总数: {data['total']} | 成功: {data['success']} | 失败: {data['failed']} | 成功率: {rate}%")
|
|
|
|
# Normal 模式详细分解
|
|
lines.append(f"\n🔍 Normal 模式详细分解")
|
|
for event_type, data in stats["normal_mode_breakdown"].items():
|
|
type_name = {"debate": "辩论", "review": "评审", "audit": "审计"}.get(event_type, event_type)
|
|
rate = round(data["success"] / data["total"] * 100, 2) if data["total"] > 0 else 0
|
|
lines.append(f" {type_name}:")
|
|
lines.append(f" 总数: {data['total']} | 成功: {data['success']} | 失败: {data['failed']} | 成功率: {rate}%")
|
|
|
|
lines.append("\n" + "=" * 50)
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# 测试代码
|
|
print("测试 stats.py 模块...")
|
|
|
|
# 记录一些测试事件
|
|
record_event("debate", "cross", {"topic": "AI 安全"}, "success")
|
|
record_event("review", "normal", {"pr_id": 123}, "success")
|
|
record_event("audit", "cross", {"password": "secret123"}, "failed") # 敏感数据会被过滤
|
|
|
|
# 获取并打印统计
|
|
stats = get_stats()
|
|
print(format_stats_report(stats))
|