From 6f63b1b02ddc8cad743f9a4813fa0e8fa6eac8a0 Mon Sep 17 00:00:00 2001 From: Sebastian Hildebrandt Date: Wed, 27 Oct 2021 08:39:59 +0200 Subject: [PATCH] fix typo --- lib/filesystem.js | 2530 ++++++++++++++++++++++----------------------- 1 file changed, 1265 insertions(+), 1265 deletions(-) diff --git a/lib/filesystem.js b/lib/filesystem.js index af53eb4..4dbfb39 100644 --- a/lib/filesystem.js +++ b/lib/filesystem.js @@ -1,1265 +1,1265 @@ -'use strict'; -// @ts-check -// ================================================================================== -// filesystem.js -// ---------------------------------------------------------------------------------- -// Description: System Information - library -// for Node.js -// Copyright: (c) 2014 - 2021 -// Author: Sebastian Hildebrandt -// ---------------------------------------------------------------------------------- -// License: MIT -// ================================================================================== -// 8. File System -// ---------------------------------------------------------------------------------- - -const util = require('./util'); -const fs = require('fs'); - -const exec = require('child_process').exec; -const execSync = require('child_process').execSync; -const execPromiseSave = util.promisifySave(require('child_process').exec); - -let _platform = process.platform; - -const _linux = (_platform === 'linux'); -const _darwin = (_platform === 'darwin'); -const _windows = (_platform === 'win32'); -const _freebsd = (_platform === 'freebsd'); -const _openbsd = (_platform === 'openbsd'); -const _netbsd = (_platform === 'netbsd'); -const _sunos = (_platform === 'sunos'); - -let _fs_speed = {}; -let _disk_io = {}; - -// -------------------------- -// FS - mounted file systems - -function fsSize(callback) { - - let macOsDisks = []; - - function getmacOsFsType(fs) { - if (!fs.startsWith('/')) { return 'NFS'; } - const parts = fs.split('/'); - const fsShort = parts[parts.length - 1]; - const macOsDisksSingle = macOsDisks.filter(item => item.indexOf(fsShort) >= 0); - if (macOsDisksSingle.length === 1 && macOsDisksSingle[0].indexOf('APFS') >= 0) { return 'APFS'; } - return 'HFS'; - } - - function parseDf(lines) { - let data = []; - lines.forEach(function (line) { - if (line !== '') { - line = line.replace(/ +/g, ' ').split(' '); - if (line && ((line[0].startsWith('/')) || (line[6] && line[6] === '/') || (line[0].indexOf('/') > 0) || (line[0].indexOf(':') === 1))) { - const fs = line[0]; - const fsType = ((_linux || _freebsd || _openbsd || _netbsd) ? line[1] : getmacOsFsType(line[0])); - const size = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[2] : line[1])) * 1024; - const used = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[3] : line[2])) * 1024; - const available = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[4] : line[3])) * 1024; - const use = parseFloat((100.0 * (used / (used + available))).toFixed(2)); - line.splice(0, (_linux || _freebsd || _openbsd || _netbsd) ? 6 : 5); - const mount = line.join(' '); - // const mount = line[line.length - 1]; - if (!data.find(el => (el.fs === fs && el.type === fsType))) { - data.push({ - fs, - type: fsType, - size, - used, - available, - use, - mount - }); - } - } - } - }); - return data; - } - - return new Promise((resolve) => { - process.nextTick(() => { - let data = []; - if (_linux || _freebsd || _openbsd || _netbsd || _darwin) { - let cmd = ''; - if (_darwin) { - cmd = 'df -kP'; - try { - macOsDisks = execSync('diskutil list').toString().split('\n').filter(line => { - return !line.startsWith('/') && line.indexOf(':') > 0; - }); - } catch (e) { - macOsDisks = []; - } - } - if (_linux) { cmd = 'df -lkPTx squashfs | grep -E "^/|^.\\:"'; } - if (_freebsd || _openbsd || _netbsd) { cmd = 'df -lkPT'; } - exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - let lines = stdout.toString().split('\n'); - data = parseDf(lines); - if (callback) { - callback(data); - } - resolve(data); - } else { - exec('df -kPT', { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - let lines = stdout.toString().split('\n'); - data = parseDf(lines); - } - if (callback) { - callback(data); - } - resolve(data); - }); - } - }); - } - if (_sunos) { - if (callback) { callback(data); } - resolve(data); - } - if (_windows) { - try { - util.wmic('logicaldisk get Caption,FileSystem,FreeSpace,Size').then((stdout) => { - let lines = stdout.split('\r\n').filter(line => line.trim() !== '').filter((line, idx) => idx > 0); - lines.forEach(function (line) { - if (line !== '') { - line = line.trim().split(/\s\s+/); - if (line.length >= 4 && parseInt(line[3], 10)) { - data.push({ - fs: line[0], - type: line[1], - size: parseInt(line[3], 10), - used: parseInt(line[3], 10) - parseInt(line[2], 10), - available: parseInt(line[2], 10), - use: parseFloat(((100.0 * (parseInt(line[3]) - parseInt(line[2]))) / parseInt(line[3])).toFixed(2)), - mount: line[0] - }); - } - } - }); - if (callback) { - callback(data); - } - resolve(data); - }); - } catch (e) { - if (callback) { callback(data); } - resolve(data); - } - } - }); - }); -} - -exports.fsSize = fsSize; - -// -------------------------- -// FS - open files count - -function fsOpenFiles(callback) { - - return new Promise((resolve) => { - process.nextTick(() => { - const result = { - max: null, - allocated: null, - available: null - }; - if (_freebsd || _openbsd || _netbsd || _darwin) { - let cmd = 'sysctl -a | grep \'kern.*files\''; - exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - let lines = stdout.toString().split('\n'); - result.max = parseInt(util.getValue(lines, 'kern.maxfiles', ':'), 10); - result.allocated = parseInt(util.getValue(lines, 'kern.num_files', ':'), 10); - result.available = result.max - result.allocated; - } - if (callback) { - callback(result); - } - resolve(result); - }); - } - if (_linux) { - fs.readFile('/proc/sys/fs/file-nr', function (error, stdout) { - if (!error) { - let lines = stdout.toString().split('\n'); - if (lines[0]) { - const parts = lines[0].replace(/\s+/g, ' ').split(' '); - if (parts.length === 3) { - result.allocated = parseInt(parts[0], 10); - result.available = parseInt(parts[1], 10); - result.max = parseInt(parts[2], 10); - if (!result.available) { result.available = result.max - result.allocated; } - } - } - if (callback) { - callback(result); - } - resolve(result); - } else { - fs.readFile('/proc/sys/fs/file-max', function (error, stdout) { - if (!error) { - let lines = stdout.toString().split('\n'); - if (lines[0]) { - result.max = parseInt(lines[0], 10); - } - } - if (callback) { - callback(result); - } - resolve(result); - }); - } - }); - } - if (_sunos) { - if (callback) { callback(null); } - resolve(null); - } - if (_windows) { - if (callback) { callback(null); } - resolve(null); - } - }); - }); -} - -exports.fsOpenFiles = fsOpenFiles; - -// -------------------------- -// disks - -function parseBytes(s) { - return parseInt(s.substr(s.indexOf(' (') + 2, s.indexOf(' Bytes)') - 10)); -} - -function parseDevices(lines) { - let devices = []; - let i = 0; - lines.forEach(line => { - if (line.length > 0) { - if (line[0] === '*') { - i++; - } else { - let parts = line.split(':'); - if (parts.length > 1) { - if (!devices[i]) { - devices[i] = { - name: '', - identifier: '', - type: 'disk', - fsType: '', - mount: '', - size: 0, - physical: 'HDD', - uuid: '', - label: '', - model: '', - serial: '', - removable: false, - protocol: '' - }; - } - parts[0] = parts[0].trim().toUpperCase().replace(/ +/g, ''); - parts[1] = parts[1].trim(); - if ('DEVICEIDENTIFIER' === parts[0]) { devices[i].identifier = parts[1]; } - if ('DEVICENODE' === parts[0]) { devices[i].name = parts[1]; } - if ('VOLUMENAME' === parts[0]) { - if (parts[1].indexOf('Not applicable') === -1) { devices[i].label = parts[1]; } - } - if ('PROTOCOL' === parts[0]) { devices[i].protocol = parts[1]; } - if ('DISKSIZE' === parts[0]) { devices[i].size = parseBytes(parts[1]); } - if ('FILESYSTEMPERSONALITY' === parts[0]) { devices[i].fsType = parts[1]; } - if ('MOUNTPOINT' === parts[0]) { devices[i].mount = parts[1]; } - if ('VOLUMEUUID' === parts[0]) { devices[i].uuid = parts[1]; } - if ('READ-ONLYMEDIA' === parts[0] && parts[1] === 'Yes') { devices[i].physical = 'CD/DVD'; } - if ('SOLIDSTATE' === parts[0] && parts[1] === 'Yes') { devices[i].physical = 'SSD'; } - if ('VIRTUAL' === parts[0]) { devices[i].type = 'virtual'; } - if ('REMOVABLEMEDIA' === parts[0]) { devices[i].removable = (parts[1] === 'Removable'); } - if ('PARTITIONTYPE' === parts[0]) { devices[i].type = 'part'; } - if ('DEVICE/MEDIANAME' === parts[0]) { devices[i].model = parts[1]; } - } - } - } - }); - return devices; -} - -function parseBlk(lines) { - let data = []; - - lines.filter(line => line !== '').forEach((line) => { - try { - line = decodeURIComponent(line.replace(/\\x/g, '%')); - line = line.replace(/\\/g, '\\\\'); - let disk = JSON.parse(line); - data.push({ - 'name': disk.name, - 'type': disk.type, - 'fsType': disk.fsType, - 'mount': disk.mountpoint, - 'size': parseInt(disk.size), - 'physical': (disk.type === 'disk' ? (disk.rota === '0' ? 'SSD' : 'HDD') : (disk.type === 'rom' ? 'CD/DVD' : '')), - 'uuid': disk.uuid, - 'label': disk.label, - 'model': disk.model, - 'serial': disk.serial, - 'removable': disk.rm === '1', - 'protocol': disk.tran, - 'group': disk.group, - }); - } catch (e) { - util.noop(); - } - }); - data = util.unique(data); - data = util.sortByKey(data, ['type', 'name']); - return data; -} - -function blkStdoutToObject(stdout) { - return stdout.toString() - .replace(/NAME=/g, '{"name":') - .replace(/FSTYPE=/g, ',"fsType":') - .replace(/TYPE=/g, ',"type":') - .replace(/SIZE=/g, ',"size":') - .replace(/MOUNTPOINT=/g, ',"mountpoint":') - .replace(/UUID=/g, ',"uuid":') - .replace(/ROTA=/g, ',"rota":') - .replace(/RO=/g, ',"ro":') - .replace(/RM=/g, ',"rm":') - .replace(/TRAN=/g, ',"tran":') - .replace(/SERIAL=/g, ',"serial":') - .replace(/LABEL=/g, ',"label":') - .replace(/MODEL=/g, ',"model":') - .replace(/OWNER=/g, ',"owner":') - .replace(/GROUP=/g, ',"group":') - .replace(/\n/g, '}\n'); -} - -function blockDevices(callback) { - - return new Promise((resolve) => { - process.nextTick(() => { - let data = []; - if (_linux) { - // see https://wiki.ubuntuusers.de/lsblk/ - // exec("lsblk -bo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,TRAN,SERIAL,LABEL,MODEL,OWNER,GROUP,MODE,ALIGNMENT,MIN-IO,OPT-IO,PHY-SEC,LOG-SEC,SCHED,RQ-SIZE,RA,WSAME", function (error, stdout) { - exec('lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,TRAN,SERIAL,LABEL,MODEL,OWNER 2>/dev/null', { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - let lines = blkStdoutToObject(stdout).split('\n'); - data = parseBlk(lines); - if (callback) { - callback(data); - } - resolve(data); - } else { - exec('lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,LABEL,MODEL,OWNER 2>/dev/null', { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - let lines = blkStdoutToObject(stdout).split('\n'); - data = parseBlk(lines); - } - if (callback) { - callback(data); - } - resolve(data); - }); - } - }); - } - if (_darwin) { - exec('diskutil info -all', { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - let lines = stdout.toString().split('\n'); - // parse lines into temp array of devices - data = parseDevices(lines); - } - if (callback) { - callback(data); - } - resolve(data); - }); - } - if (_sunos) { - if (callback) { callback(data); } - resolve(data); - } - if (_windows) { - let drivetypes = ['Unknown', 'NoRoot', 'Removable', 'Local', 'Network', 'CD/DVD', 'RAM']; - try { - // util.wmic('logicaldisk get Caption,Description,DeviceID,DriveType,FileSystem,FreeSpace,Name,Size,VolumeName,VolumeSerialNumber /value').then((stdout, error) => { - util.powerShell('Get-CimInstance -ClassName Win32_LogicalDisk | Format-List *').then((stdout, error) => { - if (!error) { - let devices = stdout.toString().split(/\n\s*\n/); - devices.forEach(function (device) { - let lines = device.split('\r\n'); - let drivetype = util.getValue(lines, 'drivetype', ':'); - if (drivetype) { - data.push({ - name: util.getValue(lines, 'name', ':'), - identifier: util.getValue(lines, 'caption', ':'), - type: 'disk', - fsType: util.getValue(lines, 'filesystem', ':').toLowerCase(), - mount: util.getValue(lines, 'caption', ':'), - size: util.getValue(lines, 'size', ':'), - physical: (drivetype >= 0 && drivetype <= 6) ? drivetypes[drivetype] : drivetypes[0], - uuid: util.getValue(lines, 'volumeserialnumber', ':'), - label: util.getValue(lines, 'volumename', ':'), - model: '', - serial: util.getValue(lines, 'volumeserialnumber', ':'), - removable: drivetype === '2', - protocol: '' - }); - } - }); - } - if (callback) { - callback(data); - } - resolve(data); - }); - } catch (e) { - if (callback) { callback(data); } - resolve(data); - } - } - if (_freebsd || _openbsd || _netbsd) { - // will follow - if (callback) { callback(null); } - resolve(null); - } - - }); - }); -} - -exports.blockDevices = blockDevices; - -// -------------------------- -// FS - speed - -function calcFsSpeed(rx, wx) { - let result = { - rx: 0, - wx: 0, - tx: 0, - rx_sec: null, - wx_sec: null, - tx_sec: null, - ms: 0 - }; - - if (_fs_speed && _fs_speed.ms) { - result.rx = rx; - result.wx = wx; - result.tx = result.rx + result.wx; - result.ms = Date.now() - _fs_speed.ms; - result.rx_sec = (result.rx - _fs_speed.bytes_read) / (result.ms / 1000); - result.wx_sec = (result.wx - _fs_speed.bytes_write) / (result.ms / 1000); - result.tx_sec = result.rx_sec + result.wx_sec; - _fs_speed.rx_sec = result.rx_sec; - _fs_speed.wx_sec = result.wx_sec; - _fs_speed.tx_sec = result.tx_sec; - _fs_speed.bytes_read = result.rx; - _fs_speed.bytes_write = result.wx; - _fs_speed.bytes_overall = result.rx + result.wx; - _fs_speed.ms = Date.now(); - _fs_speed.last_ms = result.ms; - } else { - result.rx = rx; - result.wx = wx; - result.tx = result.rx + result.wx; - _fs_speed.rx_sec = null; - _fs_speed.wx_sec = null; - _fs_speed.tx_sec = null; - _fs_speed.bytes_read = result.rx; - _fs_speed.bytes_write = result.wx; - _fs_speed.bytes_overall = result.rx + result.wx; - _fs_speed.ms = Date.now(); - _fs_speed.last_ms = 0; - } - return result; -} - -function fsStats(callback) { - - return new Promise((resolve) => { - process.nextTick(() => { - if (_windows) { - return resolve(null); - } - - let result = { - rx: 0, - wx: 0, - tx: 0, - rx_sec: null, - wx_sec: null, - tx_sec: null, - ms: 0 - }; - - let rx = 0; - let wx = 0; - if ((_fs_speed && !_fs_speed.ms) || (_fs_speed && _fs_speed.ms && Date.now() - _fs_speed.ms >= 500)) { - if (_linux) { - // exec("df -k | grep /dev/", function(error, stdout) { - exec('lsblk -r 2>/dev/null | grep /', { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - let lines = stdout.toString().split('\n'); - let fs_filter = []; - lines.forEach(function (line) { - if (line !== '') { - line = line.trim().split(' '); - if (fs_filter.indexOf(line[0]) === -1) { fs_filter.push(line[0]); } - } - }); - - let output = fs_filter.join('|'); - exec('cat /proc/diskstats | egrep "' + output + '"', { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - let lines = stdout.toString().split('\n'); - lines.forEach(function (line) { - line = line.trim(); - if (line !== '') { - line = line.replace(/ +/g, ' ').split(' '); - - rx += parseInt(line[5]) * 512; - wx += parseInt(line[9]) * 512; - } - }); - result = calcFsSpeed(rx, wx); - } - if (callback) { - callback(result); - } - resolve(result); - }); - } else { - if (callback) { - callback(result); - } - resolve(result); - } - }); - } - if (_darwin) { - exec('ioreg -c IOBlockStorageDriver -k Statistics -r -w0 | sed -n "/IOBlockStorageDriver/,/Statistics/p" | grep "Statistics" | tr -cd "01234567890,\n"', { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - let lines = stdout.toString().split('\n'); - lines.forEach(function (line) { - line = line.trim(); - if (line !== '') { - line = line.split(','); - - rx += parseInt(line[2]); - wx += parseInt(line[9]); - } - }); - result = calcFsSpeed(rx, wx); - } - if (callback) { - callback(result); - } - resolve(result); - }); - } - } else { - result.ms = _fs_speed.last_ms; - result.rx = _fs_speed.bytes_read; - result.wx = _fs_speed.bytes_write; - result.tx = _fs_speed.bytes_read + _fs_speed.bytes_write; - result.rx_sec = _fs_speed.rx_sec; - result.wx_sec = _fs_speed.wx_sec; - result.tx_sec = _fs_speed.tx_sec; - if (callback) { - callback(result); - } - resolve(result); - } - }); - }); -} - -exports.fsStats = fsStats; - -function calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime) { - let result = { - rIO: 0, - wIO: 0, - tIO: 0, - rIO_sec: null, - wIO_sec: null, - tIO_sec: null, - rWaitTime: 0, - wWaitTime: 0, - tWaitTime: 0, - rWaitPercent: null, - wWaitPercent: null, - tWaitPercent: null, - ms: 0 - }; - if (_disk_io && _disk_io.ms) { - result.rIO = rIO; - result.wIO = wIO; - result.tIO = rIO + wIO; - result.ms = Date.now() - _disk_io.ms; - result.rIO_sec = (result.rIO - _disk_io.rIO) / (result.ms / 1000); - result.wIO_sec = (result.wIO - _disk_io.wIO) / (result.ms / 1000); - result.tIO_sec = result.rIO_sec + result.wIO_sec; - result.rWaitTime = rWaitTime; - result.wWaitTime = wWaitTime; - result.tWaitTime = tWaitTime; - result.rWaitPercent = (result.rWaitTime - _disk_io.rWaitTime) * 100 / (result.ms); - result.wWaitPercent = (result.wWaitTime - _disk_io.wWaitTime) * 100 / (result.ms); - result.tWaitPercent = (result.tWaitTime - _disk_io.tWaitTime) * 100 / (result.ms); - _disk_io.rIO = rIO; - _disk_io.wIO = wIO; - _disk_io.rIO_sec = result.rIO_sec; - _disk_io.wIO_sec = result.wIO_sec; - _disk_io.tIO_sec = result.tIO_sec; - _disk_io.rWaitTime = rWaitTime; - _disk_io.wWaitTime = wWaitTime; - _disk_io.tWaitTime = tWaitTime; - _disk_io.rWaitPercent = result.rWaitPercent; - _disk_io.wWaitPercent = result.wWaitPercent; - _disk_io.tWaitPercent = result.tWaitPercent; - _disk_io.last_ms = result.ms; - _disk_io.ms = Date.now(); - } else { - result.rIO = rIO; - result.wIO = wIO; - result.tIO = rIO + wIO; - result.rWaitTime = rWaitTime; - result.wWaitTime = wWaitTime; - result.tWaitTime = tWaitTime; - _disk_io.rIO = rIO; - _disk_io.wIO = wIO; - _disk_io.rIO_sec = null; - _disk_io.wIO_sec = null; - _disk_io.tIO_sec = null; - _disk_io.rWaitTime = rWaitTime; - _disk_io.wWaitTime = wWaitTime; - _disk_io.tWaitTime = tWaitTime; - _disk_io.rWaitPercent = null; - _disk_io.wWaitPercent = null; - _disk_io.tWaitPercent = null; - _disk_io.last_ms = 0; - _disk_io.ms = Date.now(); - } - return result; -} - -function disksIO(callback) { - - return new Promise((resolve) => { - process.nextTick(() => { - if (_windows) { - return resolve(null); - } - if (_sunos) { - return resolve(null); - } - - let result = { - rIO: 0, - wIO: 0, - tIO: 0, - rIO_sec: null, - wIO_sec: null, - tIO_sec: null, - rWaitTime: 0, - wWaitTime: 0, - tWaitTime: 0, - rWaitPercent: null, - wWaitPercent: null, - tWaitPercent: null, - ms: 0 - }; - let rIO = 0; - let wIO = 0; - let rWaitTime = 0; - let wWaitTime = 0; - let tWaitTime = 0; - - if ((_disk_io && !_disk_io.ms) || (_disk_io && _disk_io.ms && Date.now() - _disk_io.ms >= 500)) { - if (_linux || _freebsd || _openbsd || _netbsd) { - // prints Block layer statistics for all mounted volumes - // var cmd = "for mount in `lsblk | grep / | sed -r 's/│ └─//' | cut -d ' ' -f 1`; do cat /sys/block/$mount/stat | sed -r 's/ +/;/g' | sed -r 's/^;//'; done"; - // var cmd = "for mount in `lsblk | grep / | sed 's/[│└─├]//g' | awk '{$1=$1};1' | cut -d ' ' -f 1 | sort -u`; do cat /sys/block/$mount/stat | sed -r 's/ +/;/g' | sed -r 's/^;//'; done"; - let cmd = 'for mount in `lsblk 2>/dev/null | grep " disk " | sed "s/[│└─├]//g" | awk \'{$1=$1};1\' | cut -d " " -f 1 | sort -u`; do cat /sys/block/$mount/stat | sed -r "s/ +/;/g" | sed -r "s/^;//"; done'; - - exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - let lines = stdout.split('\n'); - lines.forEach(function (line) { - // ignore empty lines - if (!line) { return; } - - // sum r/wIO of all disks to compute all disks IO - let stats = line.split(';'); - rIO += parseInt(stats[0]); - wIO += parseInt(stats[4]); - rWaitTime += parseInt(stats[3]); - wWaitTime += parseInt(stats[7]); - tWaitTime += parseInt(stats[10]); - }); - result = calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime); - - if (callback) { - callback(result); - } - resolve(result); - } else { - if (callback) { - callback(result); - } - resolve(result); - } - }); - } - if (_darwin) { - exec('ioreg -c IOBlockStorageDriver -k Statistics -r -w0 | sed -n "/IOBlockStorageDriver/,/Statistics/p" | grep "Statistics" | tr -cd "01234567890,\n"', { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - let lines = stdout.toString().split('\n'); - lines.forEach(function (line) { - line = line.trim(); - if (line !== '') { - line = line.split(','); - - rIO += parseInt(line[10]); - wIO += parseInt(line[0]); - } - }); - result = calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime); - } - if (callback) { - callback(result); - } - resolve(result); - }); - } - } else { - result.rIO = _disk_io.rIO; - result.wIO = _disk_io.wIO; - result.tIO = _disk_io.rIO + _disk_io.wIO; - result.ms = _disk_io.last_ms; - result.rIO_sec = _disk_io.rIO_sec; - result.wIO_sec = _disk_io.wIO_sec; - result.tIO_sec = _disk_io.tIO_sec; - result.rWaitTime = _disk_io.rWaitTime; - result.wWaitTime = _disk_io.wWaitTime; - result.tWaitTime = _disk_io.tWaitTime; - result.rWaitPercent = _disk_io.rWaitPercent; - result.wWaitPercent = _disk_io.wWaitPercent; - result.tWaitPercent = _disk_io.tWaitPercent; - if (callback) { - callback(result); - } - resolve(result); - } - }); - }); -} - -exports.disksIO = disksIO; - -function diskLayout(callback) { - - function getVendorFromModel(model) { - const diskManufacturers = [ - { pattern: '^WESTERN.+', manufacturer: 'Western Digital' }, - { pattern: '^WDC.+', manufacturer: 'Western Digital' }, - { pattern: 'WD.+', manufacturer: 'Western Digital' }, - { pattern: '^TOSHIBA.+', manufacturer: 'Toshiba' }, - { pattern: '^HITACHI.+', manufacturer: 'Hitachi' }, - { pattern: '^IC.+', manufacturer: 'Hitachi' }, - { pattern: '^HTS.+', manufacturer: 'Hitachi' }, - { pattern: '^SANDISK.+', manufacturer: 'SanDisk' }, - { pattern: '^KINGSTON.+', manufacturer: 'Kingston Technonogy' }, - { pattern: '^SONY.+', manufacturer: 'Sony' }, - { pattern: '^TRANSCEND.+', manufacturer: 'Transcend' }, - { pattern: 'SAMSUNG.+', manufacturer: 'Samsung' }, - { pattern: '^ST(?!I\\ ).+', manufacturer: 'Seagate' }, - { pattern: '^STI\\ .+', manufacturer: 'SimpleTech' }, - { pattern: '^D...-.+', manufacturer: 'IBM' }, - { pattern: '^IBM.+', manufacturer: 'IBM' }, - { pattern: '^FUJITSU.+', manufacturer: 'Fujitsu' }, - { pattern: '^MP.+', manufacturer: 'Fujitsu' }, - { pattern: '^MK.+', manufacturer: 'Toshiba' }, - { pattern: '^MAXTOR.+', manufacturer: 'Maxtor' }, - { pattern: '^Pioneer.+', manufacturer: 'Pioneer' }, - { pattern: '^PHILIPS.+', manufacturer: 'Philips' }, - { pattern: '^QUANTUM.+', manufacturer: 'Quantum Technology' }, - { pattern: 'FIREBALL.+', manufacturer: 'Quantum Technology' }, - { pattern: '^VBOX.+', manufacturer: 'VirtualBox' }, - { pattern: 'CORSAIR.+', manufacturer: 'Corsair Components' }, - { pattern: 'CRUCIAL.+', manufacturer: 'Crucial' }, - { pattern: 'ECM.+', manufacturer: 'ECM' }, - { pattern: 'INTEL.+', manufacturer: 'INTEL' }, - { pattern: '.+EVO', manufacturer: 'Samsung' }, - { pattern: 'APPLE.+', manufacturer: 'Apple' }, - ]; - - let result = ''; - if (model) { - model = model.toUpperCase(); - diskManufacturers.forEach((manufacturer) => { - const re = RegExp(manufacturer.pattern); - if (re.test(model)) { result = manufacturer.manufacturer; } - }); - } - return result; - } - - return new Promise((resolve) => { - process.nextTick(() => { - - const commitResult = res => { - for (let i = 0; i < res.length; i++) { - delete res[i].BSDName; - } - if (callback) { - callback(res); - } - resolve(res); - }; - - let result = []; - let cmd = ''; - - if (_linux) { - let cmdFullSmart = ''; - - exec('export LC_ALL=C; lsblk -ablJO 2>/dev/null; unset LC_ALL', { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - try { - const out = stdout.toString().trim(); - let devices = []; - try { - const outJSON = JSON.parse(out); - if (outJSON && {}.hasOwnProperty.call(outJSON, 'blockdevices')) { - devices = outJSON.blockdevices.filter(item => { return (item.type === 'disk') && item.size > 0 && (item.model !== null || (item.mountpoint === null && item.label === null && item.fsType === null && item.parttype === null)); }); - } - } catch (e) { - // fallback to older version of lsblk - const out2 = execSync('export LC_ALL=C; lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,LABEL,MODEL,OWNER,GROUP 2>/dev/null; unset LC_ALL').toString(); - let lines = blkStdoutToObject(out2).split('\n'); - const data = parseBlk(lines); - devices = data.filter(item => { return (item.type === 'disk') && item.size > 0 && ((item.model !== null && item.model !== '') || (item.mount === '' && item.label === '' && item.fsType === '')); }); - } - devices.forEach((device) => { - let mediumType = ''; - const BSDName = '/dev/' + device.name; - const logical = device.name; - try { - mediumType = execSync('cat /sys/block/' + logical + '/queue/rotational 2>/dev/null').toString().split('\n')[0]; - } catch (e) { - util.noop(); - } - let interfaceType = device.tran ? device.tran.toUpperCase().trim() : ''; - if (interfaceType === 'NVME') { - mediumType = '2'; - interfaceType = 'PCIe'; - } - result.push({ - device: BSDName, - type: (mediumType === '0' ? 'SSD' : (mediumType === '1' ? 'HD' : (mediumType === '2' ? 'NVMe' : (device.model && device.model.indexOf('SSD') > -1 ? 'SSD' : (device.model && device.model.indexOf('NVM') > -1 ? 'NVMe' : 'HD'))))), - name: device.model || '', - vendor: getVendorFromModel(device.model) || (device.vendor ? device.vendor.trim() : ''), - size: device.size || 0, - bytesPerSector: null, - totalCylinders: null, - totalHeads: null, - totalSectors: null, - totalTracks: null, - tracksPerCylinder: null, - sectorsPerTrack: null, - firmwareRevision: device.rev ? device.rev.trim() : '', - serialNum: device.serial ? device.serial.trim() : '', - interfaceType: interfaceType, - smartStatus: 'unknown', - temperature: null, - BSDName: BSDName - }); - cmd += `printf "\n${BSDName}|"; smartctl -H ${BSDName} | grep overall;`; - cmdFullSmart += `${cmdFullSmart ? 'printf ",";' : ''}smartctl -a -j ${BSDName};`; - }); - } catch (e) { - util.noop(); - } - } - // check S.M.A.R.T. status - if (cmdFullSmart) { - exec(cmdFullSmart, { maxBuffer: 1024 * 1024 }, function (error, stdout) { - try { - const data = JSON.parse(`[${stdout}]`); - data.forEach(disk => { - const diskBSDName = disk.smartctl.argv[disk.smartctl.argv.length - 1]; - - for (let i = 0; i < result.length; i++) { - if (result[i].BSDName === diskBSDName) { - result[i].smartStatus = (disk.smart_status.passed ? 'Ok' : (disk.smart_status.passed === false ? 'Predicted Failure' : 'unknown')); - if (disk.temperature && disk.temperature.current) { - result[i].temperature = disk.temperature.current; - } - result[i].smartData = disk; - } - } - }); - commitResult(result); - } catch (e) { - if (cmd) { - cmd = cmd + 'printf "\n"'; - exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) { - let lines = stdout.toString().split('\n'); - lines.forEach(line => { - if (line) { - let parts = line.split('|'); - if (parts.length === 2) { - let BSDName = parts[0]; - parts[1] = parts[1].trim(); - let parts2 = parts[1].split(':'); - if (parts2.length === 2) { - parts2[1] = parts2[1].trim(); - let status = parts2[1].toLowerCase(); - for (let i = 0; i < result.length; i++) { - if (result[i].BSDName === BSDName) { - result[i].smartStatus = (status === 'passed' ? 'Ok' : (status === 'failed!' ? 'Predicted Failure' : 'unknown')); - } - } - } - } - } - }); - commitResult(result); - }); - } else { - commitResult(result); - } - } - }); - } else { - commitResult(result); - } - }); - } - if (_freebsd || _openbsd || _netbsd) { - if (callback) { callback(result); } - resolve(result); - } - if (_sunos) { - if (callback) { callback(result); } - resolve(result); - } - if (_darwin) { - exec('system_profiler SPSerialATADataType SPNVMeDataType SPUSBDataType', { maxBuffer: 1024 * 1024 }, function (error, stdout) { - if (!error) { - // split by type: - let lines = stdout.toString().split('\n'); - let linesSATA = []; - let linesNVMe = []; - let linesUSB = []; - let dataType = 'SATA'; - lines.forEach(line => { - if (line === 'NVMExpress:') { dataType = 'NVMe'; } - else if (line === 'USB:') { dataType = 'USB'; } - else if (line === 'SATA/SATA Express:') { dataType = 'SATA'; } - else if (dataType === 'SATA') { linesSATA.push(line); } - else if (dataType === 'NVMe') { linesNVMe.push(line); } - else if (dataType === 'USB') { linesUSB.push(line); } - }); - try { - // Serial ATA Drives - let devices = linesSATA.join('\n').split(' Physical Interconnect: '); - devices.shift(); - devices.forEach(function (device) { - device = 'InterfaceType: ' + device; - let lines = device.split('\n'); - const mediumType = util.getValue(lines, 'Medium Type', ':', true).trim(); - const sizeStr = util.getValue(lines, 'capacity', ':', true).trim(); - const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim(); - if (sizeStr) { - let sizeValue = 0; - if (sizeStr.indexOf('(') >= 0) { - sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, '')); - } - if (!sizeValue) { - sizeValue = parseInt(sizeStr); - } - if (sizeValue) { - const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase(); - result.push({ - device: BSDName, - type: mediumType.startsWith('Solid') ? 'SSD' : 'HD', - name: util.getValue(lines, 'Model', ':', true).trim(), - vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()) || util.getValue(lines, 'Manufacturer', ':', true), - size: sizeValue, - bytesPerSector: null, - totalCylinders: null, - totalHeads: null, - totalSectors: null, - totalTracks: null, - tracksPerCylinder: null, - sectorsPerTrack: null, - firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(), - serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(), - interfaceType: util.getValue(lines, 'InterfaceType', ':', true).trim(), - smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown', - temperature: null, - BSDName: BSDName - }); - cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;'; - } - } - }); - } catch (e) { - util.noop(); - } - - // NVME Drives - try { - let devices = linesNVMe.join('\n').split('\n\n Capacity:'); - devices.shift(); - devices.forEach(function (device) { - device = '!Capacity: ' + device; - let lines = device.split('\n'); - const linkWidth = util.getValue(lines, 'link width', ':', true).trim(); - const sizeStr = util.getValue(lines, '!capacity', ':', true).trim(); - const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim(); - if (sizeStr) { - let sizeValue = 0; - if (sizeStr.indexOf('(') >= 0) { - sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, '')); - } - if (!sizeValue) { - sizeValue = parseInt(sizeStr); - } - if (sizeValue) { - const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase(); - result.push({ - device: BSDName, - type: 'NVMe', - name: util.getValue(lines, 'Model', ':', true).trim(), - vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()), - size: sizeValue, - bytesPerSector: null, - totalCylinders: null, - totalHeads: null, - totalSectors: null, - totalTracks: null, - tracksPerCylinder: null, - sectorsPerTrack: null, - firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(), - serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(), - interfaceType: ('PCIe ' + linkWidth).trim(), - smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown', - temperature: null, - BSDName: BSDName - }); - cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;'; - } - } - }); - } catch (e) { - util.noop(); - } - // USB Drives - try { - let devices = linesUSB.join('\n').replaceAll('Media:\n ', 'Model:').split('\n\n Product ID:'); - devices.shift(); - devices.forEach(function (device) { - let lines = device.split('\n'); - const sizeStr = util.getValue(lines, 'Capacity', ':', true).trim(); - const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim(); - if (sizeStr) { - let sizeValue = 0; - if (sizeStr.indexOf('(') >= 0) { - sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, '')); - } - if (!sizeValue) { - sizeValue = parseInt(sizeStr); - } - if (sizeValue) { - const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase(); - result.push({ - device: BSDName, - type: 'USB', - name: util.getValue(lines, 'Model', ':', true).trim().replaceAll(':', ''), - vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()), - size: sizeValue, - bytesPerSector: null, - totalCylinders: null, - totalHeads: null, - totalSectors: null, - totalTracks: null, - tracksPerCylinder: null, - sectorsPerTrack: null, - firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(), - serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(), - interfaceType: 'USB', - smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown', - temperature: null, - BSDName: BSDName - }); - cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;'; - } - } - }); - } catch (e) { - util.noop(); - } - if (cmd) { - cmd = cmd + 'printf "\n"'; - exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) { - let lines = stdout.toString().split('\n'); - lines.forEach(line => { - if (line) { - let parts = line.split('|'); - if (parts.length === 2) { - let BSDName = parts[0]; - parts[1] = parts[1].trim(); - let parts2 = parts[1].split(':'); - if (parts2.length === 2) { - parts2[1] = parts2[1].trim(); - let status = parts2[1].toLowerCase(); - for (let i = 0; i < result.length; i++) { - if (result[i].BSDName === BSDName) { - result[i].smartStatus = (status === 'not supported' ? 'not supported' : (status === 'verified' ? 'Ok' : (status === 'failing' ? 'Predicted Failure' : 'unknown'))); - } - } - } - } - } - }); - for (let i = 0; i < result.length; i++) { - delete result[i].BSDName; - } - if (callback) { - callback(result); - } - resolve(result); - }); - } else { - for (let i = 0; i < result.length; i++) { - delete result[i].BSDName; - } - if (callback) { - callback(result); - } - resolve(result); - } - } - }); - } - if (_windows) { - try { - const workload = []; - workload.push(util.powerShell('Get-WmiObject Win32_DiskDrive | fl *')); - workload.push(util.powerShell('Get-PhysicalDisk | Format-List')); - if (util.smartMonToolsInstalled()) { - try { - const smartDev = JSON.parse(execSync('smartctl --scan -j')); - if (smartDev && smartDev.devices && smartDev.devices.length > 0) { - smartDev.devices.forEach((dev) => { - workload.push(execPromiseSave(`smartctl -j -a ${dev.name}`, util.execOptsWin)); - }); - } - } catch (e) { - util.noop(); - } - } - util.promiseAll( - workload - ).then(data => { - let devices = data.results[0].toString().split(/\n\s*\n/); - devices.forEach(function (device) { - let lines = device.split('\r\n'); - const size = util.getValue(lines, 'Size', ':').trim(); - const status = util.getValue(lines, 'Status', ':').trim().toLowerCase(); - if (size) { - result.push({ - device: util.getValue(lines, 'PNPDeviceId', ':'), - type: device.indexOf('SSD') > -1 ? 'SSD' : 'HD', // just a starting point ... better: MSFT_PhysicalDisk - Media Type ... see below - name: util.getValue(lines, 'Caption', ':'), - vendor: getVendorFromModel(util.getValue(lines, 'Caption', ':', true).trim()), - size: parseInt(size), - bytesPerSector: parseInt(util.getValue(lines, 'BytesPerSector', ':')), - totalCylinders: parseInt(util.getValue(lines, 'TotalCylinders', ':')), - totalHeads: parseInt(util.getValue(lines, 'TotalHeads', ':')), - totalSectors: parseInt(util.getValue(lines, 'TotalSectors', ':')), - totalTracks: parseInt(util.getValue(lines, 'TotalTracks', ':')), - tracksPerCylinder: parseInt(util.getValue(lines, 'TracksPerCylinder', ':')), - sectorsPerTrack: parseInt(util.getValue(lines, 'SectorsPerTrack', ':')), - firmwareRevision: util.getValue(lines, 'FirmwareRevision', ':').trim(), - serialNum: util.getValue(lines, 'SerialNumber', ':').trim(), - interfaceType: util.getValue(lines, 'InterfaceType', ':').trim(), - smartStatus: (status === 'ok' ? 'Ok' : (status === 'degraded' ? 'Degraded' : (status === 'pred fail' ? 'Predicted Failure' : 'Unknown'))), - temperature: null, - }); - } - }); - devices = data.results[1].split(/\n\s*\n/); - devices.forEach(function (device) { - let lines = device.split('\r\n'); - const serialNum = util.getValue(lines, 'SerialNumber', ':').trim(); - const name = util.getValue(lines, 'FriendlyName', ':').trim().replace('Msft ', 'Microsoft'); - const size = util.getValue(lines, 'Size', ':').trim(); - const model = util.getValue(lines, 'Model', ':').trim(); - const interfaceType = util.getValue(lines, 'BusType', ':').trim(); - let mediaType = util.getValue(lines, 'MediaType', ':').trim(); - if (mediaType === '3' || mediaType === 'HDD') { mediaType = 'HD'; } - if (mediaType === '4') { mediaType = 'SSD'; } - if (mediaType === '5') { mediaType = 'SCM'; } - if (mediaType === 'Unspecified' && (model.toLowerCase().indexOf('virtual') > -1 || model.toLowerCase().indexOf('vbox') > -1)) { mediaType = 'Virtual'; } - if (size) { - let i = util.findObjectByKey(result, 'serialNum', serialNum); - if (i === -1 || serialNum === '') { - i = util.findObjectByKey(result, 'name', name); - } - if (i != -1) { - result[i].type = mediaType; - result[i].interfaceType = interfaceType; - } - } - }); - // S.M.A.R.T - data.results.shift(); - data.results.shift(); - data.results.forEach((smartStr) => { - const smartData = JSON.parse(smartStr); - if (smartData.serial_number) { - const serialNum = smartData.serial_number; - let i = util.findObjectByKey(result, 'serialNum', serialNum); - if (i != -1) { - result[i].smartStatus = (smartData.smart_status.passed ? 'Ok' : (smartData.smart_status.passed === false ? 'Predicted Failure' : 'unknown')); - if (smartData.temperature && smartData.temperature.current) { - result[i].temperature = smartData.temperature.current; - } - result[i].smartData = smartData; - } - } - }); - if (callback) { - callback(result); - } - resolve(result); - }); - } catch (e) { - if (callback) { callback(result); } - resolve(result); - } - } - }); - }); -} - -exports.diskLayout = diskLayout; +'use strict'; +// @ts-check +// ================================================================================== +// filesystem.js +// ---------------------------------------------------------------------------------- +// Description: System Information - library +// for Node.js +// Copyright: (c) 2014 - 2021 +// Author: Sebastian Hildebrandt +// ---------------------------------------------------------------------------------- +// License: MIT +// ================================================================================== +// 8. File System +// ---------------------------------------------------------------------------------- + +const util = require('./util'); +const fs = require('fs'); + +const exec = require('child_process').exec; +const execSync = require('child_process').execSync; +const execPromiseSave = util.promisifySave(require('child_process').exec); + +let _platform = process.platform; + +const _linux = (_platform === 'linux'); +const _darwin = (_platform === 'darwin'); +const _windows = (_platform === 'win32'); +const _freebsd = (_platform === 'freebsd'); +const _openbsd = (_platform === 'openbsd'); +const _netbsd = (_platform === 'netbsd'); +const _sunos = (_platform === 'sunos'); + +let _fs_speed = {}; +let _disk_io = {}; + +// -------------------------- +// FS - mounted file systems + +function fsSize(callback) { + + let macOsDisks = []; + + function getmacOsFsType(fs) { + if (!fs.startsWith('/')) { return 'NFS'; } + const parts = fs.split('/'); + const fsShort = parts[parts.length - 1]; + const macOsDisksSingle = macOsDisks.filter(item => item.indexOf(fsShort) >= 0); + if (macOsDisksSingle.length === 1 && macOsDisksSingle[0].indexOf('APFS') >= 0) { return 'APFS'; } + return 'HFS'; + } + + function parseDf(lines) { + let data = []; + lines.forEach(function (line) { + if (line !== '') { + line = line.replace(/ +/g, ' ').split(' '); + if (line && ((line[0].startsWith('/')) || (line[6] && line[6] === '/') || (line[0].indexOf('/') > 0) || (line[0].indexOf(':') === 1))) { + const fs = line[0]; + const fsType = ((_linux || _freebsd || _openbsd || _netbsd) ? line[1] : getmacOsFsType(line[0])); + const size = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[2] : line[1])) * 1024; + const used = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[3] : line[2])) * 1024; + const available = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[4] : line[3])) * 1024; + const use = parseFloat((100.0 * (used / (used + available))).toFixed(2)); + line.splice(0, (_linux || _freebsd || _openbsd || _netbsd) ? 6 : 5); + const mount = line.join(' '); + // const mount = line[line.length - 1]; + if (!data.find(el => (el.fs === fs && el.type === fsType))) { + data.push({ + fs, + type: fsType, + size, + used, + available, + use, + mount + }); + } + } + } + }); + return data; + } + + return new Promise((resolve) => { + process.nextTick(() => { + let data = []; + if (_linux || _freebsd || _openbsd || _netbsd || _darwin) { + let cmd = ''; + if (_darwin) { + cmd = 'df -kP'; + try { + macOsDisks = execSync('diskutil list').toString().split('\n').filter(line => { + return !line.startsWith('/') && line.indexOf(':') > 0; + }); + } catch (e) { + macOsDisks = []; + } + } + if (_linux) { cmd = 'df -lkPTx squashfs | grep -E "^/|^.\\:"'; } + if (_freebsd || _openbsd || _netbsd) { cmd = 'df -lkPT'; } + exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + data = parseDf(lines); + if (callback) { + callback(data); + } + resolve(data); + } else { + exec('df -kPT', { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + data = parseDf(lines); + } + if (callback) { + callback(data); + } + resolve(data); + }); + } + }); + } + if (_sunos) { + if (callback) { callback(data); } + resolve(data); + } + if (_windows) { + try { + util.wmic('logicaldisk get Caption,FileSystem,FreeSpace,Size').then((stdout) => { + let lines = stdout.split('\r\n').filter(line => line.trim() !== '').filter((line, idx) => idx > 0); + lines.forEach(function (line) { + if (line !== '') { + line = line.trim().split(/\s\s+/); + if (line.length >= 4 && parseInt(line[3], 10)) { + data.push({ + fs: line[0], + type: line[1], + size: parseInt(line[3], 10), + used: parseInt(line[3], 10) - parseInt(line[2], 10), + available: parseInt(line[2], 10), + use: parseFloat(((100.0 * (parseInt(line[3]) - parseInt(line[2]))) / parseInt(line[3])).toFixed(2)), + mount: line[0] + }); + } + } + }); + if (callback) { + callback(data); + } + resolve(data); + }); + } catch (e) { + if (callback) { callback(data); } + resolve(data); + } + } + }); + }); +} + +exports.fsSize = fsSize; + +// -------------------------- +// FS - open files count + +function fsOpenFiles(callback) { + + return new Promise((resolve) => { + process.nextTick(() => { + const result = { + max: null, + allocated: null, + available: null + }; + if (_freebsd || _openbsd || _netbsd || _darwin) { + let cmd = 'sysctl -a | grep \'kern.*files\''; + exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + result.max = parseInt(util.getValue(lines, 'kern.maxfiles', ':'), 10); + result.allocated = parseInt(util.getValue(lines, 'kern.num_files', ':'), 10); + result.available = result.max - result.allocated; + } + if (callback) { + callback(result); + } + resolve(result); + }); + } + if (_linux) { + fs.readFile('/proc/sys/fs/file-nr', function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + if (lines[0]) { + const parts = lines[0].replace(/\s+/g, ' ').split(' '); + if (parts.length === 3) { + result.allocated = parseInt(parts[0], 10); + result.available = parseInt(parts[1], 10); + result.max = parseInt(parts[2], 10); + if (!result.available) { result.available = result.max - result.allocated; } + } + } + if (callback) { + callback(result); + } + resolve(result); + } else { + fs.readFile('/proc/sys/fs/file-max', function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + if (lines[0]) { + result.max = parseInt(lines[0], 10); + } + } + if (callback) { + callback(result); + } + resolve(result); + }); + } + }); + } + if (_sunos) { + if (callback) { callback(null); } + resolve(null); + } + if (_windows) { + if (callback) { callback(null); } + resolve(null); + } + }); + }); +} + +exports.fsOpenFiles = fsOpenFiles; + +// -------------------------- +// disks + +function parseBytes(s) { + return parseInt(s.substr(s.indexOf(' (') + 2, s.indexOf(' Bytes)') - 10)); +} + +function parseDevices(lines) { + let devices = []; + let i = 0; + lines.forEach(line => { + if (line.length > 0) { + if (line[0] === '*') { + i++; + } else { + let parts = line.split(':'); + if (parts.length > 1) { + if (!devices[i]) { + devices[i] = { + name: '', + identifier: '', + type: 'disk', + fsType: '', + mount: '', + size: 0, + physical: 'HDD', + uuid: '', + label: '', + model: '', + serial: '', + removable: false, + protocol: '' + }; + } + parts[0] = parts[0].trim().toUpperCase().replace(/ +/g, ''); + parts[1] = parts[1].trim(); + if ('DEVICEIDENTIFIER' === parts[0]) { devices[i].identifier = parts[1]; } + if ('DEVICENODE' === parts[0]) { devices[i].name = parts[1]; } + if ('VOLUMENAME' === parts[0]) { + if (parts[1].indexOf('Not applicable') === -1) { devices[i].label = parts[1]; } + } + if ('PROTOCOL' === parts[0]) { devices[i].protocol = parts[1]; } + if ('DISKSIZE' === parts[0]) { devices[i].size = parseBytes(parts[1]); } + if ('FILESYSTEMPERSONALITY' === parts[0]) { devices[i].fsType = parts[1]; } + if ('MOUNTPOINT' === parts[0]) { devices[i].mount = parts[1]; } + if ('VOLUMEUUID' === parts[0]) { devices[i].uuid = parts[1]; } + if ('READ-ONLYMEDIA' === parts[0] && parts[1] === 'Yes') { devices[i].physical = 'CD/DVD'; } + if ('SOLIDSTATE' === parts[0] && parts[1] === 'Yes') { devices[i].physical = 'SSD'; } + if ('VIRTUAL' === parts[0]) { devices[i].type = 'virtual'; } + if ('REMOVABLEMEDIA' === parts[0]) { devices[i].removable = (parts[1] === 'Removable'); } + if ('PARTITIONTYPE' === parts[0]) { devices[i].type = 'part'; } + if ('DEVICE/MEDIANAME' === parts[0]) { devices[i].model = parts[1]; } + } + } + } + }); + return devices; +} + +function parseBlk(lines) { + let data = []; + + lines.filter(line => line !== '').forEach((line) => { + try { + line = decodeURIComponent(line.replace(/\\x/g, '%')); + line = line.replace(/\\/g, '\\\\'); + let disk = JSON.parse(line); + data.push({ + 'name': disk.name, + 'type': disk.type, + 'fsType': disk.fsType, + 'mount': disk.mountpoint, + 'size': parseInt(disk.size), + 'physical': (disk.type === 'disk' ? (disk.rota === '0' ? 'SSD' : 'HDD') : (disk.type === 'rom' ? 'CD/DVD' : '')), + 'uuid': disk.uuid, + 'label': disk.label, + 'model': disk.model, + 'serial': disk.serial, + 'removable': disk.rm === '1', + 'protocol': disk.tran, + 'group': disk.group, + }); + } catch (e) { + util.noop(); + } + }); + data = util.unique(data); + data = util.sortByKey(data, ['type', 'name']); + return data; +} + +function blkStdoutToObject(stdout) { + return stdout.toString() + .replace(/NAME=/g, '{"name":') + .replace(/FSTYPE=/g, ',"fsType":') + .replace(/TYPE=/g, ',"type":') + .replace(/SIZE=/g, ',"size":') + .replace(/MOUNTPOINT=/g, ',"mountpoint":') + .replace(/UUID=/g, ',"uuid":') + .replace(/ROTA=/g, ',"rota":') + .replace(/RO=/g, ',"ro":') + .replace(/RM=/g, ',"rm":') + .replace(/TRAN=/g, ',"tran":') + .replace(/SERIAL=/g, ',"serial":') + .replace(/LABEL=/g, ',"label":') + .replace(/MODEL=/g, ',"model":') + .replace(/OWNER=/g, ',"owner":') + .replace(/GROUP=/g, ',"group":') + .replace(/\n/g, '}\n'); +} + +function blockDevices(callback) { + + return new Promise((resolve) => { + process.nextTick(() => { + let data = []; + if (_linux) { + // see https://wiki.ubuntuusers.de/lsblk/ + // exec("lsblk -bo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,TRAN,SERIAL,LABEL,MODEL,OWNER,GROUP,MODE,ALIGNMENT,MIN-IO,OPT-IO,PHY-SEC,LOG-SEC,SCHED,RQ-SIZE,RA,WSAME", function (error, stdout) { + exec('lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,TRAN,SERIAL,LABEL,MODEL,OWNER 2>/dev/null', { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + let lines = blkStdoutToObject(stdout).split('\n'); + data = parseBlk(lines); + if (callback) { + callback(data); + } + resolve(data); + } else { + exec('lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,LABEL,MODEL,OWNER 2>/dev/null', { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + let lines = blkStdoutToObject(stdout).split('\n'); + data = parseBlk(lines); + } + if (callback) { + callback(data); + } + resolve(data); + }); + } + }); + } + if (_darwin) { + exec('diskutil info -all', { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + // parse lines into temp array of devices + data = parseDevices(lines); + } + if (callback) { + callback(data); + } + resolve(data); + }); + } + if (_sunos) { + if (callback) { callback(data); } + resolve(data); + } + if (_windows) { + let drivetypes = ['Unknown', 'NoRoot', 'Removable', 'Local', 'Network', 'CD/DVD', 'RAM']; + try { + // util.wmic('logicaldisk get Caption,Description,DeviceID,DriveType,FileSystem,FreeSpace,Name,Size,VolumeName,VolumeSerialNumber /value').then((stdout, error) => { + util.powerShell('Get-CimInstance -ClassName Win32_LogicalDisk | Format-List *').then((stdout, error) => { + if (!error) { + let devices = stdout.toString().split(/\n\s*\n/); + devices.forEach(function (device) { + let lines = device.split('\r\n'); + let drivetype = util.getValue(lines, 'drivetype', ':'); + if (drivetype) { + data.push({ + name: util.getValue(lines, 'name', ':'), + identifier: util.getValue(lines, 'caption', ':'), + type: 'disk', + fsType: util.getValue(lines, 'filesystem', ':').toLowerCase(), + mount: util.getValue(lines, 'caption', ':'), + size: util.getValue(lines, 'size', ':'), + physical: (drivetype >= 0 && drivetype <= 6) ? drivetypes[drivetype] : drivetypes[0], + uuid: util.getValue(lines, 'volumeserialnumber', ':'), + label: util.getValue(lines, 'volumename', ':'), + model: '', + serial: util.getValue(lines, 'volumeserialnumber', ':'), + removable: drivetype === '2', + protocol: '' + }); + } + }); + } + if (callback) { + callback(data); + } + resolve(data); + }); + } catch (e) { + if (callback) { callback(data); } + resolve(data); + } + } + if (_freebsd || _openbsd || _netbsd) { + // will follow + if (callback) { callback(null); } + resolve(null); + } + + }); + }); +} + +exports.blockDevices = blockDevices; + +// -------------------------- +// FS - speed + +function calcFsSpeed(rx, wx) { + let result = { + rx: 0, + wx: 0, + tx: 0, + rx_sec: null, + wx_sec: null, + tx_sec: null, + ms: 0 + }; + + if (_fs_speed && _fs_speed.ms) { + result.rx = rx; + result.wx = wx; + result.tx = result.rx + result.wx; + result.ms = Date.now() - _fs_speed.ms; + result.rx_sec = (result.rx - _fs_speed.bytes_read) / (result.ms / 1000); + result.wx_sec = (result.wx - _fs_speed.bytes_write) / (result.ms / 1000); + result.tx_sec = result.rx_sec + result.wx_sec; + _fs_speed.rx_sec = result.rx_sec; + _fs_speed.wx_sec = result.wx_sec; + _fs_speed.tx_sec = result.tx_sec; + _fs_speed.bytes_read = result.rx; + _fs_speed.bytes_write = result.wx; + _fs_speed.bytes_overall = result.rx + result.wx; + _fs_speed.ms = Date.now(); + _fs_speed.last_ms = result.ms; + } else { + result.rx = rx; + result.wx = wx; + result.tx = result.rx + result.wx; + _fs_speed.rx_sec = null; + _fs_speed.wx_sec = null; + _fs_speed.tx_sec = null; + _fs_speed.bytes_read = result.rx; + _fs_speed.bytes_write = result.wx; + _fs_speed.bytes_overall = result.rx + result.wx; + _fs_speed.ms = Date.now(); + _fs_speed.last_ms = 0; + } + return result; +} + +function fsStats(callback) { + + return new Promise((resolve) => { + process.nextTick(() => { + if (_windows) { + return resolve(null); + } + + let result = { + rx: 0, + wx: 0, + tx: 0, + rx_sec: null, + wx_sec: null, + tx_sec: null, + ms: 0 + }; + + let rx = 0; + let wx = 0; + if ((_fs_speed && !_fs_speed.ms) || (_fs_speed && _fs_speed.ms && Date.now() - _fs_speed.ms >= 500)) { + if (_linux) { + // exec("df -k | grep /dev/", function(error, stdout) { + exec('lsblk -r 2>/dev/null | grep /', { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + let fs_filter = []; + lines.forEach(function (line) { + if (line !== '') { + line = line.trim().split(' '); + if (fs_filter.indexOf(line[0]) === -1) { fs_filter.push(line[0]); } + } + }); + + let output = fs_filter.join('|'); + exec('cat /proc/diskstats | egrep "' + output + '"', { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + lines.forEach(function (line) { + line = line.trim(); + if (line !== '') { + line = line.replace(/ +/g, ' ').split(' '); + + rx += parseInt(line[5]) * 512; + wx += parseInt(line[9]) * 512; + } + }); + result = calcFsSpeed(rx, wx); + } + if (callback) { + callback(result); + } + resolve(result); + }); + } else { + if (callback) { + callback(result); + } + resolve(result); + } + }); + } + if (_darwin) { + exec('ioreg -c IOBlockStorageDriver -k Statistics -r -w0 | sed -n "/IOBlockStorageDriver/,/Statistics/p" | grep "Statistics" | tr -cd "01234567890,\n"', { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + lines.forEach(function (line) { + line = line.trim(); + if (line !== '') { + line = line.split(','); + + rx += parseInt(line[2]); + wx += parseInt(line[9]); + } + }); + result = calcFsSpeed(rx, wx); + } + if (callback) { + callback(result); + } + resolve(result); + }); + } + } else { + result.ms = _fs_speed.last_ms; + result.rx = _fs_speed.bytes_read; + result.wx = _fs_speed.bytes_write; + result.tx = _fs_speed.bytes_read + _fs_speed.bytes_write; + result.rx_sec = _fs_speed.rx_sec; + result.wx_sec = _fs_speed.wx_sec; + result.tx_sec = _fs_speed.tx_sec; + if (callback) { + callback(result); + } + resolve(result); + } + }); + }); +} + +exports.fsStats = fsStats; + +function calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime) { + let result = { + rIO: 0, + wIO: 0, + tIO: 0, + rIO_sec: null, + wIO_sec: null, + tIO_sec: null, + rWaitTime: 0, + wWaitTime: 0, + tWaitTime: 0, + rWaitPercent: null, + wWaitPercent: null, + tWaitPercent: null, + ms: 0 + }; + if (_disk_io && _disk_io.ms) { + result.rIO = rIO; + result.wIO = wIO; + result.tIO = rIO + wIO; + result.ms = Date.now() - _disk_io.ms; + result.rIO_sec = (result.rIO - _disk_io.rIO) / (result.ms / 1000); + result.wIO_sec = (result.wIO - _disk_io.wIO) / (result.ms / 1000); + result.tIO_sec = result.rIO_sec + result.wIO_sec; + result.rWaitTime = rWaitTime; + result.wWaitTime = wWaitTime; + result.tWaitTime = tWaitTime; + result.rWaitPercent = (result.rWaitTime - _disk_io.rWaitTime) * 100 / (result.ms); + result.wWaitPercent = (result.wWaitTime - _disk_io.wWaitTime) * 100 / (result.ms); + result.tWaitPercent = (result.tWaitTime - _disk_io.tWaitTime) * 100 / (result.ms); + _disk_io.rIO = rIO; + _disk_io.wIO = wIO; + _disk_io.rIO_sec = result.rIO_sec; + _disk_io.wIO_sec = result.wIO_sec; + _disk_io.tIO_sec = result.tIO_sec; + _disk_io.rWaitTime = rWaitTime; + _disk_io.wWaitTime = wWaitTime; + _disk_io.tWaitTime = tWaitTime; + _disk_io.rWaitPercent = result.rWaitPercent; + _disk_io.wWaitPercent = result.wWaitPercent; + _disk_io.tWaitPercent = result.tWaitPercent; + _disk_io.last_ms = result.ms; + _disk_io.ms = Date.now(); + } else { + result.rIO = rIO; + result.wIO = wIO; + result.tIO = rIO + wIO; + result.rWaitTime = rWaitTime; + result.wWaitTime = wWaitTime; + result.tWaitTime = tWaitTime; + _disk_io.rIO = rIO; + _disk_io.wIO = wIO; + _disk_io.rIO_sec = null; + _disk_io.wIO_sec = null; + _disk_io.tIO_sec = null; + _disk_io.rWaitTime = rWaitTime; + _disk_io.wWaitTime = wWaitTime; + _disk_io.tWaitTime = tWaitTime; + _disk_io.rWaitPercent = null; + _disk_io.wWaitPercent = null; + _disk_io.tWaitPercent = null; + _disk_io.last_ms = 0; + _disk_io.ms = Date.now(); + } + return result; +} + +function disksIO(callback) { + + return new Promise((resolve) => { + process.nextTick(() => { + if (_windows) { + return resolve(null); + } + if (_sunos) { + return resolve(null); + } + + let result = { + rIO: 0, + wIO: 0, + tIO: 0, + rIO_sec: null, + wIO_sec: null, + tIO_sec: null, + rWaitTime: 0, + wWaitTime: 0, + tWaitTime: 0, + rWaitPercent: null, + wWaitPercent: null, + tWaitPercent: null, + ms: 0 + }; + let rIO = 0; + let wIO = 0; + let rWaitTime = 0; + let wWaitTime = 0; + let tWaitTime = 0; + + if ((_disk_io && !_disk_io.ms) || (_disk_io && _disk_io.ms && Date.now() - _disk_io.ms >= 500)) { + if (_linux || _freebsd || _openbsd || _netbsd) { + // prints Block layer statistics for all mounted volumes + // var cmd = "for mount in `lsblk | grep / | sed -r 's/│ └─//' | cut -d ' ' -f 1`; do cat /sys/block/$mount/stat | sed -r 's/ +/;/g' | sed -r 's/^;//'; done"; + // var cmd = "for mount in `lsblk | grep / | sed 's/[│└─├]//g' | awk '{$1=$1};1' | cut -d ' ' -f 1 | sort -u`; do cat /sys/block/$mount/stat | sed -r 's/ +/;/g' | sed -r 's/^;//'; done"; + let cmd = 'for mount in `lsblk 2>/dev/null | grep " disk " | sed "s/[│└─├]//g" | awk \'{$1=$1};1\' | cut -d " " -f 1 | sort -u`; do cat /sys/block/$mount/stat | sed -r "s/ +/;/g" | sed -r "s/^;//"; done'; + + exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + let lines = stdout.split('\n'); + lines.forEach(function (line) { + // ignore empty lines + if (!line) { return; } + + // sum r/wIO of all disks to compute all disks IO + let stats = line.split(';'); + rIO += parseInt(stats[0]); + wIO += parseInt(stats[4]); + rWaitTime += parseInt(stats[3]); + wWaitTime += parseInt(stats[7]); + tWaitTime += parseInt(stats[10]); + }); + result = calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime); + + if (callback) { + callback(result); + } + resolve(result); + } else { + if (callback) { + callback(result); + } + resolve(result); + } + }); + } + if (_darwin) { + exec('ioreg -c IOBlockStorageDriver -k Statistics -r -w0 | sed -n "/IOBlockStorageDriver/,/Statistics/p" | grep "Statistics" | tr -cd "01234567890,\n"', { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + lines.forEach(function (line) { + line = line.trim(); + if (line !== '') { + line = line.split(','); + + rIO += parseInt(line[10]); + wIO += parseInt(line[0]); + } + }); + result = calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime); + } + if (callback) { + callback(result); + } + resolve(result); + }); + } + } else { + result.rIO = _disk_io.rIO; + result.wIO = _disk_io.wIO; + result.tIO = _disk_io.rIO + _disk_io.wIO; + result.ms = _disk_io.last_ms; + result.rIO_sec = _disk_io.rIO_sec; + result.wIO_sec = _disk_io.wIO_sec; + result.tIO_sec = _disk_io.tIO_sec; + result.rWaitTime = _disk_io.rWaitTime; + result.wWaitTime = _disk_io.wWaitTime; + result.tWaitTime = _disk_io.tWaitTime; + result.rWaitPercent = _disk_io.rWaitPercent; + result.wWaitPercent = _disk_io.wWaitPercent; + result.tWaitPercent = _disk_io.tWaitPercent; + if (callback) { + callback(result); + } + resolve(result); + } + }); + }); +} + +exports.disksIO = disksIO; + +function diskLayout(callback) { + + function getVendorFromModel(model) { + const diskManufacturers = [ + { pattern: '^WESTERN.+', manufacturer: 'Western Digital' }, + { pattern: '^WDC.+', manufacturer: 'Western Digital' }, + { pattern: 'WD.+', manufacturer: 'Western Digital' }, + { pattern: '^TOSHIBA.+', manufacturer: 'Toshiba' }, + { pattern: '^HITACHI.+', manufacturer: 'Hitachi' }, + { pattern: '^IC.+', manufacturer: 'Hitachi' }, + { pattern: '^HTS.+', manufacturer: 'Hitachi' }, + { pattern: '^SANDISK.+', manufacturer: 'SanDisk' }, + { pattern: '^KINGSTON.+', manufacturer: 'Kingston Technology' }, + { pattern: '^SONY.+', manufacturer: 'Sony' }, + { pattern: '^TRANSCEND.+', manufacturer: 'Transcend' }, + { pattern: 'SAMSUNG.+', manufacturer: 'Samsung' }, + { pattern: '^ST(?!I\\ ).+', manufacturer: 'Seagate' }, + { pattern: '^STI\\ .+', manufacturer: 'SimpleTech' }, + { pattern: '^D...-.+', manufacturer: 'IBM' }, + { pattern: '^IBM.+', manufacturer: 'IBM' }, + { pattern: '^FUJITSU.+', manufacturer: 'Fujitsu' }, + { pattern: '^MP.+', manufacturer: 'Fujitsu' }, + { pattern: '^MK.+', manufacturer: 'Toshiba' }, + { pattern: '^MAXTOR.+', manufacturer: 'Maxtor' }, + { pattern: '^Pioneer.+', manufacturer: 'Pioneer' }, + { pattern: '^PHILIPS.+', manufacturer: 'Philips' }, + { pattern: '^QUANTUM.+', manufacturer: 'Quantum Technology' }, + { pattern: 'FIREBALL.+', manufacturer: 'Quantum Technology' }, + { pattern: '^VBOX.+', manufacturer: 'VirtualBox' }, + { pattern: 'CORSAIR.+', manufacturer: 'Corsair Components' }, + { pattern: 'CRUCIAL.+', manufacturer: 'Crucial' }, + { pattern: 'ECM.+', manufacturer: 'ECM' }, + { pattern: 'INTEL.+', manufacturer: 'INTEL' }, + { pattern: '.+EVO', manufacturer: 'Samsung' }, + { pattern: 'APPLE.+', manufacturer: 'Apple' }, + ]; + + let result = ''; + if (model) { + model = model.toUpperCase(); + diskManufacturers.forEach((manufacturer) => { + const re = RegExp(manufacturer.pattern); + if (re.test(model)) { result = manufacturer.manufacturer; } + }); + } + return result; + } + + return new Promise((resolve) => { + process.nextTick(() => { + + const commitResult = res => { + for (let i = 0; i < res.length; i++) { + delete res[i].BSDName; + } + if (callback) { + callback(res); + } + resolve(res); + }; + + let result = []; + let cmd = ''; + + if (_linux) { + let cmdFullSmart = ''; + + exec('export LC_ALL=C; lsblk -ablJO 2>/dev/null; unset LC_ALL', { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + try { + const out = stdout.toString().trim(); + let devices = []; + try { + const outJSON = JSON.parse(out); + if (outJSON && {}.hasOwnProperty.call(outJSON, 'blockdevices')) { + devices = outJSON.blockdevices.filter(item => { return (item.type === 'disk') && item.size > 0 && (item.model !== null || (item.mountpoint === null && item.label === null && item.fsType === null && item.parttype === null)); }); + } + } catch (e) { + // fallback to older version of lsblk + const out2 = execSync('export LC_ALL=C; lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,LABEL,MODEL,OWNER,GROUP 2>/dev/null; unset LC_ALL').toString(); + let lines = blkStdoutToObject(out2).split('\n'); + const data = parseBlk(lines); + devices = data.filter(item => { return (item.type === 'disk') && item.size > 0 && ((item.model !== null && item.model !== '') || (item.mount === '' && item.label === '' && item.fsType === '')); }); + } + devices.forEach((device) => { + let mediumType = ''; + const BSDName = '/dev/' + device.name; + const logical = device.name; + try { + mediumType = execSync('cat /sys/block/' + logical + '/queue/rotational 2>/dev/null').toString().split('\n')[0]; + } catch (e) { + util.noop(); + } + let interfaceType = device.tran ? device.tran.toUpperCase().trim() : ''; + if (interfaceType === 'NVME') { + mediumType = '2'; + interfaceType = 'PCIe'; + } + result.push({ + device: BSDName, + type: (mediumType === '0' ? 'SSD' : (mediumType === '1' ? 'HD' : (mediumType === '2' ? 'NVMe' : (device.model && device.model.indexOf('SSD') > -1 ? 'SSD' : (device.model && device.model.indexOf('NVM') > -1 ? 'NVMe' : 'HD'))))), + name: device.model || '', + vendor: getVendorFromModel(device.model) || (device.vendor ? device.vendor.trim() : ''), + size: device.size || 0, + bytesPerSector: null, + totalCylinders: null, + totalHeads: null, + totalSectors: null, + totalTracks: null, + tracksPerCylinder: null, + sectorsPerTrack: null, + firmwareRevision: device.rev ? device.rev.trim() : '', + serialNum: device.serial ? device.serial.trim() : '', + interfaceType: interfaceType, + smartStatus: 'unknown', + temperature: null, + BSDName: BSDName + }); + cmd += `printf "\n${BSDName}|"; smartctl -H ${BSDName} | grep overall;`; + cmdFullSmart += `${cmdFullSmart ? 'printf ",";' : ''}smartctl -a -j ${BSDName};`; + }); + } catch (e) { + util.noop(); + } + } + // check S.M.A.R.T. status + if (cmdFullSmart) { + exec(cmdFullSmart, { maxBuffer: 1024 * 1024 }, function (error, stdout) { + try { + const data = JSON.parse(`[${stdout}]`); + data.forEach(disk => { + const diskBSDName = disk.smartctl.argv[disk.smartctl.argv.length - 1]; + + for (let i = 0; i < result.length; i++) { + if (result[i].BSDName === diskBSDName) { + result[i].smartStatus = (disk.smart_status.passed ? 'Ok' : (disk.smart_status.passed === false ? 'Predicted Failure' : 'unknown')); + if (disk.temperature && disk.temperature.current) { + result[i].temperature = disk.temperature.current; + } + result[i].smartData = disk; + } + } + }); + commitResult(result); + } catch (e) { + if (cmd) { + cmd = cmd + 'printf "\n"'; + exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) { + let lines = stdout.toString().split('\n'); + lines.forEach(line => { + if (line) { + let parts = line.split('|'); + if (parts.length === 2) { + let BSDName = parts[0]; + parts[1] = parts[1].trim(); + let parts2 = parts[1].split(':'); + if (parts2.length === 2) { + parts2[1] = parts2[1].trim(); + let status = parts2[1].toLowerCase(); + for (let i = 0; i < result.length; i++) { + if (result[i].BSDName === BSDName) { + result[i].smartStatus = (status === 'passed' ? 'Ok' : (status === 'failed!' ? 'Predicted Failure' : 'unknown')); + } + } + } + } + } + }); + commitResult(result); + }); + } else { + commitResult(result); + } + } + }); + } else { + commitResult(result); + } + }); + } + if (_freebsd || _openbsd || _netbsd) { + if (callback) { callback(result); } + resolve(result); + } + if (_sunos) { + if (callback) { callback(result); } + resolve(result); + } + if (_darwin) { + exec('system_profiler SPSerialATADataType SPNVMeDataType SPUSBDataType', { maxBuffer: 1024 * 1024 }, function (error, stdout) { + if (!error) { + // split by type: + let lines = stdout.toString().split('\n'); + let linesSATA = []; + let linesNVMe = []; + let linesUSB = []; + let dataType = 'SATA'; + lines.forEach(line => { + if (line === 'NVMExpress:') { dataType = 'NVMe'; } + else if (line === 'USB:') { dataType = 'USB'; } + else if (line === 'SATA/SATA Express:') { dataType = 'SATA'; } + else if (dataType === 'SATA') { linesSATA.push(line); } + else if (dataType === 'NVMe') { linesNVMe.push(line); } + else if (dataType === 'USB') { linesUSB.push(line); } + }); + try { + // Serial ATA Drives + let devices = linesSATA.join('\n').split(' Physical Interconnect: '); + devices.shift(); + devices.forEach(function (device) { + device = 'InterfaceType: ' + device; + let lines = device.split('\n'); + const mediumType = util.getValue(lines, 'Medium Type', ':', true).trim(); + const sizeStr = util.getValue(lines, 'capacity', ':', true).trim(); + const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim(); + if (sizeStr) { + let sizeValue = 0; + if (sizeStr.indexOf('(') >= 0) { + sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, '')); + } + if (!sizeValue) { + sizeValue = parseInt(sizeStr); + } + if (sizeValue) { + const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase(); + result.push({ + device: BSDName, + type: mediumType.startsWith('Solid') ? 'SSD' : 'HD', + name: util.getValue(lines, 'Model', ':', true).trim(), + vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()) || util.getValue(lines, 'Manufacturer', ':', true), + size: sizeValue, + bytesPerSector: null, + totalCylinders: null, + totalHeads: null, + totalSectors: null, + totalTracks: null, + tracksPerCylinder: null, + sectorsPerTrack: null, + firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(), + serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(), + interfaceType: util.getValue(lines, 'InterfaceType', ':', true).trim(), + smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown', + temperature: null, + BSDName: BSDName + }); + cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;'; + } + } + }); + } catch (e) { + util.noop(); + } + + // NVME Drives + try { + let devices = linesNVMe.join('\n').split('\n\n Capacity:'); + devices.shift(); + devices.forEach(function (device) { + device = '!Capacity: ' + device; + let lines = device.split('\n'); + const linkWidth = util.getValue(lines, 'link width', ':', true).trim(); + const sizeStr = util.getValue(lines, '!capacity', ':', true).trim(); + const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim(); + if (sizeStr) { + let sizeValue = 0; + if (sizeStr.indexOf('(') >= 0) { + sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, '')); + } + if (!sizeValue) { + sizeValue = parseInt(sizeStr); + } + if (sizeValue) { + const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase(); + result.push({ + device: BSDName, + type: 'NVMe', + name: util.getValue(lines, 'Model', ':', true).trim(), + vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()), + size: sizeValue, + bytesPerSector: null, + totalCylinders: null, + totalHeads: null, + totalSectors: null, + totalTracks: null, + tracksPerCylinder: null, + sectorsPerTrack: null, + firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(), + serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(), + interfaceType: ('PCIe ' + linkWidth).trim(), + smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown', + temperature: null, + BSDName: BSDName + }); + cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;'; + } + } + }); + } catch (e) { + util.noop(); + } + // USB Drives + try { + let devices = linesUSB.join('\n').replaceAll('Media:\n ', 'Model:').split('\n\n Product ID:'); + devices.shift(); + devices.forEach(function (device) { + let lines = device.split('\n'); + const sizeStr = util.getValue(lines, 'Capacity', ':', true).trim(); + const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim(); + if (sizeStr) { + let sizeValue = 0; + if (sizeStr.indexOf('(') >= 0) { + sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, '')); + } + if (!sizeValue) { + sizeValue = parseInt(sizeStr); + } + if (sizeValue) { + const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase(); + result.push({ + device: BSDName, + type: 'USB', + name: util.getValue(lines, 'Model', ':', true).trim().replaceAll(':', ''), + vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()), + size: sizeValue, + bytesPerSector: null, + totalCylinders: null, + totalHeads: null, + totalSectors: null, + totalTracks: null, + tracksPerCylinder: null, + sectorsPerTrack: null, + firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(), + serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(), + interfaceType: 'USB', + smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown', + temperature: null, + BSDName: BSDName + }); + cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;'; + } + } + }); + } catch (e) { + util.noop(); + } + if (cmd) { + cmd = cmd + 'printf "\n"'; + exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) { + let lines = stdout.toString().split('\n'); + lines.forEach(line => { + if (line) { + let parts = line.split('|'); + if (parts.length === 2) { + let BSDName = parts[0]; + parts[1] = parts[1].trim(); + let parts2 = parts[1].split(':'); + if (parts2.length === 2) { + parts2[1] = parts2[1].trim(); + let status = parts2[1].toLowerCase(); + for (let i = 0; i < result.length; i++) { + if (result[i].BSDName === BSDName) { + result[i].smartStatus = (status === 'not supported' ? 'not supported' : (status === 'verified' ? 'Ok' : (status === 'failing' ? 'Predicted Failure' : 'unknown'))); + } + } + } + } + } + }); + for (let i = 0; i < result.length; i++) { + delete result[i].BSDName; + } + if (callback) { + callback(result); + } + resolve(result); + }); + } else { + for (let i = 0; i < result.length; i++) { + delete result[i].BSDName; + } + if (callback) { + callback(result); + } + resolve(result); + } + } + }); + } + if (_windows) { + try { + const workload = []; + workload.push(util.powerShell('Get-WmiObject Win32_DiskDrive | fl *')); + workload.push(util.powerShell('Get-PhysicalDisk | Format-List')); + if (util.smartMonToolsInstalled()) { + try { + const smartDev = JSON.parse(execSync('smartctl --scan -j')); + if (smartDev && smartDev.devices && smartDev.devices.length > 0) { + smartDev.devices.forEach((dev) => { + workload.push(execPromiseSave(`smartctl -j -a ${dev.name}`, util.execOptsWin)); + }); + } + } catch (e) { + util.noop(); + } + } + util.promiseAll( + workload + ).then(data => { + let devices = data.results[0].toString().split(/\n\s*\n/); + devices.forEach(function (device) { + let lines = device.split('\r\n'); + const size = util.getValue(lines, 'Size', ':').trim(); + const status = util.getValue(lines, 'Status', ':').trim().toLowerCase(); + if (size) { + result.push({ + device: util.getValue(lines, 'PNPDeviceId', ':'), + type: device.indexOf('SSD') > -1 ? 'SSD' : 'HD', // just a starting point ... better: MSFT_PhysicalDisk - Media Type ... see below + name: util.getValue(lines, 'Caption', ':'), + vendor: getVendorFromModel(util.getValue(lines, 'Caption', ':', true).trim()), + size: parseInt(size), + bytesPerSector: parseInt(util.getValue(lines, 'BytesPerSector', ':')), + totalCylinders: parseInt(util.getValue(lines, 'TotalCylinders', ':')), + totalHeads: parseInt(util.getValue(lines, 'TotalHeads', ':')), + totalSectors: parseInt(util.getValue(lines, 'TotalSectors', ':')), + totalTracks: parseInt(util.getValue(lines, 'TotalTracks', ':')), + tracksPerCylinder: parseInt(util.getValue(lines, 'TracksPerCylinder', ':')), + sectorsPerTrack: parseInt(util.getValue(lines, 'SectorsPerTrack', ':')), + firmwareRevision: util.getValue(lines, 'FirmwareRevision', ':').trim(), + serialNum: util.getValue(lines, 'SerialNumber', ':').trim(), + interfaceType: util.getValue(lines, 'InterfaceType', ':').trim(), + smartStatus: (status === 'ok' ? 'Ok' : (status === 'degraded' ? 'Degraded' : (status === 'pred fail' ? 'Predicted Failure' : 'Unknown'))), + temperature: null, + }); + } + }); + devices = data.results[1].split(/\n\s*\n/); + devices.forEach(function (device) { + let lines = device.split('\r\n'); + const serialNum = util.getValue(lines, 'SerialNumber', ':').trim(); + const name = util.getValue(lines, 'FriendlyName', ':').trim().replace('Msft ', 'Microsoft'); + const size = util.getValue(lines, 'Size', ':').trim(); + const model = util.getValue(lines, 'Model', ':').trim(); + const interfaceType = util.getValue(lines, 'BusType', ':').trim(); + let mediaType = util.getValue(lines, 'MediaType', ':').trim(); + if (mediaType === '3' || mediaType === 'HDD') { mediaType = 'HD'; } + if (mediaType === '4') { mediaType = 'SSD'; } + if (mediaType === '5') { mediaType = 'SCM'; } + if (mediaType === 'Unspecified' && (model.toLowerCase().indexOf('virtual') > -1 || model.toLowerCase().indexOf('vbox') > -1)) { mediaType = 'Virtual'; } + if (size) { + let i = util.findObjectByKey(result, 'serialNum', serialNum); + if (i === -1 || serialNum === '') { + i = util.findObjectByKey(result, 'name', name); + } + if (i != -1) { + result[i].type = mediaType; + result[i].interfaceType = interfaceType; + } + } + }); + // S.M.A.R.T + data.results.shift(); + data.results.shift(); + data.results.forEach((smartStr) => { + const smartData = JSON.parse(smartStr); + if (smartData.serial_number) { + const serialNum = smartData.serial_number; + let i = util.findObjectByKey(result, 'serialNum', serialNum); + if (i != -1) { + result[i].smartStatus = (smartData.smart_status.passed ? 'Ok' : (smartData.smart_status.passed === false ? 'Predicted Failure' : 'unknown')); + if (smartData.temperature && smartData.temperature.current) { + result[i].temperature = smartData.temperature.current; + } + result[i].smartData = smartData; + } + } + }); + if (callback) { + callback(result); + } + resolve(result); + }); + } catch (e) { + if (callback) { callback(result); } + resolve(result); + } + } + }); + }); +} + +exports.diskLayout = diskLayout;