lup-system
Version:
NodeJS library to retrieve system information and utilization.
224 lines (223 loc) • 11.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NET_COMPUTE_UTILIZATION_INTERVAL = void 0;
exports.stopNetworkUtilizationComputation = stopNetworkUtilizationComputation;
exports.getNetworkInterfaces = getNetworkInterfaces;
const utils_1 = require("./utils");
const promises_1 = __importDefault(require("fs/promises"));
const os_1 = __importDefault(require("os"));
/** Intervall in milliseconds at which network interface utilization is computed. */
exports.NET_COMPUTE_UTILIZATION_INTERVAL = 1000;
let NET_LAST_COMPUTE = 0;
const NET_LAST_STATS = {};
const NET_BYTES_PER_SECOND = {}; // bytes/s
let NET_COMPUTE_RUNNING = false;
let NET_COMPUTE_TIMEOUT = null;
async function computeNetworkUtilization() {
switch (process.platform) {
case 'linux': {
await Promise.allSettled([
...Object.keys(NET_LAST_STATS).map(async (nic) => promises_1.default.readFile('/sys/class/net/' + nic + '/statistics/rx_bytes', 'utf8').then((data) => {
const receivedBytes = parseInt(data.trim(), 10) || 0;
const prevReceived = NET_LAST_STATS[nic]?.receivedBytes;
if (prevReceived !== undefined) {
NET_BYTES_PER_SECOND[nic].receive =
(receivedBytes - prevReceived) / ((Date.now() - NET_LAST_COMPUTE) / 1000);
}
NET_LAST_STATS[nic].receivedBytes = receivedBytes;
})),
...Object.keys(NET_LAST_STATS).map(async (nic) => promises_1.default.readFile('/sys/class/net/' + nic + '/statistics/tx_bytes', 'utf8').then((data) => {
const sentBytes = parseInt(data.trim(), 10) || 0;
const prevSent = NET_LAST_STATS[nic]?.transmittedBytes;
if (prevSent !== undefined) {
NET_BYTES_PER_SECOND[nic].transmit = (sentBytes - prevSent) / ((Date.now() - NET_LAST_COMPUTE) / 1000);
}
NET_LAST_STATS[nic].transmittedBytes = sentBytes;
})),
]);
break;
}
case 'win32': {
const output = await (0, utils_1.execCommand)('powershell -Command "Get-NetAdapterStatistics | Select-Object Name, ReceivedBytes, SentBytes | Format-List"').catch(() => '');
const lines = output.split('\n');
const durationSec = (Date.now() - NET_LAST_COMPUTE) / 1000;
let currNic = null;
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < lines.length; i++) {
const [key, value] = lines[i].split(' : ').map((part) => part.trim());
if (key === 'Name') {
currNic = value;
}
if (!currNic)
continue;
if (!NET_LAST_STATS[currNic])
NET_LAST_STATS[currNic] = { receivedBytes: 0, transmittedBytes: 0 };
if (!NET_BYTES_PER_SECOND[currNic])
NET_BYTES_PER_SECOND[currNic] = { receive: 0, transmit: 0 };
if (key === 'ReceivedBytes') {
const receivedBytes = parseInt(value, 10) || 0;
const prevReceived = NET_LAST_STATS[currNic]?.receivedBytes;
if (prevReceived !== undefined) {
NET_BYTES_PER_SECOND[currNic].receive = (receivedBytes - prevReceived) / durationSec;
}
NET_LAST_STATS[currNic].receivedBytes = receivedBytes;
}
else if (key === 'SentBytes') {
const sentBytes = parseInt(value, 10) || 0;
const prevSent = NET_LAST_STATS[currNic]?.transmittedBytes;
if (prevSent !== undefined) {
NET_BYTES_PER_SECOND[currNic].transmit = (sentBytes - prevSent) / durationSec;
}
NET_LAST_STATS[currNic].transmittedBytes = sentBytes;
}
}
break;
}
}
NET_LAST_COMPUTE = Date.now();
}
async function runNetComputeInterval() {
NET_COMPUTE_RUNNING = true;
await computeNetworkUtilization();
if (NET_COMPUTE_RUNNING)
NET_COMPUTE_TIMEOUT = setTimeout(runNetComputeInterval, Math.max(exports.NET_COMPUTE_UTILIZATION_INTERVAL, 1));
}
/**
* Stops the computation of network utilization.
* As soon as the getNetworkInterfaces function is called again, the computation will be restarted.
*/
function stopNetworkUtilizationComputation() {
if (NET_COMPUTE_TIMEOUT)
clearTimeout(NET_COMPUTE_TIMEOUT);
NET_COMPUTE_TIMEOUT = null;
NET_COMPUTE_RUNNING = false;
}
/**
* Returns information about the network interfaces on the system.
*
* @returns List of NICInfo objects.
*/
async function getNetworkInterfaces() {
if (!NET_COMPUTE_RUNNING) {
await runNetComputeInterval(); // runs the first computation immediately
await computeNetworkUtilization(); // run second computation immediately to get initial values
}
const nics = {};
for (const [name, addresses] of Object.entries(os_1.default.networkInterfaces())) {
const nic = {
name,
addresses: addresses?.map((addr) => ({
type: addr.family.toLowerCase(),
mac: addr.mac,
ip: addr.address,
netmask: addr.netmask,
cidr: addr.cidr,
internal: addr.internal,
})) || [],
status: {
operational: 'unknown',
admin: true, // Default to true, will be updated based on platform
cable: false, // Default to false, will be updated based on platform
},
physical: !name.startsWith('v') && !name.startsWith('lo'), // Assume non-loopback and non-virtual interfaces are physical
};
nics[name] = nic;
}
switch (process.platform) {
case 'linux': {
await Promise.allSettled([
...Object.keys(nics).map(async (name) => promises_1.default.readFile('/sys/class/net/' + name + '/carrier', 'utf8').then((data) => {
const present = data.trim() === '1';
nics[name].status.cable = present;
if (present)
nics[name].physical = true; // If a cable is present, it is a physical interface
})),
...Object.keys(nics).map(async (name) => promises_1.default.readFile('/sys/class/net/' + name + '/operstate', 'utf8').then((data) => {
nics[name].status.operational = data.trim().toLowerCase();
})),
...Object.keys(nics).map(async (name) => promises_1.default.readFile('/sys/class/net/' + name + '/proto_down', 'utf8').then((data) => {
nics[name].status.admin = data.trim() !== '1'; // If proto_down is 1, the interface is administratively down
})),
...Object.keys(nics).map(async (name) => promises_1.default.readFile('/sys/class/net/' + name + '/speed', 'utf8').then((data) => {
const speed = parseInt(data.trim(), 10);
if (!isNaN(speed) && speed > 0) {
const bits = speed * 1000000; // Convert from Mbps to bps (bits/s)
const bytes = Math.floor(bits / 8); // Convert from bps to Bps (bytes/s)
nics[name].speed = {
bits,
bytes, // Convert from bps to Bps (bytes/s)
};
}
})),
]);
break;
}
case 'win32': {
const additionallyFound = []; // os.networkInterfaces() does not return all interfaces if they have the same MAC address
// fetch status
await (0, utils_1.execCommand)('powershell -Command "Get-NetAdapter | Format-List"')
.then((output) => {
const lines = output.split('\n');
let currNic = null;
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < lines.length; i++) {
const [key, value] = lines[i].split(' : ').map((part) => part.trim());
if (key === 'Name') {
currNic = value;
}
if (!currNic)
continue;
if (!nics[currNic]) {
// if the NIC is not in the list, add it
nics[currNic] = {
name: currNic,
addresses: [],
status: {
operational: 'unknown',
admin: true, // Default to true, will be updated based on platform
cable: false, // Default to false, will be updated based on platform
},
physical: !currNic.startsWith('v') && !currNic.startsWith('lo'), // Assume non-loopback and non-virtual interfaces are physical
};
additionallyFound.push(currNic);
}
if (key.startsWith('InterfaceOperationalStat')) {
nics[currNic].status.operational = value.toLowerCase();
}
else if (key.startsWith('Admin')) {
nics[currNic].status.admin = value.toLowerCase() === 'up';
}
else if (key.startsWith('LinkSpeed')) {
const speed = parseFloat(value.replace(/[^0-9.]/g, ''));
const bits = Math.floor(speed * 1000000000); // Convert from Gbps to bps (bits/s)
const bytes = Math.floor(bits / 8); // Convert from bps to Bps (bytes/s)
nics[currNic].speed = {
bits,
bytes, // Convert from bps to Bps (bytes/s)
};
// compute utilization
const utilization = NET_BYTES_PER_SECOND[currNic];
if (utilization) {
nics[currNic].utilization = {
receive: utilization.receive / bytes,
transmit: utilization.transmit / bytes,
};
}
}
else if (key.startsWith('ConnectorPresent')) {
const present = value.toLowerCase() === 'true';
nics[currNic].status.cable = present;
if (present)
nics[currNic].physical = true; // If a cable is present, it is a physical interface
}
}
})
.catch(() => '');
break;
}
}
return Object.values(nics);
}