From 4e36fa1b4eb65df0ae94673d8217c94e99d8c1fc Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Tue, 16 Jan 2024 17:38:36 +0100 Subject: [PATCH 1/4] Rename SystemMetrics to SystemInfo There are more pieces of information about a system than just its CPU, Memory and disk metrics. To avoid having to build a separate collection and subscription for metrics and system info, rename the existing system metrics to system info in anticipation of providing additonal static system information such as CPU model in it. --- packages/controller/src/Controller.ts | 52 +++++++++---------- packages/host/src/Host.ts | 6 +-- packages/lib/src/data/messages_controller.ts | 20 +++---- packages/lib/src/link/messages.ts | 4 +- packages/lib/src/permissions.ts | 8 +-- packages/lib/src/system_collectors.ts | 6 +-- .../web_ui/src/components/ControllerPage.tsx | 18 +++---- .../web_ui/src/components/HostViewPage.tsx | 20 +++---- packages/web_ui/src/components/HostsPage.tsx | 28 +++++----- .../web_ui/src/components/system_metrics.tsx | 42 +++++++-------- .../model/{system_metrics.ts => system.ts} | 6 +-- packages/web_ui/src/util/websocket.ts | 2 +- test/lib/data/SystemInfo.js | 22 ++++++++ test/lib/data/SystemMetrics.js | 22 -------- 14 files changed, 128 insertions(+), 128 deletions(-) rename packages/web_ui/src/model/{system_metrics.ts => system.ts} (58%) create mode 100644 test/lib/data/SystemInfo.js delete mode 100644 test/lib/data/SystemMetrics.js diff --git a/packages/controller/src/Controller.ts b/packages/controller/src/Controller.ts index 1c299927..d8686527 100644 --- a/packages/controller/src/Controller.ts +++ b/packages/controller/src/Controller.ts @@ -53,8 +53,8 @@ export default class Controller { config: lib.ControllerConfig; app: Application; - /** True if (@link Controller.systemMetrics} has changed since last time it was saved */ - systemMetricsDirty = false; + /** True if (@link Controller.systems} has changed since last time it was saved */ + systemsDirty = false; /** True if {@link Controller.hosts} has changed since last time it was saved */ hostsDirty = false; /** True if {@link Controller.instances} has changed since last time it was saved */ @@ -102,7 +102,7 @@ export default class Controller { let databaseDirectory = config.get("controller.database_directory"); await fs.ensureDir(databaseDirectory); - const systemMetrics = await Controller.loadSystemMetrics(path.join(databaseDirectory, "metrics.json")); + const systems = await Controller.loadSystems(path.join(databaseDirectory, "systems.json")); const hosts = await Controller.loadHosts(path.join(databaseDirectory, "hosts.json")); const instances = await Controller.loadInstances(path.join(databaseDirectory, "instances.json")); const saves = await Controller.loadSaves(path.join(databaseDirectory, "saves.json")); @@ -115,7 +115,7 @@ export default class Controller { const modStore = await lib.ModStore.fromDirectory(modsDirectory); return [ - systemMetrics, + systems, hosts, instances, saves, @@ -131,7 +131,7 @@ export default class Controller { configPath: string, config: lib.ControllerConfig, - public systemMetrics = new Map(), + public systems = new Map(), /** Mapping of host id to host info */ public hosts = new Map(), /** Mapping of instance id to instance info */ @@ -162,7 +162,7 @@ export default class Controller { }); // Handle subscriptions for all internal properties - this.subscriptions.handle(lib.SystemMetricsUpdateEvent, this.handleSystemMetricsSubscription.bind(this)); + this.subscriptions.handle(lib.SystemInfoUpdateEvent, this.handleSystemInfoSubscription.bind(this)); this.subscriptions.handle(lib.HostUpdatesEvent, this.handleHostSubscription.bind(this)); this.subscriptions.handle(lib.InstanceDetailsUpdatesEvent, this.handleInstanceDetailsSubscription.bind(this)); this.subscriptions.handle( @@ -444,30 +444,30 @@ export default class Controller { const systemMetricsIntervalSeconds = this.config.get("controller.system_metrics_interval"); if (systemMetricsIntervalSeconds > 0) { this.systemMetricsInterval = setInterval( - this.updateSystemMetrics.bind(this), + this.updateSystems.bind(this), systemMetricsIntervalSeconds * 1000 ); } } - async updateSystemMetrics() { + async updateSystems() { try { - const requests: Promise[] = []; + const requests: Promise[] = []; for (let hostConnection of this.wsServer.hostConnections.values()) { if (!hostConnection.connected) { continue; } - requests.push(hostConnection.send(new lib.SystemMetricsRequest())); + requests.push(hostConnection.send(new lib.SystemInfoRequest())); } - requests.push(lib.gatherSystemMetrics("controller")); + requests.push(lib.gatherSystemInfo("controller")); const newMetrics = await Promise.all(requests); for (const metric of newMetrics) { - this.systemMetrics.set(metric.id, metric); + this.systems.set(metric.id, metric); } - this.systemMetricsDirty = true; - this.subscriptions.broadcast(new lib.SystemMetricsUpdateEvent(newMetrics)); + this.systemsDirty = true; + this.subscriptions.broadcast(new lib.SystemInfoUpdateEvent(newMetrics)); } catch (err: any) { - logger.error(`Unexpected error updating system metrics:\n${err.stack ?? err.message}`); + logger.error(`Unexpected error updating system infos:\n${err.stack ?? err.message}`); } } @@ -507,9 +507,9 @@ export default class Controller { } let databaseDirectory = this.config.get("controller.database_directory"); - if (this.systemMetricsDirty) { - this.systemMetricsDirty = false; - await Controller.saveSystemMetrics(path.join(databaseDirectory, "metrics.json"), this.systemMetrics); + if (this.systemsDirty) { + this.systemsDirty = false; + await Controller.saveSystems(path.join(databaseDirectory, "systems.json"), this.systems); } if (this.hostsDirty) { @@ -539,8 +539,8 @@ export default class Controller { await lib.invokeHook(this.plugins, "onSaveData"); } - static async loadSystemMetrics(filePath: string): Promise> { - let json: Static[]; + static async loadSystems(filePath: string): Promise> { + let json: Static[]; try { json = JSON.parse(await fs.readFile(filePath, { encoding: "utf8" })); @@ -551,11 +551,11 @@ export default class Controller { return new Map(); } - return new Map(json.map(m => lib.SystemMetrics.fromJSON(m)).map(m => [m.id, m])); + return new Map(json.map(m => lib.SystemInfo.fromJSON(m)).map(m => [m.id, m])); } - static async saveSystemMetrics(filePath: string, systemMetrics: Map) { - await lib.safeOutputFile(filePath, JSON.stringify([...systemMetrics.values()], null, "\t")); + static async saveSystems(filePath: string, systems: Map) { + await lib.safeOutputFile(filePath, JSON.stringify([...systems.values()], null, "\t")); } static async loadHosts(filePath: string): Promise> { @@ -741,11 +741,11 @@ export default class Controller { } } - async handleSystemMetricsSubscription(request: lib.SubscriptionRequest) { - const systemMetrics = [...this.systemMetrics.values()].filter( + async handleSystemInfoSubscription(request: lib.SubscriptionRequest) { + const systems = [...this.systems.values()].filter( metric => metric.updatedAt > request.lastRequestTime, ); - return systemMetrics.length ? new lib.SystemMetricsUpdateEvent(systemMetrics) : null; + return systems.length ? new lib.SystemInfoUpdateEvent(systems) : null; } /** diff --git a/packages/host/src/Host.ts b/packages/host/src/Host.ts index a274f6a6..96b3161b 100644 --- a/packages/host/src/Host.ts +++ b/packages/host/src/Host.ts @@ -354,7 +354,7 @@ export default class Host extends lib.Link { this.snoopEvent(lib.InstanceWhitelistUpdateEvent, this.handleWhitelistUpdateEvent.bind(this)); this.handle(lib.InstanceAssignInternalRequest, this.handleInstanceAssignInternalRequest.bind(this)); this.handle(lib.InstanceUnassignInternalRequest, this.handleInstanceUnassignInternalRequest.bind(this)); - this.handle(lib.SystemMetricsRequest, this.handleSystemMetricsRequest.bind(this)); + this.handle(lib.SystemInfoRequest, this.handleSystemInfoRequest.bind(this)); this.handle(lib.HostMetricsRequest, this.handleHostMetricsRequest.bind(this)); this.fallbackRequest( lib.InstanceSaveDetailsListRequest, this.fallbackInstanceSaveDetailsListRequest.bind(this), @@ -750,8 +750,8 @@ export default class Host extends lib.Link { return instanceConnection; } - async handleSystemMetricsRequest() { - return lib.gatherSystemMetrics(this.config.get("host.id")); + async handleSystemInfoRequest() { + return lib.gatherSystemInfo(this.config.get("host.id")); } async handleHostMetricsRequest() { diff --git a/packages/lib/src/data/messages_controller.ts b/packages/lib/src/data/messages_controller.ts index 6f547164..77393fe0 100644 --- a/packages/lib/src/data/messages_controller.ts +++ b/packages/lib/src/data/messages_controller.ts @@ -204,7 +204,7 @@ export class LogMessageEvent { } } -export class SystemMetrics { +export class SystemInfo { constructor( /** * Id of the host these metrics originate from, or the string @@ -278,31 +278,31 @@ export class SystemMetrics { } } -export class SystemMetricsRequest { - declare ["constructor"]: typeof SystemMetricsRequest; +export class SystemInfoRequest { + declare ["constructor"]: typeof SystemInfoRequest; static type = "request" as const; static src = "controller" as const; static dst = "host" as const; - static Response = SystemMetrics; + static Response = SystemInfo; } -export class SystemMetricsUpdateEvent { - declare ["constructor"]: typeof SystemMetricsUpdateEvent; +export class SystemInfoUpdateEvent { + declare ["constructor"]: typeof SystemInfoUpdateEvent; static type = "event" as const; static src = "controller" as const; static dst = "control" as const; - static permission = "core.system_metrics.subscribe" as const; + static permission = "core.system.subscribe" as const; constructor( - public updates: SystemMetrics[], + public updates: SystemInfo[], ) { } static jsonSchema = Type.Object({ - "updates": Type.Array(SystemMetrics.jsonSchema), + "updates": Type.Array(SystemInfo.jsonSchema), }); static fromJSON(json: Static) { - return new this(json.updates.map(update => SystemMetrics.fromJSON(update))); + return new this(json.updates.map(update => SystemInfo.fromJSON(update))); } } diff --git a/packages/lib/src/link/messages.ts b/packages/lib/src/link/messages.ts index 560a5da6..5431eb0c 100644 --- a/packages/lib/src/link/messages.ts +++ b/packages/lib/src/link/messages.ts @@ -25,8 +25,8 @@ export const dataClasses: (RequestClass | EventClass) controller.LogSetSubscriptionsRequest, controller.LogQueryRequest, controller.LogMessageEvent, - controller.SystemMetricsRequest, - controller.SystemMetricsUpdateEvent, + controller.SystemInfoRequest, + controller.SystemInfoUpdateEvent, controller.DebugDumpWsRequest, controller.DebugWsMessageEvent, diff --git a/packages/lib/src/permissions.ts b/packages/lib/src/permissions.ts index 72c31005..e2b577b9 100644 --- a/packages/lib/src/permissions.ts +++ b/packages/lib/src/permissions.ts @@ -130,11 +130,11 @@ definePermission({ description: "Modify the controller config or entries of the controller config.", }); definePermission({ - name: "core.system_metrics.subscribe", - title: "Subscribe to system metrics updates", + name: "core.system.subscribe", + title: "Subscribe to system updates", description: - "Subscribe to be notified when the system metrics detailing cpu, memory and disk usage for the " + - "controller and hosts are updated.", + "Subscribe to be notified when the system info detailing system specific information along with metrics " + + "such as cpu, memory and disk usage for the controller and hosts are updated.", }); definePermission({ name: "core.host.get_config", diff --git a/packages/lib/src/system_collectors.ts b/packages/lib/src/system_collectors.ts index f957f16b..be0c64f7 100644 --- a/packages/lib/src/system_collectors.ts +++ b/packages/lib/src/system_collectors.ts @@ -2,7 +2,7 @@ import fs from "fs-extra"; import os from "os"; import util from "util"; import { Gauge } from "./prometheus"; -import { SystemMetrics } from "./data"; +import { SystemInfo } from "./data"; function cpuModel() { const model = os.cpus()[0].model; @@ -133,7 +133,7 @@ function minZip(a: T[], b: T[]): [T, T][] { let previousTotalCpuMs: number[] = []; let previousIdleCpuMs: number[] = []; -export async function gatherSystemMetrics(id: SystemMetrics["id"]) { +export async function gatherSystemInfo(id: SystemInfo["id"]) { const cpus = os.cpus(); const currentTotalCpuMs = cpus.map(({ times }) => times.user + times.idle + times.irq + times.nice + times.sys); const currentIdleCpuMs = cpus.map(({ times }) => times.idle); @@ -151,7 +151,7 @@ export async function gatherSystemMetrics(id: SystemMetrics["id"]) { diskAvailable = stats.bavail * stats.bsize; } - return new SystemMetrics( + return new SystemInfo( id, cpuUsage, os.totalmem(), diff --git a/packages/web_ui/src/components/ControllerPage.tsx b/packages/web_ui/src/components/ControllerPage.tsx index e4c61ae9..5cea6eda 100644 --- a/packages/web_ui/src/components/ControllerPage.tsx +++ b/packages/web_ui/src/components/ControllerPage.tsx @@ -8,7 +8,7 @@ import { MetricDiskUsed, MetricDiskRatio, } from "./system_metrics"; import { useAccount } from "../model/account"; -import { useSystemMetrics } from "../model/system_metrics"; +import { useSystems } from "../model/system"; import ControllerConfigTree from "./ControllerConfigTree"; import PageLayout from "./PageLayout"; @@ -17,18 +17,18 @@ const { Title } = Typography; export default function ControllerPage() { let account = useAccount(); - const [systemMetrics] = useSystemMetrics(); - const metrics = systemMetrics.get("controller"); + const [systems] = useSystems(); + const system = systems.get("controller"); return

Controller

- - - - - - + + + + + + {account.hasPermission("core.log.follow") && <> Console diff --git a/packages/web_ui/src/components/HostViewPage.tsx b/packages/web_ui/src/components/HostViewPage.tsx index c5cfb0d3..bac762f0 100644 --- a/packages/web_ui/src/components/HostViewPage.tsx +++ b/packages/web_ui/src/components/HostViewPage.tsx @@ -19,7 +19,7 @@ import { MetricDiskUsed, MetricDiskRatio, } from "./system_metrics"; import { formatTimestamp } from "../util/time_format"; -import { useSystemMetrics } from "../model/system_metrics"; +import { useSystems } from "../model/system"; const { Title } = Typography; @@ -29,8 +29,8 @@ export default function HostViewPage() { let control = useContext(ControlContext); let account = useAccount(); let [instances] = useInstances(); - const [systemMetrics] = useSystemMetrics(); - const metrics = systemMetrics.get(hostId); + const [systems] = useSystems(); + const system = systems.get(hostId); const [host, synced] = useHost(hostId); const hostInstances = new Map([...instances].filter(([_id, instance]) => instance.assignedHost === hostId)); @@ -78,13 +78,13 @@ export default function HostViewPage() { {host["connected"] ? "Connected" : "Disconnected"} - - - - - - - {host["version"]} + + + + + + + {host.publicAddress} { host.tokenValidAfter ? diff --git a/packages/web_ui/src/components/HostsPage.tsx b/packages/web_ui/src/components/HostsPage.tsx index 510f6e2b..58cddfaf 100644 --- a/packages/web_ui/src/components/HostsPage.tsx +++ b/packages/web_ui/src/components/HostsPage.tsx @@ -15,7 +15,7 @@ import { MetricDiskUsed, MetricDiskRatio, } from "./system_metrics"; import { useHosts } from "../model/host"; -import { useSystemMetrics } from "../model/system_metrics"; +import { useSystems } from "../model/system"; import notify, { notifyErrorHandler } from "../util/notify"; const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare; @@ -189,7 +189,7 @@ export default function HostsPage() { let account = useAccount(); let navigate = useNavigate(); let [hosts] = useHosts(); - const [metrics] = useSystemMetrics(); + const [systems] = useSystems(); const [showRatios, setShowRatios] = useState(true); const [showNumbers, setShowNumbers] = useState(false); @@ -209,46 +209,46 @@ export default function HostsPage() { }, { title: "CPU%", - sorter: (a, b) => (metrics.get(a.id)?.cpuRatio ?? 0) - (metrics.get(b.id)?.cpuRatio ?? 0), - render: (_, host) => , + sorter: (a, b) => (systems.get(a.id)?.cpuRatio ?? 0) - (systems.get(b.id)?.cpuRatio ?? 0), + render: (_, host) => , hidden: !showRatios, }, { title: "Cores", sorter: (a, b) => ( - (metrics.get(a.id)?.cpuUsed ?? 0) - (metrics.get(b.id)?.cpuUsed ?? 0) + (systems.get(a.id)?.cpuUsed ?? 0) - (systems.get(b.id)?.cpuUsed ?? 0) ), - render: (_, host) => , + render: (_, host) => , hidden: !showNumbers, }, { title: "Mem%", sorter: (a, b) => ( - (metrics.get(a.id)?.memoryRatio ?? 0) - (metrics.get(b.id)?.memoryRatio ?? 0) + (systems.get(a.id)?.memoryRatio ?? 0) - (systems.get(b.id)?.memoryRatio ?? 0) ), - render: (_, host) => , + render: (_, host) => , hidden: !showRatios, }, { title: "Memory", sorter: (a, b) => ( - (metrics.get(a.id)?.memoryUsed ?? 0) - (metrics.get(b.id)?.memoryUsed ?? 0) + (systems.get(a.id)?.memoryUsed ?? 0) - (systems.get(b.id)?.memoryUsed ?? 0) ), - render: (_, host) => , + render: (_, host) => , hidden: !showNumbers, }, { title: "Disk%", - sorter: (a, b) => (metrics.get(a.id)?.diskAvailable ?? 0) - (metrics.get(b.id)?.diskAvailable ?? 0), - render: (_, host) => , + sorter: (a, b) => (systems.get(a.id)?.diskAvailable ?? 0) - (systems.get(b.id)?.diskAvailable ?? 0), + render: (_, host) => , hidden: !showRatios, }, { title: "Disk", sorter: (a, b) => ( - (metrics.get(a.id)?.diskUsed ?? 0) - (metrics.get(b.id)?.diskUsed ?? 0) + (systems.get(a.id)?.diskUsed ?? 0) - (systems.get(b.id)?.diskUsed ?? 0) ), - render: (_, host) => , + render: (_, host) => , hidden: !showNumbers, }, { diff --git a/packages/web_ui/src/components/system_metrics.tsx b/packages/web_ui/src/components/system_metrics.tsx index 15ca5e5b..4a6b3b03 100644 --- a/packages/web_ui/src/components/system_metrics.tsx +++ b/packages/web_ui/src/components/system_metrics.tsx @@ -3,53 +3,53 @@ import React from "react"; import * as lib from "@clusterio/lib"; import { Progress } from "antd"; -export function MetricCpuRatio(props: { metrics?: lib.SystemMetrics }) { - if (!props.metrics) { +export function MetricCpuRatio(props: { system?: lib.SystemInfo }) { + if (!props.system) { return "N/A"; } - const ratio = props.metrics.cpuRatio; + const ratio = props.system.cpuRatio; return ; } -export function MetricCpuUsed(props: { metrics?: lib.SystemMetrics }) { - if (!props.metrics) { +export function MetricCpuUsed(props: { system?: lib.SystemInfo }) { + if (!props.system) { return "N/A"; } - const used = props.metrics.cpuUsed.toLocaleString(undefined, { maximumFractionDigits: 1 }); - const capacity = props.metrics.cpuCapacity; + const used = props.system.cpuUsed.toLocaleString(undefined, { maximumFractionDigits: 1 }); + const capacity = props.system.cpuCapacity; return `${used} / ${capacity}`; } -export function MetricMemoryRatio(props: { metrics?: lib.SystemMetrics }) { - if (!props.metrics) { +export function MetricMemoryRatio(props: { system?: lib.SystemInfo }) { + if (!props.system) { return "N/A"; } - const ratio = props.metrics.memoryRatio; + const ratio = props.system.memoryRatio; return ; } -export function MetricMemoryUsed(props: { metrics?: lib.SystemMetrics }) { - if (!props.metrics) { +export function MetricMemoryUsed(props: { system?: lib.SystemInfo }) { + if (!props.system) { return "N/A"; } - const used = lib.formatBytes(props.metrics.memoryUsed, "binary"); - const capacity = lib.formatBytes(props.metrics.memoryCapacity, "binary"); + const used = lib.formatBytes(props.system.memoryUsed, "binary"); + const capacity = lib.formatBytes(props.system.memoryCapacity, "binary"); return `${used} / ${capacity}`; } -export function MetricDiskRatio(props: { metrics?: lib.SystemMetrics }) { - if (!props.metrics) { +export function MetricDiskRatio(props: { system?: lib.SystemInfo }) { + if (!props.system) { return "N/A"; } - const ratio = props.metrics.diskRatio; + const ratio = props.system.diskRatio; return ; } -export function MetricDiskUsed(props: { metrics?: lib.SystemMetrics }) { - if (!props.metrics) { +export function MetricDiskUsed(props: { system?: lib.SystemInfo }) { + if (!props.system) { return "N/A"; } - const used = lib.formatBytes(props.metrics.diskUsed); - const capacity = lib.formatBytes(props.metrics.diskCapacity); + const used = lib.formatBytes(props.system.diskUsed); + const capacity = lib.formatBytes(props.system.diskCapacity); return `${used} / ${capacity}`; } diff --git a/packages/web_ui/src/model/system_metrics.ts b/packages/web_ui/src/model/system.ts similarity index 58% rename from packages/web_ui/src/model/system_metrics.ts rename to packages/web_ui/src/model/system.ts index 4f1f0e5a..b88dcc9c 100644 --- a/packages/web_ui/src/model/system_metrics.ts +++ b/packages/web_ui/src/model/system.ts @@ -1,8 +1,8 @@ import { useCallback, useContext, useSyncExternalStore } from "react"; import ControlContext from "../components/ControlContext"; -export function useSystemMetrics() { +export function useSystems() { const control = useContext(ControlContext); - const subscribe = useCallback((callback: () => void) => control.systemMetrics.subscribe(callback), [control]); - return useSyncExternalStore(subscribe, () => control.systemMetrics.getSnapshot()); + const subscribe = useCallback((callback: () => void) => control.systems.subscribe(callback), [control]); + return useSyncExternalStore(subscribe, () => control.systems.getSnapshot()); } diff --git a/packages/web_ui/src/util/websocket.ts b/packages/web_ui/src/util/websocket.ts index f987ce15..6ab109b3 100644 --- a/packages/web_ui/src/util/websocket.ts +++ b/packages/web_ui/src/util/websocket.ts @@ -59,7 +59,7 @@ export class Control extends lib.Link { logHandlers: Map = new Map(); /** Updates handled by the subscription service */ - systemMetrics = new lib.EventSubscriber(lib.SystemMetricsUpdateEvent, this); + systems = new lib.EventSubscriber(lib.SystemInfoUpdateEvent, this); hosts = new lib.EventSubscriber(lib.HostUpdatesEvent, this); instances = new lib.EventSubscriber(lib.InstanceDetailsUpdatesEvent, this); saves = new lib.EventSubscriber(lib.InstanceSaveDetailsUpdatesEvent, this); diff --git a/test/lib/data/SystemInfo.js b/test/lib/data/SystemInfo.js new file mode 100644 index 00000000..030ddf24 --- /dev/null +++ b/test/lib/data/SystemInfo.js @@ -0,0 +1,22 @@ +"use strict"; +const assert = require("assert").strict; + +const lib = require("@clusterio/lib"); +const { SystemInfo } = lib; + + +describe("lib/data/ModPack", function() { + describe("class SystemInfo", function() { + it("correctly calculates derived metrics", function() { + const system = new SystemInfo("controller", [0.5, 0.25, 0, 1], 1024, 768, 2048, 512, 0, false); + assert.equal(system.cpuCapacity, 4); + assert.equal(system.cpuUsed, 1.75); + assert.equal(system.cpuAvailable, 2.25); + assert.equal(system.cpuRatio, 0.4375); + assert.equal(system.memoryUsed, 256); + assert.equal(system.memoryRatio, 0.25); + assert.equal(system.diskUsed, 1536); + assert.equal(system.diskRatio, 0.75); + }); + }); +}); diff --git a/test/lib/data/SystemMetrics.js b/test/lib/data/SystemMetrics.js deleted file mode 100644 index 163abcd2..00000000 --- a/test/lib/data/SystemMetrics.js +++ /dev/null @@ -1,22 +0,0 @@ -"use strict"; -const assert = require("assert").strict; - -const lib = require("@clusterio/lib"); -const { SystemMetrics } = lib; - - -describe("lib/data/ModPack", function() { - describe("class SystemMetrics", function() { - it("correctly calculates derived metrics", function() { - const metrics = new SystemMetrics("controller", [0.5, 0.25, 0, 1], 1024, 768, 2048, 512, 0, false); - assert.equal(metrics.cpuCapacity, 4); - assert.equal(metrics.cpuUsed, 1.75); - assert.equal(metrics.cpuAvailable, 2.25); - assert.equal(metrics.cpuRatio, 0.4375); - assert.equal(metrics.memoryUsed, 256); - assert.equal(metrics.memoryRatio, 0.25); - assert.equal(metrics.diskUsed, 1536); - assert.equal(metrics.diskRatio, 0.75); - }); - }); -}); From de4798952771b87c5b94e8d2dbc9256776919fc8 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Tue, 16 Jan 2024 18:12:30 +0100 Subject: [PATCH 2/4] Add system info to SystemInfo Provide the kernel, machine, CPU Model, hostname, and Node.js version fields to SystemInfo and display them in the Web UI. These were added as metrics exposed to prometheus in b46efeb. --- packages/lib/src/data/messages_controller.ts | 15 +++++++++++++++ packages/lib/src/system_collectors.ts | 10 ++++++++-- .../web_ui/src/components/ControllerPage.tsx | 7 +++++++ packages/web_ui/src/components/HostViewPage.tsx | 6 ++++++ packages/web_ui/src/components/HostsPage.tsx | 12 ++++++++++++ test/lib/data/SystemInfo.js | 16 +++++++++++++++- 6 files changed, 63 insertions(+), 3 deletions(-) diff --git a/packages/lib/src/data/messages_controller.ts b/packages/lib/src/data/messages_controller.ts index 77393fe0..2a755b0e 100644 --- a/packages/lib/src/data/messages_controller.ts +++ b/packages/lib/src/data/messages_controller.ts @@ -211,6 +211,11 @@ export class SystemInfo { * "controller" if these metrics are for the controller. */ public id: number | "controller", + public hostname: string, + public node: string, + public kernel: string, + public machine: string, + public cpuModel: string, public coreRatios: number[], public memoryCapacity: number, public memoryAvailable: number, @@ -223,6 +228,11 @@ export class SystemInfo { static jsonSchema = Type.Object({ "id": Type.Union([Type.Number(), Type.Literal("controller")]), + "hostname": Type.String(), + "node": Type.String(), + "kernel": Type.String(), + "machine": Type.String(), + "cpuModel": Type.String(), "coreRatios": Type.Array(Type.Number()), "memoryCapacity": Type.Number(), "memoryAvailable": Type.Number(), @@ -235,6 +245,11 @@ export class SystemInfo { static fromJSON(json: Static) { return new this( json.id, + json.hostname, + json.node, + json.kernel, + json.machine, + json.cpuModel, json.coreRatios, json.memoryCapacity, json.memoryAvailable, diff --git a/packages/lib/src/system_collectors.ts b/packages/lib/src/system_collectors.ts index be0c64f7..98d92397 100644 --- a/packages/lib/src/system_collectors.ts +++ b/packages/lib/src/system_collectors.ts @@ -4,7 +4,7 @@ import util from "util"; import { Gauge } from "./prometheus"; import { SystemInfo } from "./data"; -function cpuModel() { +function filterCpuModel() { const model = os.cpus()[0].model; // Remove irelevant clutter return model @@ -16,6 +16,7 @@ function cpuModel() { .trim() // AMD CPUs may have trailing spaces in the model. ; } +const cpuModel = filterCpuModel(); export const systemInfo = new Gauge( "system_info", @@ -27,7 +28,7 @@ export const systemInfo = new Gauge( systemInfo.labels({ "kernel": os.type(), "machine": os.machine(), - "cpu_model": cpuModel(), + "cpu_model": cpuModel, "hostname": os.hostname(), "node": process.version, }).set(1); @@ -153,6 +154,11 @@ export async function gatherSystemInfo(id: SystemInfo["id"]) { return new SystemInfo( id, + os.hostname(), + process.version, + os.type(), + os.machine(), + cpuModel, cpuUsage, os.totalmem(), os.freemem(), diff --git a/packages/web_ui/src/components/ControllerPage.tsx b/packages/web_ui/src/components/ControllerPage.tsx index 5cea6eda..7e49d233 100644 --- a/packages/web_ui/src/components/ControllerPage.tsx +++ b/packages/web_ui/src/components/ControllerPage.tsx @@ -11,6 +11,7 @@ import { useAccount } from "../model/account"; import { useSystems } from "../model/system"; import ControllerConfigTree from "./ControllerConfigTree"; import PageLayout from "./PageLayout"; +import webUiPackage from "../../package.json"; const { Title } = Typography; @@ -23,6 +24,12 @@ export default function ControllerPage() { return

Controller

+ {webUiPackage.version} + {system?.node} + {system?.kernel} + {system?.machine} + {system?.hostname} + {system?.cpuModel} diff --git a/packages/web_ui/src/components/HostViewPage.tsx b/packages/web_ui/src/components/HostViewPage.tsx index bac762f0..7963a619 100644 --- a/packages/web_ui/src/components/HostViewPage.tsx +++ b/packages/web_ui/src/components/HostViewPage.tsx @@ -78,6 +78,12 @@ export default function HostViewPage() { {host["connected"] ? "Connected" : "Disconnected"}
+ {host.version} + {system?.node} + {system?.kernel} + {system?.machine} + {system?.hostname} + {system?.cpuModel} diff --git a/packages/web_ui/src/components/HostsPage.tsx b/packages/web_ui/src/components/HostsPage.tsx index 58cddfaf..a1bcc0fb 100644 --- a/packages/web_ui/src/components/HostsPage.tsx +++ b/packages/web_ui/src/components/HostsPage.tsx @@ -192,6 +192,7 @@ export default function HostsPage() { const [systems] = useSystems(); const [showRatios, setShowRatios] = useState(true); const [showNumbers, setShowNumbers] = useState(false); + const [showCpuModel, setShowCpuModel] = useState(false); return strcmp(a.name, b.name), }, + { + title: "CPU Model", + dataIndex: "cpuModel", + sorter: (a, b) => strcmp(systems.get(a.id)?.cpuModel ?? "", systems.get(b.id)?.cpuModel ?? ""), + render: (_, host) => systems.get(host.id)?.cpuModel, + hidden: !showCpuModel, + }, { title: "CPU%", sorter: (a, b) => (systems.get(a.id)?.cpuRatio ?? 0) - (systems.get(b.id)?.cpuRatio ?? 0), @@ -290,6 +298,10 @@ export default function HostsPage() { Show metric numbers: { setShowNumbers(checked); }} /> + ; diff --git a/test/lib/data/SystemInfo.js b/test/lib/data/SystemInfo.js index 030ddf24..b84b9c34 100644 --- a/test/lib/data/SystemInfo.js +++ b/test/lib/data/SystemInfo.js @@ -8,7 +8,21 @@ const { SystemInfo } = lib; describe("lib/data/ModPack", function() { describe("class SystemInfo", function() { it("correctly calculates derived metrics", function() { - const system = new SystemInfo("controller", [0.5, 0.25, 0, 1], 1024, 768, 2048, 512, 0, false); + const system = new SystemInfo( + "controller", + "server", + "v18.0.0", + "Linux", + "x86_64", + "x86 CPU", + [0.5, 0.25, 0, 1], + 1024, + 768, + 2048, + 512, + 0, + false + ); assert.equal(system.cpuCapacity, 4); assert.equal(system.cpuUsed, 1.75); assert.equal(system.cpuAvailable, 2.25); From b5ed0800cf8b38b4359e446ce44c504e5f35e433 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Tue, 16 Jan 2024 18:24:30 +0100 Subject: [PATCH 3/4] Add address host connected from to the host info Provide an easy way to see where a host is connecting from in the web UI and clusterioctl list. --- packages/controller/src/HostConnection.ts | 2 ++ packages/controller/src/HostInfo.ts | 8 +++++++- packages/controller/src/WsServer.ts | 2 +- packages/lib/src/data/messages_host.ts | 7 +++++-- packages/web_ui/src/components/HostViewPage.tsx | 1 + 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/controller/src/HostConnection.ts b/packages/controller/src/HostConnection.ts index 7c2bdc4c..f4cf1d84 100644 --- a/packages/controller/src/HostConnection.ts +++ b/packages/controller/src/HostConnection.ts @@ -23,6 +23,7 @@ export default class HostConnection extends BaseConnection { registerData: lib.RegisterHostData, connector: WsServerConnector, controller: Controller, + remoteAddress: string, ) { super(connector, controller); @@ -35,6 +36,7 @@ export default class HostConnection extends BaseConnection { registerData.version, this.plugins, true, + remoteAddress, previousHostInfo?.publicAddress, previousHostInfo?.tokenValidAfter, 0, diff --git a/packages/controller/src/HostInfo.ts b/packages/controller/src/HostInfo.ts index 6b523c9c..7a7a105e 100644 --- a/packages/controller/src/HostInfo.ts +++ b/packages/controller/src/HostInfo.ts @@ -16,9 +16,11 @@ export default class HostInfo { public plugins: Map, /** True if this host is currently connected to controller */ public connected: boolean = false, + /** IP this host last connected from */ + public remoteAddress: string = "", /** Value of host.public_address configured for this host */ public publicAddress: string = "", - /** Unix timestamp in seconds host token must be issued afterto be valid */ + /** Unix timestamp in seconds host token must be issued after to be valid */ public tokenValidAfter: number = 0, /** Millisecond Unix timestamp this entry was last updated at */ public updatedAt: number = 0, @@ -32,6 +34,7 @@ export default class HostInfo { "version": Type.String(), "plugins": Type.Record(Type.String(), Type.String()), "connected": Type.Boolean(), + "remote_address": Type.String(), "public_address": Type.String(), "token_valid_after": Type.Number(), "updated_at": Type.Number(), @@ -45,6 +48,7 @@ export default class HostInfo { json.version, new Map(Object.entries(json.plugins)), json.connected, + json.remote_address, json.public_address, json.token_valid_after, json.updated_at, @@ -59,6 +63,7 @@ export default class HostInfo { version: this.version, plugins: Object.fromEntries(this.plugins), connected: this.connected, + remote_address: this.remoteAddress, public_address: this.publicAddress, token_valid_after: this.tokenValidAfter, updated_at: this.updatedAt, @@ -72,6 +77,7 @@ export default class HostInfo { this.name, this.id, this.connected, + this.remoteAddress, this.publicAddress, this.tokenValidAfter, this.updatedAt, diff --git a/packages/controller/src/WsServer.ts b/packages/controller/src/WsServer.ts index 4ebb257f..50c37d9c 100644 --- a/packages/controller/src/WsServer.ts +++ b/packages/controller/src/WsServer.ts @@ -284,7 +284,7 @@ ${err.stack}` ); } - connection = new HostConnection(data, connector, this.controller); + connection = new HostConnection(data, connector, this.controller, this.remoteAddr(req)); connector.on("close", () => { if (this.hostConnections.get(data.id) === connection) { this.hostConnections.delete(data.id); diff --git a/packages/lib/src/data/messages_host.ts b/packages/lib/src/data/messages_host.ts index 5f2cf88a..df8d719f 100644 --- a/packages/lib/src/data/messages_host.ts +++ b/packages/lib/src/data/messages_host.ts @@ -64,8 +64,9 @@ export class HostDetails { public name: string, public id: number, public connected: boolean, - public publicAddress?: string, - public tokenValidAfter?: number, + public remoteAddress = "", + public publicAddress: string = "", + public tokenValidAfter: number = 0, /** Millisecond Unix timestamp this entry was last updated at */ public updatedAt = 0, public isDeleted = false, @@ -76,6 +77,7 @@ export class HostDetails { "name": Type.String(), "id": Type.Integer(), "connected": Type.Boolean(), + "remoteAddress": Type.Optional(Type.String()), "publicAddress": Type.Optional(Type.String()), "tokenValidAfter": Type.Optional(Type.Number()), "updatedAt": Type.Optional(Type.Number()), @@ -88,6 +90,7 @@ export class HostDetails { json.name, json.id, json.connected, + json.remoteAddress, json.publicAddress, json.tokenValidAfter, json.updatedAt, diff --git a/packages/web_ui/src/components/HostViewPage.tsx b/packages/web_ui/src/components/HostViewPage.tsx index 7963a619..efe128f6 100644 --- a/packages/web_ui/src/components/HostViewPage.tsx +++ b/packages/web_ui/src/components/HostViewPage.tsx @@ -83,6 +83,7 @@ export default function HostViewPage() { {system?.kernel} {system?.machine} {system?.hostname} + {host.remoteAddress} {system?.cpuModel} From 4628e0ad597933fcff5017f02e4f0f48958db419 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Wed, 17 Jan 2024 06:46:18 +0100 Subject: [PATCH 4/4] Fix incorrect test description for SystemInfo Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> --- test/lib/data/SystemInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/data/SystemInfo.js b/test/lib/data/SystemInfo.js index b84b9c34..8053e6e9 100644 --- a/test/lib/data/SystemInfo.js +++ b/test/lib/data/SystemInfo.js @@ -5,7 +5,7 @@ const lib = require("@clusterio/lib"); const { SystemInfo } = lib; -describe("lib/data/ModPack", function() { +describe("lib/data/SystemInfo", function() { describe("class SystemInfo", function() { it("correctly calculates derived metrics", function() { const system = new SystemInfo(