提交 c73ffd58 authored 作者: 陈泽健's avatar 陈泽健

feat(perf): AI 分析报告资源使用分析四维度增强(Java 进程 + MySQL)

- 资源使用分析扩展为四维度:执行机整体 / 目标机整体 / Java 进程 / MySQL
- 新增 _extract_java_process_stats / _extract_mysql_stats / _fmt_num 辅助方法
- Chapter 四重写为 4 小节,瓶颈表新增 Java 进程与 MySQL 条目,建议表同步
- 更新 LLM Prompt 第四章要求四维度分析,资源表头改 [资源,指标,值,状态]
- 修复 _rule_based_analysis 中 exec_cpu_avg / exec_cpu_max 未定义问题(上一轮遗留)
- 测试新增 4 个资源维度用例,共 20 passed,全量 295 passed
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 2a6ecd8a
...@@ -90,7 +90,13 @@ SINGLE_ANALYSIS_PROMPT = """你是一个性能测试专家,分析以下性能 ...@@ -90,7 +90,13 @@ SINGLE_ANALYSIS_PROMPT = """你是一个性能测试专家,分析以下性能
- **网络传输分析**:收发数据量、网络吞吐量 - **网络传输分析**:收发数据量、网络吞吐量
### 4. 资源使用分析(resource_analysis) ### 4. 资源使用分析(resource_analysis)
分析执行机和目标机的资源使用情况:CPU、内存峰值与平均值,是否存在资源瓶颈。结合资源监控数据判断系统是否达到瓶颈。 从**四个维度**分别分析执行机与目标机的资源使用情况,每个维度先给数据再给判断,判断是否达到瓶颈:
- **执行机整体资源**:CPU/内存平均值与峰值、网络收发速率,是否超过阈值,压测工具自身是否成为瓶颈
- **目标机整体资源**:CPU/内存平均值与峰值、负载(load average)
- **Java 进程资源(目标机内)**:针对每个 Java 进程的 CPU/内存/RSS 平均与峰值,识别占用最高的进程
- **MySQL 数据库资源(目标机 umysql 容器)**:连接数、活跃线程、慢查询累计、最大已用连接/最大连接数、缓冲池命中率,判断数据库层是否成为瓶颈
无数据的维度明确标注「未采集」,不得虚构结论。
### 5. 瓶颈定位(bottlenecks) ### 5. 瓶颈定位(bottlenecks)
基于数据驱动定位性能瓶颈,每个瓶颈需要说明:类型、位置、严重程度、数据证据。包括响应时间瓶颈、吞吐量瓶颈、资源瓶颈、错误率瓶颈。 基于数据驱动定位性能瓶颈,每个瓶颈需要说明:类型、位置、严重程度、数据证据。包括响应时间瓶颈、吞吐量瓶颈、资源瓶颈、错误率瓶颈。
...@@ -151,9 +157,9 @@ SINGLE_ANALYSIS_PROMPT = """你是一个性能测试专家,分析以下性能 ...@@ -151,9 +157,9 @@ SINGLE_ANALYSIS_PROMPT = """你是一个性能测试专家,分析以下性能
{{ {{
"id": "resource_analysis", "id": "resource_analysis",
"title": "四、资源使用分析", "title": "四、资源使用分析",
"content": "段落文字...", "content": "出现段落文字,综合执行机 / 目标机 / Java 进程 / MySQL 四维度资源使用情况",
"tables": [ "tables": [
{{"headers": ["资源", "指标", "值"], "rows": [...]}} {{"headers": ["资源", "指标", "值", "状态"], "rows": [...]}}
] ]
}}, }},
{{ {{
...@@ -1036,6 +1042,48 @@ class PerformanceAiService: ...@@ -1036,6 +1042,48 @@ class PerformanceAiService:
mem_max = stats.get("maxMemPercent") or resource_summary.get("memory_max") or resource_summary.get("memory_max_percent") mem_max = stats.get("maxMemPercent") or resource_summary.get("memory_max") or resource_summary.get("memory_max_percent")
return {"cpu_avg": cpu_avg, "cpu_max": cpu_max, "mem_avg": mem_avg, "mem_max": mem_max} return {"cpu_avg": cpu_avg, "cpu_max": cpu_max, "mem_avg": mem_avg, "mem_max": mem_max}
@staticmethod
def _extract_java_process_stats(target_resource_summary: dict) -> list:
"""
从目标机资源汇总提取 Java 进程统计列表。
executor 产出结构:
{"javaProcessStats": [{"processName", "avgCpuPercent", "maxCpuPercent",
"avgMemPercent", "maxMemPercent", "avgRssMb", "maxRssMb"}, ...]}
Returns:
list: Java 进程统计列表(无数据返回空列表)
"""
if not target_resource_summary:
return []
return target_resource_summary.get("javaProcessStats") or []
@staticmethod
def _extract_mysql_stats(target_resource_summary: dict) -> dict:
"""
从目标机资源汇总提取 MySQL 监控统计。
executor 产出结构:
{"mysql": {"available", "container", "threadsConnected", "threadsRunning",
"maxUsedConnections", "maxConnections", "slowQueries", "bufferPoolHitRate", ...}}
Returns:
dict: MySQL 统计(无数据返回空 dict)
"""
if not target_resource_summary:
return {}
mysql = target_resource_summary.get("mysql") or {}
return mysql if mysql.get("available") else {}
@staticmethod
def _fmt_num(value) -> str:
"""格式化数值:None 显示 —,浮点保留 2 位,其余原样"""
if value is None:
return "—"
if isinstance(value, float):
return f"{value:.2f}"
return str(value)
def _build_rule_document_sections(self, report_data: dict) -> list: def _build_rule_document_sections(self, report_data: dict) -> list:
""" """
基于规则构建 7 章节正式文档结构 基于规则构建 7 章节正式文档结构
...@@ -1083,14 +1131,14 @@ class PerformanceAiService: ...@@ -1083,14 +1131,14 @@ class PerformanceAiService:
# 资源数据(兼容 stats 嵌套与旧平铺键,同时取平均/峰值) # 资源数据(兼容 stats 嵌套与旧平铺键,同时取平均/峰值)
exec_stats = self._extract_resource_stats(resource_summary) exec_stats = self._extract_resource_stats(resource_summary)
tgt_stats = self._extract_resource_stats(target_resource_summary) tgt_stats = self._extract_resource_stats(target_resource_summary)
cpu_usage = exec_stats["cpu_avg"] exec_cpu_avg = exec_stats["cpu_avg"]
cpu_max = exec_stats["cpu_max"] exec_cpu_max = exec_stats["cpu_max"]
mem_usage = exec_stats["mem_avg"] exec_mem_avg = exec_stats["mem_avg"]
mem_max = exec_stats["mem_max"] exec_mem_max = exec_stats["mem_max"]
target_cpu = tgt_stats["cpu_avg"] tgt_cpu_avg = tgt_stats["cpu_avg"]
target_cpu_max = tgt_stats["cpu_max"] tgt_cpu_max = tgt_stats["cpu_max"]
target_mem = tgt_stats["mem_avg"] tgt_mem_avg = tgt_stats["mem_avg"]
target_mem_max = tgt_stats["mem_max"] tgt_mem_max = tgt_stats["mem_max"]
# ========== 一、测试概述 ========== # ========== 一、测试概述 ==========
mode_label = {"concurrent": "并发模式", "qps": "QPS 模式", "step": "阶梯模式"}.get(mode, mode or "未设置") mode_label = {"concurrent": "并发模式", "qps": "QPS 模式", "step": "阶梯模式"}.get(mode, mode or "未设置")
...@@ -1241,45 +1289,146 @@ class PerformanceAiService: ...@@ -1241,45 +1289,146 @@ class PerformanceAiService:
else: else:
net_analysis = "未采集到网络传输数据。" net_analysis = "未采集到网络传输数据。"
# ========== 四、资源使用分析 ========== # ========== 四、资源使用分析(执行机 / 目标机 / Java 进程 / MySQL 四维度) ==========
resource_content_parts = [] resource_content_parts = []
resource_rows = [] resource_rows = []
if cpu_usage is not None:
flag = "超过阈值" if cpu_usage > THRESHOLD_CPU_HIGH else "正常" # --- 4.1 执行机整体资源 ---
peak_desc = f",峰值 {cpu_max:.1f}%" if cpu_max is not None else "" exec_cpu_avg = exec_stats["cpu_avg"]
resource_content_parts.append(f"执行机 CPU 平均使用率 {cpu_usage:.1f}%{peak_desc},{flag}。") exec_cpu_max = exec_stats["cpu_max"]
resource_rows.append(["执行机", "CPU 平均", f"{cpu_usage:.1f}%", flag]) exec_mem_avg = exec_stats["mem_avg"]
if cpu_max is not None: exec_mem_max = exec_stats["mem_max"]
resource_rows.append(["执行机", "CPU 峰值", f"{cpu_max:.1f}%", "超过阈值" if cpu_max > THRESHOLD_CPU_HIGH else "正常"]) if exec_cpu_avg is not None:
flag = "超过阈值" if exec_cpu_avg > THRESHOLD_CPU_HIGH else "正常"
peak_desc = f",峰值 {exec_cpu_max:.1f}%" if exec_cpu_max is not None else ""
resource_content_parts.append(f"执行机 CPU 平均使用率 {exec_cpu_avg:.1f}%{peak_desc},{flag}。")
resource_rows.append(["执行机", "CPU 平均", f"{exec_cpu_avg:.1f}%", flag])
if exec_cpu_max is not None:
resource_rows.append(["执行机", "CPU 峰值", f"{exec_cpu_max:.1f}%", "超过阈值" if exec_cpu_max > THRESHOLD_CPU_HIGH else "正常"])
else: else:
resource_content_parts.append("执行机 CPU 数据未采集。") resource_content_parts.append("执行机 CPU 数据未采集。")
if mem_usage is not None: if exec_mem_avg is not None:
flag = "超过阈值" if mem_usage > THRESHOLD_MEMORY_HIGH else "正常" flag = "超过阈值" if exec_mem_avg > THRESHOLD_MEMORY_HIGH else "正常"
peak_desc = f",峰值 {mem_max:.1f}%" if mem_max is not None else "" peak_desc = f",峰值 {exec_mem_max:.1f}%" if exec_mem_max is not None else ""
resource_content_parts.append(f"执行机内存平均使用率 {mem_usage:.1f}%{peak_desc},{flag}。") resource_content_parts.append(f"执行机内存平均使用率 {exec_mem_avg:.1f}%{peak_desc},{flag}。")
resource_rows.append(["执行机", "内存平均", f"{mem_usage:.1f}%", flag]) resource_rows.append(["执行机", "内存平均", f"{exec_mem_avg:.1f}%", flag])
if mem_max is not None: if exec_mem_max is not None:
resource_rows.append(["执行机", "内存峰值", f"{mem_max:.1f}%", "超过阈值" if mem_max > THRESHOLD_MEMORY_HIGH else "正常"]) resource_rows.append(["执行机", "内存峰值", f"{exec_mem_max:.1f}%", "超过阈值" if exec_mem_max > THRESHOLD_MEMORY_HIGH else "正常"])
else: else:
resource_content_parts.append("执行机内存数据未采集。") resource_content_parts.append("执行机内存数据未采集。")
if target_cpu is not None:
flag = "超过阈值" if target_cpu > THRESHOLD_CPU_HIGH else "正常" # --- 4.2 目标机整体资源 ---
peak_desc = f",峰值 {target_cpu_max:.1f}%" if target_cpu_max is not None else "" tgt_cpu_avg = tgt_stats["cpu_avg"]
resource_content_parts.append(f"目标机 CPU 平均使用率 {target_cpu:.1f}%{peak_desc},{flag}。") tgt_cpu_max = tgt_stats["cpu_max"]
resource_rows.append(["目标机", "CPU 平均", f"{target_cpu:.1f}%", flag]) tgt_mem_avg = tgt_stats["mem_avg"]
if target_cpu_max is not None: tgt_mem_max = tgt_stats["mem_max"]
resource_rows.append(["目标机", "CPU 峰值", f"{target_cpu_max:.1f}%", "超过阈值" if target_cpu_max > THRESHOLD_CPU_HIGH else "正常"]) if tgt_cpu_avg is not None:
flag = "超过阈值" if tgt_cpu_avg > THRESHOLD_CPU_HIGH else "正常"
peak_desc = f",峰值 {tgt_cpu_max:.1f}%" if tgt_cpu_max is not None else ""
resource_content_parts.append(f"目标机 CPU 平均使用率 {tgt_cpu_avg:.1f}%{peak_desc},{flag}。")
resource_rows.append(["目标机", "CPU 平均", f"{tgt_cpu_avg:.1f}%", flag])
if tgt_cpu_max is not None:
resource_rows.append(["目标机", "CPU 峰值", f"{tgt_cpu_max:.1f}%", "超过阈值" if tgt_cpu_max > THRESHOLD_CPU_HIGH else "正常"])
else: else:
resource_content_parts.append("目标机 CPU 数据未采集。") resource_content_parts.append("目标机 CPU 数据未采集。")
if target_mem is not None: if tgt_mem_avg is not None:
flag = "超过阈值" if target_mem > THRESHOLD_MEMORY_HIGH else "正常" flag = "超过阈值" if tgt_mem_avg > THRESHOLD_MEMORY_HIGH else "正常"
peak_desc = f",峰值 {target_mem_max:.1f}%" if target_mem_max is not None else "" peak_desc = f",峰值 {tgt_mem_max:.1f}%" if tgt_mem_max is not None else ""
resource_content_parts.append(f"目标机内存平均使用率 {target_mem:.1f}%{peak_desc},{flag}。") resource_content_parts.append(f"目标机内存平均使用率 {tgt_mem_avg:.1f}%{peak_desc},{flag}。")
resource_rows.append(["目标机", "内存平均", f"{target_mem:.1f}%", flag]) resource_rows.append(["目标机", "内存平均", f"{tgt_mem_avg:.1f}%", flag])
if target_mem_max is not None: if tgt_mem_max is not None:
resource_rows.append(["目标机", "内存峰值", f"{target_mem_max:.1f}%", "超过阈值" if target_mem_max > THRESHOLD_MEMORY_HIGH else "正常"]) resource_rows.append(["目标机", "内存峰值", f"{tgt_mem_max:.1f}%", "超过阈值" if tgt_mem_max > THRESHOLD_MEMORY_HIGH else "正常"])
else: else:
resource_content_parts.append("目标机内存数据未采集。") resource_content_parts.append("目标机内存数据未采集。")
# --- 4.3 Java 进程资源(目标机内各 Java 进程) ---
java_procs = self._extract_java_process_stats(target_resource_summary)
java_content_parts = []
if java_procs:
java_content_parts.append(f"目标机共监控到 {len(java_procs)} 个 Java 进程,各进程 CPU/内存/RSS 使用如下:")
for p in java_procs:
pname = p.get("processName", "未知")
pcpu_avg = p.get("avgCpuPercent")
pcpu_max = p.get("maxCpuPercent")
pmem_avg = p.get("avgMemPercent")
pmem_max = p.get("maxMemPercent")
prss_avg = p.get("avgRssMb")
prss_max = p.get("maxRssMb")
parts = []
if pcpu_avg is not None:
flag = "超过阈值" if pcpu_avg > THRESHOLD_CPU_HIGH else "正常"
parts.append(f"CPU 平均 {pcpu_avg:.1f}%")
if pcpu_max is not None:
parts.append(f"峰值 {pcpu_max:.1f}%")
cpu_flag = flag
else:
cpu_flag = "—"
if pmem_avg is not None:
parts.append(f"内存平均 {pmem_avg:.1f}%")
if pmem_max is not None:
parts.append(f"峰值 {pmem_max:.1f}%")
if prss_avg is not None:
parts.append(f"RSS 平均 {prss_avg:.1f}MB")
if prss_max is not None:
parts.append(f"峰值 {prss_max:.1f}MB")
java_content_parts.append(
f"「{pname}」:{'、'.join(parts)}({'超过阈值' if cpu_flag == '超过阈值' else '正常'})。"
)
resource_rows.append([f"Java 进程 · {pname}", "CPU 平均", self._fmt_num(pcpu_avg) + ("%" if pcpu_avg is not None else ""), cpu_flag if cpu_flag != "—" else "—"])
if pcpu_max is not None:
resource_rows.append([f"Java 进程 · {pname}", "CPU 峰值", f"{pcpu_max:.1f}%", "超过阈值" if pcpu_max > THRESHOLD_CPU_HIGH else "正常"])
resource_rows.append([f"Java 进程 · {pname}", "内存平均", self._fmt_num(pmem_avg) + ("%" if pmem_avg is not None else ""), "—"])
if pmem_max is not None:
resource_rows.append([f"Java 进程 · {pname}", "内存峰值", f"{pmem_max:.1f}%", "—"])
if prss_avg is not None:
resource_rows.append([f"Java 进程 · {pname}", "RSS 平均", f"{prss_avg:.1f}MB", "—"])
if prss_max is not None:
resource_rows.append([f"Java 进程 · {pname}", "RSS 峰值", f"{prss_max:.1f}MB", "—"])
else:
java_content_parts.append("未采集到 Java 进程资源数据(未启用目标机 Java 进程监控或无 Java 进程)。")
# --- 4.4 MySQL 数据库资源(目标机 umysql 容器) ---
mysql_stats = self._extract_mysql_stats(target_resource_summary)
mysql_content_parts = []
if mysql_stats:
container = mysql_stats.get("container", "umysql")
conn = mysql_stats.get("threadsConnected")
running = mysql_stats.get("threadsRunning")
slow = mysql_stats.get("slowQueries")
max_used = mysql_stats.get("maxUsedConnections")
max_conn = mysql_stats.get("maxConnections")
buf_hit = mysql_stats.get("bufferPoolHitRate")
questions = mysql_stats.get("questions")
queries = mysql_stats.get("queries")
recv_mb = mysql_stats.get("bytesReceivedMb")
sent_mb = mysql_stats.get("bytesSentMb")
mysql_content_parts.append(
f"MySQL(容器 {container})连接数 {conn if conn is not None else '—'}"
f"{f'(活跃线程 {running})' if running is not None else ''}"
f"{f',慢查询累计 {slow}' if slow is not None else ''}"
f"{f',最大已用连接 {max_used}/{max_conn}' if max_used is not None and max_conn is not None else ''}"
f"{f',缓冲池命中率 {buf_hit:.1f}%' if buf_hit is not None else ''}。"
)
resource_rows.append(["MySQL", "连接数", self._fmt_num(conn), "—"])
if running is not None:
resource_rows.append(["MySQL", "活跃线程", self._fmt_num(running), "—"])
if slow is not None:
resource_rows.append(["MySQL", "慢查询累计", self._fmt_num(slow), "—"])
if max_used is not None and max_conn is not None:
resource_rows.append(["MySQL", "最大已用连接", f"{self._fmt_num(max_used)}/{max_conn}", "—"])
if buf_hit is not None:
buf_flag = "低于阈值" if buf_hit < 99.0 else "正常"
resource_rows.append(["MySQL", "缓冲池命中率", f"{buf_hit:.1f}%", buf_flag])
if questions is not None:
resource_rows.append(["MySQL", "查询总量", self._fmt_num(questions), "—"])
if recv_mb is not None:
resource_rows.append(["MySQL", "累计接收数据", f"{recv_mb:.1f}MB", "—"])
if sent_mb is not None:
resource_rows.append(["MySQL", "累计发送数据", f"{sent_mb:.1f}MB", "—"])
else:
mysql_content_parts.append("未采集到 MySQL 资源数据(目标机未启用 MySQL 监控或采集失败)。")
resource_content_parts += java_content_parts + mysql_content_parts
if not resource_rows: if not resource_rows:
resource_content_parts = ["无资源监控数据,建议开启目标机/执行机资源监控以获得更全面的分析。"] resource_content_parts = ["无资源监控数据,建议开启目标机/执行机资源监控以获得更全面的分析。"]
...@@ -1297,30 +1446,50 @@ class PerformanceAiService: ...@@ -1297,30 +1446,50 @@ class PerformanceAiService:
if error_rate > THRESHOLD_ERROR_RATE: if error_rate > THRESHOLD_ERROR_RATE:
bottleneck_rows.append(["错误率", "应用层", "high", f"错误率 {error_rate:.2f} 超过 {THRESHOLD_ERROR_RATE}% 阈值", f"错误率={error_rate:.2f}%"]) bottleneck_rows.append(["错误率", "应用层", "high", f"错误率 {error_rate:.2f} 超过 {THRESHOLD_ERROR_RATE}% 阈值", f"错误率={error_rate:.2f}%"])
bottleneck_content_parts.append(f"错误率瓶颈:错误率 {error_rate:.2f}% 超过 {THRESHOLD_ERROR_RATE}% 阈值。") bottleneck_content_parts.append(f"错误率瓶颈:错误率 {error_rate:.2f}% 超过 {THRESHOLD_ERROR_RATE}% 阈值。")
if cpu_usage is not None and cpu_usage > THRESHOLD_CPU_HIGH: if exec_cpu_avg is not None and exec_cpu_avg > THRESHOLD_CPU_HIGH:
bottleneck_rows.append(["资源", "执行机 CPU", "high", f"CPU 平均 {cpu_usage:.1f}% 超过 {THRESHOLD_CPU_HIGH}%", f"CPU平均={cpu_usage:.1f}%"]) bottleneck_rows.append(["资源", "执行机 CPU", "high", f"CPU 平均 {exec_cpu_avg:.1f}% 超过 {THRESHOLD_CPU_HIGH}%", f"CPU平均={exec_cpu_avg:.1f}%"])
bottleneck_content_parts.append(f"执行机 CPU 瓶颈:平均使用率 {cpu_usage:.1f}%。") bottleneck_content_parts.append(f"执行机 CPU 瓶颈:平均使用率 {exec_cpu_avg:.1f}%。")
elif cpu_max is not None and cpu_max > THRESHOLD_CPU_HIGH: elif exec_cpu_max is not None and exec_cpu_max > THRESHOLD_CPU_HIGH:
bottleneck_rows.append(["资源", "执行机 CPU(峰值)", "medium", f"CPU 峰值 {cpu_max:.1f}% 超过 {THRESHOLD_CPU_HIGH}%", f"CPU峰值={cpu_max:.1f}%"]) bottleneck_rows.append(["资源", "执行机 CPU(峰值)", "medium", f"CPU 峰值 {exec_cpu_max:.1f}% 超过 {THRESHOLD_CPU_HIGH}%", f"CPU峰值={exec_cpu_max:.1f}%"])
bottleneck_content_parts.append(f"执行机 CPU 峰值达 {cpu_max:.1f}%,需关注。") bottleneck_content_parts.append(f"执行机 CPU 峰值达 {exec_cpu_max:.1f}%,需关注。")
if mem_usage is not None and mem_usage > THRESHOLD_MEMORY_HIGH: if exec_mem_avg is not None and exec_mem_avg > THRESHOLD_MEMORY_HIGH:
bottleneck_rows.append(["资源", "执行机内存", "high", f"内存平均 {mem_usage:.1f}% 超过 {THRESHOLD_MEMORY_HIGH}%", f"内存平均={mem_usage:.1f}%"]) bottleneck_rows.append(["资源", "执行机内存", "high", f"内存平均 {exec_mem_avg:.1f}% 超过 {THRESHOLD_MEMORY_HIGH}%", f"内存平均={exec_mem_avg:.1f}%"])
bottleneck_content_parts.append(f"执行机内存瓶颈:平均使用率 {mem_usage:.1f}%。") bottleneck_content_parts.append(f"执行机内存瓶颈:平均使用率 {exec_mem_avg:.1f}%。")
elif mem_max is not None and mem_max > THRESHOLD_MEMORY_HIGH: elif exec_mem_max is not None and exec_mem_max > THRESHOLD_MEMORY_HIGH:
bottleneck_rows.append(["资源", "执行机内存(峰值)", "medium", f"内存峰值 {mem_max:.1f}% 超过 {THRESHOLD_MEMORY_HIGH}%", f"内存峰值={mem_max:.1f}%"]) bottleneck_rows.append(["资源", "执行机内存(峰值)", "medium", f"内存峰值 {exec_mem_max:.1f}% 超过 {THRESHOLD_MEMORY_HIGH}%", f"内存峰值={exec_mem_max:.1f}%"])
bottleneck_content_parts.append(f"执行机内存峰值达 {mem_max:.1f}%,需关注。") bottleneck_content_parts.append(f"执行机内存峰值达 {exec_mem_max:.1f}%,需关注。")
if target_cpu is not None and target_cpu > THRESHOLD_CPU_HIGH: if tgt_cpu_avg is not None and tgt_cpu_avg > THRESHOLD_CPU_HIGH:
bottleneck_rows.append(["资源", "目标机 CPU", "high", f"CPU 平均 {target_cpu:.1f}% 超过 {THRESHOLD_CPU_HIGH}%", f"CPU平均={target_cpu:.1f}%"]) bottleneck_rows.append(["资源", "目标机 CPU", "high", f"CPU 平均 {tgt_cpu_avg:.1f}% 超过 {THRESHOLD_CPU_HIGH}%", f"CPU平均={tgt_cpu_avg:.1f}%"])
bottleneck_content_parts.append(f"目标机 CPU 瓶颈:平均使用率 {target_cpu:.1f}%。") bottleneck_content_parts.append(f"目标机 CPU 瓶颈:平均使用率 {tgt_cpu_avg:.1f}%。")
elif target_cpu_max is not None and target_cpu_max > THRESHOLD_CPU_HIGH: elif tgt_cpu_max is not None and tgt_cpu_max > THRESHOLD_CPU_HIGH:
bottleneck_rows.append(["资源", "目标机 CPU(峰值)", "medium", f"CPU 峰值 {target_cpu_max:.1f}% 超过 {THRESHOLD_CPU_HIGH}%", f"CPU峰值={target_cpu_max:.1f}%"]) bottleneck_rows.append(["资源", "目标机 CPU(峰值)", "medium", f"CPU 峰值 {tgt_cpu_max:.1f}% 超过 {THRESHOLD_CPU_HIGH}%", f"CPU峰值={tgt_cpu_max:.1f}%"])
bottleneck_content_parts.append(f"目标机 CPU 峰值达 {target_cpu_max:.1f}%,需关注。") bottleneck_content_parts.append(f"目标机 CPU 峰值达 {tgt_cpu_max:.1f}%,需关注。")
if target_mem is not None and target_mem > THRESHOLD_MEMORY_HIGH: if tgt_mem_avg is not None and tgt_mem_avg > THRESHOLD_MEMORY_HIGH:
bottleneck_rows.append(["资源", "目标机内存", "high", f"内存平均 {target_mem:.1f}% 超过 {THRESHOLD_MEMORY_HIGH}%", f"内存平均={target_mem:.1f}%"]) bottleneck_rows.append(["资源", "目标机内存", "high", f"内存平均 {tgt_mem_avg:.1f}% 超过 {THRESHOLD_MEMORY_HIGH}%", f"内存平均={tgt_mem_avg:.1f}%"])
bottleneck_content_parts.append(f"目标机内存瓶颈:平均使用率 {target_mem:.1f}%。") bottleneck_content_parts.append(f"目标机内存瓶颈:平均使用率 {tgt_mem_avg:.1f}%。")
elif target_mem_max is not None and target_mem_max > THRESHOLD_MEMORY_HIGH: elif tgt_mem_max is not None and tgt_mem_max > THRESHOLD_MEMORY_HIGH:
bottleneck_rows.append(["资源", "目标机内存(峰值)", "medium", f"内存峰值 {target_mem_max:.1f}% 超过 {THRESHOLD_MEMORY_HIGH}%", f"内存峰值={target_mem_max:.1f}%"]) bottleneck_rows.append(["资源", "目标机内存(峰值)", "medium", f"内存峰值 {tgt_mem_max:.1f}% 超过 {THRESHOLD_MEMORY_HIGH}%", f"内存峰值={tgt_mem_max:.1f}%"])
bottleneck_content_parts.append(f"目标机内存峰值达 {target_mem_max:.1f}%,需关注。") bottleneck_content_parts.append(f"目标机内存峰值达 {tgt_mem_max:.1f}%,需关注。")
# Java 进程资源瓶颈
java_procs_bn = self._extract_java_process_stats(target_resource_summary)
for p in java_procs_bn:
pname = p.get("processName", "未知")
pcpu_avg = p.get("avgCpuPercent")
pcpu_max = p.get("maxCpuPercent")
pmem_avg = p.get("avgMemPercent")
pmem_max = p.get("maxMemPercent")
if pcpu_avg is not None and pcpu_avg > THRESHOLD_CPU_HIGH:
bottleneck_rows.append(["资源", f"Java 进程 {pname}", "high", f"CPU 平均 {pcpu_avg:.1f}% 超过 {THRESHOLD_CPU_HIGH}%", f"CPU平均={pcpu_avg:.1f}%"])
bottleneck_content_parts.append(f"Java 进程「{pname}」CPU 平均 {pcpu_avg:.1f}%,超过阈值。")
elif pcpu_max is not None and pcpu_max > THRESHOLD_CPU_HIGH:
bottleneck_rows.append(["资源", f"Java 进程 {pname}(峰值)", "medium", f"CPU 峰值 {pcpu_max:.1f}% 超过 {THRESHOLD_CPU_HIGH}%", f"CPU峰值={pcpu_max:.1f}%"])
bottleneck_content_parts.append(f"Java 进程「{pname}」CPU 峰值达 {pcpu_max:.1f}%,需关注。")
if pmem_avg is not None and pmem_avg > THRESHOLD_MEMORY_HIGH:
bottleneck_rows.append(["资源", f"Java 进程 {pname} 内存", "high", f"内存平均 {pmem_avg:.1f}% 超过 {THRESHOLD_MEMORY_HIGH}%", f"内存平均={pmem_avg:.1f}%"])
bottleneck_content_parts.append(f"Java 进程「{pname}」内存平均 {pmem_avg:.1f}%,超过阈值。")
elif pmem_max is not None and pmem_max > THRESHOLD_MEMORY_HIGH:
bottleneck_rows.append(["资源", f"Java 进程 {pname} 内存(峰值)", "medium", f"内存峰值 {pmem_max:.1f}% 超过 {THRESHOLD_MEMORY_HIGH}%", f"内存峰值={pmem_max:.1f}%"])
bottleneck_content_parts.append(f"Java 进程「{pname}」内存峰值达 {pmem_max:.1f}%,需关注。")
if not bottleneck_rows: if not bottleneck_rows:
bottleneck_content_parts.append("本次压测未发现超过阈值的明显瓶颈,系统在当前压力下表现稳定。") bottleneck_content_parts.append("本次压测未发现超过阈值的明显瓶颈,系统在当前压力下表现稳定。")
bottleneck_rows.append(["—", "—", "—", "未发现超过阈值的瓶颈", "—"]) bottleneck_rows.append(["—", "—", "—", "未发现超过阈值的瓶颈", "—"])
...@@ -1336,10 +1505,24 @@ class PerformanceAiService: ...@@ -1336,10 +1505,24 @@ class PerformanceAiService:
suggestion_rows.append(["P0", f"优化接口响应时间:P95 当前 {p95:.0f}ms,建议检查数据库查询、缓存策略或代码逻辑", f"预期可将 P95 降至 {THRESHOLD_P95_YELLOW}ms 以下"]) suggestion_rows.append(["P0", f"优化接口响应时间:P95 当前 {p95:.0f}ms,建议检查数据库查询、缓存策略或代码逻辑", f"预期可将 P95 降至 {THRESHOLD_P95_YELLOW}ms 以下"])
if error_rate > THRESHOLD_ERROR_RATE: if error_rate > THRESHOLD_ERROR_RATE:
suggestion_rows.append(["P0", f"排查错误请求:错误率 {error_rate:.2f}%,建议检查 4xx/5xx 具体错误类型并修复", "预期可将错误率降至 5% 以下"]) suggestion_rows.append(["P0", f"排查错误请求:错误率 {error_rate:.2f}%,建议检查 4xx/5xx 具体错误类型并修复", "预期可将错误率降至 5% 以下"])
if cpu_usage is not None and cpu_usage > THRESHOLD_CPU_HIGH: if exec_cpu_avg is not None and exec_cpu_avg > THRESHOLD_CPU_HIGH:
suggestion_rows.append(["P1", f"执行机 CPU 过高({cpu_usage:.1f}%),建议增加并发线程数控制或升级执行机配置", "降低 CPU 负载,避免压测工具自身成为瓶颈"]) suggestion_rows.append(["P1", f"执行机 CPU 过高({exec_cpu_avg:.1f}%),建议增加并发线程数控制或升级执行机配置", "降低 CPU 负载,避免压测工具自身成为瓶颈"])
if target_cpu is not None and target_cpu > THRESHOLD_CPU_HIGH: if tgt_cpu_avg is not None and tgt_cpu_avg > THRESHOLD_CPU_HIGH:
suggestion_rows.append(["P1", f"目标机 CPU 过高({target_cpu:.1f}%),建议扩容或优化服务端处理能力", "提升服务端吞吐能力,降低响应时间"]) suggestion_rows.append(["P1", f"目标机 CPU 过高({tgt_cpu_avg:.1f}%),建议扩容或优化服务端处理能力", "提升服务端吞吐能力,降低响应时间"])
# Java 进程 CPU 建议仅取最显著一项(最高平均 CPU)
java_peak = None
for p in self._extract_java_process_stats(target_resource_summary):
if p.get("avgCpuPercent") is not None and (java_peak is None or p["avgCpuPercent"] > java_peak[1]):
java_peak = (p.get("processName", "未知"), p["avgCpuPercent"])
if java_peak and java_peak[1] > THRESHOLD_CPU_HIGH:
suggestion_rows.append(["P1", f"Java 进程「{java_peak[0]}」CPU 过高(平均 {java_peak[1]:.1f}%),建议检查该服务的线程池/GC 配置与热点代码", "降低应用进程 CPU 占用,提升处理能力"])
# MySQL 建议基于监控数据
mysql_sug = self._extract_mysql_stats(target_resource_summary)
if mysql_sug:
if mysql_sug.get("slowQueries") is not None and mysql_sug["slowQueries"] > 10:
suggestion_rows.append(["P1", f"MySQL 慢查询累计 {mysql_sug['slowQueries']} 条,建议开启慢查询日志定位问题 SQL 并添加索引", "减少慢查询,降低数据库层响应时间"])
if mysql_sug.get("bufferPoolHitRate") is not None and mysql_sug["bufferPoolHitRate"] < 99.0:
suggestion_rows.append(["P1", f"MySQL 缓冲池命中率 {mysql_sug['bufferPoolHitRate']:.1f}%(低于 99%),建议评估 innodb_buffer_pool_size 配置", "提高缓存命中率,减少磁盘 IO"])
if not suggestion_rows: if not suggestion_rows:
suggestion_rows.append(["P2", "当前性能表现良好,建议持续监控并在更高并发下验证", "确保系统在更大压力下仍保持稳定"]) suggestion_rows.append(["P2", "当前性能表现良好,建议持续监控并在更高并发下验证", "确保系统在更大压力下仍保持稳定"])
...@@ -1693,80 +1876,151 @@ class PerformanceAiService: ...@@ -1693,80 +1876,151 @@ class PerformanceAiService:
# 资源瓶颈(兼容 stats 嵌套与旧平铺键,同时取平均/峰值) # 资源瓶颈(兼容 stats 嵌套与旧平铺键,同时取平均/峰值)
exec_stats = self._extract_resource_stats(resource_summary) exec_stats = self._extract_resource_stats(resource_summary)
tgt_stats = self._extract_resource_stats(target_resource_summary) tgt_stats = self._extract_resource_stats(target_resource_summary)
cpu_usage = exec_stats["cpu_avg"] exec_cpu_avg = exec_stats["cpu_avg"]
cpu_max = exec_stats["cpu_max"] exec_cpu_max = exec_stats["cpu_max"]
mem_usage = exec_stats["mem_avg"] exec_mem_avg = exec_stats["mem_avg"]
mem_max = exec_stats["mem_max"] exec_mem_max = exec_stats["mem_max"]
target_cpu = tgt_stats["cpu_avg"] tgt_cpu_avg = tgt_stats["cpu_avg"]
target_cpu_max = tgt_stats["cpu_max"] tgt_cpu_max = tgt_stats["cpu_max"]
target_mem = tgt_stats["mem_avg"] tgt_mem_avg = tgt_stats["mem_avg"]
target_mem_max = tgt_stats["mem_max"] tgt_mem_max = tgt_stats["mem_max"]
if cpu_usage is not None and cpu_usage > THRESHOLD_CPU_HIGH: if exec_cpu_avg is not None and exec_cpu_avg > THRESHOLD_CPU_HIGH:
bottlenecks.append({ bottlenecks.append({
"type": "资源", "type": "资源",
"location": "执行机 CPU", "location": "执行机 CPU",
"severity": "high", "severity": "high",
"description": "执行机 CPU 平均使用率 {:.1f}%,超过 {}% 阈值".format(cpu_usage, THRESHOLD_CPU_HIGH), "description": "执行机 CPU 平均使用率 {:.1f}%,超过 {}% 阈值".format(exec_cpu_avg, THRESHOLD_CPU_HIGH),
"evidence": "CPU 使用率 = {:.1f}%(阈值 {}%)".format(cpu_usage, THRESHOLD_CPU_HIGH), "evidence": "CPU 使用率 = {:.1f}%(阈值 {}%)".format(exec_cpu_avg, THRESHOLD_CPU_HIGH),
}) })
if mem_usage is not None and mem_usage > THRESHOLD_MEMORY_HIGH: if exec_mem_avg is not None and exec_mem_avg > THRESHOLD_MEMORY_HIGH:
bottlenecks.append({ bottlenecks.append({
"type": "资源", "type": "资源",
"location": "执行机内存", "location": "执行机内存",
"severity": "high", "severity": "high",
"description": "执行机内存平均使用率 {:.1f}%,超过 {}% 阈值".format(mem_usage, THRESHOLD_MEMORY_HIGH), "description": "执行机内存平均使用率 {:.1f}%,超过 {}% 阈值".format(exec_mem_avg, THRESHOLD_MEMORY_HIGH),
"evidence": "内存使用率 = {:.1f}%(阈值 {}%)".format(mem_usage, THRESHOLD_MEMORY_HIGH), "evidence": "内存使用率 = {:.1f}%(阈值 {}%)".format(exec_mem_avg, THRESHOLD_MEMORY_HIGH),
}) })
if target_cpu is not None and target_cpu > THRESHOLD_CPU_HIGH: if tgt_cpu_avg is not None and tgt_cpu_avg > THRESHOLD_CPU_HIGH:
bottlenecks.append({ bottlenecks.append({
"type": "资源", "type": "资源",
"location": "目标机 CPU", "location": "目标机 CPU",
"severity": "high", "severity": "high",
"description": "目标机 CPU 平均使用率 {:.1f}%,超过 {}% 阈值".format(target_cpu, THRESHOLD_CPU_HIGH), "description": "目标机 CPU 平均使用率 {:.1f}%,超过 {}% 阈值".format(tgt_cpu_avg, THRESHOLD_CPU_HIGH),
"evidence": "目标机 CPU 使用率 = {:.1f}%(阈值 {}%)".format(target_cpu, THRESHOLD_CPU_HIGH), "evidence": "目标机 CPU 使用率 = {:.1f}%(阈值 {}%)".format(tgt_cpu_avg, THRESHOLD_CPU_HIGH),
}) })
if target_mem is not None and target_mem > THRESHOLD_MEMORY_HIGH: if tgt_mem_avg is not None and tgt_mem_avg > THRESHOLD_MEMORY_HIGH:
bottlenecks.append({ bottlenecks.append({
"type": "资源", "type": "资源",
"location": "目标机内存", "location": "目标机内存",
"severity": "high", "severity": "high",
"description": "目标机内存平均使用率 {:.1f}%,超过 {}% 阈值".format(target_mem, THRESHOLD_MEMORY_HIGH), "description": "目标机内存平均使用率 {:.1f}%,超过 {}% 阈值".format(tgt_mem_avg, THRESHOLD_MEMORY_HIGH),
"evidence": "目标机内存使用率 = {:.1f}%(阈值 {}%)".format(target_mem, THRESHOLD_MEMORY_HIGH), "evidence": "目标机内存使用率 = {:.1f}%(阈值 {}%)".format(tgt_mem_avg, THRESHOLD_MEMORY_HIGH),
}) })
# 峰值资源瓶颈(平均未超阈值但峰值超阈时提醒) # 峰值资源瓶颈(平均未超阈值但峰值超阈时提醒)
if (cpu_usage is None or cpu_usage <= THRESHOLD_CPU_HIGH) and cpu_max is not None and cpu_max > THRESHOLD_CPU_HIGH: if (exec_cpu_avg is None or exec_cpu_avg <= THRESHOLD_CPU_HIGH) and exec_stats["cpu_max"] is not None and exec_stats["cpu_max"] > THRESHOLD_CPU_HIGH:
bottlenecks.append({ bottlenecks.append({
"type": "资源", "type": "资源",
"location": "执行机 CPU(峰值)", "location": "执行机 CPU(峰值)",
"severity": "medium", "severity": "medium",
"description": "执行机 CPU 峰值使用率 {:.1f}%,超过 {}% 阈值".format(cpu_max, THRESHOLD_CPU_HIGH), "description": "执行机 CPU 峰值使用率 {:.1f}%,超过 {}% 阈值".format(exec_stats["cpu_max"], THRESHOLD_CPU_HIGH),
"evidence": "CPU 峰值 = {:.1f}%(阈值 {}%)".format(cpu_max, THRESHOLD_CPU_HIGH), "evidence": "CPU 峰值 = {:.1f}%(阈值 {}%)".format(exec_stats["cpu_max"], THRESHOLD_CPU_HIGH),
}) })
if (mem_usage is None or mem_usage <= THRESHOLD_MEMORY_HIGH) and mem_max is not None and mem_max > THRESHOLD_MEMORY_HIGH: if (exec_mem_avg is None or exec_mem_avg <= THRESHOLD_MEMORY_HIGH) and exec_mem_max is not None and exec_mem_max > THRESHOLD_MEMORY_HIGH:
bottlenecks.append({ bottlenecks.append({
"type": "资源", "type": "资源",
"location": "执行机内存(峰值)", "location": "执行机内存(峰值)",
"severity": "medium", "severity": "medium",
"description": "执行机内存峰值使用率 {:.1f}%,超过 {}% 阈值".format(mem_max, THRESHOLD_MEMORY_HIGH), "description": "执行机内存峰值使用率 {:.1f}%,超过 {}% 阈值".format(exec_mem_max, THRESHOLD_MEMORY_HIGH),
"evidence": "内存峰值 = {:.1f}%(阈值 {}%)".format(mem_max, THRESHOLD_MEMORY_HIGH), "evidence": "内存峰值 = {:.1f}%(阈值 {}%)".format(exec_mem_max, THRESHOLD_MEMORY_HIGH),
}) })
if (target_cpu is None or target_cpu <= THRESHOLD_CPU_HIGH) and target_cpu_max is not None and target_cpu_max > THRESHOLD_CPU_HIGH: if (tgt_cpu_avg is None or tgt_cpu_avg <= THRESHOLD_CPU_HIGH) and tgt_cpu_max is not None and tgt_cpu_max > THRESHOLD_CPU_HIGH:
bottlenecks.append({ bottlenecks.append({
"type": "资源", "type": "资源",
"location": "目标机 CPU(峰值)", "location": "目标机 CPU(峰值)",
"severity": "medium", "severity": "medium",
"description": "目标机 CPU 峰值使用率 {:.1f}%,超过 {}% 阈值".format(target_cpu_max, THRESHOLD_CPU_HIGH), "description": "目标机 CPU 峰值使用率 {:.1f}%,超过 {}% 阈值".format(tgt_cpu_max, THRESHOLD_CPU_HIGH),
"evidence": "目标机 CPU 峰值 = {:.1f}%(阈值 {}%)".format(target_cpu_max, THRESHOLD_CPU_HIGH), "evidence": "目标机 CPU 峰值 = {:.1f}%(阈值 {}%)".format(tgt_cpu_max, THRESHOLD_CPU_HIGH),
}) })
if (target_mem is None or target_mem <= THRESHOLD_MEMORY_HIGH) and target_mem_max is not None and target_mem_max > THRESHOLD_MEMORY_HIGH: if (tgt_mem_avg is None or tgt_mem_avg <= THRESHOLD_MEMORY_HIGH) and tgt_mem_max is not None and tgt_mem_max > THRESHOLD_MEMORY_HIGH:
bottlenecks.append({ bottlenecks.append({
"type": "资源", "type": "资源",
"location": "目标机内存(峰值)", "location": "目标机内存(峰值)",
"severity": "medium", "severity": "medium",
"description": "目标机内存峰值使用率 {:.1f}%,超过 {}% 阈值".format(target_mem_max, THRESHOLD_MEMORY_HIGH), "description": "目标机内存峰值使用率 {:.1f}%,超过 {}% 阈值".format(tgt_mem_max, THRESHOLD_MEMORY_HIGH),
"evidence": "目标机内存峰值 = {:.1f}%(阈值 {}%)".format(target_mem_max, THRESHOLD_MEMORY_HIGH), "evidence": "目标机内存峰值 = {:.1f}%(阈值 {}%)".format(tgt_mem_max, THRESHOLD_MEMORY_HIGH),
}) })
# Java 进程资源瓶颈
for p in self._extract_java_process_stats(target_resource_summary):
pname = p.get("processName", "未知")
pcpu_avg = p.get("avgCpuPercent")
pcpu_max = p.get("maxCpuPercent")
pmem_avg = p.get("avgMemPercent")
pmem_max = p.get("maxMemPercent")
if pcpu_avg is not None and pcpu_avg > THRESHOLD_CPU_HIGH:
bottlenecks.append({
"type": "资源",
"location": f"Java 进程 {pname}",
"severity": "high",
"description": "Java 进程「{}」CPU 平均使用率 {:.1f}%,超过 {}% 阈值".format(pname, pcpu_avg, THRESHOLD_CPU_HIGH),
"evidence": "CPU 平均 = {:.1f}%(阈值 {}%)".format(pcpu_avg, THRESHOLD_CPU_HIGH),
})
elif pcpu_max is not None and pcpu_max > THRESHOLD_CPU_HIGH:
bottlenecks.append({
"type": "资源",
"location": f"Java 进程 {pname}(峰值)",
"severity": "medium",
"description": "Java 进程「{}」CPU 峰值使用率 {:.1f}%,超过 {}% 阈值".format(pname, pcpu_max, THRESHOLD_CPU_HIGH),
"evidence": "CPU 峰值 = {:.1f}%(阈值 {}%)".format(pcpu_max, THRESHOLD_CPU_HIGH),
})
if pmem_avg is not None and pmem_avg > THRESHOLD_MEMORY_HIGH:
bottlenecks.append({
"type": "资源",
"location": f"Java 进程 {pname} 内存",
"severity": "high",
"description": "Java 进程「{}」内存平均使用率 {:.1f}%,超过 {}% 阈值".format(pname, pmem_avg, THRESHOLD_MEMORY_HIGH),
"evidence": "内存平均 = {:.1f}%(阈值 {}%)".format(pmem_avg, THRESHOLD_MEMORY_HIGH),
})
elif pmem_max is not None and pmem_max > THRESHOLD_MEMORY_HIGH:
bottlenecks.append({
"type": "资源",
"location": f"Java 进程 {pname} 内存(峰值)",
"severity": "medium",
"description": "Java 进程「{}」内存峰值使用率 {:.1f}%,超过 {}% 阈值".format(pname, pmem_max, THRESHOLD_MEMORY_HIGH),
"evidence": "内存峰值 = {:.1f}%(阈值 {}%)".format(pmem_max, THRESHOLD_MEMORY_HIGH),
})
# MySQL 资源瓶颈(慢查询/连接水位/命中率)
mysql_bn = self._extract_mysql_stats(target_resource_summary)
if mysql_bn:
m_slow = mysql_bn.get("slowQueries")
m_conn = mysql_bn.get("threadsConnected")
m_max_used = mysql_bn.get("maxUsedConnections")
m_max_conn = mysql_bn.get("maxConnections")
m_buf = mysql_bn.get("bufferPoolHitRate")
if m_slow is not None and m_slow > 10:
bottlenecks.append({
"type": "资源",
"location": "MySQL",
"severity": "medium",
"description": "MySQL 慢查询累计 {:.0f} 条,可能存在慢 SQL".format(m_slow),
"evidence": "Slow queries = {}".format(m_slow),
})
if m_buf is not None and m_buf < 99.0:
bottlenecks.append({
"type": "资源",
"location": "MySQL",
"severity": "medium",
"description": "MySQL 缓冲池命中率 {:.1f}%,低于 99% 健康线".format(m_buf),
"evidence": "Buffer pool hit rate = {:.1f}%".format(m_buf),
})
if m_max_conn and m_max_used is not None and m_max_used >= m_max_conn * 0.9:
bottlenecks.append({
"type": "资源",
"location": "MySQL",
"severity": "high",
"description": "MySQL 连接数接近上限({} / {}),存在连接耗尽风险".format(m_max_used, m_max_conn),
"evidence": "maxUsedConnections = {} / {}".format(m_max_used, m_max_conn),
})
# 优化建议 # 优化建议
suggestions = [] suggestions = []
...@@ -1782,18 +2036,44 @@ class PerformanceAiService: ...@@ -1782,18 +2036,44 @@ class PerformanceAiService:
"content": "排查错误请求:错误率 {:.2f}%,建议检查 4xx/5xx 具体错误类型并修复".format(error_rate), "content": "排查错误请求:错误率 {:.2f}%,建议检查 4xx/5xx 具体错误类型并修复".format(error_rate),
"expectedEffect": "预期可将错误率降至 5% 以下", "expectedEffect": "预期可将错误率降至 5% 以下",
}) })
if cpu_usage is not None and cpu_usage > THRESHOLD_CPU_HIGH: if exec_cpu_avg is not None and exec_cpu_avg > THRESHOLD_CPU_HIGH:
suggestions.append({ suggestions.append({
"priority": "P1", "priority": "P1",
"content": "执行机 CPU 过高({:.1f}%),建议增加并发线程数控制或升级执行机配置".format(cpu_usage), "content": "执行机 CPU 过高({:.1f}%),建议增加并发线程数控制或升级执行机配置".format(exec_cpu_avg),
"expectedEffect": "降低 CPU 负载,避免压测工具自身成为瓶颈", "expectedEffect": "降低 CPU 负载,避免压测工具自身成为瓶颈",
}) })
if target_cpu is not None and target_cpu > THRESHOLD_CPU_HIGH: if tgt_cpu_avg is not None and tgt_cpu_avg > THRESHOLD_CPU_HIGH:
suggestions.append({ suggestions.append({
"priority": "P1", "priority": "P1",
"content": "目标机 CPU 过高({:.1f}%),建议扩容或优化服务端处理能力".format(target_cpu), "content": "目标机 CPU 过高({:.1f}%),建议扩容或优化服务端处理能力".format(tgt_cpu_avg),
"expectedEffect": "提升服务端吞吐能力,降低响应时间", "expectedEffect": "提升服务端吞吐能力,降低响应时间",
}) })
# Java 进程 CPU 建议(取最高平均 CPU 项)
java_peak = None
for p in self._extract_java_process_stats(target_resource_summary):
if p.get("avgCpuPercent") is not None and (java_peak is None or p["avgCpuPercent"] > java_peak[1]):
java_peak = (p.get("processName", "未知"), p["avgCpuPercent"])
if java_peak and java_peak[1] > THRESHOLD_CPU_HIGH:
suggestions.append({
"priority": "P1",
"content": "Java 进程「{}」CPU 过高(平均 {:.1f}%),建议检查该服务的线程池/GC 配置与热点代码".format(java_peak[0], java_peak[1]),
"expectedEffect": "降低应用进程 CPU 占用,提升处理能力",
})
# MySQL 建议
mysql_sug = self._extract_mysql_stats(target_resource_summary)
if mysql_sug:
if mysql_sug.get("slowQueries") is not None and mysql_sug["slowQueries"] > 10:
suggestions.append({
"priority": "P1",
"content": "MySQL 慢查询累计 {} 条,建议开启慢查询日志定位问题 SQL 并添加索引".format(mysql_sug["slowQueries"]),
"expectedEffect": "减少慢查询,降低数据库层响应时间",
})
if mysql_sug.get("bufferPoolHitRate") is not None and mysql_sug["bufferPoolHitRate"] < 99.0:
suggestions.append({
"priority": "P1",
"content": "MySQL 缓冲池命中率 {:.1f}%(低于 99%),建议评估 innodb_buffer_pool_size 配置".format(mysql_sug["bufferPoolHitRate"]),
"expectedEffect": "提高缓存命中率,减少磁盘 IO",
})
if not suggestions: if not suggestions:
suggestions.append({ suggestions.append({
"priority": "P2", "priority": "P2",
...@@ -1801,24 +2081,30 @@ class PerformanceAiService: ...@@ -1801,24 +2081,30 @@ class PerformanceAiService:
"expectedEffect": "确保系统在更大压力下仍保持稳定", "expectedEffect": "确保系统在更大压力下仍保持稳定",
}) })
# 资源分析 # 资源分析(与 Chapter 四保持四维度一致)
resource_parts = [] resource_parts = []
if cpu_usage is not None: if exec_cpu_avg is not None:
peak_txt = ",峰值 {:.1f}%".format(cpu_max) if cpu_max is not None else "" resource_parts.append("**执行机整体 CPU**:平均使用率 {:.1f}%{}{}".format(exec_cpu_avg, ",峰值 {:.1f}%".format(exec_cpu_max) if exec_cpu_max is not None else "", " ⚠️ 超过阈值" if exec_cpu_avg > THRESHOLD_CPU_HIGH else ""))
warn = " ⚠️ 超过阈值" if cpu_usage > THRESHOLD_CPU_HIGH else "" if exec_mem_avg is not None:
resource_parts.append("**执行机 CPU**:平均使用率 {:.1f}%{}{}".format(cpu_usage, peak_txt, warn)) resource_parts.append("**执行机整体内存**:平均使用率 {:.1f}%{}{}".format(exec_mem_avg, ",峰值 {:.1f}%".format(exec_mem_max) if exec_mem_max is not None else "", " ⚠️ 超过阈值" if exec_mem_avg > THRESHOLD_MEMORY_HIGH else ""))
if mem_usage is not None: if tgt_cpu_avg is not None:
peak_txt = ",峰值 {:.1f}%".format(mem_max) if mem_max is not None else "" resource_parts.append("**目标机整体 CPU**:平均使用率 {:.1f}%{}{}".format(tgt_cpu_avg, ",峰值 {:.1f}%".format(tgt_cpu_max) if tgt_cpu_max is not None else "", " ⚠️ 超过阈值" if tgt_cpu_avg > THRESHOLD_CPU_HIGH else ""))
warn = " ⚠️ 超过阈值" if mem_usage > THRESHOLD_MEMORY_HIGH else "" if tgt_mem_avg is not None:
resource_parts.append("**执行机内存**:平均使用率 {:.1f}%{}{}".format(mem_usage, peak_txt, warn)) resource_parts.append("**目标机整体内存**:平均使用率 {:.1f}%{}{}".format(tgt_mem_avg, ",峰值 {:.1f}%".format(tgt_mem_max) if tgt_mem_max is not None else "", " ⚠️ 超过阈值" if tgt_mem_avg > THRESHOLD_MEMORY_HIGH else ""))
if target_cpu is not None: for p in self._extract_java_process_stats(target_resource_summary):
peak_txt = ",峰值 {:.1f}%".format(target_cpu_max) if target_cpu_max is not None else "" resource_parts.append("**Java 进程 {}**:CPU 平均 {}%,峰值 {}%;内存平均 {}%,峰值 {}%;RSS 平均 {}MB,峰值 {}MB".format(
warn = " ⚠️ 超过阈值" if target_cpu > THRESHOLD_CPU_HIGH else "" p.get("processName", "未知"), self._fmt_num(p.get("avgCpuPercent")), self._fmt_num(p.get("maxCpuPercent")),
resource_parts.append("**目标机 CPU**:平均使用率 {:.1f}%{}{}".format(target_cpu, peak_txt, warn)) self._fmt_num(p.get("avgMemPercent")), self._fmt_num(p.get("maxMemPercent")),
if target_mem is not None: self._fmt_num(p.get("avgRssMb")), self._fmt_num(p.get("maxRssMb"))))
peak_txt = ",峰值 {:.1f}%".format(target_mem_max) if target_mem_max is not None else "" mysql_analysis = self._extract_mysql_stats(target_resource_summary)
warn = " ⚠️ 超过阈值" if target_mem > THRESHOLD_MEMORY_HIGH else "" if mysql_analysis:
resource_parts.append("**目标机内存**:平均使用率 {:.1f}%{}{}".format(target_mem, peak_txt, warn)) resource_parts.append("**MySQL {}**:连接数 {},活跃线程 {},慢查询累计 {},最大已用连接 {}/{},缓冲池命中率 {}%,查询总量 {},Queries {},接收 {}MB,发送 {}MB".format(
mysql_analysis.get("container", "umysql"), self._fmt_num(mysql_analysis.get("threadsConnected")),
self._fmt_num(mysql_analysis.get("threadsRunning")), self._fmt_num(mysql_analysis.get("slowQueries")),
self._fmt_num(mysql_analysis.get("maxUsedConnections")), self._fmt_num(mysql_analysis.get("maxConnections")),
self._fmt_num(mysql_analysis.get("bufferPoolHitRate")), self._fmt_num(mysql_analysis.get("questions")),
self._fmt_num(mysql_analysis.get("queries")), self._fmt_num(mysql_analysis.get("bytesReceivedMb")),
self._fmt_num(mysql_analysis.get("bytesSentMb"))))
if not resource_parts: if not resource_parts:
resource_parts.append("无资源监控数据,建议开启目标机/执行机资源监控以获得更全面的分析") resource_parts.append("无资源监控数据,建议开启目标机/执行机资源监控以获得更全面的分析")
......
...@@ -211,6 +211,71 @@ class TestSnapshotSummary: ...@@ -211,6 +211,71 @@ class TestSnapshotSummary:
assert str(summary).count("elapsed") == 4 assert str(summary).count("elapsed") == 4
# ==================== 资源四维度提取测试 ====================
class TestResourceDimensionExtraction:
"""资源分析四维度:执行机整体 / 目标机整体 / Java 进程 / MySQL"""
TARGET = {
"host": "192.168.5.44",
"stats": {"avgCpuPercent": 85.0, "maxCpuPercent": 95.0, "avgMemPercent": 55.0, "maxMemPercent": 65.0},
"javaProcessStats": [
{"processName": "platform-app", "avgCpuPercent": 82.0, "maxCpuPercent": 95.0,
"avgMemPercent": 40.0, "maxMemPercent": 45.0, "avgRssMb": 1500.0, "maxRssMb": 1800.0}
],
"mysql": {"available": True, "container": "umysql", "threadsConnected": 120, "threadsRunning": 8,
"maxUsedConnections": 150, "maxConnections": 300, "slowQueries": 35, "bufferPoolHitRate": 97.5,
"questions": 100000, "queries": 400000, "bytesReceivedMb": 12.0, "bytesSentMb": 88.0},
}
def test_java_process_stats_extraction(self):
svc = PerformanceAiService()
procs = svc._extract_java_process_stats(self.TARGET)
assert len(procs) == 1
assert procs[0]["processName"] == "platform-app"
assert procs[0]["avgCpuPercent"] == 82.0
assert svc._extract_java_process_stats(None) == []
assert svc._extract_java_process_stats({"stats": {}}) == []
def test_mysql_stats_extraction(self):
svc = PerformanceAiService()
mysql = svc._extract_mysql_stats(self.TARGET)
assert mysql["available"] is True
assert mysql["slowQueries"] == 35
assert mysql["bufferPoolHitRate"] == 97.5
# 未采集或 available=False 时返回空
assert svc._extract_mysql_stats(None) == {}
assert svc._extract_mysql_stats({"mysql": {"available": False, "slowQueries": 1}}) == {}
def test_rule_analysis_covers_four_dimensions(self):
"""规则分析文档含 执行机 / 目标机 / Java / MySQL 四维度"""
svc = PerformanceAiService()
data = _make_report_data(
resource_summary={"stats": {"avgCpuPercent": 45.0, "maxCpuPercent": 70.0,
"avgMemPercent": 60.0, "maxMemPercent": 75.0}},
target_resource_summary=self.TARGET,
)
result = svc.analyze_single(data)
html = result["document_html"]
assert "执行机" in html
assert "目标机" in html
assert "Java 进程" in html
assert "MySQL" in html
# 高 CPU 目标机 + Java 进程 + 慢查询/命中率 均进入瓶颈
locs = [b["location"] for b in result["bottlenecks"]]
assert any("目标机" in l for l in locs)
assert any("Java 进程" in l for l in locs)
assert any("MySQL" in l for l in locs)
def test_rule_analysis_no_resource_data(self):
"""无资源数据时不虚构,标注未采集"""
svc = PerformanceAiService()
data = _make_report_data(resource_summary={}, target_resource_summary={})
result = svc.analyze_single(data)
html = result["document_html"]
assert "未采集" in html or "无资源监控数据" in html or "无数据" in html
# ==================== 文档规范化测试 ==================== # ==================== 文档规范化测试 ====================
class TestNormalizeDocumentResult: class TestNormalizeDocumentResult:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论