mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2025-11-16 12:34:33 +00:00
135 lines
5.4 KiB
Python
135 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
||
import json
|
||
import os
|
||
from datetime import datetime, timedelta
|
||
from typing import List, Dict, Any
|
||
|
||
def load_monitoring_results() -> List[Dict[str, Any]]:
|
||
"""加载 monitoring_results.json"""
|
||
if not os.path.exists("monitoring_results.json"):
|
||
print("No monitoring results found")
|
||
return []
|
||
try:
|
||
with open("monitoring_results.json", "r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
except (json.JSONDecodeError, OSError) as e:
|
||
print(f"Error loading monitoring_results.json: {e}")
|
||
return []
|
||
|
||
def get_beijing_time() -> datetime:
|
||
return datetime.utcnow() + timedelta(hours=8)
|
||
|
||
def format_time(dt: datetime) -> str:
|
||
return dt.strftime("%Y-%m-%d %H:%M")
|
||
|
||
def classify_error(step_name: str, job_name: str) -> str:
|
||
"""错误类型分类"""
|
||
step_lower = step_name.lower()
|
||
if any(x in step_lower for x in ["test", "suite", "pytest", "unittest"]):
|
||
return "TEST_FAILURE"
|
||
if "lint" in step_lower or "flake8" in step_lower:
|
||
return "LINT_ERROR"
|
||
if "build" in step_lower or "compile" in step_lower:
|
||
return "BUILD_ERROR"
|
||
if "deploy" in step_lower or "upload" in step_lower or "publish" in step_lower:
|
||
return "DEPLOY_ERROR"
|
||
if "check" in step_lower or "validate" in step_lower or "verify" in step_lower:
|
||
return "VALIDATION_ERROR"
|
||
if "generate" in step_lower or "render" in step_lower:
|
||
return "GENERATION_ERROR"
|
||
return "UNKNOWN"
|
||
|
||
def generate_report():
|
||
"""生成符合最新样式的故障聚合报告"""
|
||
results = load_monitoring_results()
|
||
if not results:
|
||
return
|
||
|
||
failed_workflows = [r for r in results if r.get('conclusion') == 'failure']
|
||
if not failed_workflows:
|
||
print("No failed workflows to report")
|
||
return
|
||
|
||
now = get_beijing_time()
|
||
date_str = now.strftime("%Y%m%d")
|
||
|
||
# 时间范围
|
||
created_times = [
|
||
datetime.fromisoformat(r["created_at"].replace("Z", "+00:00")) + timedelta(hours=8)
|
||
for r in failed_workflows
|
||
]
|
||
updated_times = [
|
||
datetime.fromisoformat(r["updated_at"].replace("Z", "+00:00")) + timedelta(hours=8)
|
||
for r in failed_workflows
|
||
]
|
||
start_time = min(created_times)
|
||
end_time = max(updated_times)
|
||
|
||
total = len(results)
|
||
failed_count = len(failed_workflows)
|
||
success_rate = 0.0 if total == 0 else round((total - failed_count) / total * 100, 1)
|
||
|
||
# === 第一行:用于 JS 提取标题(必须)===
|
||
report = f"# {date_str}_ci_integration-failed-report\n\n"
|
||
|
||
# === 第二行:用户看到的主标题(H1)===
|
||
report += f"# 🚨 {date_str} GitHub Actions 故障聚合报告 | Incident Aggregate Report\n\n"
|
||
|
||
# === 执行概览 ===
|
||
report += f"## 执行概览 | Executive Summary\n"
|
||
report += f"- **监控时间范围 | Monitoring Period**: {format_time(start_time)}–{format_time(end_time)} (UTC+8)\n"
|
||
report += f"- **检测到失败运行 | Failed Runs Detected**: {failed_count}个\n"
|
||
report += f"- **成功率 | Success Rate**: {success_rate}% \n\n"
|
||
|
||
# === 故障详情 ===
|
||
report += f"## 🔍 故障详情 | Failure Details\n\n"
|
||
|
||
for wf in failed_workflows:
|
||
run_id = wf.get("run_id", "N/A")
|
||
name = wf["name"]
|
||
html_url = wf.get("html_url", "#")
|
||
details = wf.get("failure_details", [])
|
||
|
||
report += f"**📌 Run-{run_id}** | [{name}]({html_url})\n"
|
||
|
||
if not details:
|
||
report += "└─ 无失败作业详情 | No details of failed jobs\n\n"
|
||
continue
|
||
|
||
failed_jobs = [j for j in details if j.get("steps")]
|
||
for i, job in enumerate(failed_jobs):
|
||
job_name = job["name"]
|
||
steps = job["steps"]
|
||
job_prefix = "└─" if i == len(failed_jobs) - 1 else "├─"
|
||
report += f"{job_prefix} **失败作业 | Failed Job**: {job_name}\n"
|
||
|
||
for j, step in enumerate(steps):
|
||
step_name = step["name"]
|
||
step_num = step["number"]
|
||
error_type = classify_error(step_name, job_name)
|
||
step_prefix = " └─" if j == len(steps) - 1 else " ├─"
|
||
report += f"{step_prefix} **失败步骤 | Failed Step**: {step_name} (Step {step_num})\n"
|
||
indent = " " if j == len(steps) - 1 else " │ "
|
||
report += f"{indent}**错误类型 | Error Type**: `{error_type}`\n"
|
||
report += "\n"
|
||
|
||
# === Team Collaboration & Support ===
|
||
report += f"## 👥 团队协作与支持 | Team Collaboration & Support\n\n"
|
||
report += f"请求维护支持:本报告需要RT-Thread官方团队的专业经验进行审核与指导。 \n"
|
||
report += f"Call for Maintenance Support: This report requires the expertise of the RT-Thread official team for review and guidance.\n\n"
|
||
report += f"提审负责人:@Rbb666 @kurisaW\n"
|
||
report += f"Requested Reviewers from RT-Thread: @Rbb666 @kurisaW\n\n"
|
||
report += f"烦请尽快关注此事,万分感谢。 \n"
|
||
report += f"Your prompt attention to this matter is greatly appreciated.\n"
|
||
|
||
# 保存
|
||
try:
|
||
with open("failure_details.md", "w", encoding="utf-8") as f:
|
||
f.write(report.rstrip() + "\n")
|
||
print("Report generated: failure_details.md")
|
||
print(f"Report size: {os.path.getsize('failure_details.md')} bytes")
|
||
except Exception as e:
|
||
print(f"Error writing report: {e}")
|
||
|
||
if __name__ == "__main__":
|
||
generate_report() |