UNPKG

node-system-stats

Version:

Comprehensive library for monitoring system statistics including CPU, memory, disk, network, battery, and process information with time-series monitoring

639 lines 26.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTopProcesses = exports.getBatteryInfo = exports.getCpuTemperature = exports.getLoadAverage = exports.getNetworkInterfaces = exports.formatBytes = exports.getDiskInfo = exports.showFreeMemory = exports.showTotalMemory = exports.showMemoryUsage = exports.avgClockMHz = exports.clockMHz = exports.title = exports.PID = exports.platform = exports.name = exports.version = exports.cpuModel = exports.totalCores = exports.usagePercent = void 0; const os_1 = __importDefault(require("os")); const child_process_1 = require("child_process"); const util_1 = require("util"); const utils_1 = require("./utils/utils"); var packageJsonFile = require("../package.json"); const execAsync = (0, util_1.promisify)(child_process_1.exec); /* PUBLIC */ /** * This function measures the CPU usage * @param {IOptsInput} optsInput The options input * @returns {Promise<ICallback>} returns a resolvable promise */ async function usagePercent(optsInput) { let opts = { coreIndex: optsInput?.coreIndex || -1, sampleMs: optsInput?.sampleMs || 1000, }; let cpus = os_1.default.cpus(); //check core exists if (opts.coreIndex < -1 || opts.coreIndex >= cpus.length || typeof opts.coreIndex !== "number" || Math.abs(opts.coreIndex % 1) !== 0) { _error(opts.coreIndex, cpus.length); } //all cpu's average if (opts.coreIndex === -1) { return (0, utils_1.measureCPUMulti)(opts); //return Promise.resolve(res).then((res) => [ res.percent, res.seconds ] ) //only one cpu core } else { return (0, utils_1.measureCPUSingle)(opts); } } exports.usagePercent = usagePercent; /** * @returns {number} The number of total cores in the system. */ exports.totalCores = os_1.default.cpus().length; /** * @returns {string} The name of the cpu in your system. */ exports.cpuModel = os_1.default.cpus()[0].model; /** * @returns {string} Returns the current version of the package */ exports.version = packageJsonFile.version; /** * @returns {string} Returns the name of the package */ exports.name = packageJsonFile.name; /** * @returns {string} Returns the processes platform */ exports.platform = process.platform; /** * @returns {number} The processes PID */ exports.PID = process.pid; /** * @returns {string} The processes title */ exports.title = process.title; /** * This function returns the speed of all cores or only just the selected core. * @param {number} coreIndex The index of the core. It begins with 0. If not specified, it will return an array with all of the cores * @returns {number | number[]} A number of the speed of the core OR a array with all of the cores speeds. */ function clockMHz(coreIndex) { let cpus = os_1.default.cpus(); if (!coreIndex) { return cpus.map((cpus) => cpus.speed); } //check core exists if (coreIndex < 0 || coreIndex >= cpus.length || Math.abs(coreIndex % 1) !== 0) { _error(coreIndex, cpus.length); } if (typeof coreIndex !== "number") { throw new Error("[node-system-stats] coreIndex must be a number."); } ; return cpus[coreIndex].speed; } exports.clockMHz = clockMHz; /** * This function shows the average Clock Frequency from all of the cores. * @returns {number} returns a number with the average Clock MHz over all cores */ function avgClockMHz() { let cpus = os_1.default.cpus(); let totalHz = 0; for (let i = 0; i < cpus.length; i++) { totalHz += cpus[i].speed; } let avgHz = totalHz / cpus.length; return avgHz; } exports.avgClockMHz = avgClockMHz; /** * Shows the formmated Memory Usage information * @returns {MemoryUsageReturn} An object with every converted memory usage type in redable form. */ function showMemoryUsage() { // Initializing variables const mUV = process.memoryUsage(); const dataKeys = [ "rss", "heapTotal", "heapUsed", "external", "arrayBuffers", ]; // Using reduce to "map" out the memory usage. return dataKeys.reduce((acc, cur) => { acc[cur] = Math.round((mUV[cur] / 1024 / 1024) * 100) / 100; return acc; }, {}); } exports.showMemoryUsage = showMemoryUsage; /** * This function is used to display the total memory that the system has. It can output in Gigabyte and Megabyte. * @param {boolean?} convertedGB If the returned value should be in Gigabytes or in MB. If set to true, then it will output the Gigabyte value. * @default {false} Megabyte format. * * @returns {number} The converted total Memory that is available. */ function showTotalMemory(convertedGB = false) { // In GB if (convertedGB) return Math.round(((os_1.default.totalmem() / 1024 / 1024) * 100) / 100) / 1000; // In MB return Math.round((os_1.default.totalmem() / 1024 / 1024) * 100) / 100; } exports.showTotalMemory = showTotalMemory; /** * This function is used to display the free memory that the system has. It can output in Gigabyte and Megabyte. * @param {boolean?} convertedGB If the returned value should be in Gigabytes or in MB. If set to true, then it will output the Gigabyte value. * @default {false} Megabyte format. * * @returns {number} The converted free Memory that is available. */ function showFreeMemory(convertedGB = false) { // In GB if (convertedGB) return Math.round(((os_1.default.freemem() / 1024 / 1024) * 100) / 100) / 1000; // In MB return Math.round((os_1.default.freemem() / 1024 / 1024) * 100) / 100; } exports.showFreeMemory = showFreeMemory; ; /** * Gets the disk usage information for the specified path or for all mounted file systems * @param {string} [pathToCheck=undefined] Specific directory path to check. If not provided, returns all file systems. * @returns {Promise<DiskDriveInfo[]>} Array of disk usage information objects */ async function getDiskInfo(pathToCheck) { try { const isWindows = process.platform === 'win32'; const result = []; if (isWindows) { // Windows implementation const { stdout } = await execAsync('wmic logicaldisk get DeviceID,FreeSpace,Size /format:csv'); const lines = stdout.trim().split('\r\n').filter(line => line.length > 0); // Skip the header line for (let i = 1; i < lines.length; i++) { const parts = lines[i].split(','); // Format: Node,DeviceID,FreeSpace,Size if (parts.length >= 4) { const deviceId = parts[1]; const freeSpace = Number(parts[2]); const size = Number(parts[3]); if (size > 0) { const used = size - freeSpace; const percentUsed = Math.round((used / size) * 10000) / 100; result.push({ filesystem: deviceId, size: size, used: used, available: freeSpace, percentUsed: percentUsed, mountpoint: deviceId }); } } } } else { // Unix-based OS implementation const { stdout } = await execAsync('df -k'); const lines = stdout.trim().split('\n'); // Skip the header line for (let i = 1; i < lines.length; i++) { const line = lines[i].trim(); const parts = line.split(/\s+/); if (parts.length >= 6) { const filesystem = parts[0]; const size = parseInt(parts[1], 10) * 1024; // Convert KB to bytes const used = parseInt(parts[2], 10) * 1024; const available = parseInt(parts[3], 10) * 1024; const percentUsed = parseInt(parts[4], 10); const mountpoint = parts[5]; // If pathToCheck is specified, only return info for that path if (pathToCheck && !pathToCheck.startsWith(mountpoint)) { continue; } result.push({ filesystem, size, used, available, percentUsed, mountpoint }); } } } // If path is specified and we didn't find a match yet, get the closest mount point if (pathToCheck && result.length === 0) { const allDrives = await getDiskInfo(); let bestMatch = null; let longestPrefix = 0; for (const drive of allDrives) { if (pathToCheck.startsWith(drive.mountpoint) && drive.mountpoint.length > longestPrefix) { bestMatch = drive; longestPrefix = drive.mountpoint.length; } } if (bestMatch) { return [bestMatch]; } } return result; } catch (error) { console.error('Error getting disk information:', error); return []; } } exports.getDiskInfo = getDiskInfo; /** * Format bytes to a human-readable string with appropriate units * @param {number} bytes The number of bytes * @param {number} [decimals=2] Number of decimal places to show * @returns {string} Human-readable file size string (e.g. '1.5 GB') */ function formatBytes(bytes, decimals = 2) { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } exports.formatBytes = formatBytes; /** * Gets detailed information about network interfaces * @returns {NetworkInterfaceInfo[]} Array of network interface information */ function getNetworkInterfaces() { try { const networkInterfaces = os_1.default.networkInterfaces(); const result = []; // Process each network interface for (const [name, interfaces] of Object.entries(networkInterfaces)) { if (interfaces) { result.push({ name, addresses: interfaces.map(iface => ({ address: iface.address, netmask: iface.netmask, family: iface.family, mac: iface.mac, internal: iface.internal })) }); } } return result; } catch (error) { console.error('Error getting network interfaces:', error); return []; } } exports.getNetworkInterfaces = getNetworkInterfaces; /** * Get system load averages for 1, 5, and 15 minutes * @returns {LoadAverageInfo} Object containing load average information */ function getLoadAverage() { const [oneMinute, fiveMinutes, fifteenMinutes] = os_1.default.loadavg(); return { oneMinute, fiveMinutes, fifteenMinutes, cpuCount: os_1.default.cpus().length }; } exports.getLoadAverage = getLoadAverage; /** * Attempts to get CPU temperature information (platform-dependent) * @returns {Promise<CpuTemperature | null>} CPU temperature data or null if unavailable */ async function getCpuTemperature() { try { const platform = process.platform; if (platform === 'linux') { // Linux implementation - Read from thermal zones try { const { stdout } = await execAsync('cat /sys/class/thermal/thermal_zone*/temp'); const temps = stdout.trim().split('\n').map(t => parseInt(t, 10) / 1000); if (temps.length > 0) { return { main: temps[0], cores: temps, max: 100 // Typical max safe temperature }; } } catch (err) { // Try alternate method with lm-sensors try { const { stdout } = await execAsync('sensors -j'); const data = JSON.parse(stdout); const temps = []; // Extract CPU temperatures from the sensors output for (const [key, value] of Object.entries(data)) { if (typeof value === 'object' && value !== null && key.includes('core')) { for (const [subKey, subValue] of Object.entries(value)) { if (subKey.includes('temp') && typeof subValue === 'object' && subValue !== null) { const temp = subValue.input; if (typeof temp === 'number') { temps.push(temp); } } } } } if (temps.length > 0) { return { main: temps.reduce((sum, temp) => sum + temp, 0) / temps.length, cores: temps, max: 100 }; } } catch (err) { // Silently fail and return null later } } } else if (platform === 'darwin') { // macOS implementation try { const { stdout } = await execAsync('sudo powermetrics --samplers smc -n 1 -i 1'); const match = stdout.match(/CPU die temperature: (\d+\.\d+) C/); if (match && match[1]) { const temp = parseFloat(match[1]); return { main: temp, cores: [temp], // macOS often only provides a single temperature max: 105 // Typical max for Apple CPUs }; } } catch (err) { // Silently fail and return null later } } else if (platform === 'win32') { // Windows implementation using wmic try { const { stdout } = await execAsync('wmic /namespace:\\\\root\\wmi PATH MSAcpi_ThermalZoneTemperature get CurrentTemperature'); const lines = stdout.trim().split('\n').slice(1); const temps = lines .map(line => line.trim()) .filter(line => line.length > 0) .map(line => parseInt(line, 10) / 10 - 273.15); // Convert from deciKelvin to Celsius if (temps.length > 0) { return { main: temps[0], cores: temps, max: 100 }; } } catch (err) { // Silently fail and return null later } } return null; // Temperature information not available } catch (error) { console.error('Error getting CPU temperature:', error); return null; } } exports.getCpuTemperature = getCpuTemperature; /** * Gets battery information if available * @returns {Promise<BatteryInfo | null>} Battery information or null if not available */ async function getBatteryInfo() { try { const platform = process.platform; if (platform === 'darwin') { // macOS implementation try { const { stdout } = await execAsync('pmset -g batt'); const hasBattery = !stdout.includes('No batteries available'); if (!hasBattery) { return { hasBattery: false }; } const percentMatch = stdout.match(/(\d+)%/); const chargingMatch = stdout.match(/(?:charging|discharging|charged)/i); const timeRemainingMatch = stdout.match(/(\d+:\d+) remaining/); const percent = percentMatch ? parseInt(percentMatch[1], 10) : undefined; const isCharging = chargingMatch ? chargingMatch[0].toLowerCase() !== 'discharging' : undefined; let timeRemaining = undefined; if (timeRemainingMatch) { const [hours, minutes] = timeRemainingMatch[1].split(':').map(Number); timeRemaining = hours * 60 + minutes; } return { hasBattery: true, percent, isCharging, timeRemaining }; } catch (err) { return { hasBattery: false }; } } else if (platform === 'linux') { // Linux implementation try { // Check if battery exists const { stdout: batteryCheck } = await execAsync('ls /sys/class/power_supply/BAT*'); const hasBattery = batteryCheck.length > 0; if (!hasBattery) { return { hasBattery: false }; } const batteryPath = batteryCheck.trim().split('\n')[0]; // Get capacity (percent) const { stdout: capacityOutput } = await execAsync(`cat ${batteryPath}/capacity`); const percent = parseInt(capacityOutput.trim(), 10); // Get charging status const { stdout: statusOutput } = await execAsync(`cat ${batteryPath}/status`); const status = statusOutput.trim(); const isCharging = status === 'Charging'; // Try to get time remaining (not always available) let timeRemaining = undefined; try { const { stdout: energyOutput } = await execAsync(`cat ${batteryPath}/energy_now`); const { stdout: powerOutput } = await execAsync(`cat ${batteryPath}/power_now`); const energyNow = parseInt(energyOutput.trim(), 10); const powerNow = parseInt(powerOutput.trim(), 10); if (powerNow > 0) { timeRemaining = Math.floor((energyNow / powerNow) * 60); } } catch (err) { // Time remaining calculation failed, continue without it } return { hasBattery: true, percent, isCharging, timeRemaining }; } catch (err) { return { hasBattery: false }; } } else if (platform === 'win32') { // Windows implementation try { const { stdout } = await execAsync('wmic path Win32_Battery get BatteryStatus, EstimatedChargeRemaining'); const lines = stdout.trim().split('\n'); if (lines.length < 2) { return { hasBattery: false }; } const parts = lines[1].trim().split(/\s+/); if (parts.length >= 2) { const batteryStatus = parseInt(parts[0], 10); const percent = parseInt(parts[1], 10); // BatteryStatus: 1 = discharging, 2 = AC, 3-10 = charging or other states const isCharging = batteryStatus >= 2; return { hasBattery: true, percent, isCharging }; } return { hasBattery: true }; } catch (err) { return { hasBattery: false }; } } return { hasBattery: false }; } catch (error) { console.error('Error getting battery information:', error); return { hasBattery: false }; } } exports.getBatteryInfo = getBatteryInfo; /** * Gets information about the top processes by CPU or memory usage * @param {number} [limit=10] Maximum number of processes to return * @param {'cpu'|'memory'} [sortBy='cpu'] Sort processes by CPU or memory usage * @returns {Promise<ProcessInfo[]>} Array of process information */ async function getTopProcesses(limit = 10, sortBy = 'cpu') { try { const platform = process.platform; const result = []; if (platform === 'win32') { // Windows implementation const command = sortBy === 'cpu' ? 'wmic process get ProcessId,Name,WorkingSetSize,UserModeTime,KernelModeTime /format:csv' : 'wmic process get ProcessId,Name,WorkingSetSize /format:csv'; const { stdout } = await execAsync(command); const lines = stdout.trim().split('\r\n').filter(line => line.length > 0); // Skip the header line const processes = []; const totalMemory = os_1.default.totalmem(); for (let i = 1; i < lines.length; i++) { const parts = lines[i].split(','); if (sortBy === 'cpu' && parts.length >= 5) { // Format: Node,ProcessId,Name,WorkingSetSize,UserModeTime,KernelModeTime const pid = parseInt(parts[1], 10); const name = parts[2]; const memory = parseInt(parts[3], 10); const userTime = parseInt(parts[4], 10); const kernelTime = parseInt(parts[5], 10); const totalTime = userTime + kernelTime; // This isn't a perfect measure of CPU % but gives a relative value const cpuPercent = totalTime / 10000; processes.push({ pid, name, cpu: parseFloat(cpuPercent.toFixed(1)), memory, memoryPercent: parseFloat(((memory / totalMemory) * 100).toFixed(1)) }); } else if (sortBy === 'memory' && parts.length >= 4) { // Format: Node,ProcessId,Name,WorkingSetSize const pid = parseInt(parts[1], 10); const name = parts[2]; const memory = parseInt(parts[3], 10); processes.push({ pid, name, cpu: 0, // We don't have CPU info in this query memory, memoryPercent: parseFloat(((memory / totalMemory) * 100).toFixed(1)) }); } } // Sort and limit results processes.sort((a, b) => sortBy === 'cpu' ? b.cpu - a.cpu : b.memory - a.memory); return processes.slice(0, limit); } else { // Unix-based systems (Linux, macOS) const command = sortBy === 'cpu' ? 'ps -ewwo pid,comm,pcpu,pmem,rss,state' : 'ps -ewwo pid,comm,pmem,pcpu,rss,state'; const { stdout } = await execAsync(command); const lines = stdout.trim().split('\n'); // Skip the header line const processes = []; for (let i = 1; i < lines.length; i++) { const line = lines[i].trim(); const parts = line.split(/\s+/); if (parts.length >= 6) { let idx = 0; const pid = parseInt(parts[idx++], 10); const name = parts[idx++]; let cpu, mem; if (sortBy === 'cpu') { cpu = parseFloat(parts[idx++]); mem = parseFloat(parts[idx++]); } else { mem = parseFloat(parts[idx++]); cpu = parseFloat(parts[idx++]); } const rss = parseInt(parts[idx++], 10) * 1024; // Convert KB to bytes const state = parts[idx++]; processes.push({ pid, name, cpu, memory: rss, memoryPercent: mem, state }); } } // Sort and limit results processes.sort((a, b) => sortBy === 'cpu' ? b.cpu - a.cpu : b.memory - a.memory); return processes.slice(0, limit); } } catch (error) { console.error('Error getting top processes:', error); return []; } } exports.getTopProcesses = getTopProcesses; /* PRIVATE */ /** * This function thros a new error for the specific error when you specify a higher core count then you have in your system. * * @PRIVATE This is a private function, that you will never need to call nor use in your Code Base * @param {number} coreIndex * @param {number} cores * @returns {Error} A throwable new Error */ function _error(coreIndex, cores) { throw new Error(`[node-system-stats] Error: Core ${coreIndex} not found. Use on of these cores [0, ${cores - 1}], since your system has a total of ${cores} cores.`); } //# sourceMappingURL=node-system-stats.js.map