analyze_performance.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. #!/usr/bin/env python3
  2. """
  3. 性能日志分析工具
  4. 分析后端日志,统计STT、LLM、TTS各阶段耗时
  5. """
  6. import re
  7. import sys
  8. from typing import Dict, List
  9. from dataclasses import dataclass, field
  10. from statistics import mean, median
  11. @dataclass
  12. class RequestStats:
  13. """单个请求的统计数据"""
  14. request_id: str
  15. stt_duration: float = 0.0
  16. llm_duration: float = 0.0
  17. tts_duration: float = 0.0
  18. total_duration: float = 0.0
  19. user_input: str = ""
  20. response_text: str = ""
  21. emotion: str = ""
  22. def is_complete(self) -> bool:
  23. """检查是否有完整的统计数据"""
  24. return self.total_duration > 0
  25. @dataclass
  26. class PerformanceStats:
  27. """性能统计汇总"""
  28. total_requests: int = 0
  29. completed_requests: int = 0
  30. stt_times: List[float] = field(default_factory=list)
  31. llm_times: List[float] = field(default_factory=list)
  32. tts_times: List[float] = field(default_factory=list)
  33. total_times: List[float] = field(default_factory=list)
  34. def add_request(self, req: RequestStats):
  35. """添加一个请求的统计数据"""
  36. self.total_requests += 1
  37. if req.is_complete():
  38. self.completed_requests += 1
  39. self.stt_times.append(req.stt_duration)
  40. self.llm_times.append(req.llm_duration)
  41. self.tts_times.append(req.tts_duration)
  42. self.total_times.append(req.total_duration)
  43. def print_summary(self):
  44. """打印统计摘要"""
  45. print("\n" + "=" * 70)
  46. print("性能统计分析报告".center(70))
  47. print("=" * 70)
  48. print(f"\n总请求数: {self.total_requests}")
  49. print(f"完成请求数: {self.completed_requests}")
  50. if not self.completed_requests:
  51. print("\n⚠️ 没有找到完整的请求数据")
  52. return
  53. # STT统计
  54. print("\n" + "-" * 70)
  55. print("🎤 STT (语音识别) 性能".center(70))
  56. print("-" * 70)
  57. self._print_stats("STT", self.stt_times)
  58. # LLM统计
  59. print("\n" + "-" * 70)
  60. print("🤖 LLM (大语言模型) 性能".center(70))
  61. print("-" * 70)
  62. self._print_stats("LLM", self.llm_times)
  63. # TTS统计
  64. print("\n" + "-" * 70)
  65. print("🔊 TTS (语音合成) 性能".center(70))
  66. print("-" * 70)
  67. self._print_stats("TTS", self.tts_times)
  68. # 总计统计
  69. print("\n" + "-" * 70)
  70. print("⏱️ 总体性能".center(70))
  71. print("-" * 70)
  72. self._print_stats("总计", self.total_times)
  73. # 耗时占比
  74. print("\n" + "-" * 70)
  75. print("📊 耗时占比分析".center(70))
  76. print("-" * 70)
  77. avg_total = mean(self.total_times)
  78. avg_stt = mean(self.stt_times)
  79. avg_llm = mean(self.llm_times)
  80. avg_tts = mean(self.tts_times)
  81. stt_pct = (avg_stt / avg_total * 100) if avg_total > 0 else 0
  82. llm_pct = (avg_llm / avg_total * 100) if avg_total > 0 else 0
  83. tts_pct = (avg_tts / avg_total * 100) if avg_total > 0 else 0
  84. print(f"STT 占比: {stt_pct:5.1f}% | {'█' * int(stt_pct / 2)}")
  85. print(f"LLM 占比: {llm_pct:5.1f}% | {'█' * int(llm_pct / 2)}")
  86. print(f"TTS 占比: {tts_pct:5.1f}% | {'█' * int(tts_pct / 2)}")
  87. # 优化建议
  88. print("\n" + "-" * 70)
  89. print("💡 优化建议".center(70))
  90. print("-" * 70)
  91. max_stage = max([
  92. ("STT", avg_stt, stt_pct),
  93. ("LLM", avg_llm, llm_pct),
  94. ("TTS", avg_tts, tts_pct)
  95. ], key=lambda x: x[1])
  96. print(f"1. {max_stage[0]} 耗时最长 ({max_stage[1]:.2f}秒, {max_stage[2]:.1f}%),建议优先优化")
  97. if avg_stt > 2.0:
  98. print("2. STT 响应较慢,建议检查网络连接或考虑本地STT模型")
  99. if avg_llm > 3.0:
  100. print("3. LLM 响应较慢,可考虑使用更快的模型或流式响应")
  101. if avg_tts > 2.0:
  102. print("4. TTS 合成较慢,可考虑音频缓存或预生成常用回复")
  103. print("\n" + "=" * 70 + "\n")
  104. def _print_stats(self, name: str, times: List[float]):
  105. """打印单个阶段的统计信息"""
  106. if not times:
  107. print(f"{name}: 无数据")
  108. return
  109. avg = mean(times)
  110. med = median(times)
  111. min_time = min(times)
  112. max_time = max(times)
  113. print(f"平均耗时: {avg:6.2f}秒")
  114. print(f"中位耗时: {med:6.2f}秒")
  115. print(f"最小耗时: {min_time:6.2f}秒")
  116. print(f"最大耗时: {max_time:6.2f}秒")
  117. print(f"样本数量: {len(times):6d}次")
  118. def analyze_log(log_content: str) -> PerformanceStats:
  119. """分析日志内容"""
  120. stats = PerformanceStats()
  121. requests: Dict[str, RequestStats] = {}
  122. lines = log_content.split('\n')
  123. for line in lines:
  124. # 提取 request_id
  125. request_id_match = re.search(r'\[([a-f0-9\-]{36})\]', line)
  126. if not request_id_match:
  127. continue
  128. request_id = request_id_match.group(1)
  129. # 确保有这个请求的记录
  130. if request_id not in requests:
  131. requests[request_id] = RequestStats(request_id=request_id)
  132. req = requests[request_id]
  133. # 提取STT耗时
  134. stt_match = re.search(r'⏱️\s+STT耗时:\s+([\d.]+)秒', line)
  135. if stt_match:
  136. req.stt_duration = float(stt_match.group(1))
  137. # 提取LLM耗时
  138. llm_match = re.search(r'⏱️\s+LLM耗时:\s+([\d.]+)秒', line)
  139. if llm_match:
  140. req.llm_duration = float(llm_match.group(1))
  141. # 提取TTS耗时
  142. tts_match = re.search(r'⏱️\s+TTS耗时:\s+([\d.]+)秒', line)
  143. if tts_match:
  144. req.tts_duration = float(tts_match.group(1))
  145. # 提取总耗时
  146. total_match = re.search(r'⏱️\s+总耗时:\s+([\d.]+)秒', line)
  147. if total_match:
  148. req.total_duration = float(total_match.group(1))
  149. # 提取用户输入
  150. if 'STT识别结果:' in line:
  151. text_match = re.search(r'STT识别结果:\s*(.+)$', line)
  152. if text_match:
  153. req.user_input = text_match.group(1).strip()
  154. # 提取回复文本
  155. if '回复文本:' in line:
  156. text_match = re.search(r'回复文本:\s*(.+)$', line)
  157. if text_match:
  158. req.response_text = text_match.group(1).strip()
  159. # 提取情感
  160. if re.search(r'^情感:', line):
  161. emotion_match = re.search(r'情感:\s*(\w+)', line)
  162. if emotion_match:
  163. req.emotion = emotion_match.group(1)
  164. # 添加所有请求到统计
  165. for req in requests.values():
  166. stats.add_request(req)
  167. # 打印详细信息(可选)
  168. print("\n" + "=" * 70)
  169. print("详细请求列表".center(70))
  170. print("=" * 70)
  171. for i, req in enumerate(requests.values(), 1):
  172. if req.is_complete():
  173. print(f"\n请求 #{i} [{req.request_id[:8]}...]")
  174. print(f" 用户输入: {req.user_input[:50]}..." if len(req.user_input) > 50 else f" 用户输入: {req.user_input}")
  175. print(f" 情感: {req.emotion}")
  176. print(f" STT: {req.stt_duration:.2f}秒 | LLM: {req.llm_duration:.2f}秒 | TTS: {req.tts_duration:.2f}秒 | 总计: {req.total_duration:.2f}秒")
  177. return stats
  178. def main():
  179. """主函数"""
  180. if len(sys.argv) > 1:
  181. # 从文件读取
  182. log_file = sys.argv[1]
  183. print(f"正在分析日志文件: {log_file}")
  184. try:
  185. with open(log_file, 'r', encoding='utf-8') as f:
  186. log_content = f.read()
  187. except Exception as e:
  188. print(f"读取日志文件失败: {e}")
  189. sys.exit(1)
  190. else:
  191. # 从标准输入读取
  192. print("从标准输入读取日志内容 (Ctrl+D 结束输入)")
  193. log_content = sys.stdin.read()
  194. # 分析日志
  195. stats = analyze_log(log_content)
  196. # 打印统计
  197. stats.print_summary()
  198. if __name__ == "__main__":
  199. main()