lib/models/hardware-info.js
'use strict';
const execa = require('execa');
const fs = require('fs');
const logger = require('heimdalljs-logger')('ember-cli:hardware-info');
const os = require('os');
function isUsingBatteryAcpi() {
try {
const { stdout } = execa.sync('acpi', ['--ac-adapter']);
const lines = stdout.split('\n').filter(Boolean);
return lines.every((line) => /off-line/.test(line));
} catch (ex) {
logger.warn(`Could not get battery status from acpi: ${ex}`);
logger.warn(ex.stack);
return null;
}
}
function isUsingBatteryApm() {
try {
const { stdout } = execa.sync('apm', ['-a']);
return parseInt(stdout, 10) === 0;
} catch (ex) {
logger.warn(`Could not get battery status from apm: ${ex}`);
logger.warn(ex.stack);
return null;
}
}
function isUsingBatteryBsd() {
const apm = isUsingBatteryApm();
if (apm !== null) {
return apm;
}
return isUsingBatteryUpower();
}
function isUsingBatteryDarwin() {
try {
const { stdout } = execa.sync('pmset', ['-g', 'batt']);
return stdout.indexOf('Battery Power') !== -1;
} catch (ex) {
logger.warn(`Could not get battery status from pmset: ${ex}`);
logger.warn(ex.stack);
return null;
}
}
function isUsingBatteryLinux() {
const sysClassPowerSupply = isUsingBatterySysClassPowerSupply();
if (sysClassPowerSupply !== null) {
return sysClassPowerSupply;
}
const acpi = isUsingBatteryAcpi();
if (acpi !== null) {
return acpi;
}
return isUsingBatteryUpower();
}
function isUsingBatterySysClassPowerSupply() {
try {
const value = fs.readFileSync('/sys/class/power_supply/AC/online');
return parseInt(value, 10) === 0;
} catch (ex) {
logger.warn(`Could not get battery status from /sys/class/power_supply: ${ex}`);
logger.warn(ex.stack);
return null;
}
}
function isUsingBatteryUpower() {
try {
const { stdout } = execa.sync('upower', ['--enumerate']);
const devices = stdout.split('\n').filter(Boolean);
return devices.some((device) => {
const { stdout } = execa.sync('upower', ['--show-info', device]);
return /\bpower supply:\s+yes\b/.test(stdout) && /\bstate:\s+discharging\b/.test(stdout);
});
} catch (ex) {
logger.warn(`Could not get battery status from upower: ${ex}`);
logger.warn(ex.stack);
return null;
}
}
function isUsingBatteryWindows() {
try {
const { stdout } = execa.sync('wmic', [
'/namespace:',
'\\\\root\\WMI',
'path',
'BatteryStatus',
'get',
'PowerOnline',
'/format:list',
]);
return /\bPowerOnline=FALSE\b/.test(stdout);
} catch (ex) {
logger.warn(`Could not get battery status from wmic: ${ex}`);
logger.warn(ex.stack);
return null;
}
}
function memorySwapUsedDarwin() {
try {
const { stdout } = execa.sync('sysctl', ['vm.swapusage']);
const match = /\bused = (\d+\.\d+)M\b/.exec(stdout);
if (!match) {
throw new Error('vm.swapusage not in output.');
}
// convert from fractional megabytes to bytes
return parseFloat(match[1]) * 1048576;
} catch (ex) {
logger.warn(`Could not get swap status from sysctl: ${ex}`);
logger.warn(ex.stack);
return null;
}
}
function memorySwapUsedBsd() {
try {
const { stdout } = execa.sync('pstat', ['-s']);
const devices = stdout.split('\n').filter(Boolean);
const header = devices.shift();
const match = /^Device\s+(\d+)(K?)-blocks\s+Used\b/.exec(header);
if (!match) {
throw new Error('Block size not found in output.');
}
const blockSize = parseInt(match[1], 10) * (match[2] === 'K' ? 1024 : 1);
return devices.reduce((total, line) => {
const match = /^\S+\s+\d+\s+(\d+)/.exec(line);
if (!match) {
throw new Error(`Unrecognized line in output: '${line}'`);
}
return total + parseInt(match[1], 10) * blockSize;
}, 0);
} catch (ex) {
logger.warn(`Could not get swap status from pstat: ${ex}`);
logger.warn(ex.stack);
return null;
}
}
function memorySwapUsedLinux() {
try {
const { stdout } = execa.sync('free', ['-b']);
const lines = stdout.split('\n').filter(Boolean);
const header = lines.shift();
const columns = header.split(/\s+/).filter(Boolean);
const columnUsed = columns.reduce((columnUsed, column, index) => {
if (columnUsed !== undefined) {
return columnUsed;
}
if (/used/i.test(column)) {
// there is no heading on the first column, so indices are off by 1
return index + 1;
}
}, undefined);
if (columnUsed === undefined) {
throw new Error('Could not find "used" column.');
}
for (const line of lines) {
const columns = line.split(/\s+/).filter(Boolean);
if (/swap/i.test(columns[0])) {
return parseInt(columns[columnUsed], 10);
}
}
throw new Error('Could not find "swap" row.');
} catch (ex) {
logger.warn(`Could not get swap status from free: ${ex}`);
logger.warn(ex.stack);
return null;
}
}
function memorySwapUsedWindows() {
try {
const { stdout } = execa.sync('wmic', ['PAGEFILE', 'get', 'CurrentUsage', '/format:list']);
const match = /\bCurrentUsage=(\d+)/.exec(stdout);
if (!match) {
throw new Error('Page file usage info not in output.');
}
return parseInt(match[1], 10) * 1048576;
} catch (ex) {
logger.warn(`Could not get swap status from wmic: ${ex}`);
logger.warn(ex.stack);
return null;
}
}
const hwinfo = {
/**
* Indicates whether the host is running on battery power. This can cause
* performance degredation.
*
* @private
* @method isUsingBattery
* @for HardwareInfo
* @param {String=process.platform} platform The current hardware platform.
* USED FOR TESTING ONLY.
* @return {null|Boolean} `true` iff the host is running on battery power or
* `false` if not. `null` if the battery status
* cannot be determined.
*/
isUsingBattery(platform = process.platform) {
switch (platform) {
case 'darwin':
return isUsingBatteryDarwin();
case 'freebsd':
case 'openbsd':
return isUsingBatteryBsd();
case 'linux':
return isUsingBatteryLinux();
case 'win32':
return isUsingBatteryWindows();
}
logger.warn(`Battery status is unsupported on the '${platform}' platform.`);
return null;
},
/**
* Determines the amount of swap/virtual memory currently in use.
*
* @private
* @method memorySwapUsed
* @param {String=process.platform} platform The current hardware platform.
* USED FOR TESTING ONLY.
* @return {null|Number} The amount of used swap space, in bytes. `null` if
* the used swap space cannot be determined.
*/
memorySwapUsed(platform = process.platform) {
switch (platform) {
case 'darwin':
return memorySwapUsedDarwin();
case 'freebsd':
case 'openbsd':
return memorySwapUsedBsd();
case 'linux':
return memorySwapUsedLinux();
case 'win32':
return memorySwapUsedWindows();
}
logger.warn(`Swap status is unsupported on the '${platform}' platform.`);
return null;
},
/**
* Determines the total amount of memory available to the host, as from
* `os.totalmem`.
*
* @private
* @method memoryTotal
* @return {Number} The total memory in bytes.
*/
memoryTotal() {
return os.totalmem();
},
/**
* Determines the amount of memory currently being used by the current Node
* process, as from `process.memoryUsage`.
*
* @private
* @method memoryUsed
* @return {Object} The Resident Set Size, as reported by
* `process.memoryUsage`.
*/
memoryUsed() {
return process.memoryUsage().rss;
},
/**
* Determines the number of logical processors available to the host, as from
* `os.cpus`.
*
* @private
* @method processorCount
* @return {Number} The number of logical processors.
*/
processorCount() {
return os.cpus().length;
},
/**
* Determines the average processor load across the system. This is
* expressed as a fractional number between 0 and the number of logical
* processors.
*
* @private
* @method processorLoad
* @param {String=process.platform} platform The current hardware platform.
* USED FOR TESTING ONLY.
* @return {Array<Number>} The one-, five-, and fifteen-minute processor load
* averages.
*/
processorLoad(platform = process.platform) {
// The os.loadavg() call works on win32, but never returns correct
// data. Better to intercept and warn that it's unsupported.
if (platform === 'win32') {
logger.warn(`Processor load is unsupported on the '${platform}' platform.`);
return null;
}
return os.loadavg();
},
/**
* Gets the speed of the host's processors.
*
* If more than one processor is found, the average of their speeds is taken.
*
* @private
* @method processorSpeed
* @return {Number} The average processor speed in MHz.
*/
processorSpeed() {
const cpus = os.cpus();
return cpus.reduce((sum, cpu) => sum + cpu.speed, 0) / cpus.length;
},
/**
* Determines the time since the host was started, as from `os.uptime`.
*
* @private
* @method uptime
* @return {Number} The number of seconds since the host was started.
*/
uptime() {
return os.uptime();
},
};
module.exports = hwinfo;