343 lines
14 KiB
Python
343 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
系统性能监控脚本
|
|
实时监控系统资源和应用性能指标
|
|
"""
|
|
|
|
import time
|
|
import json
|
|
import subprocess
|
|
import threading
|
|
import signal
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
import requests
|
|
from dataclasses import dataclass, asdict
|
|
from typing import Dict, List, Any, Optional
|
|
|
|
@dataclass
|
|
class SystemMetrics:
|
|
timestamp: str
|
|
cpu_percent: float
|
|
memory_percent: float
|
|
memory_used_gb: float
|
|
memory_total_gb: float
|
|
disk_usage_percent: float
|
|
network_connections: int
|
|
load_average: List[float]
|
|
|
|
@dataclass
|
|
class ApplicationMetrics:
|
|
timestamp: str
|
|
response_time: float
|
|
request_count: int
|
|
success_rate: float
|
|
queue_size: int
|
|
worker_threads: int
|
|
processed_total: int
|
|
errors: int
|
|
|
|
class SystemMonitor:
|
|
"""系统性能监控器"""
|
|
|
|
def __init__(self, app_url: str = "http://localhost:5225",
|
|
interval: float = 2.0, output_file: str = None):
|
|
self.app_url = app_url
|
|
self.interval = interval
|
|
self.running = False
|
|
|
|
# 输出文件
|
|
if output_file is None:
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
self.output_file = f"logs/system_monitor_{timestamp}.log"
|
|
else:
|
|
self.output_file = output_file
|
|
|
|
# 确保日志目录存在
|
|
Path(self.output_file).parent.mkdir(exist_ok=True)
|
|
|
|
# 数据存储
|
|
self.system_metrics_history = []
|
|
self.app_metrics_history = []
|
|
|
|
# 信号处理
|
|
signal.signal(signal.SIGINT, self._signal_handler)
|
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
|
|
print(f"📊 系统监控器初始化完成")
|
|
print(f"📝 监控日志: {self.output_file}")
|
|
print(f"🎯 应用URL: {app_url}")
|
|
print(f"⏱️ 监控间隔: {interval}秒")
|
|
|
|
def _signal_handler(self, signum, frame):
|
|
"""信号处理器"""
|
|
print(f"\n收到信号 {signum}, 停止监控...")
|
|
self.running = False
|
|
|
|
def get_system_metrics(self) -> SystemMetrics:
|
|
"""获取系统指标"""
|
|
try:
|
|
# CPU使用率
|
|
cpu_cmd = "top -l 1 -n 0 | grep 'CPU usage' | awk '{print $3}' | sed 's/%//'"
|
|
cpu_result = subprocess.run(cpu_cmd, shell=True, capture_output=True, text=True)
|
|
cpu_percent = float(cpu_result.stdout.strip()) if cpu_result.stdout.strip() else 0.0
|
|
|
|
# 内存使用情况
|
|
vm_stat = subprocess.run(['vm_stat'], capture_output=True, text=True)
|
|
if vm_stat.returncode == 0:
|
|
lines = vm_stat.stdout.split('\n')
|
|
page_size = 4096 # macOS页面大小
|
|
|
|
free_pages = 0
|
|
inactive_pages = 0
|
|
speculative_pages = 0
|
|
wired_pages = 0
|
|
active_pages = 0
|
|
|
|
for line in lines:
|
|
if 'Pages free:' in line:
|
|
free_pages = int(line.split(':')[1].strip().rstrip('.'))
|
|
elif 'Pages inactive:' in line:
|
|
inactive_pages = int(line.split(':')[1].strip().rstrip('.'))
|
|
elif 'Pages speculative:' in line:
|
|
speculative_pages = int(line.split(':')[1].strip().rstrip('.'))
|
|
elif 'Pages wired down:' in line:
|
|
wired_pages = int(line.split(':')[1].strip().rstrip('.'))
|
|
elif 'Pages active:' in line:
|
|
active_pages = int(line.split(':')[1].strip().rstrip('.'))
|
|
|
|
# 计算内存使用情况
|
|
total_pages = free_pages + inactive_pages + speculative_pages + wired_pages + active_pages
|
|
used_pages = wired_pages + active_pages
|
|
|
|
memory_total_gb = (total_pages * page_size) / (1024**3)
|
|
memory_used_gb = (used_pages * page_size) / (1024**3)
|
|
memory_percent = (used_pages / total_pages * 100) if total_pages > 0 else 0
|
|
else:
|
|
memory_total_gb = memory_used_gb = memory_percent = 0.0
|
|
|
|
# 磁盘使用率
|
|
df_result = subprocess.run(['df', '-h', '/'], capture_output=True, text=True)
|
|
disk_usage_percent = 0.0
|
|
if df_result.returncode == 0:
|
|
lines = df_result.stdout.split('\n')
|
|
if len(lines) > 1:
|
|
fields = lines[1].split()
|
|
if len(fields) >= 5:
|
|
disk_usage_percent = float(fields[4].rstrip('%'))
|
|
|
|
# 网络连接数
|
|
netstat_result = subprocess.run(['netstat', '-an'], capture_output=True, text=True)
|
|
network_connections = len([line for line in netstat_result.stdout.split('\n')
|
|
if 'ESTABLISHED' in line]) if netstat_result.returncode == 0 else 0
|
|
|
|
# 负载平均值
|
|
uptime_result = subprocess.run(['uptime'], capture_output=True, text=True)
|
|
load_average = [0.0, 0.0, 0.0]
|
|
if uptime_result.returncode == 0:
|
|
try:
|
|
uptime_line = uptime_result.stdout.strip()
|
|
load_part = uptime_line.split('load averages: ')[1]
|
|
load_values = [float(x.strip()) for x in load_part.split()]
|
|
load_average = load_values[:3]
|
|
except:
|
|
pass
|
|
|
|
return SystemMetrics(
|
|
timestamp=datetime.now().isoformat(),
|
|
cpu_percent=cpu_percent,
|
|
memory_percent=memory_percent,
|
|
memory_used_gb=memory_used_gb,
|
|
memory_total_gb=memory_total_gb,
|
|
disk_usage_percent=disk_usage_percent,
|
|
network_connections=network_connections,
|
|
load_average=load_average
|
|
)
|
|
|
|
except Exception as e:
|
|
print(f"❌ 获取系统指标失败: {e}")
|
|
return SystemMetrics(
|
|
timestamp=datetime.now().isoformat(),
|
|
cpu_percent=0, memory_percent=0, memory_used_gb=0, memory_total_gb=0,
|
|
disk_usage_percent=0, network_connections=0, load_average=[0, 0, 0]
|
|
)
|
|
|
|
def get_application_metrics(self) -> ApplicationMetrics:
|
|
"""获取应用指标"""
|
|
try:
|
|
# 测试响应时间
|
|
start_time = time.time()
|
|
health_response = requests.get(f"{self.app_url}/health", timeout=5)
|
|
response_time = time.time() - start_time
|
|
|
|
# 获取优化器统计
|
|
stats_response = requests.get(f"{self.app_url}/get_optimizer_stats", timeout=5)
|
|
optimizer_stats = stats_response.json() if stats_response.status_code == 200 else {}
|
|
|
|
# 尝试获取异步系统统计
|
|
async_stats = {}
|
|
try:
|
|
async_response = requests.get(f"{self.app_url}/get_async_system_stats", timeout=5)
|
|
if async_response.status_code == 200:
|
|
async_stats = async_response.json()
|
|
except:
|
|
pass
|
|
|
|
return ApplicationMetrics(
|
|
timestamp=datetime.now().isoformat(),
|
|
response_time=response_time,
|
|
request_count=optimizer_stats.get('processed', 0),
|
|
success_rate=100.0 if health_response.status_code == 200 else 0.0,
|
|
queue_size=optimizer_stats.get('queue_size', 0),
|
|
worker_threads=optimizer_stats.get('max_workers', 0),
|
|
processed_total=optimizer_stats.get('processed', 0),
|
|
errors=optimizer_stats.get('errors', 0)
|
|
)
|
|
|
|
except Exception as e:
|
|
print(f"❌ 获取应用指标失败: {e}")
|
|
return ApplicationMetrics(
|
|
timestamp=datetime.now().isoformat(),
|
|
response_time=0, request_count=0, success_rate=0,
|
|
queue_size=0, worker_threads=0, processed_total=0, errors=0
|
|
)
|
|
|
|
def log_metrics(self, system_metrics: SystemMetrics, app_metrics: ApplicationMetrics):
|
|
"""记录指标到日志文件"""
|
|
try:
|
|
with open(self.output_file, 'a') as f:
|
|
log_entry = {
|
|
'timestamp': datetime.now().isoformat(),
|
|
'system': asdict(system_metrics),
|
|
'application': asdict(app_metrics)
|
|
}
|
|
f.write(json.dumps(log_entry) + '\n')
|
|
except Exception as e:
|
|
print(f"❌ 写入日志失败: {e}")
|
|
|
|
def display_metrics(self, system_metrics: SystemMetrics, app_metrics: ApplicationMetrics):
|
|
"""在控制台显示指标"""
|
|
timestamp = datetime.now().strftime('%H:%M:%S')
|
|
|
|
print(f"\r📊 [{timestamp}] "
|
|
f"CPU: {system_metrics.cpu_percent:5.1f}% | "
|
|
f"MEM: {system_metrics.memory_percent:5.1f}% "
|
|
f"({system_metrics.memory_used_gb:.1f}GB/{system_metrics.memory_total_gb:.1f}GB) | "
|
|
f"DISK: {system_metrics.disk_usage_percent:4.1f}% | "
|
|
f"NET: {system_metrics.network_connections:3d} | "
|
|
f"LOAD: {system_metrics.load_average[0]:.2f} | "
|
|
f"APP_RT: {app_metrics.response_time*1000:6.1f}ms | "
|
|
f"QUEUE: {app_metrics.queue_size:4d} | "
|
|
f"PROC: {app_metrics.processed_total:6d} | "
|
|
f"ERR: {app_metrics.errors:3d}", end='', flush=True)
|
|
|
|
def start_monitoring(self):
|
|
"""开始监控"""
|
|
print(f"\n🔍 开始系统监控 (按 Ctrl+C 停止)")
|
|
print("=" * 120)
|
|
print("时间 | CPU% | 内存使用 | 磁盘% | 连接数 | 负载 | 应用RT(ms) | 队列 | 处理数 | 错误")
|
|
print("-" * 120)
|
|
|
|
self.running = True
|
|
|
|
try:
|
|
while self.running:
|
|
# 获取指标
|
|
system_metrics = self.get_system_metrics()
|
|
app_metrics = self.get_application_metrics()
|
|
|
|
# 存储历史数据
|
|
self.system_metrics_history.append(system_metrics)
|
|
self.app_metrics_history.append(app_metrics)
|
|
|
|
# 保持历史记录数量(最近500个数据点)
|
|
if len(self.system_metrics_history) > 500:
|
|
self.system_metrics_history.pop(0)
|
|
if len(self.app_metrics_history) > 500:
|
|
self.app_metrics_history.pop(0)
|
|
|
|
# 记录日志
|
|
self.log_metrics(system_metrics, app_metrics)
|
|
|
|
# 显示指标
|
|
self.display_metrics(system_metrics, app_metrics)
|
|
|
|
# 等待下次采集
|
|
time.sleep(self.interval)
|
|
|
|
except KeyboardInterrupt:
|
|
print(f"\n\n⏸️ 监控已停止")
|
|
|
|
self.generate_summary_report()
|
|
|
|
def generate_summary_report(self):
|
|
"""生成监控总结报告"""
|
|
if not self.system_metrics_history or not self.app_metrics_history:
|
|
print("📊 无足够数据生成报告")
|
|
return
|
|
|
|
print(f"\n" + "=" * 80)
|
|
print("📊 监控总结报告")
|
|
print("=" * 80)
|
|
|
|
# 系统指标统计
|
|
cpu_values = [m.cpu_percent for m in self.system_metrics_history]
|
|
memory_values = [m.memory_percent for m in self.system_metrics_history]
|
|
load_values = [m.load_average[0] for m in self.system_metrics_history]
|
|
|
|
print(f"🖥️ 系统指标:")
|
|
print(f" CPU使用率 - 平均: {sum(cpu_values)/len(cpu_values):5.1f}% "
|
|
f"最高: {max(cpu_values):5.1f}% 最低: {min(cpu_values):5.1f}%")
|
|
print(f" 内存使用率 - 平均: {sum(memory_values)/len(memory_values):5.1f}% "
|
|
f"最高: {max(memory_values):5.1f}% 最低: {min(memory_values):5.1f}%")
|
|
print(f" 系统负载 - 平均: {sum(load_values)/len(load_values):5.2f} "
|
|
f"最高: {max(load_values):5.2f}")
|
|
|
|
# 应用指标统计
|
|
response_times = [m.response_time * 1000 for m in self.app_metrics_history] # 转换为毫秒
|
|
queue_sizes = [m.queue_size for m in self.app_metrics_history]
|
|
|
|
print(f"\n📱 应用指标:")
|
|
print(f" 响应时间(ms) - 平均: {sum(response_times)/len(response_times):6.1f} "
|
|
f"最高: {max(response_times):6.1f} 最低: {min(response_times):6.1f}")
|
|
print(f" 队列深度 - 平均: {sum(queue_sizes)/len(queue_sizes):6.1f} "
|
|
f"最高: {max(queue_sizes):6d}")
|
|
|
|
# 性能趋势分析
|
|
latest_metrics = self.app_metrics_history[-10:] # 最近10个数据点
|
|
if len(latest_metrics) >= 10:
|
|
recent_avg_rt = sum(m.response_time * 1000 for m in latest_metrics) / len(latest_metrics)
|
|
print(f" 最近10次平均响应时间: {recent_avg_rt:.1f}ms")
|
|
|
|
print(f"\n📝 详细日志已保存到: {self.output_file}")
|
|
print(f"📊 总监控时间: {len(self.system_metrics_history) * self.interval:.1f}秒")
|
|
|
|
def main():
|
|
"""主函数"""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description='系统性能监控工具')
|
|
parser.add_argument('--url', default='http://localhost:5225',
|
|
help='应用服务URL (默认: http://localhost:5225)')
|
|
parser.add_argument('--interval', type=float, default=2.0,
|
|
help='监控间隔秒数 (默认: 2.0)')
|
|
parser.add_argument('--output', help='输出日志文件路径')
|
|
|
|
args = parser.parse_args()
|
|
|
|
print("🚀 系统性能监控工具")
|
|
print("=" * 50)
|
|
|
|
# 创建监控器
|
|
monitor = SystemMonitor(
|
|
app_url=args.url,
|
|
interval=args.interval,
|
|
output_file=args.output
|
|
)
|
|
|
|
# 启动监控
|
|
monitor.start_monitoring()
|
|
|
|
if __name__ == "__main__":
|
|
main() |