From cda853457ed49941b98d53ce63359f4fec804863 Mon Sep 17 00:00:00 2001 From: Sebastian Hildebrandt Date: Thu, 22 Jun 2017 15:24:09 +0200 Subject: [PATCH] added memLayout, diskLayout, extended windows support (inetChecksite) --- CHANGELOG.md | 4 ++ README.md | 37 ++++++++++-- lib/battery.js | 3 +- lib/filesystem.js | 141 ++++++++++++++++++++++++++++++++++++++++++++++ lib/index.js | 14 ++++- lib/internet.js | 59 ++++++++++++++----- lib/memory.js | 123 +++++++++++++++++++++++++++++++++++++++- 7 files changed, 354 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cc6301..4917bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ New Functions +- `diskLayout`: returns hard disk layout (new in version 3.23) +- `memLayout`: returns memory chip layout (new in version 3.23) +- Windows support: for some basic functions (new in version 3.17 ff) - `cpuCache`: returns CPU cache (L1, L2, L3) sizes (new in version 3.14) - `cpuFlags`: returns CPU flags (new in version 3.14) - `currentLoad.cpus`: returns current load per cpu/core in an array (new in version 3.14) @@ -95,6 +98,7 @@ Other changes | Version | Date | Comment | | -------------- | -------------- | -------- | +| 3.23.0 | 2017-06-22 | added `memLayout`, `diskLayout`, extended windows support (`inetChecksite`)| | 3.22.0 | 2017-06-19 | extended windows support (`users`, `inetLatency`) | | 3.21.0 | 2017-06-18 | extended time (timezone), extended windows support (battery, getAll...) | | 3.20.1 | 2017-06-17 | updated docs | diff --git a/README.md b/README.md index 6584ffe..d18b9b6 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ si.cpu() ## News and Changes ### Latest Activity - +- Version 3.23.0: added `memLayout`, `diskLayout`, extended windows support (`inetChecksite`) - Version 3.22.0: extended windows support (`users`, `inetLatency`) - Version 3.21.0: extended `time` (timezone), extended windows support (`battery`, `getAll...`) - Version 3.20.0: added additional windows support (`cpu`, `cpuCache`, `cpuCurrentspeed`, `mem`, `networkInterfaces`, `docker`) @@ -155,6 +155,31 @@ I also created a nice little command line tool called [mmon][mmon-github-url] ( | | swaptotal | X | X | X | | | | swapused | X | X | X | | | | swapfree | X | X | X | | +| si.memLayout(cb) | {...} | X | X | X | Memory Layout | +| | size | X | X | X | size in bytes | +| | bank | X | | X | memory bank | +| | type | X | X | X | memory type | +| | clockSpeed | X | X | X | clock speed | +| | formFactor | X | | X | form factor | +| | partNum | | X | X | part number | +| | serialNum | X | X | X | serial number | +| | voltageConfigured | X | | X | voltage conf. | +| | voltageMin | X | | X | voltage min | +| | voltageMax | X | | X | voltage max | +| si.diskLayout(cb) | {...} | X | X | X | Physical Disk Layout (HD) | +| | name | X | X | X | Disk Name | +| | vendor | X | X | X | vendor/producer | +| | firmwareRevision | X | X | X | firmware revision | +| | serialNum | X | X | X | serial number | +| | interfaceType | | X | X | | +| | size | X | X | X | size in bytes | +| | totalCylinders | | X | X | total cylinders | +| | totalHeads | | X | X | total heads | +| | totalTracks | | X | X | total tracks | +| | tracksPerCylinder | | X | X | tracks per cylinder | +| | sectorsPerTrack | | X | X | sectors per track | +| | totalSectors | | X | X | total sectors | +| | bytesPerSector | | X | X | bytes per sector | | si.battery(cb) | {...} | X | X | X | battery information | | | hasbattery | X | X | X | indicates presence of battery | | | cyclecount | X | X | | numbers of recharges | @@ -268,11 +293,11 @@ I also created a nice little command line tool called [mmon][mmon-github-url] ( | | [0].peeraddress | X | X | | peer address | | | [0].peerport | X | X | | peer port | | | [0].state | X | X | | like ESTABLISHED, TIME_WAIT, ... | -| si.inetChecksite(url, cb) | {...} | X | X | | response-time (ms) to fetch given URL | -| | url | X | X | | given url | -| | ok | X | X | | status code OK (2xx, 3xx) | -| | status | X | X | | status code | -| | ms | X | X | | response time in ms | +| si.inetChecksite(url, cb) | {...} | X | X | X | response-time (ms) to fetch given URL | +| | url | X | X | X | given url | +| | ok | X | X | X | status code OK (2xx, 3xx) | +| | status | X | X | X | status code | +| | ms | X | X | X | response time in ms | | si.inetLatency(host, cb) | : number | X | X | X | response-time (ms) to external resource
host parameter is optional (default 8.8.8.8)| #### 7. Current Load, Processes & Services diff --git a/lib/battery.js b/lib/battery.js index ab328d8..28233fa 100644 --- a/lib/battery.js +++ b/lib/battery.js @@ -120,10 +120,11 @@ module.exports = function (callback) { if (!error) { let lines = stdout.split('\r\n'); let status = parseInt(getValue(lines, 'BatteryStatus', '=') || '2'); - result.ischarging = (status >= 6 && status <= 9); + result.hasbattery = true; result.maxcapacity = parseInt(getValue(lines, 'DesignCapacity', '=') || 0); result.percent = parseInt(getValue(lines, 'EstimatedChargeRemaining', '=') || 0); result.currentcapacity = parseInt(result.maxcapacity * result.percent / 100); + result.ischarging = (status >= 6 && status <= 9) || (!(status === 3) && !(status === 1) && result.percent < 100); } if (callback) { callback(result) } resolve(result); diff --git a/lib/filesystem.js b/lib/filesystem.js index 3d8cdd2..31c54a8 100644 --- a/lib/filesystem.js +++ b/lib/filesystem.js @@ -14,6 +14,7 @@ const os = require('os'); const exec = require('child_process').exec; +const execSync = require('child_process').execSync; const fs = require('fs'); const util = require('./util'); @@ -27,6 +28,27 @@ const NOT_SUPPORTED = 'not supported'; let _fs_speed = {}; let _disk_io = {}; +function getValue(lines, property, separator, trimmed) { + separator = separator || ':'; + property = property.toLowerCase(); + for (let i = 0; i < lines.length; i++) { + let line = lines[i].toLowerCase(); + if (trimmed) { + line = line.trim(); + } + if (line.toLowerCase().startsWith(property)) { + const parts = lines[i].split(separator); + if (parts.length >= 2) { + parts.shift(); + return parts.join(':').trim(); + } else { + return '' + } + } + } + return ''; +} + // -------------------------- // FS - mounted file systems @@ -559,3 +581,122 @@ function disksIO(callback) { } exports.disksIO = disksIO; + +function diskLayout(callback) { + + return new Promise((resolve, reject) => { + process.nextTick(() => { + + let result = []; + + if (_linux) { + exec("lshw -class disk", function (error, stdout) { + if (!error) { + let devices = stdout.toString().split('*-'); + devices.shift(); + devices.forEach(function (device) { + let lines = device.split('\n'); + const logical = getValue(lines, 'logical name', ':', true).trim().replace(/\/dev\//g, ''); + const mediumType = execSync('cat /sys/block/' + logical + '/queue/rotational').toString().split('\n')[0]; + const size = getValue(lines, 'size', ':', true).trim(); + if (size && lines.length > 0 && lines[0].trim() === 'disk') { + result.push({ + type: (mediumType === '0' ? 'SSD' : (mediumType === '1' ? 'HD' : (device.indexOf('SSD') > -1 ? 'SSD' : 'HD'))), // to be tested ... /sys/block/sda/queue/rotational + name: getValue(lines, 'product:', ':', true), + vendor: getValue(lines, 'vendor:', ':', true), + size: parseInt(size.match(/\(([^)]+)\)/)[1]) * 1000 * 1000 * 1000 * 1000, + bytesPerSector: -1, + totalCylinders: -1, + totalHeads: -1, + totalSectors: -1, + totalTracks: -1, + tracksPerCylinder: -1, + sectorsPerTrack: -1, + firmwareRevision: getValue(lines, 'version:', ':', true).trim(), + serialNum: getValue(lines, 'serial:', ':', true).trim(), + interfaceType: '', + }) + } + }); + } + if (callback) { callback(result) } + resolve(result); + }); + } + + if (_darwin) { + exec("system_profiler SPSerialATADataType", function (error, stdout) { + if (!error) { + let devices = stdout.toString().split(' Physical Interconnect: SATA'); + devices.shift(); + devices.forEach(function (device) { + let lines = device.split('\n'); + const mediumType = getValue(lines, 'Medium Type', ':', true).trim(); + const sizeStr = getValue(lines, 'capacity', ':', true).trim(); + if (sizeStr) { + let size = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, "")); + if (!size) { + size = parseInt(sizeStr); + } + if (size) { + result.push({ + type: mediumType.startsWith('Solid') ? 'SSD' : 'HD', + name: getValue(lines, 'Model', ':', true).trim(), + vendor: '', + size: size, + bytesPerSector: -1, + totalCylinders: -1, + totalHeads: -1, + totalSectors: -1, + totalTracks: -1, + tracksPerCylinder: -1, + sectorsPerTrack: -1, + firmwareRevision: getValue(lines, 'Revision', ':', true).trim(), + serialNum: getValue(lines, 'Serial Number', ':', true).trim(), + interfaceType: getValue(lines, 'InterfaceType', ':', true).trim() + }) + } + } + }); + } + if (callback) { callback(result) } + resolve(result); + }); + } + if (_windows) { + + exec("wmic diskdrive get /value", function (error, stdout) { + if (!error) { + let devices = stdout.toString().split('\r\n\r\n\r\n'); + devices.forEach(function (device) { + let lines = device.split('\r\n'); + const size = getValue(lines, 'Size', '=').trim(); + if (size) { + result.push({ + type: device.indexOf('SSD') > -1 ? 'SSD' : 'HD', // not really correct(!) ... maybe this obe is better: MSFT_PhysicalDisk - Media Type?? + name: getValue(lines, 'Caption', '='), + vendor: getValue(lines, 'Manufacturer', '='), + size: parseInt(size), + bytesPerSector: parseInt(getValue(lines, 'BytesPerSector', '=')), + totalCylinders: parseInt(getValue(lines, 'TotalCylinders', '=')), + totalHeads: parseInt(getValue(lines, 'TotalHeads', '=')), + totalSectors: parseInt(getValue(lines, 'TotalSectors', '=')), + totalTracks: parseInt(getValue(lines, 'TotalTracks', '=')), + tracksPerCylinder: parseInt(getValue(lines, 'TracksPerCylinder', '=')), + sectorsPerTrack: parseInt(getValue(lines, 'SectorsPerTrack', '=')), + firmwareRevision: getValue(lines, 'FirmwareRevision', '=').trim(), + serialNum: getValue(lines, 'SerialNumber', '=').trim(), + interfaceType: getValue(lines, 'InterfaceType', '=').trim() + }) + } + }); + } + if (callback) { callback(result) } + resolve(result); + }); + } + }); + }); +} + +exports.diskLayout = diskLayout; diff --git a/lib/index.js b/lib/index.js index 9bdac88..54bf82c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -142,8 +142,14 @@ function getStaticData(callback) { data.graphics = res; network.networkInterfaces().then(res => { data.net = res; - if (callback) { callback(data) } - resolve(data); + mem.memLayout().then(res => { + data.memLayout = res; + filesystem.diskLayout().then(res => { + data.diskLayout = res; + if (callback) { callback(data) } + resolve(data); + }) + }) }) }) }) @@ -351,7 +357,8 @@ exports.cpuTemperature = cpu.cpuTemperature; exports.currentLoad = cpu.currentLoad; exports.fullLoad = cpu.fullLoad; -exports.mem = mem; +exports.mem = mem.mem; +exports.memLayout = mem.memLayout; exports.battery = battery; @@ -361,6 +368,7 @@ exports.fsSize = filesystem.fsSize; exports.blockDevices = filesystem.blockDevices; exports.fsStats = filesystem.fsStats; exports.disksIO = filesystem.disksIO; +exports.diskLayout = filesystem.diskLayout; exports.networkInterfaceDefault = network.networkInterfaceDefault; exports.networkInterfaces = network.networkInterfaces; diff --git a/lib/internet.js b/lib/internet.js index 46ae07d..9f77417 100644 --- a/lib/internet.js +++ b/lib/internet.js @@ -30,11 +30,6 @@ function inetChecksite(url, callback) { return new Promise((resolve, reject) => { process.nextTick(() => { - if (_windows) { - let error = new Error(NOT_SUPPORTED); - if (callback) { callback(NOT_SUPPORTED) } - reject(error); - } let result = { url: url, @@ -43,17 +38,51 @@ function inetChecksite(url, callback) { ms: -1 }; if (url) { + url = url.toLowerCase(); let t = Date.now(); - let args = " -I --connect-timeout 5 -m 5 " + url + " 2>/dev/null | head -n 1 | cut -d ' ' -f2"; - let cmd = "curl"; - exec(cmd + args, function (error, stdout) { - let statusCode = parseInt(stdout.toString()); - result.status = statusCode || 404; - result.ok = !error && (statusCode === 200 || statusCode === 301 || statusCode === 302 || statusCode === 304); - result.ms = (result.ok ? Date.now() - t : -1); - if (callback) { callback(result) } - resolve(result); - }) + if (_linux || _darwin) { + let args = " -I --connect-timeout 5 -m 5 " + url + " 2>/dev/null | head -n 1 | cut -d ' ' -f2"; + let cmd = "curl"; + exec(cmd + args, function (error, stdout) { + let statusCode = parseInt(stdout.toString()); + result.status = statusCode || 404; + result.ok = !error && (statusCode === 200 || statusCode === 301 || statusCode === 302 || statusCode === 304); + result.ms = (result.ok ? Date.now() - t : -1); + if (callback) { callback(result) } + resolve(result); + }) + } + if (_windows) { // if this is stable, this can be used for all OS types + const http = (url.startsWith('https:') ? require('https') : require('http')); + try { + http.get(url, (res) => { + const { statusCode } = res; + + result.status = statusCode || 404; + result.ok = (statusCode === 200 || statusCode === 301 || statusCode === 302 || statusCode === 304); + + if (statusCode !== 200) { + res.resume(); + result.ms = (result.ok ? Date.now() - t : -1); + if (callback) { callback(result) } + resolve(result); + } else { + res.on('data', (chunk) => { }); + res.on('end', () => { + result.ms = (result.ok ? Date.now() - t : -1); + if (callback) { callback(result) } + resolve(result); + }) + } + }).on('error', err => { + if (callback) { callback(result) } + resolve(result); + }); + } catch (err) { + if (callback) { callback(result) } + resolve(result); + } + } } else { if (callback) { callback(result) } resolve(result); diff --git a/lib/memory.js b/lib/memory.js index 1f583c3..f6131e5 100644 --- a/lib/memory.js +++ b/lib/memory.js @@ -24,6 +24,26 @@ const _darwin = (_platform === 'Darwin'); const _windows = (_platform === 'Windows_NT'); const NOT_SUPPORTED = 'not supported'; +function getValue(lines, property, separator, trimmed) { + separator = separator || ':'; + property = property.toLowerCase(); + for (let i = 0; i < lines.length; i++) { + let line = lines[i].toLowerCase(); + if (trimmed) { + line = line.trim(); + } + if (line.toLowerCase().startsWith(property)) { + const parts = lines[i].split(separator); + if (parts.length >= 2) { + parts.shift(); + return parts.join(':').trim(); + } else { + return '' + } + } + } + return ''; +} // _______________________________________________________________________________________ // | R A M | H D | // |______________________|_________________________| | | @@ -74,7 +94,7 @@ const NOT_SUPPORTED = 'not supported'; // // Reference: http://www.software-architect.net/blog/article/date/2015/06/12/-826c6e5052.html -module.exports = function (callback) { +function mem(callback) { return new Promise((resolve, reject) => { process.nextTick(() => { @@ -174,4 +194,103 @@ module.exports = function (callback) { } }); }); -}; +} + +exports.mem = mem; + +function memLayout(callback) { + + return new Promise((resolve, reject) => { + process.nextTick(() => { + + let result = []; + + if (_linux) { + exec("dmidecode -t memory | grep -iE 'Size:|Type|Speed|Manufacturer|Form Factor|Locator|Memory Device|Serial Number|Voltage'", function (error, stdout) { + if (!error) { + let devices = stdout.toString().split('Memory Device'); + devices.shift(); + devices.forEach(function (device) { + let lines = device.split('\n'); + result.push({ + size: parseInt(getValue(lines, ' Size'))*1024*1024, + bank: getValue(lines, ' Bank Locator'), + type: getValue(lines, ' Type:'), + clockSpeed: (getValue(lines, ' Configured Clock Speed:') ? parseInt(getValue(lines, ' Configured Clock Speed:')) : parseInt(getValue(lines, ' Speed:'))), + formFactor: getValue(lines, ' Form Factor:'), + partNum: '', + serialNum: getValue(lines, ' Serial Number:'), + voltageConfigured: parseFloat(getValue(lines, ' Configured Voltage:')), + voltageMin: parseFloat(getValue(lines, ' Minimum Voltage:')), + voltageMax: parseFloat(getValue(lines, ' Maximum Voltage:')), + }) + }); + } + if (callback) { callback(result) } + resolve(result); + }); + } + + if (_darwin) { + exec("system_profiler SPMemoryDataType", function (error, stdout) { + if (!error) { + let devices = stdout.toString().split(' BANK '); + devices.shift(); + devices.forEach(function (device) { + let lines = device.split('\n'); + const size = parseInt(getValue(lines, ' Size')); + if (size) { + result.push({ + size: size * 1024 * 1024 * 1024, + bank: '', + type: getValue(lines, ' Type:'), + clockSpeed: parseInt(getValue(lines, ' Speed:')), + formFactor: '', + partNum: getValue(lines, ' Part Number:'), + serialNum: getValue(lines, ' Serial Number:'), + voltageConfigured: -1, + voltageMin: -1, + voltageMax: -1, + }) + } + }); + } + if (callback) { callback(result) } + resolve(result); + }); + } + if (_windows) { + const memoryTypes = 'Unknown|Other|DRAM|Synchronous DRAM|Cache DRAM|EDO|EDRAM|VRAM|SRAM|RAM|ROM|FLASH|EEPROM|FEPROM|EPROM|CDRAM|3DRAM|SDRAM|SGRAM|RDRAM|DDR|DDR2|DDR2 FB-DIMM|Reserved|DDR3|FBD2|DDR4|LPDDR|LPDDR2|LPDDR3|LPDDR4'.split('|') + const FormFactors = 'Unknown|Other|SIP|DIP|ZIP|SOJ|Proprietary|SIMM|DIMM|TSOP|PGA|RIMM|SODIMM|SRIMM|SMD|SSMP|QFP|TQFP|SOIC|LCC|PLCC|BGA|FPBGA|LGA'.split('|'); + + exec("wmic memorychip get BankLabel, Capacity, ConfiguredClockSpeed, ConfiguredVoltage, MaxVoltage, MinVoltage, DataWidth, FormFactor, Manufacturer, MemoryType, PartNumber, SerialNumber, Speed, Tag /value", function (error, stdout) { + if (!error) { + let devices = stdout.toString().split('BankL'); + devices.shift(); + devices.forEach(function (device) { + let lines = device.split('\r\n'); + result.push({ + size: parseInt(getValue(lines, 'Capacity', '=')), + bank: getValue(lines, 'abel', '='), // BankLabel + type: memoryTypes[parseInt(getValue(lines, 'MemoryType', '='))], + clockSpeed: parseInt(getValue(lines, 'ConfiguredClockSpeed', '=')), + formFactor: FormFactors[parseInt(getValue(lines, 'FormFactor', '='))], + partNum: getValue(lines, 'PartNumber', '='), + serialNum: getValue(lines, 'SerialNumber', '='), + voltageConfigured: parseInt(getValue(lines, 'ConfiguredVoltage', '=')) / 1000.0, + voltageMin: parseInt(getValue(lines, 'MinVoltage', '=')) / 1000.0, + voltageMax: parseInt(getValue(lines, 'MaxVoltage', '=')) / 1000.0, + }) + }); + } + + if (callback) { callback(result) } + resolve(result); + }); + } + }); + }); +} + +exports.memLayout = memLayout; +