LOADING

加载过慢请开启缓存 浏览器默认开启

性能监视服务端-客户端构建

性能监视服务端-客户端构建

毛毛睡似了吗?看了这个项目,突发奇想,打算也做个视监自己电脑的服务端出来。

于是,昨天晚上,费了九牛二虎以及AI之力,终于把这个东西给实现出来了。

项目已开源,仓库位置:YMDG-BM/device-monitoring-server-and-client

项目结构

TREE
├── server
│   ├── static
│   │   ├── css
│   │   │   └── style.css				# 样式文件
│   │   ├── img
│   │   │   └── background.jpg			# 背景图片
│   │   └── js
│   │       └── script.js				# JavaScript 文件
│   ├── templates
│   │   └── display.html				# HTML 模板文件
│   ├── server.py					# 服务端入口点,设置HTTP服务器并处理请求
│   └── requirements.txt				# 项目依赖项
├── client
│   ├── utils
│   │   └── __init__.py				# 工具函数
│   └──client.py					# 客户端入口点,发送请求并处理响应
├── dockerfile					# Docker 配置文件
├── README.md						# 项目文档
└── requirements.txt					# 项目依赖项

客户端

客户端的目的有以下几个:

	1. 监视设备是否在线
	2. 监视设备各项性能信息
	3. 监视当前激活的窗口
	4. 发送json信息给服务器用于解析

于是,围绕这些需求开始构建代码。

监视设备是否在线

这个非常简单,在服务端设置一个heartbeat,在一定时间内如果没有发包就判定客户端离线。

部分实现如下:

client_last_seen = time.time()
client_timeout = 30  # 超时时间,单位为秒

def monitor_client_status():
    global client_last_seen
    while True:
        if time.time() - client_last_seen > client_timeout:
            client_data.clear()  # 清空客户端数据,表示客户端下线
        time.sleep(5)
        
@app.route('/status', methods=['GET'])
def get_status():
    global client_data
    if not client_data:
        return jsonify({"error": "No data received from client"}), 404
    return jsonify(client_data)

@app.route('/client_data', methods=['GET'])
def client_data_endpoint():
    global client_data
    if not client_data:
        return jsonify({"error": "No data received from client"}), 404
    return jsonify(client_data)

前端js实现

const container = document.getElementById('progressBars');
	if (data.error) {
		container.innerHTML = '<div class="Offline">计算机已离线</div>';
}        

监视设备各项性能信息

这部分在客户端实现,主要在utils中实现。

import psutil
import GPUtil
import subprocess

一些必要的模块,用于获取各项信息,比如CPU名、各项占用等。

def get_cpu_name():
    try:
        # 执行wmic命令获取CPU名称
        command = 'wmic cpu get name'
        cpu_name = subprocess.check_output(command, shell=True).decode('utf-8').split('\n')[1].strip()
        return cpu_name
    except Exception as e:
        print("无法获取CPU名称:", e)
        return None

获取CPU名。

def get_performance():
    cpu_usage = psutil.cpu_percent(interval=1)
    memory_info = psutil.virtual_memory()
    disk_info = psutil.disk_usage('/')
    gpus = GPUtil.getGPUs()
    gpu_info = [{"name": gpu.name, "load": gpu.load * 100, "memory_total": gpu.memoryTotal, "memory_used": gpu.memoryUsed, "memory_percent": gpu.memoryUtil * 100} for gpu in gpus]

    performance_data = {
        "cpu": {
            "name": get_cpu_name(),
            "usage": round(cpu_usage, 1)
        },
        "memory": {
            "percent": memory_info.percent
        },
        "disk": {
            "percent": disk_info.percent
        },
        "gpu": {
            "name": gpu_info[0]["name"] if gpu_info else "No GPU",
            "load": round(gpu_info[0]["load"], 1) if gpu_info else 0.0,
            "memory_total": int(gpu_info[0]["memory_total"]) if gpu_info else 0,
            "memory_percent": round(gpu_info[0]["memory_percent"], 1) if gpu_info else 0.0
        },
        "active_window": {
            "title": get_active_window()["title"],
        }
    }
    return performance_data

获取各项性能信息。

监视当前激活的窗口

import pygetwindow as gw

导入pygetwindow用来监视当前激活窗口

def get_active_window():
    try:
        window = gw.getActiveWindow()
        if window is not None:
            return {
                'title': window.title,
                'hwnd': window._hWnd
            }
        else:
            return None
    except Exception as e:
        print(f"An error occurred: {e}")
        return None

获取当前激活窗口信息

发送json信息给服务器用于解析

首先准备要发送的data

def send_data(self, data):
        headers = {'Content-Type': 'application/json'}
        payload = {
            "device_key": self.device_key, #设备认证信息,后面要考
            "performance_data": data
        }
        try:
            response = requests.post(self.server_url, data=json.dumps(payload), headers=headers)
            response.raise_for_status()  # 如果响应状态码不是200,抛出HTTPError
            return response.json()
        except requests.exceptions.RequestException as e:
            print(f"Error sending data: {e}")
            return None
 def get_performance_data(self):
        return utils.get_performance()

死循环防止炸客户端

while True:
        # retry_duration = 3  # 重试间隔时间
        performance_data = client.get_performance_data()
        response = client.send_data(performance_data)
        if response:
            print("Response from server:", response)
        else:
            print(f"Failed to send data, will retry in {retry_duration} seconds.")
        time.sleep(retry_duration)  # 每隔retry_duration秒重试一次

服务端

服务端要实现的内容也很简单,有以下几项:

1.获取客户端发送的信息
2.将客户端发送的信息进行解析,并呈现在网页上
3.认证客户端信息

获取客户端发送的信息

用flask实现

from flask import Flask, jsonify, request, render_template

@app.route('/monitor', methods=['POST'])
def monitor():
    global client_data, client_last_seen
    data = request.json
    if data.get("device_key") != device_key:
        return jsonify({"error": "Unauthorized"}), 401
    client_data = data.get("performance_data")
    client_last_seen = time.time()
    return jsonify({"status": "success"})

@app.route('/client_data', methods=['GET'])
def client_data_endpoint():
    global client_data
    if not client_data:
        return jsonify({"error": "No data received from client"}), 404
    return jsonify(client_data)

将客户端发送的信息进行解析,并呈现在网页上

python部分

@app.route('/display', methods=['GET'])
def display():
    return render_template('display.html')

@app.route('/client_data', methods=['GET'])
def client_data_endpoint():
    global client_data
    if not client_data:
        return jsonify({"error": "No data received from client"}), 404
    return jsonify(client_data)

json解析部分用js实现

function createProgressBar(category, label, percent) {
    return `
        <div class="progress-item ${category}">
            <div class="progress-title">
                <span>${label}</span>
                <span>${percent.toFixed(1)}%</span>
            </div>
            <div class="progress-bar">
                <div class="progress-fill" style="width: ${percent}%"></div>
            </div>
        </div>
    `;
}
function renderProgressBars() {
    fetch('/client_data')
        .then(response => response.json())
        .then(data => {
            const container = document.getElementById('progressBars');
            if (data.error) {
                container.innerHTML = '<div class="Offline">计算机已离线</div>';
            }
          else{
    container.innerHTML = [
        createProgressBar('cpu', 'CPU 使用率', data.cpu.usage),
        createProgressBar('memory', '内存使用率', data.memory.percent),
        createProgressBar('disk', '硬盘使用率', data.disk.percent),
        createProgressBar('gpu-load', 'GPU 使用率', data.gpu.load),
        createProgressBar('gpu-mem', '显存使用率', data.gpu.memory_percent),
        `<div class="active_window">当前激活的窗口为:${data.active_window.title}</div>`
    ].join('');}
    });
}
setInterval(renderProgressBars, 5000); // 每5秒更新一次数据
renderProgressBars(); // 页面加载时立即获取一次数据

认证客户端信息

客户端与服务端协同实现

这里我使用客户端的mac地址作为device_key认证。

import getmac

def send_data(self, data):
        headers = {'Content-Type': 'application/json'}
        payload = {
            "device_key": self.device_key,
            "performance_data": data
        }
        try:
            response = requests.post(self.server_url, data=json.dumps(payload), headers=headers)
            response.raise_for_status()  # 如果响应状态码不是200,抛出HTTPError
            return response.json()
        except requests.exceptions.RequestException as e:
            print(f"Error sending data: {e}")
            return None

服务端存放device_key信息

with open("./device_key.txt", "r") as f:
    device_key = f.read().strip()

monitor方法附带认证

def monitor():
    global client_data, client_last_seen
    data = request.json
    if data.get("device_key") != device_key:
        return jsonify({"error": "Unauthorized"}), 401
    client_data = data.get("performance_data")
    client_last_seen = time.time()
    return jsonify({"status": "success"})