1277 lines
50 KiB
JavaScript
1277 lines
50 KiB
JavaScript
|
'use strict';
|
||
|
// @ts-check
|
||
|
// ==================================================================================
|
||
|
// filesystem.js
|
||
|
// ----------------------------------------------------------------------------------
|
||
|
// Description: System Information - library
|
||
|
// for Node.js
|
||
|
// Copyright: (c) 2014 - 2022
|
||
|
// 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' || _platform === 'android');
|
||
|
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) => {
|
||
|
util.powerShell('Get-WmiObject Win32_logicaldisk | select Caption,FileSystem,FreeSpace,Size | fl').then((stdout, error) => {
|
||
|
if (!error) {
|
||
|
let devices = stdout.toString().split(/\n\s*\n/);
|
||
|
devices.forEach(function (device) {
|
||
|
let lines = device.split('\r\n');
|
||
|
const size = util.toInt(util.getValue(lines, 'size', ':'));
|
||
|
const free = util.toInt(util.getValue(lines, 'freespace', ':'));
|
||
|
const caption = util.getValue(lines, 'caption', ':');
|
||
|
if (size) {
|
||
|
data.push({
|
||
|
fs: caption,
|
||
|
type: util.getValue(lines, 'filesystem', ':'),
|
||
|
size,
|
||
|
used: size - free,
|
||
|
available: free,
|
||
|
use: parseFloat(((100.0 * (size - free)) / size).toFixed(2)),
|
||
|
mount: caption
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
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 -i kern.maxfiles kern.num_files kern.open_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) || parseInt(util.getValue(lines, 'kern.open_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-WmiObject Win32_logicaldisk | select Caption,DriveType,Name,FileSystem,Size,VolumeSerialNumber,VolumeName | fl').then((stdout, error) => {
|
||
|
util.powerShell('Get-CimInstance -ClassName Win32_LogicalDisk | select Caption,DriveType,Name,FileSystem,Size,VolumeSerialNumber,VolumeName | fl').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 || _freebsd || _openbsd || _netbsd || _sunos) {
|
||
|
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: 'MAXTO.*', 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 | select Caption,Size,Status,PNPDeviceId,BytesPerSector,TotalCylinders,TotalHeads,TotalSectors,TotalTracks,TracksPerCylinder,SectorsPerTrack,FirmwareRevision,SerialNumber,InterfaceType | fl'));
|
||
|
workload.push(util.powerShell('Get-PhysicalDisk | select BusType,MediaType,FriendlyName,Model,SerialNumber,Size | fl'));
|
||
|
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();
|
||
|
if (data.results.length) {
|
||
|
data.results.forEach((smartStr) => {
|
||
|
try {
|
||
|
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 && smartData.smart_status.passed ? 'Ok' : (smartData.smart_status && smartData.smart_status.passed === false ? 'Predicted Failure' : 'unknown'));
|
||
|
if (smartData.temperature && smartData.temperature.current) {
|
||
|
result[i].temperature = smartData.temperature.current;
|
||
|
}
|
||
|
result[i].smartData = smartData;
|
||
|
}
|
||
|
}
|
||
|
} catch (e) {
|
||
|
util.noop();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
if (callback) {
|
||
|
callback(result);
|
||
|
}
|
||
|
resolve(result);
|
||
|
});
|
||
|
} catch (e) {
|
||
|
if (callback) { callback(result); }
|
||
|
resolve(result);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
exports.diskLayout = diskLayout;
|