创建虚拟环境目录 python3 -m venv /sda1/xunjian/venv # 激活虚拟环境 source /sda1/xunjian/venv/bin/activate # 激活后终端会显示 (venv)
创建虚拟环境(在当前目录):
bash
python3 -m venv venv
激活虚拟环境:
bash
source venv/bin/activate # 激活后终端会显示 (venv)
在虚拟环境中安装依赖(此时无权限限制):
bash
pip install flask psutil
运行脚本(需在虚拟环境中):
bash
python xun.py
完成后,若要退出虚拟环境,执行:
bash
deactivate
虚拟环境能完全隔离项目依赖,避免影响系统 Python 环境,是标准且安全的做法。
安装依赖(在虚拟环境中):
pip install flask paramiko
import psutil
import subprocess
import time
import paramiko
from flask import Flask, render_template_string, jsonify
from threading import Thread, Lock
app = Flask(__name__)
lock = Lock()
# 监控配置:本地 + 远程机器(修改远程机器SSH信息)
MONITOR_CONFIG = {
"local": {
"name": "本地服务器",
"type": "local"
},
"remote-1": {
"name": "远程服务器 (192.168.1.7)",
"type": "remote",
"ip": "192.168.1.7",
"username": "your_ssh_username", # 替换为远程SSH用户名
"password": "your_ssh_password", # 替换为远程SSH密码
"services": ["nginx", "mysql", "ssh"] # 远程监控服务
}
}
# 本地监控服务列表
LOCAL_SERVICES = ["nginx", "mysql", "docker"]
# -------------------------- 工具函数:单位转换(核心修复) --------------------------
def convert_to_gb(value_str):
"""将带单位的存储值(如462M、2.5G)转换为GB的浮点数"""
if not value_str:
return 0.0
value_str = value_str.strip().upper()
if 'G' in value_str:
return float(value_str.replace('G', ''))
elif 'M' in value_str:
return float(value_str.replace('M', '')) / 1024 # M转G
elif 'K' in value_str:
return float(value_str.replace('K', '')) / (1024 **2) # K转G
else:
return 0.0
# -------------------------- 本地机器监控 --------------------------
def get_local_system_info():
"""获取本地系统资源信息(修复状态判断)"""
# CPU
cpu_percent = psutil.cpu_percent(interval=1)
cpu_cores = psutil.cpu_count(logical=False)
# 内存
memory = psutil.virtual_memory()
memory_used = round(memory.used / (1024** 3), 2)
memory_total = round(memory.total / (1024 **3), 2)
# 磁盘(根目录)
disk = psutil.disk_usage('/')
disk_used = round(disk.used / (1024** 3), 2)
disk_total = round(disk.total / (1024 **3), 2)
# Swap
swap = psutil.swap_memory()
swap_used = round(swap.used / (1024** 3), 2)
swap_total = round(swap.total / (1024 **3), 2)
return {
"cpu": {"percent": cpu_percent, "cores": cpu_cores},
"memory": {"used": memory_used, "total": memory_total, "percent": memory.percent},
"disk": {"used": disk_used, "total": disk_total, "percent": disk.percent},
"swap": {"used": swap_used, "total": swap_total, "percent": swap.percent}
}
def check_local_service_status(service):
"""检查本地服务状态"""
try:
result = subprocess.run(
f"systemctl is-active {service}",
shell=True,
capture_output=True,
text=True
)
return result.stdout.strip() == "active"
except:
for p in psutil.process_iter(["name"]):
if service in p.info["name"].lower():
return True
return False
# -------------------------- 远程机器监控(核心修复) --------------------------
def ssh_exec_command(ip, username, password, command):
"""通过SSH执行远程命令"""
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(ip, username=username, password=password, timeout=10)
stdin, stdout, stderr = ssh.exec_command(command)
output = stdout.read().decode().strip()
error = stderr.read().decode().strip()
return output, error
except Exception as e:
return None, str(e)
finally:
ssh.close()
def get_remote_system_info(config):
"""获取远程系统资源信息(修复单位转换错误)"""
ip = config["ip"]
username = config["username"]
password = config["password"]
# CPU使用率(修复命令,兼容更多系统)
cpu_output, err = ssh_exec_command(
ip, username, password,
"grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage}'"
)
cpu_percent = float(cpu_output) if cpu_output and cpu_output.replace('.', '', 1).isdigit() else 0.0
# 内存信息(处理M/GB单位)
mem_output, err = ssh_exec_command(
ip, username, password,
"free -h | grep Mem | awk '{print $3, $2}'" # 只取已用和总量(如"462M 1.8G")
)
mem_used = 0.0
mem_total = 0.0
mem_percent = 0.0
if mem_output and len(mem_output.split()) == 2:
mem_used_str, mem_total_str = mem_output.split()
mem_used = convert_to_gb(mem_used_str)
mem_total = convert_to_gb(mem_total_str)
mem_percent = (mem_used / mem_total) * 100 if mem_total > 0 else 0.0
# 磁盘信息(处理M/GB单位)
disk_output, err = ssh_exec_command(
ip, username, password,
"df -h / | tail -1 | awk '{print $3, $2}'" # 已用和总量(如"5.8G 6.3G")
)
disk_used = 0.0
disk_total = 0.0
disk_percent = 0.0
if disk_output and len(disk_output.split()) == 2:
disk_used_str, disk_total_str = disk_output.split()
disk_used = convert_to_gb(disk_used_str)
disk_total = convert_to_gb(disk_total_str)
disk_percent = (disk_used / disk_total) * 100 if disk_total > 0 else 0.0
# Swap信息(处理M/GB单位)
swap_output, err = ssh_exec_command(
ip, username, password,
"free -h | grep Swap | awk '{print $3, $2}'"
)
swap_used = 0.0
swap_total = 0.0
swap_percent = 0.0
if swap_output and len(swap_output.split()) == 2:
swap_used_str, swap_total_str = swap_output.split()
swap_used = convert_to_gb(swap_used_str)
swap_total = convert_to_gb(swap_total_str)
swap_percent = (swap_used / swap_total) * 100 if swap_total > 0 else 0.0
return {
"cpu": {"percent": round(cpu_percent, 1), "cores": 0},
"memory": {"used": round(mem_used, 2), "total": round(mem_total, 2), "percent": round(mem_percent, 1)},
"disk": {"used": round(disk_used, 2), "total": round(disk_total, 2), "percent": round(disk_percent, 1)},
"swap": {"used": round(swap_used, 2), "total": round(swap_total, 2), "percent": round(swap_percent, 1)}
}
def check_remote_service_status(config, service):
"""检查远程服务状态(确保返回布尔值)"""
ip = config["ip"]
username = config["username"]
password = config["password"]
# 先检查systemctl状态
output, err = ssh_exec_command(ip, username, password, f"systemctl is-active {service}")
if output == "active":
return True
# 再检查进程
output, err = ssh_exec_command(ip, username, password, f"pgrep -x {service} >/dev/null 2>&1 && echo 'running'")
return output == "running"
# -------------------------- 数据更新线程 --------------------------
def update_monitor_data():
"""定期更新所有机器的监控数据(修复状态显示)"""
while True:
with lock:
# 本地机器数据(明确标记为online)
try:
local_system = get_local_system_info()
local_services = [
{"name": s, "is_running": check_local_service_status(s)}
for s in LOCAL_SERVICES
]
monitor_data["local"] = {
"name": MONITOR_CONFIG["local"]["name"],
"system": local_system,
"services": local_services,
"update_time": time.strftime('%Y-%m-%d %H:%M:%S'),
"status": "online" # 本地默认在线
}
except Exception as e:
monitor_data["local"] = {
"name": MONITOR_CONFIG["local"]["name"],
"error": str(e),
"status": "offline",
"update_time": time.strftime('%Y-%m-%d %H:%M:%S')
}
# 远程机器数据(完善错误处理)
remote_config = MONITOR_CONFIG["remote-1"]
try:
remote_system = get_remote_system_info(remote_config)
# 确保服务列表不为空
remote_services = []
for s in remote_config["services"]:
try:
remote_services.append({
"name": s,
"is_running": check_remote_service_status(remote_config, s)
})
except:
remote_services.append({
"name": s,
"is_running": False
})
monitor_data["remote-1"] = {
"name": remote_config["name"],
"system": remote_system,
"services": remote_services,
"update_time": time.strftime('%Y-%m-%d %H:%M:%S'),
"status": "online"
}
except Exception as e:
# 即使出错,也返回空服务列表避免前端undefined
monitor_data["remote-1"] = {
"name": remote_config["name"],
"error": f"连接失败: {str(e)}",
"services": [{"name": s, "is_running": False} for s in remote_config["services"]],
"status": "offline",
"update_time": time.strftime('%Y-%m-%d %H:%M:%S')
}
time.sleep(10) # 每10秒更新一次
# 初始化监控数据存储
monitor_data = {}
# 启动更新线程
update_thread = Thread(target=update_monitor_data, daemon=True)
update_thread.start()
# -------------------------- Web页面与API --------------------------
@app.route('/')
def index():
return render_template_string('''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>多机器监控仪表盘</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<style>
.machine-card {
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1rem;
}
.status-running { color: #10b981; }
.status-stopped { color: #ef4444; }
.status-online { color: #10b981; }
.status-offline { color: #ef4444; }
</style>
</head>
<body class="bg-gray-50 p-4">
<h1 class="text-2xl font-bold mb-6">多机器监控仪表盘</h1>
<div id="monitor-data">
<!-- 数据将通过JS动态填充 -->
</div>
<script>
function formatData() {
fetch('/api/data')
.then(res => res.json())
.then(data => {
let html = '';
for (let key in data) {
const machine = data[key];
// 处理未定义的系统数据
const system = machine.system || {
cpu: {percent: 0},
memory: {used: 0, total: 0},
disk: {used: 0, total: 0},
swap: {used: 0, total: 0}
};
// 处理未定义的服务
const services = machine.services || [];
html += `
<div class="machine-card">
<h2 class="text-xl font-semibold mb-4">
${machine.name}
<span class="text-sm ml-2 ${machine.status === 'online' ? 'status-online' : 'status-offline'}">
${machine.status === 'online' ? '在线' : '离线'}
</span>
</h2>
${machine.error ? `<p class="text-red-500 mb-4 text-sm">${machine.error}</p>` : ''}
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="bg-white p-3 rounded shadow">
<h3 class="text-sm text-gray-500">CPU使用率</h3>
<p class="text-xl font-bold">${system.cpu.percent.toFixed(1)}%</p>
</div>
<div class="bg-white p-3 rounded shadow">
<h3 class="text-sm text-gray-500">内存使用</h3>
<p class="text-xl font-bold">${system.memory.used.toFixed(2)}G/${system.memory.total.toFixed(2)}G</p>
</div>
<div class="bg-white p-3 rounded shadow">
<h3 class="text-sm text-gray-500">磁盘使用</h3>
<p class="text-xl font-bold">${system.disk.used.toFixed(2)}G/${system.disk.total.toFixed(2)}G</p>
</div>
<div class="bg-white p-3 rounded shadow">
<h3 class="text-sm text-gray-500">Swap使用</h3>
<p class="text-xl font-bold">${system.swap.used.toFixed(2)}G/${system.swap.total.toFixed(2)}G</p>
</div>
</div>
<div class="mb-2">
<h3 class="text-lg font-medium mb-2">服务状态</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2">
${services.map(s => `
<div class="bg-white p-2 rounded shadow flex justify-between items-center">
<span>${s.name}</span>
<span class="${s.is_running ? 'status-running' : 'status-stopped'}">
<i class="fa ${s.is_running ? 'fa-check-circle' : 'fa-times-circle'}"></i>
${s.is_running ? '运行中' : '已停止'}
</span>
</div>
`).join('')}
</div>
</div>
<p class="text-xs text-gray-500">最后更新: ${machine.update_time}</p>
</div>`;
}
document.getElementById('monitor-data').innerHTML = html;
});
}
// 初始加载和定时刷新
formatData();
setInterval(formatData, 10000);
</script>
</body>
</html>
''')
@app.route('/api/data')
def api_data():
with lock:
return jsonify(monitor_data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
