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