202 行
8.8 KiB
HTML
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">×</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>
|