Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

filesystem.js 40KB

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