From 4d9ffa212e71107b905ca85706c2abd469c9b904 Mon Sep 17 00:00:00 2001 From: lxbhahaha <32586299+lxbhahaha@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:48:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=94=A8=E6=88=B7=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++- app.py | 39 ++++++++++++++++++++++++++++++++++++--- index.html | 42 +++++++++++++++++++++++++++++++++++------- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 52b7ea4..2d0240e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ pip install flask flask-cors paramiko -i https://pypi.tuna.tsinghua.edu.cn/simpl 因为本质上是通过ssh连接服务器,然后通过命令来获取相应的信息,有的命令可能服务器系统上不自带需要另外安装,否则无法获取到对应的数据。 - **ifstat**,用于获取网络数据的工具,可通过apt安装(如果不需要显示网络数据则不用安装)。并且需要在服务器上运行一下命令,查看哪个网卡才是主要的,写到配置文件里去(如果不需要查看网络信息可以不写)。 +- **gpustat**,用于获取显卡上用户的使用情况,也可通过apt安装。 - **nvidia驱动**,需要需要安装N卡的驱动,能够通过`nvidia-smi`来获取显卡信息即可(AMD的应该就没办法了)。 其中这个ifstat查看网卡的步骤如下:通过apt安装完成之后,在终端输入`ifstat`,可以看到类似下面的输出(ctrl+c停止),因为一般会不只一个网卡,而且名称也会不一样。此时可以看一下哪个名称的网卡有数据变化,比如下方的就是`eno2`,可以写到配置文件里。 @@ -89,4 +90,5 @@ document.addEventListener('DOMContentLoaded', function() { 有域名的话也可以搞一个反向代理,可参考 [服务器上使用Nginx部署网页+反向代理](http://blog.lxblxb.top/archives/1723257245091)。 # 3. 其他 -- 永辉帮忙搞了一下顶部checkbox布局的问题 \ No newline at end of file +- `永辉`帮忙搞了一下顶部checkbox布局的问题。 +- 参考`治鹏`的方法加了每张显卡的用户使用的情况。 \ No newline at end of file diff --git a/app.py b/app.py index 64d6235..8c1ce2e 100644 --- a/app.py +++ b/app.py @@ -76,14 +76,48 @@ def get_gpus_info(client, timeout, info_list:list=None): 'free_mem': free_mem, 'util_gpu': util_gpu, 'util_mem': util_mem, - 'temperature': temperature + 'temperature': temperature, + 'users': {} }) + # 读取用户使用信息 + try: + gpustat_cmd = 'gpustat --json' + stdin, stdout, stderr = client.exec_command(gpustat_cmd, timeout=timeout) + gpustat_output = stdout.read().decode() + + # 确保 gpustat 输出不是空的 + if not gpustat_output: + raise ValueError("gpustat did not return any output.") + + gpustat_info = json.loads(gpustat_output) + + # 确保解析的 gpustat 信息格式正确 + if 'gpus' not in gpustat_info: + raise ValueError("Parsed gpustat info does not contain 'gpus' key.") + + # 解析进程信息 ----------------------------- + for gpu in gpustat_info['gpus']: + idx = gpu['index'] + processes = gpu.get('processes', []) # 使用 get() 方法避免 KeyError + for process in processes: + username = process['username'] + gpu_memory_usage = process['gpu_memory_usage'] # 占用的显存 + # 找到对应的 GPU,将用户及其显存使用情况记录下来 + for gpu_result in result: + if gpu_result['idx'] == idx: + if username not in gpu_result['users']: + gpu_result['users'][username] = 0 + gpu_result['users'][username] += gpu_memory_usage + except Exception as e: + if info_list is not None: + info_list.append(f'gpu user: {e}') + return result except Exception as e: if info_list is not None: info_list.append(f'gpus: {e}') - None + return None def get_storage_info(client, timeout, path_list, info_list:list=None): try: @@ -157,7 +191,6 @@ def keep_check_one(server: dict, shared_data_list: dict, server_title: str, inte client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(server['ip'], port=server['port'], username=server['username'], password=server.get('password', None), key_filename=server.get('key_filename', None), timeout=interval*3) - cmd = 'nvidia-smi --query-gpu=index,name,memory.total,memory.used,memory.free,utilization.gpu,utilization.memory,temperature.gpu --format=csv' shared_data_list[server_title]['err_info'] = None re_try_count = 0 diff --git a/index.html b/index.html index bf61059..0c034c6 100644 --- a/index.html +++ b/index.html @@ -25,6 +25,23 @@ } .gpu-info { margin-top: 10px; + border: 1px solid #ccc; /* 边框 */ + border-radius: 8px; /* 圆角 */ + padding: 10px; /* 内边距 */ + margin-bottom: 15px; /* 下边距 */ + background-color: #f9f9f9; /* 背景颜色 */ + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* 阴影 */ + } + + .user-info { + margin-top: 10px; /* 上边距 */ + font-size: 14px; /* 字体大小 */ + color: #555; /* 字体颜色 */ + } + + .user-item { + color: #007bff; /* 用户名颜色 */ + font-weight: bold; /* 加粗 */ } /* 头部样式 */ @@ -76,7 +93,7 @@ // 请求服务器获取GPus数据 function fetchData() { - fetch('http://lxblxb.top:15002/all_data') + fetch('http://127.0.0.1:15002/all_data') // 获取服务器和显卡数据 .then(response => response.json()) // 解析 JSON 响应 .then(data => { @@ -239,9 +256,23 @@ colorDot = redDot; } gpuInfo.innerHTML = '' + gpu.idx + ' - ' + gpu.gpu_name + colorDot + '
' - + 'Temperature: ' + gpu.temperature + '°C
' - + 'Memory: ' + gpu.used_mem + ' / ' + gpu.total_mem + " MB" + '
' - + 'Utilization: ' + gpu.util_gpu + '%'; + + '温度: ' + gpu.temperature + '°C
' + + '显存: ' + gpu.used_mem + ' / ' + gpu.total_mem + " MB" + '
' + + '利用率: ' + gpu.util_gpu + '%'; + + // 添加用户使用信息 + if ('users' in gpu) { // 检查是否有用户信息 + let userInfo = document.createElement('div'); + userInfo.classList.add('user-info'); + userInfo.innerHTML = "使用情况:"; + + for (const [username, pid] of Object.entries(gpu.users)) { + userInfo.innerHTML += `${username} (${pid}) `; + } + + gpuInfo.appendChild(userInfo); // 将用户信息添加到GPU信息中 + } + serverCard.appendChild(gpuInfo); }); // 分割线 @@ -251,9 +282,6 @@ // 错误信息 if ('err_info' in serverData[key]) { - // 分割线 - add_bar(serverCard); - let errInfo = document.createElement('div'); errInfo.classList.add('error-info'); errInfo.innerHTML = 'error info
' + serverData[key].err_info;