From d8e1ab63c0632ebe69f37755fc5fe4a364c9be52 Mon Sep 17 00:00:00 2001 From: naskya <m@naskya.net> Date: Wed, 15 May 2024 16:26:46 +0900 Subject: [PATCH] refactor: port system information checker to backend-rs network stat is removed because it might be inaccurate and/or it should be monitored by other system tools, but it may be added back later if it is wanted --- Cargo.lock | 35 ++++ Cargo.toml | 1 + docs/api-change.md | 2 + packages/backend-rs/Cargo.toml | 1 + packages/backend-rs/index.d.ts | 25 ++- packages/backend-rs/index.js | 9 +- .../backend-rs/src/{service => init}/log.rs | 0 packages/backend-rs/src/init/mod.rs | 2 + packages/backend-rs/src/init/server_stats.rs | 39 +++++ packages/backend-rs/src/lib.rs | 1 + packages/backend-rs/src/misc/mod.rs | 1 + packages/backend-rs/src/misc/server_stats.rs | 90 ++++++++++ packages/backend-rs/src/service/mod.rs | 1 - packages/backend/package.json | 2 - packages/backend/src/@types/os-utils.d.ts | 33 ---- packages/backend/src/boot/master.ts | 6 +- packages/backend/src/daemons/server-stats.ts | 60 +------ .../backend/src/misc/show-machine-info.ts | 17 -- .../server/api/endpoints/admin/server-info.ts | 40 ++--- .../src/server/api/endpoints/server-info.ts | 33 ++-- .../src/pages/admin/overview.metrics.vue | 1 - .../src/widgets/server-metric/cpu-mem.vue | 41 ++--- .../client/src/widgets/server-metric/cpu.vue | 4 +- .../client/src/widgets/server-metric/disk.vue | 9 +- .../src/widgets/server-metric/index.vue | 34 ++-- .../client/src/widgets/server-metric/mem.vue | 11 +- .../client/src/widgets/server-metric/net.vue | 156 ------------------ .../client/src/widgets/server-metric/pie.vue | 2 +- pnpm-lock.yaml | 19 --- 29 files changed, 279 insertions(+), 396 deletions(-) rename packages/backend-rs/src/{service => init}/log.rs (100%) create mode 100644 packages/backend-rs/src/init/mod.rs create mode 100644 packages/backend-rs/src/init/server_stats.rs create mode 100644 packages/backend-rs/src/misc/server_stats.rs delete mode 100644 packages/backend/src/@types/os-utils.d.ts delete mode 100644 packages/backend/src/misc/show-machine-info.ts delete mode 100644 packages/client/src/widgets/server-metric/net.vue diff --git a/Cargo.lock b/Cargo.lock index 02eb95321e..95d2c9c03f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -236,6 +236,7 @@ dependencies = [ "serde_json", "serde_yaml", "strum 0.26.2", + "sysinfo", "thiserror", "tokio", "tracing", @@ -1644,6 +1645,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -3167,6 +3177,21 @@ dependencies = [ "syn 2.0.62", ] +[[package]] +name = "sysinfo" +version = "0.30.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732ffa00f53e6b2af46208fba5718d9662a421049204e156328b66791ffa15ae" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3673,6 +3698,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.5", +] + [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index f8bb17f28b..827e4a2de3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ serde_json = "1.0.117" serde_yaml = "0.9.34" strum = "0.26.2" syn = "2.0.62" +sysinfo = "0.30.12" thiserror = "1.0.60" tokio = "1.37.0" tracing = "0.1.40" diff --git a/docs/api-change.md b/docs/api-change.md index 5cbf3922d4..3c1cc3eb69 100644 --- a/docs/api-change.md +++ b/docs/api-change.md @@ -4,6 +4,8 @@ Breaking changes are indicated by the :warning: icon. ## Unreleased +- :warning: `server-info` (an endpoint to get server hardware information) now requires credentials. +- :warning: `net` (server's default network interface) has been removed from `admin/server-info`. - Adding `lang` to the response of `i` and the request parameter of `i/update`. ## v20240504 diff --git a/packages/backend-rs/Cargo.toml b/packages/backend-rs/Cargo.toml index 936fc525cf..52ae312872 100644 --- a/packages/backend-rs/Cargo.toml +++ b/packages/backend-rs/Cargo.toml @@ -39,6 +39,7 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } serde_yaml = { workspace = true } strum = { workspace = true, features = ["derive"] } +sysinfo = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts index 1133fad209..6c549bd257 100644 --- a/packages/backend-rs/index.d.ts +++ b/packages/backend-rs/index.d.ts @@ -212,6 +212,8 @@ export interface Acct { } export function stringToAcct(acct: string): Acct export function acctToString(acct: Acct): string +export function initializeRustLogger(): void +export function showServerInfo(): void export function addNoteToAntenna(antennaId: string, note: Note): void /** * Checks if a server is blocked. @@ -299,6 +301,28 @@ export function countReactions(reactions: Record<string, number>): Record<string export function toDbReaction(reaction?: string | undefined | null, host?: string | undefined | null): Promise<string> /** Delete all entries in the "attestation_challenge" table created at more than 5 minutes ago */ export function removeOldAttestationChallenges(): Promise<void> +export interface Cpu { + model: string + cores: number +} +export interface Memory { + /** Total memory amount in bytes */ + total: number + /** Used memory amount in bytes */ + used: number + /** Available (for (re)use) memory amount in bytes */ + available: number +} +export interface Storage { + /** Total storage space in bytes */ + total: number + /** Used storage space in bytes */ + used: number +} +export function cpuInfo(): Cpu +export function cpuUsage(): number +export function memoryUsage(): Memory +export function storageUsage(): Storage | null export interface AbuseUserReport { id: string createdAt: Date @@ -1156,7 +1180,6 @@ export interface Webhook { latestSentAt: Date | null latestStatus: number | null } -export function initializeRustLogger(): void export function fetchNodeinfo(host: string): Promise<Nodeinfo> export function nodeinfo_2_1(): Promise<any> export function nodeinfo_2_0(): Promise<any> diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js index 287d4296fc..7819b4e7f1 100644 --- a/packages/backend-rs/index.js +++ b/packages/backend-rs/index.js @@ -310,7 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initializeRustLogger, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, watchNote, unwatchNote, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding +const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, initializeRustLogger, showServerInfo, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, watchNote, unwatchNote, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding module.exports.SECOND = SECOND module.exports.MINUTE = MINUTE @@ -323,6 +323,8 @@ module.exports.loadEnv = loadEnv module.exports.loadConfig = loadConfig module.exports.stringToAcct = stringToAcct module.exports.acctToString = acctToString +module.exports.initializeRustLogger = initializeRustLogger +module.exports.showServerInfo = showServerInfo module.exports.addNoteToAntenna = addNoteToAntenna module.exports.isBlockedServer = isBlockedServer module.exports.isSilencedServer = isSilencedServer @@ -353,6 +355,10 @@ module.exports.decodeReaction = decodeReaction module.exports.countReactions = countReactions module.exports.toDbReaction = toDbReaction module.exports.removeOldAttestationChallenges = removeOldAttestationChallenges +module.exports.cpuInfo = cpuInfo +module.exports.cpuUsage = cpuUsage +module.exports.memoryUsage = memoryUsage +module.exports.storageUsage = storageUsage module.exports.AntennaSrcEnum = AntennaSrcEnum module.exports.DriveFileUsageHintEnum = DriveFileUsageHintEnum module.exports.MutedNoteReasonEnum = MutedNoteReasonEnum @@ -364,7 +370,6 @@ module.exports.RelayStatusEnum = RelayStatusEnum module.exports.UserEmojimodpermEnum = UserEmojimodpermEnum module.exports.UserProfileFfvisibilityEnum = UserProfileFfvisibilityEnum module.exports.UserProfileMutingnotificationtypesEnum = UserProfileMutingnotificationtypesEnum -module.exports.initializeRustLogger = initializeRustLogger module.exports.fetchNodeinfo = fetchNodeinfo module.exports.nodeinfo_2_1 = nodeinfo_2_1 module.exports.nodeinfo_2_0 = nodeinfo_2_0 diff --git a/packages/backend-rs/src/service/log.rs b/packages/backend-rs/src/init/log.rs similarity index 100% rename from packages/backend-rs/src/service/log.rs rename to packages/backend-rs/src/init/log.rs diff --git a/packages/backend-rs/src/init/mod.rs b/packages/backend-rs/src/init/mod.rs new file mode 100644 index 0000000000..0f6e5efebc --- /dev/null +++ b/packages/backend-rs/src/init/mod.rs @@ -0,0 +1,2 @@ +pub mod log; +pub mod server_stats; diff --git a/packages/backend-rs/src/init/server_stats.rs b/packages/backend-rs/src/init/server_stats.rs new file mode 100644 index 0000000000..9b99dba013 --- /dev/null +++ b/packages/backend-rs/src/init/server_stats.rs @@ -0,0 +1,39 @@ +use std::sync::{Mutex, MutexGuard, OnceLock, PoisonError}; +use sysinfo::System; + +pub type SystemMutexError = PoisonError<MutexGuard<'static, System>>; + +// TODO: handle this in more proper way when we move the entry point to backend-rs +pub fn system() -> Result<MutexGuard<'static, System>, SystemMutexError> { + pub static SYSTEM: OnceLock<Mutex<System>> = OnceLock::new(); + SYSTEM.get_or_init(|| Mutex::new(System::new_all())).lock() +} + +#[crate::export] +pub fn show_server_info() -> Result<(), SystemMutexError> { + let system_info = system()?; + + tracing::info!( + "Hostname: {}", + System::host_name().unwrap_or("unknown".to_string()) + ); + tracing::info!( + "OS: {}", + System::long_os_version().unwrap_or("unknown".to_string()) + ); + tracing::info!( + "Kernel: {}", + System::kernel_version().unwrap_or("unknown".to_string()) + ); + tracing::info!( + "CPU architecture: {}", + System::cpu_arch().unwrap_or("unknown".to_string()) + ); + tracing::info!("CPU threads: {}", system_info.cpus().len()); + tracing::info!("Total memory: {} MiB", system_info.total_memory() / 1048576); + tracing::info!("Free memory: {} MiB", system_info.free_memory() / 1048576); + tracing::info!("Total swap: {} MiB", system_info.total_swap() / 1048576); + tracing::info!("Free swap: {} MiB", system_info.free_swap() / 1048576); + + Ok(()) +} diff --git a/packages/backend-rs/src/lib.rs b/packages/backend-rs/src/lib.rs index 2a7f60111e..50a98ad787 100644 --- a/packages/backend-rs/src/lib.rs +++ b/packages/backend-rs/src/lib.rs @@ -3,6 +3,7 @@ pub use macro_rs::{export, ts_only_warn}; pub mod config; pub mod database; pub mod federation; +pub mod init; pub mod misc; pub mod model; pub mod service; diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs index 8b81ccde1c..da65e26bc2 100644 --- a/packages/backend-rs/src/misc/mod.rs +++ b/packages/backend-rs/src/misc/mod.rs @@ -15,3 +15,4 @@ pub mod nyaify; pub mod password; pub mod reaction; pub mod remove_old_attestation_challenges; +pub mod server_stats; diff --git a/packages/backend-rs/src/misc/server_stats.rs b/packages/backend-rs/src/misc/server_stats.rs new file mode 100644 index 0000000000..02a3ee08d4 --- /dev/null +++ b/packages/backend-rs/src/misc/server_stats.rs @@ -0,0 +1,90 @@ +use crate::init::server_stats::{system, SystemMutexError}; +use sysinfo::{Disks, MemoryRefreshKind}; + +// TODO: i64 -> u64 (we can't export u64 to Node.js) + +#[crate::export(object)] +pub struct Cpu { + pub model: String, + // TODO: u16 -> usize (we can't export usize to Node.js) + pub cores: u16, +} + +#[crate::export(object)] +pub struct Memory { + /// Total memory amount in bytes + pub total: i64, + /// Used memory amount in bytes + pub used: i64, + /// Available (for (re)use) memory amount in bytes + pub available: i64, +} + +#[crate::export(object)] +pub struct Storage { + /// Total storage space in bytes + pub total: i64, + /// Used storage space in bytes + pub used: i64, +} + +#[crate::export] +pub fn cpu_info() -> Result<Cpu, SystemMutexError> { + let system_info = system()?; + + Ok(Cpu { + model: match system_info.cpus() { + cpus if cpus.is_empty() => { + tracing::debug!("failed to get CPU info"); + "unknown".to_string() + } + cpus => cpus[0].brand().to_string(), + }, + cores: system_info.cpus().len() as u16, + }) +} + +#[crate::export] +pub fn cpu_usage() -> Result<f32, SystemMutexError> { + let mut system_info = system()?; + system_info.refresh_cpu_usage(); + + let total_cpu_usage: f32 = system_info.cpus().iter().map(|cpu| cpu.cpu_usage()).sum(); + let cpu_threads = system_info.cpus().len(); + + Ok(total_cpu_usage / (cpu_threads as f32)) +} + +#[crate::export] +pub fn memory_usage() -> Result<Memory, SystemMutexError> { + let mut system_info = system()?; + + system_info.refresh_memory_specifics(MemoryRefreshKind::new().with_ram()); + + Ok(Memory { + total: system_info.total_memory() as i64, + used: system_info.used_memory() as i64, + available: system_info.available_memory() as i64, + }) +} + +#[crate::export] +pub fn storage_usage() -> Option<Storage> { + // Get the first disk that is actualy used. + let disks = Disks::new_with_refreshed_list(); + let disk = disks + .iter() + .find(|disk| disk.available_space() > 0 && disk.total_space() > disk.available_space()); + + if let Some(disk) = disk { + let total = disk.total_space() as i64; + let available = disk.available_space() as i64; + return Some(Storage { + total, + used: total - available, + }); + } + + tracing::debug!("failed to get stats"); + None +} diff --git a/packages/backend-rs/src/service/mod.rs b/packages/backend-rs/src/service/mod.rs index f755f22b4b..9569a33b63 100644 --- a/packages/backend-rs/src/service/mod.rs +++ b/packages/backend-rs/src/service/mod.rs @@ -1,4 +1,3 @@ -pub mod log; pub mod nodeinfo; pub mod note; pub mod stream; diff --git a/packages/backend/package.json b/packages/backend/package.json index cd9b157089..6a511e1b4a 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -87,7 +87,6 @@ "node-fetch": "3.3.2", "nodemailer": "6.9.13", "opencc-js": "1.0.5", - "os-utils": "0.0.14", "otpauth": "9.2.4", "parse5": "7.1.2", "pg": "8.11.5", @@ -111,7 +110,6 @@ "stringz": "2.1.0", "summaly": "2.7.0", "syslog-pro": "1.0.0", - "systeminformation": "5.22.8", "tar-stream": "3.1.7", "tesseract.js": "5.1.0", "tinycolor2": "1.6.0", diff --git a/packages/backend/src/@types/os-utils.d.ts b/packages/backend/src/@types/os-utils.d.ts deleted file mode 100644 index 504096ae2b..0000000000 --- a/packages/backend/src/@types/os-utils.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -declare module "os-utils" { - type FreeCommandCallback = (usedmem: number) => void; - - type HarddriveCallback = (total: number, free: number, used: number) => void; - - type GetProcessesCallback = (result: string) => void; - - type CPUCallback = (perc: number) => void; - - export function platform(): NodeJS.Platform; - export function cpuCount(): number; - export function sysUptime(): number; - export function processUptime(): number; - - export function freemem(): number; - export function totalmem(): number; - export function freememPercentage(): number; - export function freeCommand(callback: FreeCommandCallback): void; - - export function harddrive(callback: HarddriveCallback): void; - - export function getProcesses(callback: GetProcessesCallback): void; - export function getProcesses( - nProcess: number, - callback: GetProcessesCallback, - ): void; - - export function allLoadavg(): string; - export function loadavg(_time?: number): number; - - export function cpuFree(callback: CPUCallback): void; - export function cpuUsage(callback: CPUCallback): void; -} diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 1f63dc0136..68e7863628 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -12,10 +12,10 @@ import { fetchMeta, initializeRustLogger, removeOldAttestationChallenges, + showServerInfo, type Config, } from "backend-rs"; import { config, envOption } from "@/config.js"; -import { showMachineInfo } from "@/misc/show-machine-info.js"; import { db, initDb } from "@/db/postgre.js"; import { inspect } from "node:util"; @@ -93,12 +93,12 @@ function greet() { export async function masterMain() { // initialize app try { + initializeRustLogger(); greet(); showEnvironment(); - await showMachineInfo(bootLogger); + showServerInfo(); showNodejsVersion(); await connectDb(); - initializeRustLogger(); } catch (e) { bootLogger.error( `Fatal error occurred during initialization:\n${inspect(e)}`, diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts index df1f9b3032..2db09e8ae6 100644 --- a/packages/backend/src/daemons/server-stats.ts +++ b/packages/backend/src/daemons/server-stats.ts @@ -1,15 +1,8 @@ -import si from "systeminformation"; import Xev from "xev"; -import * as osUtils from "os-utils"; -import { fetchMeta } from "backend-rs"; +import { fetchMeta, cpuUsage, memoryUsage } from "backend-rs"; const ev = new Xev(); -const interval = 2000; - -const roundCpu = (num: number) => Math.round(num * 1000) / 1000; -const round = (num: number) => Math.round(num * 10) / 10; - /** * Report server stats regularly */ @@ -24,26 +17,9 @@ export default async function () { if (!meta.enableServerMachineStats) return; async function tick() { - const cpu = await cpuUsage(); - const memStats = await mem(); - const netStats = await net(); - const fsStats = await fs(); - const stats = { - cpu: roundCpu(cpu), - mem: { - used: round(memStats.used - memStats.buffers - memStats.cached), - active: round(memStats.active), - total: round(memStats.total), - }, - net: { - rx: round(Math.max(0, netStats.rx_sec)), - tx: round(Math.max(0, netStats.tx_sec)), - }, - fs: { - r: round(Math.max(0, fsStats.rIO_sec ?? 0)), - w: round(Math.max(0, fsStats.wIO_sec ?? 0)), - }, + cpu: cpuUsage(), + mem: memoryUsage(), }; ev.emit("serverStats", stats); log.unshift(stats); @@ -52,33 +28,5 @@ export default async function () { tick(); - setInterval(tick, interval); -} - -// CPU STAT -function cpuUsage(): Promise<number> { - return new Promise((res, rej) => { - osUtils.cpuUsage((cpuUsage) => { - res(cpuUsage); - }); - }); -} - -// MEMORY STAT -async function mem() { - const data = await si.mem(); - return data; -} - -// NETWORK STAT -async function net() { - const iface = await si.networkInterfaceDefault(); - const data = await si.networkStats(iface); - return data[0]; -} - -// FS STAT -async function fs() { - const data = await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 })); - return data || { rIO_sec: 0, wIO_sec: 0 }; + setInterval(tick, 3000); } diff --git a/packages/backend/src/misc/show-machine-info.ts b/packages/backend/src/misc/show-machine-info.ts deleted file mode 100644 index d3a28cbd37..0000000000 --- a/packages/backend/src/misc/show-machine-info.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as os from "node:os"; -import sysUtils from "systeminformation"; -import type Logger from "@/services/logger.js"; - -export async function showMachineInfo(parentLogger: Logger) { - const logger = parentLogger.createSubLogger("machine"); - logger.debug(`Hostname: ${os.hostname()}`); - logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`); - const mem = await sysUtils.mem(); - const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1); - const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1); - logger.debug( - `CPU: ${ - os.cpus().length - } core MEM: ${totalmem}GB (available: ${availmem}GB)`, - ); -} diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index ff46919694..0faa7f7264 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -1,8 +1,12 @@ import * as os from "node:os"; -import si from "systeminformation"; import define from "@/server/api/define.js"; import { redisClient } from "@/db/redis.js"; import { db } from "@/db/postgre.js"; +import { + cpuInfo, + memoryUsage, + storageUsage, +} from "backend-rs"; export const meta = { requireCredential: true, @@ -85,19 +89,6 @@ export const meta = { }, }, }, - net: { - type: "object", - optional: false, - nullable: false, - properties: { - interface: { - type: "string", - optional: false, - nullable: false, - example: "eth0", - }, - }, - }, }, }, } as const; @@ -109,13 +100,10 @@ export const paramDef = { } as const; export default define(meta, paramDef, async () => { - const memStats = await si.mem(); - const fsStats = await si.fsSize(); - const netInterface = await si.networkInterfaceDefault(); - const redisServerInfo = await redisClient.info("Server"); - const m = redisServerInfo.match(new RegExp("^redis_version:(.*)", "m")); + const m = redisServerInfo.match(/^redis_version:(.*)/m); const redis_version = m?.[1]; + const storage = storageUsage(); return { machine: os.hostname(), @@ -125,19 +113,13 @@ export default define(meta, paramDef, async () => { .query("SHOW server_version") .then((x) => x[0].server_version), redis: redis_version, - cpu: { - model: os.cpus()[0].model, - cores: os.cpus().length, - }, + cpu: cpuInfo(), mem: { - total: memStats.total, + total: memoryUsage().total, }, fs: { - total: fsStats[0].size, - used: fsStats[0].used, - }, - net: { - interface: netInterface, + total: storage?.total ?? 0, + used: storage?.used ?? 0, }, }; }); diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index 1a1ecad688..8f35daa7f8 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -1,10 +1,9 @@ import * as os from "node:os"; -import si from "systeminformation"; import define from "@/server/api/define.js"; -import { fetchMeta } from "backend-rs"; +import { fetchMeta, cpuInfo, memoryUsage, storageUsage } from "backend-rs"; export const meta = { - requireCredential: false, + requireCredential: true, requireCredentialPrivateMode: true, allowGet: true, cacheSec: 30, @@ -18,19 +17,8 @@ export const paramDef = { } as const; export default define(meta, paramDef, async () => { - const memStats = await si.mem(); - const fsStats = await si.fsSize(); - - let fsIndex = 0; - // Get the first index of fs sizes that are actualy used. - for (const [i, stat] of fsStats.entries()) { - if (stat.rw === true && stat.used > 0) { - fsIndex = i; - break; - } - } - const instanceMeta = await fetchMeta(true); + if (!instanceMeta.enableServerMachineStats) { return { machine: "Not specified", @@ -47,18 +35,19 @@ export default define(meta, paramDef, async () => { }, }; } + + const memory = memoryUsage(); + const storage = storageUsage(); + return { machine: os.hostname(), - cpu: { - model: os.cpus()[0].model, - cores: os.cpus().length, - }, + cpu: cpuInfo(), mem: { - total: memStats.total, + total: memory.total, }, fs: { - total: fsStats[fsIndex].size, - used: fsStats[fsIndex].used, + total: storage?.total ?? 0, + used: storage?.used ?? 0, }, }; }); diff --git a/packages/client/src/pages/admin/overview.metrics.vue b/packages/client/src/pages/admin/overview.metrics.vue index 498659b194..165eadd852 100644 --- a/packages/client/src/pages/admin/overview.metrics.vue +++ b/packages/client/src/pages/admin/overview.metrics.vue @@ -44,7 +44,6 @@ import icon from "@/scripts/icon"; const stream = useStream(); const meta = await os.api("server-info", {}); -const serverStats = await os.api("stats"); const cpuUsage = ref(0); diff --git a/packages/client/src/widgets/server-metric/cpu-mem.vue b/packages/client/src/widgets/server-metric/cpu-mem.vue index d304dc1627..bce50d94d5 100644 --- a/packages/client/src/widgets/server-metric/cpu-mem.vue +++ b/packages/client/src/widgets/server-metric/cpu-mem.vue @@ -36,7 +36,7 @@ /> <text x="1" y="5"> CPU - <tspan>{{ cpuP }}%</tspan> + <tspan>{{ cpuUsage }}%</tspan> </text> </svg> <svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`"> @@ -75,7 +75,7 @@ /> <text x="1" y="5"> MEM - <tspan>{{ memP }}%</tspan> + <tspan>{{ memUsage }}%</tspan> </text> </svg> </div> @@ -87,26 +87,25 @@ import { v4 as uuid } from "uuid"; const props = defineProps<{ connection: any; - meta: any; }>(); -const viewBoxX: number = ref(50); -const viewBoxY: number = ref(30); -const stats: any[] = ref([]); +const viewBoxX = ref(50); +const viewBoxY = ref(30); +const stats = ref<any[]>([]); const cpuGradientId = uuid(); const cpuMaskId = uuid(); const memGradientId = uuid(); const memMaskId = uuid(); -const cpuPolylinePoints: string = ref(""); -const memPolylinePoints: string = ref(""); -const cpuPolygonPoints: string = ref(""); -const memPolygonPoints: string = ref(""); -const cpuHeadX: any = ref(null); -const cpuHeadY: any = ref(null); -const memHeadX: any = ref(null); -const memHeadY: any = ref(null); -const cpuP: string = ref(""); -const memP: string = ref(""); +const cpuPolylinePoints = ref(""); +const memPolylinePoints = ref(""); +const cpuPolygonPoints = ref(""); +const memPolygonPoints = ref(""); +const cpuHeadX = ref<number>(); +const cpuHeadY = ref<number>(); +const memHeadX = ref<number>(); +const memHeadY = ref<number>(); +const cpuUsage = ref<string>(); +const memUsage = ref<string>(); onMounted(() => { props.connection.on("stats", onStats); @@ -127,11 +126,11 @@ function onStats(connStats) { const cpuPolylinePointsStats = stats.value.map((s, i) => [ viewBoxX.value - (stats.value.length - 1 - i), - (1 - s.cpu) * viewBoxY.value, + (1 - s.cpu / 100) * viewBoxY.value, ]); const memPolylinePointsStats = stats.value.map((s, i) => [ viewBoxX.value - (stats.value.length - 1 - i), - (1 - s.mem.active / s.mem.total) * viewBoxY.value, + (1 - s.mem.used / s.mem.total) * viewBoxY.value, ]); cpuPolylinePoints.value = cpuPolylinePointsStats .map((xy) => `${xy[0]},${xy[1]}`) @@ -152,8 +151,10 @@ function onStats(connStats) { memHeadX.value = memPolylinePointsStats[memPolylinePointsStats.length - 1][0]; memHeadY.value = memPolylinePointsStats[memPolylinePointsStats.length - 1][1]; - cpuP.value = (connStats.cpu * 100).toFixed(0); - memP.value = ((connStats.mem.active / connStats.mem.total) * 100).toFixed(0); + cpuUsage.value = connStats.cpu.toFixed(1); + memUsage.value = ((connStats.mem.used / connStats.mem.total) * 100).toFixed( + 1, + ); } function onStatsLog(statsLog) { diff --git a/packages/client/src/widgets/server-metric/cpu.vue b/packages/client/src/widgets/server-metric/cpu.vue index 4a18098fb0..644db7dc91 100644 --- a/packages/client/src/widgets/server-metric/cpu.vue +++ b/packages/client/src/widgets/server-metric/cpu.vue @@ -19,10 +19,10 @@ const props = defineProps<{ meta: any; }>(); -const usage: number = ref(0); +const usage = ref(0); function onStats(stats) { - usage.value = stats.cpu; + usage.value = stats.cpu / 100; } onMounted(() => { diff --git a/packages/client/src/widgets/server-metric/disk.vue b/packages/client/src/widgets/server-metric/disk.vue index 71ebd1732c..33d9c0eb54 100644 --- a/packages/client/src/widgets/server-metric/disk.vue +++ b/packages/client/src/widgets/server-metric/disk.vue @@ -4,7 +4,7 @@ <div> <p><i :class="icon('ph-hard-drives')"></i>Disk</p> <p>Total: {{ bytes(total, 1) }}</p> - <p>Free: {{ bytes(available, 1) }}</p> + <p>Available: {{ bytes(available, 1) }}</p> <p>Used: {{ bytes(used, 1) }}</p> </div> </div> @@ -18,7 +18,12 @@ import bytes from "@/filters/bytes"; import icon from "@/scripts/icon"; const props = defineProps<{ - meta: any; // TODO + meta: { + fs: { + used: number; + total: number; + }; + }; }>(); const usage = computed(() => props.meta.fs.used / props.meta.fs.total); diff --git a/packages/client/src/widgets/server-metric/index.vue b/packages/client/src/widgets/server-metric/index.vue index a9026efeac..6513b961a8 100644 --- a/packages/client/src/widgets/server-metric/index.vue +++ b/packages/client/src/widgets/server-metric/index.vue @@ -26,23 +26,18 @@ :connection="connection" :meta="meta" /> - <XNet + <XCpu v-else-if="widgetProps.view === 1" :connection="connection" :meta="meta" /> - <XCpu + <XMemory v-else-if="widgetProps.view === 2" :connection="connection" :meta="meta" /> - <XMemory - v-else-if="widgetProps.view === 3" - :connection="connection" - :meta="meta" - /> <XDisk - v-else-if="widgetProps.view === 4" + v-else-if="widgetProps.view === 3" :connection="connection" :meta="meta" /> @@ -52,10 +47,13 @@ <script lang="ts" setup> import { onUnmounted, ref } from "vue"; -import type { Widget, WidgetComponentExpose } from "../widget"; +import type { + WidgetComponentEmits, + WidgetComponentExpose, + WidgetComponentProps, +} from "../widget"; import { useWidgetPropsManager } from "../widget"; import XCpuMemory from "./cpu-mem.vue"; -import XNet from "./net.vue"; import XCpu from "./cpu.vue"; import XMemory from "./mem.vue"; import XDisk from "./disk.vue"; @@ -87,11 +85,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない -// const props = defineProps<WidgetComponentProps<WidgetProps>>(); -// const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps> }>(); -const emit = defineEmits<{ (ev: "updateProps", props: WidgetProps) }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure, save } = useWidgetPropsManager( name, @@ -107,14 +102,7 @@ os.apiGet("server-info", {}).then((res) => { }); const toggleView = () => { - if ( - (widgetProps.view === 5 && instance.features.searchFilters) || - (widgetProps.view === 4 && !instance.features.searchFilters) - ) { - widgetProps.view = 0; - } else { - widgetProps.view++; - } + widgetProps.view = (widgetProps.view + 1) % 4; save(); }; diff --git a/packages/client/src/widgets/server-metric/mem.vue b/packages/client/src/widgets/server-metric/mem.vue index b5ea5fd167..999b0e3d27 100644 --- a/packages/client/src/widgets/server-metric/mem.vue +++ b/packages/client/src/widgets/server-metric/mem.vue @@ -5,7 +5,7 @@ <p><i :class="icon('ph-microchip')"></i>RAM</p> <p>Total: {{ bytes(total, 1) }}</p> <p>Used: {{ bytes(used, 1) }}</p> - <p>Free: {{ bytes(free, 1) }}</p> + <p>Available: {{ bytes(available, 1) }}</p> </div> </div> </template> @@ -18,19 +18,18 @@ import icon from "@/scripts/icon"; const props = defineProps<{ connection: any; - meta: any; }>(); const usage = ref<number>(0); const total = ref<number>(0); const used = ref<number>(0); -const free = ref<number>(0); +const available = ref<number>(0); function onStats(stats) { - usage.value = stats.mem.active / stats.mem.total; + usage.value = stats.mem.used / stats.mem.total; total.value = stats.mem.total; - used.value = stats.mem.active; - free.value = total.value - used.value; + used.value = stats.mem.used; + available.value = stats.mem.available; } onMounted(() => { diff --git a/packages/client/src/widgets/server-metric/net.vue b/packages/client/src/widgets/server-metric/net.vue deleted file mode 100644 index 74571f2b10..0000000000 --- a/packages/client/src/widgets/server-metric/net.vue +++ /dev/null @@ -1,156 +0,0 @@ -<template> - <div class="oxxrhrto"> - <svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`"> - <polygon - :points="inPolygonPoints" - fill="#f6c177" - fill-opacity="0.5" - /> - <polyline - :points="inPolylinePoints" - fill="none" - stroke="#f6c177" - stroke-width="1" - /> - <circle :cx="inHeadX" :cy="inHeadY" r="1.5" fill="#f6c177" /> - <text x="1" y="5"> - NET rx - <tspan>{{ bytes(inRecent) }}</tspan> - </text> - </svg> - <svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`"> - <polygon - :points="outPolygonPoints" - fill="#31748f" - fill-opacity="0.5" - /> - <polyline - :points="outPolylinePoints" - fill="none" - stroke="#31748f" - stroke-width="1" - /> - <circle :cx="outHeadX" :cy="outHeadY" r="1.5" fill="#31748f" /> - <text x="1" y="5"> - NET tx - <tspan>{{ bytes(outRecent) }}</tspan> - </text> - </svg> - </div> -</template> - -<script lang="ts" setup> -import { onBeforeUnmount, onMounted, ref } from "vue"; -import bytes from "@/filters/bytes"; - -const props = defineProps<{ - connection: any; - meta: any; -}>(); - -const viewBoxX: number = ref(50); -const viewBoxY: number = ref(30); -const stats: any[] = ref([]); -const inPolylinePoints: string = ref(""); -const outPolylinePoints: string = ref(""); -const inPolygonPoints: string = ref(""); -const outPolygonPoints: string = ref(""); -const inHeadX: any = ref(null); -const inHeadY: any = ref(null); -const outHeadX: any = ref(null); -const outHeadY: any = ref(null); -const inRecent: number = ref(0); -const outRecent: number = ref(0); - -onMounted(() => { - props.connection.on("stats", onStats); - props.connection.on("statsLog", onStatsLog); - props.connection.send("requestLog", { - id: Math.random().toString().substring(2, 10), - }); -}); - -onBeforeUnmount(() => { - props.connection.off("stats", onStats); - props.connection.off("statsLog", onStatsLog); -}); - -function onStats(connStats) { - stats.value.push(connStats); - if (stats.value.length > 50) stats.value.shift(); - - const inPeak = Math.max( - 1024 * 64, - Math.max(...stats.value.map((s) => s.net.rx)), - ); - const outPeak = Math.max( - 1024 * 64, - Math.max(...stats.value.map((s) => s.net.tx)), - ); - - const inPolylinePointsStats = stats.value.map((s, i) => [ - viewBoxX.value - (stats.value.length - 1 - i), - (1 - s.net.rx / inPeak) * viewBoxY.value, - ]); - const outPolylinePointsStats = stats.value.map((s, i) => [ - viewBoxX.value - (stats.value.length - 1 - i), - (1 - s.net.tx / outPeak) * viewBoxY.value, - ]); - inPolylinePoints.value = inPolylinePointsStats - .map((xy) => `${xy[0]},${xy[1]}`) - .join(" "); - outPolylinePoints.value = outPolylinePointsStats - .map((xy) => `${xy[0]},${xy[1]}`) - .join(" "); - - inPolygonPoints.value = `${viewBoxX.value - (stats.value.length - 1)},${ - viewBoxY.value - } ${inPolylinePoints.value} ${viewBoxX.value},${viewBoxY.value}`; - outPolygonPoints.value = `${viewBoxX.value - (stats.value.length - 1)},${ - viewBoxY.value - } ${outPolylinePoints.value} ${viewBoxX.value},${viewBoxY.value}`; - - inHeadX.value = inPolylinePointsStats[inPolylinePointsStats.length - 1][0]; - inHeadY.value = inPolylinePointsStats[inPolylinePointsStats.length - 1][1]; - outHeadX.value = outPolylinePointsStats[outPolylinePointsStats.length - 1][0]; - outHeadY.value = outPolylinePointsStats[outPolylinePointsStats.length - 1][1]; - - inRecent.value = connStats.net.rx; - outRecent.value = connStats.net.tx; -} - -function onStatsLog(statsLog) { - for (const revStats of [...statsLog].reverse()) { - onStats(revStats); - } -} -</script> - -<style lang="scss" scoped> -.oxxrhrto { - display: flex; - - > svg { - display: block; - padding: 10px; - width: 50%; - - &:first-child { - padding-right: 5px; - } - - &:last-child { - padding-left: 5px; - } - - > text { - font-size: 5px; - fill: currentColor; - - > tspan { - opacity: 0.5; - } - } - } -} -</style> diff --git a/packages/client/src/widgets/server-metric/pie.vue b/packages/client/src/widgets/server-metric/pie.vue index 93880757b0..41addd4993 100644 --- a/packages/client/src/widgets/server-metric/pie.vue +++ b/packages/client/src/widgets/server-metric/pie.vue @@ -19,7 +19,7 @@ :stroke="color" /> <text x="50%" y="50%" dy="0.05" text-anchor="middle"> - {{ (value * 100).toFixed(0) }}% + {{ (value * 100).toFixed(1) }}% </text> </svg> </template> diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2936ba7e94..13cdd3d702 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -237,9 +237,6 @@ importers: opencc-js: specifier: 1.0.5 version: 1.0.5 - os-utils: - specifier: 0.0.14 - version: 0.0.14 otpauth: specifier: 9.2.4 version: 9.2.4 @@ -309,9 +306,6 @@ importers: syslog-pro: specifier: 1.0.0 version: 1.0.0 - systeminformation: - specifier: 5.22.8 - version: 5.22.8 tar-stream: specifier: 3.1.7 version: 3.1.7 @@ -6210,9 +6204,6 @@ packages: resolution: {integrity: sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==} engines: {node: '>=4'} - os-utils@0.0.14: - resolution: {integrity: sha512-ajB8csaHLBvJOYsHJkp8YdO2FvlBbf/ZxaYQwXXRDyQ84UoE+uTuLXxqd0shekXMX6Qr/pt/DDyLMRAMsgfWzg==} - otpauth@9.2.4: resolution: {integrity: sha512-t0Nioq2Up2ZaT5AbpXZLTjrsNtLc/g/rVSaEThmKLErAuT9mrnAKJryiPOKc3rCH+3ycWBgKpRHYn+DHqfaPiQ==} @@ -7285,12 +7276,6 @@ packages: resolution: {integrity: sha512-7SNMJKtQBJlwBUp1jxFT7bXya71cnINXPCYJ2AVhlQE4MKL7o2QiPdAXbMdWRiLeykQ2rx+7TNrnoGzvzhO+eA==} engines: {node: '>=10.0.0'} - systeminformation@5.22.8: - resolution: {integrity: sha512-F1iWQ+PSfOzvLMGh2UXASaWLDq5o+1h1db13Kddl6ojcQ47rsJhpMtRrmBXfTA5QJgutC4KV67YRmXLuroIxrA==} - engines: {node: '>=8.0.0'} - os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] - hasBin: true - syuilo-password-strength@0.0.1: resolution: {integrity: sha512-g9rPT3V1Q4WjWFZ/t5BdGC1mT/FpYnsLdBl+M5e6MlRkuE1RSR+R43wcY/3mKI59B9KEr+vxdWCuWNMD3oNHKA==} @@ -14397,8 +14382,6 @@ snapshots: dependencies: arch: 2.2.0 - os-utils@0.0.14: {} - otpauth@9.2.4: dependencies: jssha: 3.3.1 @@ -15537,8 +15520,6 @@ snapshots: dependencies: moment: 2.30.1 - systeminformation@5.22.8: {} - syuilo-password-strength@0.0.1: {} tabbable@6.2.0: {}