From 8c22f11087ba07aed42243ddd96fef38760cfea2 Mon Sep 17 00:00:00 2001 From: pk5ls20 Date: Sat, 21 Dec 2024 13:11:10 +0800 Subject: [PATCH] feat: system status helper --- package.json | 4 +- src/core/helper/status.ts | 104 ++++++++++++++++++++++++++++++++++ src/webui/src/api/Status.ts | 19 +++++++ src/webui/src/router/Base.ts | 2 + src/webui/src/types/data.d.ts | 1 + 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/core/helper/status.ts create mode 100644 src/webui/src/api/Status.ts diff --git a/package.json b/package.json index 51ba54ba..95490e23 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@types/express": "^5.0.0", "@types/fluent-ffmpeg": "^2.1.24", "@types/node": "^22.0.1", + "@types/pidusage": "^2.0.5", "@types/qrcode-terminal": "^0.12.2", "@types/ws": "^8.5.12", "@typescript-eslint/eslint-plugin": "^8.3.0", @@ -56,9 +57,10 @@ "dependencies": { "express": "^5.0.0", "fluent-ffmpeg": "^2.1.2", + "pidusage": "^3.0.2", "piscina": "^4.7.0", "qrcode-terminal": "^0.12.0", "silk-wasm": "^3.6.1", "ws": "^8.18.0" } -} \ No newline at end of file +} diff --git a/src/core/helper/status.ts b/src/core/helper/status.ts new file mode 100644 index 00000000..51b92349 --- /dev/null +++ b/src/core/helper/status.ts @@ -0,0 +1,104 @@ +import os from "node:os"; +import pidusage from 'pidusage'; +import EventEmitter from "node:events"; + +export interface SystemStatus { + cpu: { + model: string, + speed: string + usage: { + system: string + qq: string + }, + }, + memory: { + usage: { + system: string + qq: string + } + }, +} + +export class StatusHelper { + private get sysCpuInfo() { + const { total, active } = os.cpus().map(cpu => { + const times = cpu.times; + const total = times.user + times.nice + times.sys + times.idle + times.irq; + const active = total - times.idle; + return { total, active }; + }).reduce((acc, cur) => ({ + total: acc.total + cur.total, + active: acc.active + cur.active + }), { total: 0, active: 0 }); + return { + usage: ((active / total) * 100).toFixed(2), + model: os.cpus()[0].model, + speed: os.cpus()[0].speed + }; + } + + private get sysMemoryUsage() { + const { total, free } = { total: os.totalmem(), free: os.freemem() }; + return ((total - free) / total * 100).toFixed(2); + } + + private async qqUsage() { + return await pidusage(process.pid); + } + + async systemStatus(): Promise { + const qqUsage = await this.qqUsage(); + return { + cpu: { + model: this.sysCpuInfo.model, + speed: (this.sysCpuInfo.speed / 1000).toFixed(2), + usage: { + system: this.sysCpuInfo.usage, + qq: qqUsage.cpu.toFixed(2) + }, + }, + memory: { + usage: { + system: this.sysMemoryUsage, + qq: (qqUsage.memory / os.totalmem() * 100).toFixed(2) + } + }, + }; + } +} + +class StatusHelperSubscription extends EventEmitter { + private statusHelper: StatusHelper; + private interval: NodeJS.Timeout | null = null; + + constructor(time: number = 3000) { + super(); + this.statusHelper = new StatusHelper(); + this.on('newListener', (event: string) => { + if (event === 'statusUpdate' && this.listenerCount('statusUpdate') === 0) { + this.startInterval(time); + } + }); + this.on('removeListener', (event: string) => { + if (event === 'statusUpdate' && this.listenerCount('statusUpdate') === 0) { + this.stopInterval(); + } + }); + } + + private startInterval(time: number) { + this.interval ??= setInterval(async () => { + const status = await this.statusHelper.systemStatus(); + this.emit('statusUpdate', status); + }, time); + } + + private stopInterval() { + if (this.interval) { + clearInterval(this.interval); + this.interval = null; + } + } +} + +export const statusHelperSubscription = new StatusHelperSubscription(); diff --git a/src/webui/src/api/Status.ts b/src/webui/src/api/Status.ts new file mode 100644 index 00000000..4ca75efd --- /dev/null +++ b/src/webui/src/api/Status.ts @@ -0,0 +1,19 @@ +import { RequestHandler } from 'express'; +import { SystemStatus, statusHelperSubscription } from "@/core/helper/status"; + +export const StatusRealTimeHandler: RequestHandler = async (req, res) => { + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Connection', 'keep-alive'); + const sendStatus = (status: SystemStatus) => { + try{ + res.write(`data: ${JSON.stringify(status)}\n\n`); + } catch (e) { + console.error(`An error occurred when writing sendStatus data to client: ${e}`); + } + }; + statusHelperSubscription.on('statusUpdate', sendStatus); + req.on('close', () => { + statusHelperSubscription.off('statusUpdate', sendStatus); + res.end(); + }); +}; diff --git a/src/webui/src/router/Base.ts b/src/webui/src/router/Base.ts index e332770f..1327fc92 100644 --- a/src/webui/src/router/Base.ts +++ b/src/webui/src/router/Base.ts @@ -1,8 +1,10 @@ import { Router } from 'express'; import { PackageInfoHandler } from '../api/BaseInfo'; +import { StatusRealTimeHandler } from "@webapi/api/Status"; const router = Router(); // router: 获取nc的package.json信息 router.get('/PackageInfo', PackageInfoHandler); +router.get('/GetSysStatusRealTime', StatusRealTimeHandler); export { router as BaseRouter }; diff --git a/src/webui/src/types/data.d.ts b/src/webui/src/types/data.d.ts index a3ea9852..0da3ee19 100644 --- a/src/webui/src/types/data.d.ts +++ b/src/webui/src/types/data.d.ts @@ -1,4 +1,5 @@ import type { LoginListItem, SelfInfo } from '@/core'; +import type { OneBotConfig } from "@/onebot/config/config"; interface LoginRuntimeType { LoginCurrentTime: number;