filesystem.js 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276
  1. 'use strict';
  2. // @ts-check
  3. // ==================================================================================
  4. // filesystem.js
  5. // ----------------------------------------------------------------------------------
  6. // Description: System Information - library
  7. // for Node.js
  8. // Copyright: (c) 2014 - 2022
  9. // Author: Sebastian Hildebrandt
  10. // ----------------------------------------------------------------------------------
  11. // License: MIT
  12. // ==================================================================================
  13. // 8. File System
  14. // ----------------------------------------------------------------------------------
  15. const util = require('./util');
  16. const fs = require('fs');
  17. const exec = require('child_process').exec;
  18. const execSync = require('child_process').execSync;
  19. const execPromiseSave = util.promisifySave(require('child_process').exec);
  20. let _platform = process.platform;
  21. const _linux = (_platform === 'linux' || _platform === 'android');
  22. const _darwin = (_platform === 'darwin');
  23. const _windows = (_platform === 'win32');
  24. const _freebsd = (_platform === 'freebsd');
  25. const _openbsd = (_platform === 'openbsd');
  26. const _netbsd = (_platform === 'netbsd');
  27. const _sunos = (_platform === 'sunos');
  28. let _fs_speed = {};
  29. let _disk_io = {};
  30. // --------------------------
  31. // FS - mounted file systems
  32. function fsSize(callback) {
  33. let macOsDisks = [];
  34. function getmacOsFsType(fs) {
  35. if (!fs.startsWith('/')) { return 'NFS'; }
  36. const parts = fs.split('/');
  37. const fsShort = parts[parts.length - 1];
  38. const macOsDisksSingle = macOsDisks.filter(item => item.indexOf(fsShort) >= 0);
  39. if (macOsDisksSingle.length === 1 && macOsDisksSingle[0].indexOf('APFS') >= 0) { return 'APFS'; }
  40. return 'HFS';
  41. }
  42. function parseDf(lines) {
  43. let data = [];
  44. lines.forEach(function (line) {
  45. if (line !== '') {
  46. line = line.replace(/ +/g, ' ').split(' ');
  47. if (line && ((line[0].startsWith('/')) || (line[6] && line[6] === '/') || (line[0].indexOf('/') > 0) || (line[0].indexOf(':') === 1))) {
  48. const fs = line[0];
  49. const fsType = ((_linux || _freebsd || _openbsd || _netbsd) ? line[1] : getmacOsFsType(line[0]));
  50. const size = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[2] : line[1])) * 1024;
  51. const used = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[3] : line[2])) * 1024;
  52. const available = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[4] : line[3])) * 1024;
  53. const use = parseFloat((100.0 * (used / (used + available))).toFixed(2));
  54. line.splice(0, (_linux || _freebsd || _openbsd || _netbsd) ? 6 : 5);
  55. const mount = line.join(' ');
  56. // const mount = line[line.length - 1];
  57. if (!data.find(el => (el.fs === fs && el.type === fsType))) {
  58. data.push({
  59. fs,
  60. type: fsType,
  61. size,
  62. used,
  63. available,
  64. use,
  65. mount
  66. });
  67. }
  68. }
  69. }
  70. });
  71. return data;
  72. }
  73. return new Promise((resolve) => {
  74. process.nextTick(() => {
  75. let data = [];
  76. if (_linux || _freebsd || _openbsd || _netbsd || _darwin) {
  77. let cmd = '';
  78. if (_darwin) {
  79. cmd = 'df -kP';
  80. try {
  81. macOsDisks = execSync('diskutil list').toString().split('\n').filter(line => {
  82. return !line.startsWith('/') && line.indexOf(':') > 0;
  83. });
  84. } catch (e) {
  85. macOsDisks = [];
  86. }
  87. }
  88. if (_linux) { cmd = 'df -lkPTx squashfs | grep -E "^/|^.\\:"'; }
  89. if (_freebsd || _openbsd || _netbsd) { cmd = 'df -lkPT'; }
  90. exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  91. if (!error) {
  92. let lines = stdout.toString().split('\n');
  93. data = parseDf(lines);
  94. if (callback) {
  95. callback(data);
  96. }
  97. resolve(data);
  98. } else {
  99. exec('df -kPT', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  100. if (!error) {
  101. let lines = stdout.toString().split('\n');
  102. data = parseDf(lines);
  103. }
  104. if (callback) {
  105. callback(data);
  106. }
  107. resolve(data);
  108. });
  109. }
  110. });
  111. }
  112. if (_sunos) {
  113. if (callback) { callback(data); }
  114. resolve(data);
  115. }
  116. if (_windows) {
  117. try {
  118. // util.wmic('logicaldisk get Caption,FileSystem,FreeSpace,Size').then((stdout) => {
  119. util.powerShell('Get-WmiObject Win32_logicaldisk | select Caption,FileSystem,FreeSpace,Size | fl').then((stdout, error) => {
  120. if (!error) {
  121. let devices = stdout.toString().split(/\n\s*\n/);
  122. devices.forEach(function (device) {
  123. let lines = device.split('\r\n');
  124. const size = util.toInt(util.getValue(lines, 'size', ':'));
  125. const free = util.toInt(util.getValue(lines, 'freespace', ':'));
  126. const caption = util.getValue(lines, 'caption', ':');
  127. if (size) {
  128. data.push({
  129. fs: caption,
  130. type: util.getValue(lines, 'filesystem', ':'),
  131. size,
  132. used: size - free,
  133. available: free,
  134. use: parseFloat(((100.0 * (size - free)) / size).toFixed(2)),
  135. mount: caption
  136. });
  137. }
  138. });
  139. }
  140. if (callback) {
  141. callback(data);
  142. }
  143. resolve(data);
  144. });
  145. } catch (e) {
  146. if (callback) { callback(data); }
  147. resolve(data);
  148. }
  149. }
  150. });
  151. });
  152. }
  153. exports.fsSize = fsSize;
  154. // --------------------------
  155. // FS - open files count
  156. function fsOpenFiles(callback) {
  157. return new Promise((resolve) => {
  158. process.nextTick(() => {
  159. const result = {
  160. max: null,
  161. allocated: null,
  162. available: null
  163. };
  164. if (_freebsd || _openbsd || _netbsd || _darwin) {
  165. let cmd = 'sysctl -i kern.maxfiles kern.num_files kern.open_files';
  166. exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  167. if (!error) {
  168. let lines = stdout.toString().split('\n');
  169. result.max = parseInt(util.getValue(lines, 'kern.maxfiles', ':'), 10);
  170. result.allocated = parseInt(util.getValue(lines, 'kern.num_files', ':'), 10) || parseInt(util.getValue(lines, 'kern.open_files', ':'), 10);
  171. result.available = result.max - result.allocated;
  172. }
  173. if (callback) {
  174. callback(result);
  175. }
  176. resolve(result);
  177. });
  178. }
  179. if (_linux) {
  180. fs.readFile('/proc/sys/fs/file-nr', function (error, stdout) {
  181. if (!error) {
  182. let lines = stdout.toString().split('\n');
  183. if (lines[0]) {
  184. const parts = lines[0].replace(/\s+/g, ' ').split(' ');
  185. if (parts.length === 3) {
  186. result.allocated = parseInt(parts[0], 10);
  187. result.available = parseInt(parts[1], 10);
  188. result.max = parseInt(parts[2], 10);
  189. if (!result.available) { result.available = result.max - result.allocated; }
  190. }
  191. }
  192. if (callback) {
  193. callback(result);
  194. }
  195. resolve(result);
  196. } else {
  197. fs.readFile('/proc/sys/fs/file-max', function (error, stdout) {
  198. if (!error) {
  199. let lines = stdout.toString().split('\n');
  200. if (lines[0]) {
  201. result.max = parseInt(lines[0], 10);
  202. }
  203. }
  204. if (callback) {
  205. callback(result);
  206. }
  207. resolve(result);
  208. });
  209. }
  210. });
  211. }
  212. if (_sunos) {
  213. if (callback) { callback(null); }
  214. resolve(null);
  215. }
  216. if (_windows) {
  217. if (callback) { callback(null); }
  218. resolve(null);
  219. }
  220. });
  221. });
  222. }
  223. exports.fsOpenFiles = fsOpenFiles;
  224. // --------------------------
  225. // disks
  226. function parseBytes(s) {
  227. return parseInt(s.substr(s.indexOf(' (') + 2, s.indexOf(' Bytes)') - 10));
  228. }
  229. function parseDevices(lines) {
  230. let devices = [];
  231. let i = 0;
  232. lines.forEach(line => {
  233. if (line.length > 0) {
  234. if (line[0] === '*') {
  235. i++;
  236. } else {
  237. let parts = line.split(':');
  238. if (parts.length > 1) {
  239. if (!devices[i]) {
  240. devices[i] = {
  241. name: '',
  242. identifier: '',
  243. type: 'disk',
  244. fsType: '',
  245. mount: '',
  246. size: 0,
  247. physical: 'HDD',
  248. uuid: '',
  249. label: '',
  250. model: '',
  251. serial: '',
  252. removable: false,
  253. protocol: ''
  254. };
  255. }
  256. parts[0] = parts[0].trim().toUpperCase().replace(/ +/g, '');
  257. parts[1] = parts[1].trim();
  258. if ('DEVICEIDENTIFIER' === parts[0]) { devices[i].identifier = parts[1]; }
  259. if ('DEVICENODE' === parts[0]) { devices[i].name = parts[1]; }
  260. if ('VOLUMENAME' === parts[0]) {
  261. if (parts[1].indexOf('Not applicable') === -1) { devices[i].label = parts[1]; }
  262. }
  263. if ('PROTOCOL' === parts[0]) { devices[i].protocol = parts[1]; }
  264. if ('DISKSIZE' === parts[0]) { devices[i].size = parseBytes(parts[1]); }
  265. if ('FILESYSTEMPERSONALITY' === parts[0]) { devices[i].fsType = parts[1]; }
  266. if ('MOUNTPOINT' === parts[0]) { devices[i].mount = parts[1]; }
  267. if ('VOLUMEUUID' === parts[0]) { devices[i].uuid = parts[1]; }
  268. if ('READ-ONLYMEDIA' === parts[0] && parts[1] === 'Yes') { devices[i].physical = 'CD/DVD'; }
  269. if ('SOLIDSTATE' === parts[0] && parts[1] === 'Yes') { devices[i].physical = 'SSD'; }
  270. if ('VIRTUAL' === parts[0]) { devices[i].type = 'virtual'; }
  271. if ('REMOVABLEMEDIA' === parts[0]) { devices[i].removable = (parts[1] === 'Removable'); }
  272. if ('PARTITIONTYPE' === parts[0]) { devices[i].type = 'part'; }
  273. if ('DEVICE/MEDIANAME' === parts[0]) { devices[i].model = parts[1]; }
  274. }
  275. }
  276. }
  277. });
  278. return devices;
  279. }
  280. function parseBlk(lines) {
  281. let data = [];
  282. lines.filter(line => line !== '').forEach((line) => {
  283. try {
  284. line = decodeURIComponent(line.replace(/\\x/g, '%'));
  285. line = line.replace(/\\/g, '\\\\');
  286. let disk = JSON.parse(line);
  287. data.push({
  288. 'name': disk.name,
  289. 'type': disk.type,
  290. 'fsType': disk.fsType,
  291. 'mount': disk.mountpoint,
  292. 'size': parseInt(disk.size),
  293. 'physical': (disk.type === 'disk' ? (disk.rota === '0' ? 'SSD' : 'HDD') : (disk.type === 'rom' ? 'CD/DVD' : '')),
  294. 'uuid': disk.uuid,
  295. 'label': disk.label,
  296. 'model': disk.model,
  297. 'serial': disk.serial,
  298. 'removable': disk.rm === '1',
  299. 'protocol': disk.tran,
  300. 'group': disk.group,
  301. });
  302. } catch (e) {
  303. util.noop();
  304. }
  305. });
  306. data = util.unique(data);
  307. data = util.sortByKey(data, ['type', 'name']);
  308. return data;
  309. }
  310. function blkStdoutToObject(stdout) {
  311. return stdout.toString()
  312. .replace(/NAME=/g, '{"name":')
  313. .replace(/FSTYPE=/g, ',"fsType":')
  314. .replace(/TYPE=/g, ',"type":')
  315. .replace(/SIZE=/g, ',"size":')
  316. .replace(/MOUNTPOINT=/g, ',"mountpoint":')
  317. .replace(/UUID=/g, ',"uuid":')
  318. .replace(/ROTA=/g, ',"rota":')
  319. .replace(/RO=/g, ',"ro":')
  320. .replace(/RM=/g, ',"rm":')
  321. .replace(/TRAN=/g, ',"tran":')
  322. .replace(/SERIAL=/g, ',"serial":')
  323. .replace(/LABEL=/g, ',"label":')
  324. .replace(/MODEL=/g, ',"model":')
  325. .replace(/OWNER=/g, ',"owner":')
  326. .replace(/GROUP=/g, ',"group":')
  327. .replace(/\n/g, '}\n');
  328. }
  329. function blockDevices(callback) {
  330. return new Promise((resolve) => {
  331. process.nextTick(() => {
  332. let data = [];
  333. if (_linux) {
  334. // see https://wiki.ubuntuusers.de/lsblk/
  335. // 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) {
  336. 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) {
  337. if (!error) {
  338. let lines = blkStdoutToObject(stdout).split('\n');
  339. data = parseBlk(lines);
  340. if (callback) {
  341. callback(data);
  342. }
  343. resolve(data);
  344. } else {
  345. exec('lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,LABEL,MODEL,OWNER 2>/dev/null', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  346. if (!error) {
  347. let lines = blkStdoutToObject(stdout).split('\n');
  348. data = parseBlk(lines);
  349. }
  350. if (callback) {
  351. callback(data);
  352. }
  353. resolve(data);
  354. });
  355. }
  356. });
  357. }
  358. if (_darwin) {
  359. exec('diskutil info -all', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  360. if (!error) {
  361. let lines = stdout.toString().split('\n');
  362. // parse lines into temp array of devices
  363. data = parseDevices(lines);
  364. }
  365. if (callback) {
  366. callback(data);
  367. }
  368. resolve(data);
  369. });
  370. }
  371. if (_sunos) {
  372. if (callback) { callback(data); }
  373. resolve(data);
  374. }
  375. if (_windows) {
  376. let drivetypes = ['Unknown', 'NoRoot', 'Removable', 'Local', 'Network', 'CD/DVD', 'RAM'];
  377. try {
  378. // util.wmic('logicaldisk get Caption,Description,DeviceID,DriveType,FileSystem,FreeSpace,Name,Size,VolumeName,VolumeSerialNumber /value').then((stdout, error) => {
  379. // util.powerShell('Get-WmiObject Win32_logicaldisk | select Caption,DriveType,Name,FileSystem,Size,VolumeSerialNumber,VolumeName | fl').then((stdout, error) => {
  380. util.powerShell('Get-CimInstance -ClassName Win32_LogicalDisk | select Caption,DriveType,Name,FileSystem,Size,VolumeSerialNumber,VolumeName | fl').then((stdout, error) => {
  381. if (!error) {
  382. let devices = stdout.toString().split(/\n\s*\n/);
  383. devices.forEach(function (device) {
  384. let lines = device.split('\r\n');
  385. let drivetype = util.getValue(lines, 'drivetype', ':');
  386. if (drivetype) {
  387. data.push({
  388. name: util.getValue(lines, 'name', ':'),
  389. identifier: util.getValue(lines, 'caption', ':'),
  390. type: 'disk',
  391. fsType: util.getValue(lines, 'filesystem', ':').toLowerCase(),
  392. mount: util.getValue(lines, 'caption', ':'),
  393. size: util.getValue(lines, 'size', ':'),
  394. physical: (drivetype >= 0 && drivetype <= 6) ? drivetypes[drivetype] : drivetypes[0],
  395. uuid: util.getValue(lines, 'volumeserialnumber', ':'),
  396. label: util.getValue(lines, 'volumename', ':'),
  397. model: '',
  398. serial: util.getValue(lines, 'volumeserialnumber', ':'),
  399. removable: drivetype === '2',
  400. protocol: ''
  401. });
  402. }
  403. });
  404. }
  405. if (callback) {
  406. callback(data);
  407. }
  408. resolve(data);
  409. });
  410. } catch (e) {
  411. if (callback) { callback(data); }
  412. resolve(data);
  413. }
  414. }
  415. if (_freebsd || _openbsd || _netbsd) {
  416. // will follow
  417. if (callback) { callback(null); }
  418. resolve(null);
  419. }
  420. });
  421. });
  422. }
  423. exports.blockDevices = blockDevices;
  424. // --------------------------
  425. // FS - speed
  426. function calcFsSpeed(rx, wx) {
  427. let result = {
  428. rx: 0,
  429. wx: 0,
  430. tx: 0,
  431. rx_sec: null,
  432. wx_sec: null,
  433. tx_sec: null,
  434. ms: 0
  435. };
  436. if (_fs_speed && _fs_speed.ms) {
  437. result.rx = rx;
  438. result.wx = wx;
  439. result.tx = result.rx + result.wx;
  440. result.ms = Date.now() - _fs_speed.ms;
  441. result.rx_sec = (result.rx - _fs_speed.bytes_read) / (result.ms / 1000);
  442. result.wx_sec = (result.wx - _fs_speed.bytes_write) / (result.ms / 1000);
  443. result.tx_sec = result.rx_sec + result.wx_sec;
  444. _fs_speed.rx_sec = result.rx_sec;
  445. _fs_speed.wx_sec = result.wx_sec;
  446. _fs_speed.tx_sec = result.tx_sec;
  447. _fs_speed.bytes_read = result.rx;
  448. _fs_speed.bytes_write = result.wx;
  449. _fs_speed.bytes_overall = result.rx + result.wx;
  450. _fs_speed.ms = Date.now();
  451. _fs_speed.last_ms = result.ms;
  452. } else {
  453. result.rx = rx;
  454. result.wx = wx;
  455. result.tx = result.rx + result.wx;
  456. _fs_speed.rx_sec = null;
  457. _fs_speed.wx_sec = null;
  458. _fs_speed.tx_sec = null;
  459. _fs_speed.bytes_read = result.rx;
  460. _fs_speed.bytes_write = result.wx;
  461. _fs_speed.bytes_overall = result.rx + result.wx;
  462. _fs_speed.ms = Date.now();
  463. _fs_speed.last_ms = 0;
  464. }
  465. return result;
  466. }
  467. function fsStats(callback) {
  468. return new Promise((resolve) => {
  469. process.nextTick(() => {
  470. if (_windows || _freebsd || _openbsd || _netbsd || _sunos) {
  471. return resolve(null);
  472. }
  473. let result = {
  474. rx: 0,
  475. wx: 0,
  476. tx: 0,
  477. rx_sec: null,
  478. wx_sec: null,
  479. tx_sec: null,
  480. ms: 0
  481. };
  482. let rx = 0;
  483. let wx = 0;
  484. if ((_fs_speed && !_fs_speed.ms) || (_fs_speed && _fs_speed.ms && Date.now() - _fs_speed.ms >= 500)) {
  485. if (_linux) {
  486. // exec("df -k | grep /dev/", function(error, stdout) {
  487. exec('lsblk -r 2>/dev/null | grep /', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  488. if (!error) {
  489. let lines = stdout.toString().split('\n');
  490. let fs_filter = [];
  491. lines.forEach(function (line) {
  492. if (line !== '') {
  493. line = line.trim().split(' ');
  494. if (fs_filter.indexOf(line[0]) === -1) { fs_filter.push(line[0]); }
  495. }
  496. });
  497. let output = fs_filter.join('|');
  498. exec('cat /proc/diskstats | egrep "' + output + '"', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  499. if (!error) {
  500. let lines = stdout.toString().split('\n');
  501. lines.forEach(function (line) {
  502. line = line.trim();
  503. if (line !== '') {
  504. line = line.replace(/ +/g, ' ').split(' ');
  505. rx += parseInt(line[5]) * 512;
  506. wx += parseInt(line[9]) * 512;
  507. }
  508. });
  509. result = calcFsSpeed(rx, wx);
  510. }
  511. if (callback) {
  512. callback(result);
  513. }
  514. resolve(result);
  515. });
  516. } else {
  517. if (callback) {
  518. callback(result);
  519. }
  520. resolve(result);
  521. }
  522. });
  523. }
  524. if (_darwin) {
  525. 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) {
  526. if (!error) {
  527. let lines = stdout.toString().split('\n');
  528. lines.forEach(function (line) {
  529. line = line.trim();
  530. if (line !== '') {
  531. line = line.split(',');
  532. rx += parseInt(line[2]);
  533. wx += parseInt(line[9]);
  534. }
  535. });
  536. result = calcFsSpeed(rx, wx);
  537. }
  538. if (callback) {
  539. callback(result);
  540. }
  541. resolve(result);
  542. });
  543. }
  544. } else {
  545. result.ms = _fs_speed.last_ms;
  546. result.rx = _fs_speed.bytes_read;
  547. result.wx = _fs_speed.bytes_write;
  548. result.tx = _fs_speed.bytes_read + _fs_speed.bytes_write;
  549. result.rx_sec = _fs_speed.rx_sec;
  550. result.wx_sec = _fs_speed.wx_sec;
  551. result.tx_sec = _fs_speed.tx_sec;
  552. if (callback) {
  553. callback(result);
  554. }
  555. resolve(result);
  556. }
  557. });
  558. });
  559. }
  560. exports.fsStats = fsStats;
  561. function calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime) {
  562. let result = {
  563. rIO: 0,
  564. wIO: 0,
  565. tIO: 0,
  566. rIO_sec: null,
  567. wIO_sec: null,
  568. tIO_sec: null,
  569. rWaitTime: 0,
  570. wWaitTime: 0,
  571. tWaitTime: 0,
  572. rWaitPercent: null,
  573. wWaitPercent: null,
  574. tWaitPercent: null,
  575. ms: 0
  576. };
  577. if (_disk_io && _disk_io.ms) {
  578. result.rIO = rIO;
  579. result.wIO = wIO;
  580. result.tIO = rIO + wIO;
  581. result.ms = Date.now() - _disk_io.ms;
  582. result.rIO_sec = (result.rIO - _disk_io.rIO) / (result.ms / 1000);
  583. result.wIO_sec = (result.wIO - _disk_io.wIO) / (result.ms / 1000);
  584. result.tIO_sec = result.rIO_sec + result.wIO_sec;
  585. result.rWaitTime = rWaitTime;
  586. result.wWaitTime = wWaitTime;
  587. result.tWaitTime = tWaitTime;
  588. result.rWaitPercent = (result.rWaitTime - _disk_io.rWaitTime) * 100 / (result.ms);
  589. result.wWaitPercent = (result.wWaitTime - _disk_io.wWaitTime) * 100 / (result.ms);
  590. result.tWaitPercent = (result.tWaitTime - _disk_io.tWaitTime) * 100 / (result.ms);
  591. _disk_io.rIO = rIO;
  592. _disk_io.wIO = wIO;
  593. _disk_io.rIO_sec = result.rIO_sec;
  594. _disk_io.wIO_sec = result.wIO_sec;
  595. _disk_io.tIO_sec = result.tIO_sec;
  596. _disk_io.rWaitTime = rWaitTime;
  597. _disk_io.wWaitTime = wWaitTime;
  598. _disk_io.tWaitTime = tWaitTime;
  599. _disk_io.rWaitPercent = result.rWaitPercent;
  600. _disk_io.wWaitPercent = result.wWaitPercent;
  601. _disk_io.tWaitPercent = result.tWaitPercent;
  602. _disk_io.last_ms = result.ms;
  603. _disk_io.ms = Date.now();
  604. } else {
  605. result.rIO = rIO;
  606. result.wIO = wIO;
  607. result.tIO = rIO + wIO;
  608. result.rWaitTime = rWaitTime;
  609. result.wWaitTime = wWaitTime;
  610. result.tWaitTime = tWaitTime;
  611. _disk_io.rIO = rIO;
  612. _disk_io.wIO = wIO;
  613. _disk_io.rIO_sec = null;
  614. _disk_io.wIO_sec = null;
  615. _disk_io.tIO_sec = null;
  616. _disk_io.rWaitTime = rWaitTime;
  617. _disk_io.wWaitTime = wWaitTime;
  618. _disk_io.tWaitTime = tWaitTime;
  619. _disk_io.rWaitPercent = null;
  620. _disk_io.wWaitPercent = null;
  621. _disk_io.tWaitPercent = null;
  622. _disk_io.last_ms = 0;
  623. _disk_io.ms = Date.now();
  624. }
  625. return result;
  626. }
  627. function disksIO(callback) {
  628. return new Promise((resolve) => {
  629. process.nextTick(() => {
  630. if (_windows) {
  631. return resolve(null);
  632. }
  633. if (_sunos) {
  634. return resolve(null);
  635. }
  636. let result = {
  637. rIO: 0,
  638. wIO: 0,
  639. tIO: 0,
  640. rIO_sec: null,
  641. wIO_sec: null,
  642. tIO_sec: null,
  643. rWaitTime: 0,
  644. wWaitTime: 0,
  645. tWaitTime: 0,
  646. rWaitPercent: null,
  647. wWaitPercent: null,
  648. tWaitPercent: null,
  649. ms: 0
  650. };
  651. let rIO = 0;
  652. let wIO = 0;
  653. let rWaitTime = 0;
  654. let wWaitTime = 0;
  655. let tWaitTime = 0;
  656. if ((_disk_io && !_disk_io.ms) || (_disk_io && _disk_io.ms && Date.now() - _disk_io.ms >= 500)) {
  657. if (_linux || _freebsd || _openbsd || _netbsd) {
  658. // prints Block layer statistics for all mounted volumes
  659. // 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";
  660. // 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";
  661. 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';
  662. exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  663. if (!error) {
  664. let lines = stdout.split('\n');
  665. lines.forEach(function (line) {
  666. // ignore empty lines
  667. if (!line) { return; }
  668. // sum r/wIO of all disks to compute all disks IO
  669. let stats = line.split(';');
  670. rIO += parseInt(stats[0]);
  671. wIO += parseInt(stats[4]);
  672. rWaitTime += parseInt(stats[3]);
  673. wWaitTime += parseInt(stats[7]);
  674. tWaitTime += parseInt(stats[10]);
  675. });
  676. result = calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime);
  677. if (callback) {
  678. callback(result);
  679. }
  680. resolve(result);
  681. } else {
  682. if (callback) {
  683. callback(result);
  684. }
  685. resolve(result);
  686. }
  687. });
  688. }
  689. if (_darwin) {
  690. 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) {
  691. if (!error) {
  692. let lines = stdout.toString().split('\n');
  693. lines.forEach(function (line) {
  694. line = line.trim();
  695. if (line !== '') {
  696. line = line.split(',');
  697. rIO += parseInt(line[10]);
  698. wIO += parseInt(line[0]);
  699. }
  700. });
  701. result = calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime);
  702. }
  703. if (callback) {
  704. callback(result);
  705. }
  706. resolve(result);
  707. });
  708. }
  709. } else {
  710. result.rIO = _disk_io.rIO;
  711. result.wIO = _disk_io.wIO;
  712. result.tIO = _disk_io.rIO + _disk_io.wIO;
  713. result.ms = _disk_io.last_ms;
  714. result.rIO_sec = _disk_io.rIO_sec;
  715. result.wIO_sec = _disk_io.wIO_sec;
  716. result.tIO_sec = _disk_io.tIO_sec;
  717. result.rWaitTime = _disk_io.rWaitTime;
  718. result.wWaitTime = _disk_io.wWaitTime;
  719. result.tWaitTime = _disk_io.tWaitTime;
  720. result.rWaitPercent = _disk_io.rWaitPercent;
  721. result.wWaitPercent = _disk_io.wWaitPercent;
  722. result.tWaitPercent = _disk_io.tWaitPercent;
  723. if (callback) {
  724. callback(result);
  725. }
  726. resolve(result);
  727. }
  728. });
  729. });
  730. }
  731. exports.disksIO = disksIO;
  732. function diskLayout(callback) {
  733. function getVendorFromModel(model) {
  734. const diskManufacturers = [
  735. { pattern: 'WESTERN.*', manufacturer: 'Western Digital' },
  736. { pattern: '^WDC.*', manufacturer: 'Western Digital' },
  737. { pattern: 'WD.*', manufacturer: 'Western Digital' },
  738. { pattern: 'TOSHIBA.*', manufacturer: 'Toshiba' },
  739. { pattern: 'HITACHI.*', manufacturer: 'Hitachi' },
  740. { pattern: '^IC.*', manufacturer: 'Hitachi' },
  741. { pattern: '^HTS.*', manufacturer: 'Hitachi' },
  742. { pattern: 'SANDISK.*', manufacturer: 'SanDisk' },
  743. { pattern: 'KINGSTON.*', manufacturer: 'Kingston Technology' },
  744. { pattern: '^SONY.*', manufacturer: 'Sony' },
  745. { pattern: 'TRANSCEND.*', manufacturer: 'Transcend' },
  746. { pattern: 'SAMSUNG.*', manufacturer: 'Samsung' },
  747. { pattern: '^ST(?!I\\ ).*', manufacturer: 'Seagate' },
  748. { pattern: '^STI\\ .*', manufacturer: 'SimpleTech' },
  749. { pattern: '^D...-.*', manufacturer: 'IBM' },
  750. { pattern: '^IBM.*', manufacturer: 'IBM' },
  751. { pattern: '^FUJITSU.*', manufacturer: 'Fujitsu' },
  752. { pattern: '^MP.*', manufacturer: 'Fujitsu' },
  753. { pattern: '^MK.*', manufacturer: 'Toshiba' },
  754. { pattern: 'MAXTO.*', manufacturer: 'Maxtor' },
  755. { pattern: 'PIONEER.*', manufacturer: 'Pioneer' },
  756. { pattern: 'PHILIPS.*', manufacturer: 'Philips' },
  757. { pattern: 'QUANTUM.*', manufacturer: 'Quantum Technology' },
  758. { pattern: 'FIREBALL.*', manufacturer: 'Quantum Technology' },
  759. { pattern: '^VBOX.*', manufacturer: 'VirtualBox' },
  760. { pattern: 'CORSAIR.*', manufacturer: 'Corsair Components' },
  761. { pattern: 'CRUCIAL.*', manufacturer: 'Crucial' },
  762. { pattern: 'ECM.*', manufacturer: 'ECM' },
  763. { pattern: 'INTEL.*', manufacturer: 'INTEL' },
  764. { pattern: 'EVO.*', manufacturer: 'Samsung' },
  765. { pattern: 'APPLE.*', manufacturer: 'Apple' },
  766. ];
  767. let result = '';
  768. if (model) {
  769. model = model.toUpperCase();
  770. diskManufacturers.forEach((manufacturer) => {
  771. const re = RegExp(manufacturer.pattern);
  772. if (re.test(model)) { result = manufacturer.manufacturer; }
  773. });
  774. }
  775. return result;
  776. }
  777. return new Promise((resolve) => {
  778. process.nextTick(() => {
  779. const commitResult = res => {
  780. for (let i = 0; i < res.length; i++) {
  781. delete res[i].BSDName;
  782. }
  783. if (callback) {
  784. callback(res);
  785. }
  786. resolve(res);
  787. };
  788. let result = [];
  789. let cmd = '';
  790. if (_linux) {
  791. let cmdFullSmart = '';
  792. exec('export LC_ALL=C; lsblk -ablJO 2>/dev/null; unset LC_ALL', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  793. if (!error) {
  794. try {
  795. const out = stdout.toString().trim();
  796. let devices = [];
  797. try {
  798. const outJSON = JSON.parse(out);
  799. if (outJSON && {}.hasOwnProperty.call(outJSON, 'blockdevices')) {
  800. 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)); });
  801. }
  802. } catch (e) {
  803. // fallback to older version of lsblk
  804. 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();
  805. let lines = blkStdoutToObject(out2).split('\n');
  806. const data = parseBlk(lines);
  807. devices = data.filter(item => { return (item.type === 'disk') && item.size > 0 && ((item.model !== null && item.model !== '') || (item.mount === '' && item.label === '' && item.fsType === '')); });
  808. }
  809. devices.forEach((device) => {
  810. let mediumType = '';
  811. const BSDName = '/dev/' + device.name;
  812. const logical = device.name;
  813. try {
  814. mediumType = execSync('cat /sys/block/' + logical + '/queue/rotational 2>/dev/null').toString().split('\n')[0];
  815. } catch (e) {
  816. util.noop();
  817. }
  818. let interfaceType = device.tran ? device.tran.toUpperCase().trim() : '';
  819. if (interfaceType === 'NVME') {
  820. mediumType = '2';
  821. interfaceType = 'PCIe';
  822. }
  823. result.push({
  824. device: BSDName,
  825. 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'))))),
  826. name: device.model || '',
  827. vendor: getVendorFromModel(device.model) || (device.vendor ? device.vendor.trim() : ''),
  828. size: device.size || 0,
  829. bytesPerSector: null,
  830. totalCylinders: null,
  831. totalHeads: null,
  832. totalSectors: null,
  833. totalTracks: null,
  834. tracksPerCylinder: null,
  835. sectorsPerTrack: null,
  836. firmwareRevision: device.rev ? device.rev.trim() : '',
  837. serialNum: device.serial ? device.serial.trim() : '',
  838. interfaceType: interfaceType,
  839. smartStatus: 'unknown',
  840. temperature: null,
  841. BSDName: BSDName
  842. });
  843. cmd += `printf "\n${BSDName}|"; smartctl -H ${BSDName} | grep overall;`;
  844. cmdFullSmart += `${cmdFullSmart ? 'printf ",";' : ''}smartctl -a -j ${BSDName};`;
  845. });
  846. } catch (e) {
  847. util.noop();
  848. }
  849. }
  850. // check S.M.A.R.T. status
  851. if (cmdFullSmart) {
  852. exec(cmdFullSmart, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  853. try {
  854. const data = JSON.parse(`[${stdout}]`);
  855. data.forEach(disk => {
  856. const diskBSDName = disk.smartctl.argv[disk.smartctl.argv.length - 1];
  857. for (let i = 0; i < result.length; i++) {
  858. if (result[i].BSDName === diskBSDName) {
  859. result[i].smartStatus = (disk.smart_status.passed ? 'Ok' : (disk.smart_status.passed === false ? 'Predicted Failure' : 'unknown'));
  860. if (disk.temperature && disk.temperature.current) {
  861. result[i].temperature = disk.temperature.current;
  862. }
  863. result[i].smartData = disk;
  864. }
  865. }
  866. });
  867. commitResult(result);
  868. } catch (e) {
  869. if (cmd) {
  870. cmd = cmd + 'printf "\n"';
  871. exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  872. let lines = stdout.toString().split('\n');
  873. lines.forEach(line => {
  874. if (line) {
  875. let parts = line.split('|');
  876. if (parts.length === 2) {
  877. let BSDName = parts[0];
  878. parts[1] = parts[1].trim();
  879. let parts2 = parts[1].split(':');
  880. if (parts2.length === 2) {
  881. parts2[1] = parts2[1].trim();
  882. let status = parts2[1].toLowerCase();
  883. for (let i = 0; i < result.length; i++) {
  884. if (result[i].BSDName === BSDName) {
  885. result[i].smartStatus = (status === 'passed' ? 'Ok' : (status === 'failed!' ? 'Predicted Failure' : 'unknown'));
  886. }
  887. }
  888. }
  889. }
  890. }
  891. });
  892. commitResult(result);
  893. });
  894. } else {
  895. commitResult(result);
  896. }
  897. }
  898. });
  899. } else {
  900. commitResult(result);
  901. }
  902. });
  903. }
  904. if (_freebsd || _openbsd || _netbsd) {
  905. if (callback) { callback(result); }
  906. resolve(result);
  907. }
  908. if (_sunos) {
  909. if (callback) { callback(result); }
  910. resolve(result);
  911. }
  912. if (_darwin) {
  913. exec('system_profiler SPSerialATADataType SPNVMeDataType SPUSBDataType', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  914. if (!error) {
  915. // split by type:
  916. let lines = stdout.toString().split('\n');
  917. let linesSATA = [];
  918. let linesNVMe = [];
  919. let linesUSB = [];
  920. let dataType = 'SATA';
  921. lines.forEach(line => {
  922. if (line === 'NVMExpress:') { dataType = 'NVMe'; }
  923. else if (line === 'USB:') { dataType = 'USB'; }
  924. else if (line === 'SATA/SATA Express:') { dataType = 'SATA'; }
  925. else if (dataType === 'SATA') { linesSATA.push(line); }
  926. else if (dataType === 'NVMe') { linesNVMe.push(line); }
  927. else if (dataType === 'USB') { linesUSB.push(line); }
  928. });
  929. try {
  930. // Serial ATA Drives
  931. let devices = linesSATA.join('\n').split(' Physical Interconnect: ');
  932. devices.shift();
  933. devices.forEach(function (device) {
  934. device = 'InterfaceType: ' + device;
  935. let lines = device.split('\n');
  936. const mediumType = util.getValue(lines, 'Medium Type', ':', true).trim();
  937. const sizeStr = util.getValue(lines, 'capacity', ':', true).trim();
  938. const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim();
  939. if (sizeStr) {
  940. let sizeValue = 0;
  941. if (sizeStr.indexOf('(') >= 0) {
  942. sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, ''));
  943. }
  944. if (!sizeValue) {
  945. sizeValue = parseInt(sizeStr);
  946. }
  947. if (sizeValue) {
  948. const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase();
  949. result.push({
  950. device: BSDName,
  951. type: mediumType.startsWith('Solid') ? 'SSD' : 'HD',
  952. name: util.getValue(lines, 'Model', ':', true).trim(),
  953. vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()) || util.getValue(lines, 'Manufacturer', ':', true),
  954. size: sizeValue,
  955. bytesPerSector: null,
  956. totalCylinders: null,
  957. totalHeads: null,
  958. totalSectors: null,
  959. totalTracks: null,
  960. tracksPerCylinder: null,
  961. sectorsPerTrack: null,
  962. firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(),
  963. serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(),
  964. interfaceType: util.getValue(lines, 'InterfaceType', ':', true).trim(),
  965. smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown',
  966. temperature: null,
  967. BSDName: BSDName
  968. });
  969. cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;';
  970. }
  971. }
  972. });
  973. } catch (e) {
  974. util.noop();
  975. }
  976. // NVME Drives
  977. try {
  978. let devices = linesNVMe.join('\n').split('\n\n Capacity:');
  979. devices.shift();
  980. devices.forEach(function (device) {
  981. device = '!Capacity: ' + device;
  982. let lines = device.split('\n');
  983. const linkWidth = util.getValue(lines, 'link width', ':', true).trim();
  984. const sizeStr = util.getValue(lines, '!capacity', ':', true).trim();
  985. const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim();
  986. if (sizeStr) {
  987. let sizeValue = 0;
  988. if (sizeStr.indexOf('(') >= 0) {
  989. sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, ''));
  990. }
  991. if (!sizeValue) {
  992. sizeValue = parseInt(sizeStr);
  993. }
  994. if (sizeValue) {
  995. const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase();
  996. result.push({
  997. device: BSDName,
  998. type: 'NVMe',
  999. name: util.getValue(lines, 'Model', ':', true).trim(),
  1000. vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()),
  1001. size: sizeValue,
  1002. bytesPerSector: null,
  1003. totalCylinders: null,
  1004. totalHeads: null,
  1005. totalSectors: null,
  1006. totalTracks: null,
  1007. tracksPerCylinder: null,
  1008. sectorsPerTrack: null,
  1009. firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(),
  1010. serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(),
  1011. interfaceType: ('PCIe ' + linkWidth).trim(),
  1012. smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown',
  1013. temperature: null,
  1014. BSDName: BSDName
  1015. });
  1016. cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;';
  1017. }
  1018. }
  1019. });
  1020. } catch (e) {
  1021. util.noop();
  1022. }
  1023. // USB Drives
  1024. try {
  1025. let devices = linesUSB.join('\n').replaceAll('Media:\n ', 'Model:').split('\n\n Product ID:');
  1026. devices.shift();
  1027. devices.forEach(function (device) {
  1028. let lines = device.split('\n');
  1029. const sizeStr = util.getValue(lines, 'Capacity', ':', true).trim();
  1030. const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim();
  1031. if (sizeStr) {
  1032. let sizeValue = 0;
  1033. if (sizeStr.indexOf('(') >= 0) {
  1034. sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, ''));
  1035. }
  1036. if (!sizeValue) {
  1037. sizeValue = parseInt(sizeStr);
  1038. }
  1039. if (sizeValue) {
  1040. const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase();
  1041. result.push({
  1042. device: BSDName,
  1043. type: 'USB',
  1044. name: util.getValue(lines, 'Model', ':', true).trim().replaceAll(':', ''),
  1045. vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()),
  1046. size: sizeValue,
  1047. bytesPerSector: null,
  1048. totalCylinders: null,
  1049. totalHeads: null,
  1050. totalSectors: null,
  1051. totalTracks: null,
  1052. tracksPerCylinder: null,
  1053. sectorsPerTrack: null,
  1054. firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(),
  1055. serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(),
  1056. interfaceType: 'USB',
  1057. smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown',
  1058. temperature: null,
  1059. BSDName: BSDName
  1060. });
  1061. cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;';
  1062. }
  1063. }
  1064. });
  1065. } catch (e) {
  1066. util.noop();
  1067. }
  1068. if (cmd) {
  1069. cmd = cmd + 'printf "\n"';
  1070. exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
  1071. let lines = stdout.toString().split('\n');
  1072. lines.forEach(line => {
  1073. if (line) {
  1074. let parts = line.split('|');
  1075. if (parts.length === 2) {
  1076. let BSDName = parts[0];
  1077. parts[1] = parts[1].trim();
  1078. let parts2 = parts[1].split(':');
  1079. if (parts2.length === 2) {
  1080. parts2[1] = parts2[1].trim();
  1081. let status = parts2[1].toLowerCase();
  1082. for (let i = 0; i < result.length; i++) {
  1083. if (result[i].BSDName === BSDName) {
  1084. result[i].smartStatus = (status === 'not supported' ? 'not supported' : (status === 'verified' ? 'Ok' : (status === 'failing' ? 'Predicted Failure' : 'unknown')));
  1085. }
  1086. }
  1087. }
  1088. }
  1089. }
  1090. });
  1091. for (let i = 0; i < result.length; i++) {
  1092. delete result[i].BSDName;
  1093. }
  1094. if (callback) {
  1095. callback(result);
  1096. }
  1097. resolve(result);
  1098. });
  1099. } else {
  1100. for (let i = 0; i < result.length; i++) {
  1101. delete result[i].BSDName;
  1102. }
  1103. if (callback) {
  1104. callback(result);
  1105. }
  1106. resolve(result);
  1107. }
  1108. }
  1109. });
  1110. }
  1111. if (_windows) {
  1112. try {
  1113. const workload = [];
  1114. workload.push(util.powerShell('Get-WmiObject Win32_DiskDrive | select Caption,Size,Status,PNPDeviceId,BytesPerSector,TotalCylinders,TotalHeads,TotalSectors,TotalTracks,TracksPerCylinder,SectorsPerTrack,FirmwareRevision,SerialNumber,InterfaceType | fl'));
  1115. workload.push(util.powerShell('Get-PhysicalDisk | select BusType,MediaType,FriendlyName,Model,SerialNumber,Size | fl'));
  1116. if (util.smartMonToolsInstalled()) {
  1117. try {
  1118. const smartDev = JSON.parse(execSync('smartctl --scan -j'));
  1119. if (smartDev && smartDev.devices && smartDev.devices.length > 0) {
  1120. smartDev.devices.forEach((dev) => {
  1121. workload.push(execPromiseSave(`smartctl -j -a ${dev.name}`, util.execOptsWin));
  1122. });
  1123. }
  1124. } catch (e) {
  1125. util.noop();
  1126. }
  1127. }
  1128. util.promiseAll(
  1129. workload
  1130. ).then(data => {
  1131. let devices = data.results[0].toString().split(/\n\s*\n/);
  1132. devices.forEach(function (device) {
  1133. let lines = device.split('\r\n');
  1134. const size = util.getValue(lines, 'Size', ':').trim();
  1135. const status = util.getValue(lines, 'Status', ':').trim().toLowerCase();
  1136. if (size) {
  1137. result.push({
  1138. device: util.getValue(lines, 'PNPDeviceId', ':'),
  1139. type: device.indexOf('SSD') > -1 ? 'SSD' : 'HD', // just a starting point ... better: MSFT_PhysicalDisk - Media Type ... see below
  1140. name: util.getValue(lines, 'Caption', ':'),
  1141. vendor: getVendorFromModel(util.getValue(lines, 'Caption', ':', true).trim()),
  1142. size: parseInt(size),
  1143. bytesPerSector: parseInt(util.getValue(lines, 'BytesPerSector', ':')),
  1144. totalCylinders: parseInt(util.getValue(lines, 'TotalCylinders', ':')),
  1145. totalHeads: parseInt(util.getValue(lines, 'TotalHeads', ':')),
  1146. totalSectors: parseInt(util.getValue(lines, 'TotalSectors', ':')),
  1147. totalTracks: parseInt(util.getValue(lines, 'TotalTracks', ':')),
  1148. tracksPerCylinder: parseInt(util.getValue(lines, 'TracksPerCylinder', ':')),
  1149. sectorsPerTrack: parseInt(util.getValue(lines, 'SectorsPerTrack', ':')),
  1150. firmwareRevision: util.getValue(lines, 'FirmwareRevision', ':').trim(),
  1151. serialNum: util.getValue(lines, 'SerialNumber', ':').trim(),
  1152. interfaceType: util.getValue(lines, 'InterfaceType', ':').trim(),
  1153. smartStatus: (status === 'ok' ? 'Ok' : (status === 'degraded' ? 'Degraded' : (status === 'pred fail' ? 'Predicted Failure' : 'Unknown'))),
  1154. temperature: null,
  1155. });
  1156. }
  1157. });
  1158. devices = data.results[1].split(/\n\s*\n/);
  1159. devices.forEach(function (device) {
  1160. let lines = device.split('\r\n');
  1161. const serialNum = util.getValue(lines, 'SerialNumber', ':').trim();
  1162. const name = util.getValue(lines, 'FriendlyName', ':').trim().replace('Msft ', 'Microsoft');
  1163. const size = util.getValue(lines, 'Size', ':').trim();
  1164. const model = util.getValue(lines, 'Model', ':').trim();
  1165. const interfaceType = util.getValue(lines, 'BusType', ':').trim();
  1166. let mediaType = util.getValue(lines, 'MediaType', ':').trim();
  1167. if (mediaType === '3' || mediaType === 'HDD') { mediaType = 'HD'; }
  1168. if (mediaType === '4') { mediaType = 'SSD'; }
  1169. if (mediaType === '5') { mediaType = 'SCM'; }
  1170. if (mediaType === 'Unspecified' && (model.toLowerCase().indexOf('virtual') > -1 || model.toLowerCase().indexOf('vbox') > -1)) { mediaType = 'Virtual'; }
  1171. if (size) {
  1172. let i = util.findObjectByKey(result, 'serialNum', serialNum);
  1173. if (i === -1 || serialNum === '') {
  1174. i = util.findObjectByKey(result, 'name', name);
  1175. }
  1176. if (i != -1) {
  1177. result[i].type = mediaType;
  1178. result[i].interfaceType = interfaceType;
  1179. }
  1180. }
  1181. });
  1182. // S.M.A.R.T
  1183. data.results.shift();
  1184. data.results.shift();
  1185. if (data.results.length) {
  1186. data.results.forEach((smartStr) => {
  1187. try {
  1188. const smartData = JSON.parse(smartStr);
  1189. if (smartData.serial_number) {
  1190. const serialNum = smartData.serial_number;
  1191. let i = util.findObjectByKey(result, 'serialNum', serialNum);
  1192. if (i != -1) {
  1193. result[i].smartStatus = (smartData.smart_status && smartData.smart_status.passed ? 'Ok' : (smartData.smart_status && smartData.smart_status.passed === false ? 'Predicted Failure' : 'unknown'));
  1194. if (smartData.temperature && smartData.temperature.current) {
  1195. result[i].temperature = smartData.temperature.current;
  1196. }
  1197. result[i].smartData = smartData;
  1198. }
  1199. }
  1200. } catch (e) {
  1201. util.noop();
  1202. }
  1203. });
  1204. }
  1205. if (callback) {
  1206. callback(result);
  1207. }
  1208. resolve(result);
  1209. });
  1210. } catch (e) {
  1211. if (callback) { callback(result); }
  1212. resolve(result);
  1213. }
  1214. }
  1215. });
  1216. });
  1217. }
  1218. exports.diskLayout = diskLayout;