@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
147 lines (126 loc) • 5.04 kB
text/typescript
// We want to keep `system` export to be used as namespace
import os from "node:os";
import path from "node:path";
// We want to keep `system` export as it's more readable and easier to understand
import system from "systeminformation";
import {Logger} from "@lodestar/utils";
type MiscOs = "lin" | "win" | "mac" | "unk";
/**
* Singleton class to collect and provide system information
*/
class System {
// static data only needs to be collected once
private staticDataCollected = false;
// disk I/O is not measurable in some environments
private diskIOMeasurable = true;
cpuCores = 0;
cpuThreads = 0;
cpuNodeSystemSecondsTotal = 0;
cpuNodeUserSecondsTotal = 0;
// Note: CPU I/O wait is not measured by os.cpus()
cpuNodeIOWaitSecondsTotal = 0;
cpuNodeIdleSecondsTotal = 0;
memoryNodeBytesTotal = 0;
memoryNodeBytesFree = 0;
memoryNodeBytesCached = 0;
memoryNodeBytesBuffers = 0;
diskNodeBytesTotal = 0;
diskNodeBytesFree = 0;
// Note: disk I/O seconds is currently unused by beaconcha.in
diskNodeIOSeconds = 0;
diskNodeReadsTotal = 0;
diskNodeWritesTotal = 0;
networkNodeBytesTotalReceive = 0;
networkNodeBytesTotalTransmit = 0;
miscNodeBootTsSeconds = 0;
miscOs: MiscOs = "unk";
/**
* Collect system data and update cached values
*/
async collectData(logger: Logger): Promise<void> {
const debug = (dataType: string, e: Error): void => logger.debug(`Failed to collect ${dataType} data`, {}, e);
await Promise.all([
this.collectStaticData().catch((e) => debug("static system", e)),
this.collectCpuData().catch((e) => debug("CPU", e)),
this.collectMemoryData().catch((e) => debug("memory", e)),
this.collectDiskData().catch((e) => debug("disk", e)),
this.collectNetworkData().catch((e) => debug("network", e)),
]);
this.miscNodeBootTsSeconds = this.getSystemBootTime();
}
private async collectStaticData(): Promise<void> {
if (this.staticDataCollected) return;
const cpu = await system.cpu();
// Note: inside container this might be inaccurate as
// physicalCores in some cases is the count of logical CPU cores
this.cpuCores = cpu.physicalCores;
this.cpuThreads = cpu.cores;
this.miscOs = this.getNormalizedOsVersion();
this.staticDataCollected = true;
}
private async collectCpuData(): Promise<void> {
const cpuTimes: Record<string, number> = {};
for (const cpu of os.cpus()) {
// sum up CPU times per mode and convert to seconds
for (const [mode, time] of Object.entries(cpu.times)) {
if (cpuTimes[mode] == null) cpuTimes[mode] = 0;
cpuTimes[mode] += Math.floor(time / 1000);
}
}
// Note: currently beaconcha.in expects system CPU seconds to be everything
this.cpuNodeSystemSecondsTotal = Object.values(cpuTimes).reduce((total, time) => total + time, 0);
this.cpuNodeUserSecondsTotal = cpuTimes.user;
this.cpuNodeIdleSecondsTotal = cpuTimes.idle;
}
private async collectMemoryData(): Promise<void> {
const memory = await system.mem();
this.memoryNodeBytesTotal = memory.total;
this.memoryNodeBytesFree = memory.free;
this.memoryNodeBytesCached = memory.cached;
this.memoryNodeBytesBuffers = memory.buffers;
}
private async collectDiskData(): Promise<void> {
const fileSystems = await system.fsSize();
// get file system root, on windows this is the name of the hard disk partition
const rootFs = process.platform === "win32" ? process.cwd().split(path.sep)[0] : "/";
// only consider root file system, if it does not exist use first entry in the list
const fileSystem = fileSystems.find((fs) => fs.mount === rootFs) ?? fileSystems[0];
this.diskNodeBytesTotal = fileSystem.size;
this.diskNodeBytesFree = fileSystem.available;
if (this.diskIOMeasurable) {
const disk = await system.disksIO();
if (disk != null && disk.rIO !== 0) {
// Note: rIO and wIO might not be available inside container
// see https://github.com/sebhildebrandt/systeminformation/issues/777
this.diskNodeReadsTotal = disk.rIO;
this.diskNodeWritesTotal = disk.wIO;
} else {
this.diskIOMeasurable = false;
}
}
}
private async collectNetworkData(): Promise<void> {
// defaults to first external network interface
const [network] = await system.networkStats();
// Note: rx_bytes and tx_bytes will be inaccurate if process
// runs inside container as it only captures local network traffic
this.networkNodeBytesTotalReceive = network.rx_bytes;
this.networkNodeBytesTotalTransmit = network.tx_bytes;
}
private getNormalizedOsVersion(): MiscOs {
switch (process.platform) {
case "linux":
return "lin";
case "darwin":
return "mac";
case "win32":
return "win";
default:
return "unk";
}
}
private getSystemBootTime(): number {
return Math.floor(Date.now() / 1000 - os.uptime());
}
}
export default new System();