文件
wangchuanli 762b4b49a0 feat: 初始化 GPU Monitor 项目
搭建基于 Flask + SocketIO 的 GPU 集群实时监控系统,包含远程采集、定时调度、Web 前端可视化及项目文档。
2026-08-24 15:00:43 +08:00

202 行
8.8 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>A100 GPU 实时监控系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.gpu-card {
transition: all 0.3s ease;
}
.gpu-card:hover {
transform: translateY(-5px);
}
.status-dot {
height: 10px;
width: 10px;
border-radius: 50%;
display: inline-block;
margin-right: 8px;
}
.status-online { background-color: #10B981; }
.status-offline { background-color: #EF4444; }
</style>
</head>
<body class="bg-slate-900 text-slate-100 min-h-screen p-8">
<div class="max-w-7xl mx-auto">
<header class="flex justify-between items-center mb-10">
<div>
<h1 class="text-3xl font-bold text-white">GPU Cluster Monitor</h1>
<p class="text-slate-400">实时监控 NVIDIA A100 状态</p>
</div>
<div id="connection-status" class="flex items-center bg-slate-800 px-4 py-2 rounded-full text-sm">
<span id="status-dot" class="status-dot status-offline"></span>
<span id="status-text">连接中...</span>
</div>
</header>
<!-- 服务器列表区域 -->
<div id="servers-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- 动态生成服务器卡片 -->
</div>
</div>
<!-- GPU 详情模态框 (用于展示图表) -->
<div id="gpu-modal" class="fixed inset-0 bg-black/80 hidden flex items-center justify-center z-50 p-4">
<div class="bg-slate-800 rounded-2xl max-w-4xl w-full p-6 relative">
<button id="close-modal" class="absolute top-4 right-4 text-slate-400 hover:text-white text-2xl">&times;</button>
<h2 id="modal-title" class="text-2xl font-bold mb-6">GPU 详情</h2>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div id="modal-metrics" class="grid grid-cols-2 gap-4">
<!-- 实时数值 -->
</div>
<div class="h-64">
<canvas id="gpuChart"></canvas>
</div>
</div>
</div>
</div>
<script>
const socket = io();
let charts = {}; // 存储每个 GPU 的图表实例
// 更新连接状态
socket.on('connect', () => {
document.getElementById('status-dot').className = 'status-dot status-online';
document.getElementById('status-text').textContent = '连接正常';
});
socket.on('disconnect', () => {
document.getElementById('status-dot').className = 'status-dot status-offline';
document.getElementById('status-text').textContent = '连接中断';
});
// 处理接收到的 GPU 数据
socket.on('gpu_update', (data) => {
const container = document.getElementById('servers-container');
for (const [serverAlias, gpus] of Object.entries(data)) {
let serverCard = document.getElementById(`server-${serverAlias}`);
if (!serverCard) {
serverCard = createServerCard(serverAlias, gpus);
container.appendChild(serverCard);
}
updateServerCard(serverCard, serverAlias, gpus);
}
});
function createServerCard(alias, gpus) {
const div = document.createElement('div');
div.id = `server-${alias}`;
div.className = 'gpu-card bg-slate-800 rounded-2xl p-6 border border-slate-700';
div.innerHTML = `
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-semibold">${alias}</h3>
<span class="text-xs text-slate-400">A100 Cluster</span>
</div>
<div id="gpus-${alias}" class="space-y-4"></div>
`;
return div;
}
function updateServerCard(card, alias, gpus) {
const gpuContainer = document.getElementById(`gpus-${alias}`);
if (!gpus) {
gpuContainer.innerHTML = `
<div class="flex items-center justify-center p-4 bg-slate-900/50 rounded-lg text-red-400 text-sm">
<span class="status-dot status-offline"></span> 无法连接服务器
</div>
`;
return;
}
gpuContainer.innerHTML = '';
gpus.forEach((gpu, index) => {
const utilColor = gpu.util >= 80 ? 'text-red-400' : (gpu.util >= 50 ? 'text-yellow-400' : 'text-green-400');
gpuContainer.innerHTML += `
<div class="p-3 bg-slate-900/50 rounded-lg border border-slate-700 hover:bg-slate-700 cursor-pointer transition-colors"
onclick="openGpuDetail('${alias}', ${index})">
<div class="flex justify-between items-center mb-2">
<span class="text-sm font-medium text-slate-300">GPU ${gpu.index} [${gpu.name}]</span>
<span class="text-sm font-bold ${utilColor}">${gpu.util}%</span>
</div>
<div class="w-full bg-slate-700 h-2 rounded-full overflow-hidden">
<div class="bg-green-500 h-full transition-all duration-500" style="width: ${gpu.util}%"></div>
</div>
<div class="flex justify-between text-xs text-slate-400 mt-2">
<span>Temp: ${gpu.temp}°C</span>
<span>Mem: ${gpu.mem_used}/${gpu.mem_total} MiB</span>
</div>
</div>
`;
});
}
function openGpuDetail(serverAlias, gpuIndex) {
const modal = document.getElementById('gpu-modal');
const title = document.getElementById('modal-title');
const metrics = document.getElementById('modal-metrics');
const chartCanvas = document.getElementById('gpuChart');
modal.classList.remove('hidden');
title.textContent = `Server: ${serverAlias} - GPU ${gpuIndex}`;
metrics.innerHTML = `
<div class="p-4 bg-slate-900 rounded-lg text-center">
<span class="text-slate-400 text-xs block mb-1">GPU 利用率</span>
<span class="text-2xl font-bold text-green-400">实时更新中...</span>
</div>
<div class="p-4 bg-slate-900 rounded-lg text-center">
<span class="text-slate-400 text-xs block mb-1">GPU 温度</span>
<span class="text-2xl font-bold text-green-400">实时更新中...</span>
</div>
<div class="p-4 bg-slate-900 rounded-lg text-center">
<span class="text-slate-400 text-xs block mb-1">显存占用</span>
<span class="text-2xl font-bold text-green-400">实时更新中...</span>
</div>
<div class="p-4 bg-slate-900 rounded-lg text-center">
<span class="text-slate-400 text-xs block mb-1">GPU 频率</span>
<span class="text-green-400 text-2xl font-bold">实时更新中...</span>
</div>
`;
if (charts.gpuChart) {
charts.gpuChart.destroy();
}
const ctx = chartCanvas.getContext('2d');
Chart.defaults.color = '#94a3b8';
charts.gpuChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'GPU Utilization (%)',
borderColor: '#10B981',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
fill: true,
tension: 0.4,
data: []
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: { beginAtZero: true, max: 100 }
}
}
});
}
document.getElementById('close-modal').onclick = () => {
document.getElementById('gpu-modal').classList.add('hidden');
};
</script>
</body>
</html>