systeminformation 5.9.8 → 5.9.12

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