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); - }); - }); -});