diff --git a/CHANGELOG.md b/CHANGELOG.md index 8173750..dda55aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ Other changes | Version | Date | Comment | | -------------- | -------------- | -------- | +| 3.55.0 | 2019-01-04 | `networkInterfaces()` extended, `networkStats()` extended, bugfix windows | | 3.54.0 | 2018-12-30 | added TypeScript type definitions | | 3.53.1 | 2018-12-29 | `versions()` bug fix nginx version | | 3.53.0 | 2018-12-29 | `versions()` added perl, python, gcc | diff --git a/LICENSE b/LICENSE index e6c1a6e..83cb5f9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2018 Sebastian Hildebrandt +Copyright (c) 2014-2019 Sebastian Hildebrandt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 1767532..c837ca6 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,6 @@ Simple system and OS information library for [node.js][nodejs-url] [![Caretaker][caretaker-image]][caretaker-url] [![MIT license][license-img]][license-url] -#### Happy christmas holiday - -``` -. * * *. * . * . - . . __ * . * . * - * * * . . _|__|_ * __ . * - . * /\ /\ * ('') * _|__|_ . - / \ * / \ * . <( . )> * . ('') * * - * / \ / \ . * _(__.__)_ _ ,--<( . )> . . - / \ / \ * | | )),` ( . ) * -...* `||` .. `||` . *. ... ==========='` ... '--`-` ... * ... -``` - ## Quick Start Lightweight collection of 35+ functions to retrieve detailed hardware, system and OS information. @@ -77,14 +64,13 @@ async function cpu() { (last 7 major and minor version releases) +- Version 3.55.0: `networkInterfaces()` extended, `networkStats()` extended, bugfix windows - Version 3.54.0: added TypeScript type definitions - Version 3.53.0: `versions()` added perl, python, gcc - Version 3.52.0: `cpu()` added physical cores, processors, socket type - Version 3.51.0: `processLoad()` added for windows - Version 3.50.0: `services()` added possibility to specify ALL services "*" for linux/win - Version 3.49.0: `uuid()` added - os specific uuid (per installation) -- Version 3.48.0: `osInfo()` added build, serial (Windows/macOS) -- Version 3.47.0: `version()` added docker, postfix - ... You can find all changes here: [detailed changelog][changelog-url] @@ -324,17 +310,28 @@ I also created a nice little command line tool called [mmon][mmon-github-url] ( | Function | Result object | Linux | BSD | Mac | Win | Sun | Comments | | --------------- | ------------- | ----- | ------- | --- | --- | --- | -------- | | si.networkInterfaces(cb) | [{...}] | X | X | X | X | X | array of network interfaces | -| | [0].iface | X | X | X | X | X | interface name | +| | [0].iface | X | X | X | X | X | interface | +| | [0].ifaceName | X | X | X | X | X | interface name (differs on Windows) | | | [0].ip4 | X | X | X | X | X | ip4 address | | | [0].ip6 | X | X | X | X | X | ip6 address | | | [0].mac | X | X | X | X | X | MAC address | | | [0].internal | X | X | X | X | X | true if internal interface | +| | [0].operstate | X | | | X | | up / down | +| | [0].type | X | | | X | | wireless / wired | +| | [0].duplex | X | | | | | duplex | +| | [0].mtu | X | | | | | maximum transmission unit | +| | [0].speed | X | | | X | | speed in MBit / s | +| | [0].carrierChanges | X | | | | | # changes up/down | | si.networkInterfaceDefault(cb) | : string | X | X | X | X | X | get name of default network interface | | si.networkStats(iface,cb) | {...} | X | X | X | X | | current network stats of given interface
iface parameter is optional
defaults to first external network interface| | | iface | X | X | X | X | | interface | | | operstate | X | X | X | X | | up / down | | | rx | X | X | X | X | | received bytes overall | +| | rx_dropped | X | X | X | X | | received dropped overall | +| | rx_errors | X | X | X | X | | received errors overall | | | tx | X | X | X | X | | transferred bytes overall | +| | tx_dropped | X | X | X | X | | transferred dropped overall | +| | tx_errors | X | X | X | X | | transferred errors overall | | | rx_sec | X | X | X | X | | received bytes / second (* see notes) | | | tx_sec | X | X | X | X | | transferred bytes per second (* see notes) | | | ms | X | X | X | X | | interval length (for per second values) | @@ -633,7 +630,7 @@ All other trademarks are the property of their respective owners. >The [`MIT`][license-url] License (MIT) > ->Copyright © 2014-2018 Sebastian Hildebrandt, [+innovations](http://www.plus-innovations.com). +>Copyright © 2014-2019 Sebastian Hildebrandt, [+innovations](http://www.plus-innovations.com). > >Permission is hereby granted, free of charge, to any person obtaining a copy >of this software and associated documentation files (the "Software"), to deal diff --git a/lib/index.d.ts b/lib/index.d.ts index 359fc85..11235c7 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -274,17 +274,28 @@ export namespace Systeminformation { interface NetworkInterfacesData { iface: string; + ifaceName: string; ip4: string; ip6: string; mac: string; internal: boolean; + operstate: string; + type: string; + duplex: string; + mtu: number; + speed: number; + carrier_changes: number; } interface NetworkStatsData { iface: string; operstate: string; rx: number; + rx_dropped: number; + rx_errors: number; tx: number; + tx_dropped: number; + tx_errors: number; rx_sec: number; tx_sec: number; ms: number; diff --git a/lib/network.js b/lib/network.js index f80a92e..cca9c1b 100644 --- a/lib/network.js +++ b/lib/network.js @@ -29,6 +29,8 @@ const _sunos = (_platform === 'sunos'); let _network = {}; let _default_iface; +let _ifaces = []; +let _networkInterfaces = []; let _mac = {}; let pathToIp; @@ -145,41 +147,167 @@ exports.networkInterfaceDefault = networkInterfaceDefault; // -------------------------- // NET - interfaces +function parseLinesWindowsNics(sections) { + let nics = []; + for (let i in sections) { + if (sections.hasOwnProperty(i)) { + if (sections[i].trim() !== '') { + + let lines = sections[i].trim().split('\r\n'); + let netEnabled = util.getValue(lines, 'NetEnabled', '='); + if (netEnabled) { + const speed = parseInt(util.getValue(lines, 'speed', '=').trim(), 10); + nics.push({ + mac: util.getValue(lines, 'MACAddress', '=').toLowerCase(), + name: util.getValue(lines, 'Name', '=').replace(/\]/g, ')').replace(/\[/g, '('), + netEnabled: netEnabled === 'TRUE', + speed: isNaN(speed) ? -1 : speed, + operstate: util.getValue(lines, 'NetConnectionStatus', '=') === '2' ? 'up' : 'down', + type: util.getValue(lines, 'MACAddress', '=') === '9' ? 'wireless' : 'wired' + }); + } + } + } + } + return nics; +} + +function getWindoesNics() { + const cmd = util.getWmic() + ' nic get MACAddress, name, NetEnabled, Speed, NetConnectionStatus, AdapterTypeId /value'; + try { + const nsections = execSync(cmd, util.execOptsWin).split(/\n\s*\n/); + return (parseLinesWindowsNics(nsections)); + } catch (e) { + return []; + } +} + function networkInterfaces(callback) { return new Promise((resolve) => { process.nextTick(() => { let ifaces = os.networkInterfaces(); let result = []; + let nics = []; + if (JSON.stringify(ifaces) === JSON.stringify(_ifaces)) { + // no changes - just return object + result = _networkInterfaces; - for (let dev in ifaces) { - let ip4 = ''; - let ip6 = ''; - let mac = ''; - if (ifaces.hasOwnProperty(dev)) { - ifaces[dev].forEach(function (details) { - if (details.family === 'IPv4') { - ip4 = details.address; - } - if (details.family === 'IPv6') { - if (!ip6 || ip6.match(/^fe80::/i)) { - ip6 = details.address; - } - } - mac = details.mac; - if (mac.indexOf('00:00:0') > -1 && (_linux || _darwin)) { - if (Object.keys(_mac).length === 0) { - _mac = getMacAddresses(); - } - mac = _mac[dev] || ''; - } - }); - let internal = (ifaces[dev] && ifaces[dev][0]) ? ifaces[dev][0].internal : null; - result.push({ iface: dev, ip4: ip4, ip6: ip6, mac: mac, internal: internal }); + if (callback) { callback(result); } + resolve(result); + } else { + _ifaces = ifaces; + if (_windows) { + nics = getWindoesNics(); } + for (let dev in ifaces) { + let ip4 = ''; + let ip6 = ''; + let mac = ''; + let duplex = ''; + let mtu = ''; + let speed = -1; + let carrierChanges = 0; + let operstate = 'down'; + let type = ''; + + if (ifaces.hasOwnProperty(dev)) { + let ifaceName = dev; + ifaces[dev].forEach(function (details) { + + if (details.family === 'IPv4') { + ip4 = details.address; + } + if (details.family === 'IPv6') { + if (!ip6 || ip6.match(/^fe80::/i)) { + ip6 = details.address; + } + } + mac = details.mac; + // fallback due to https://github.com/nodejs/node/issues/13581 (node 8.1 - node 8.2) + if (mac.indexOf('00:00:0') > -1 && (_linux || _darwin)) { + if (Object.keys(_mac).length === 0) { + _mac = getMacAddresses(); + } + mac = _mac[dev] || ''; + } + }); + if (_linux) { + let iface = dev.split(':')[0]; + const cmd = `echo -n "addr_assign_type: "; cat /sys/class/net/${iface}/addr_assign_type 2>&1; + echo -n "address: "; cat /sys/class/net/${iface}/address 2>&1; + echo -n "addr_len: "; cat /sys/class/net/${iface}/addr_len 2>&1; + echo -n "broadcast: "; cat /sys/class/net/${iface}/broadcast 2>&1; + echo -n "carrier: "; cat /sys/class/net/${iface}/carrier 2>&1; + echo -n "carrier_changes: "; cat /sys/class/net/${iface}/carrier_changes 2>&1; + echo -n "dev_id: "; cat /sys/class/net/${iface}/dev_id 2>&1; + echo -n "dev_port: "; cat /sys/class/net/${iface}/dev_port 2>&1; + echo -n "dormant: "; cat /sys/class/net/${iface}/dormant 2>&1; + echo -n "duplex: "; cat /sys/class/net/${iface}/duplex 2>&1; + echo -n "flags: "; cat /sys/class/net/${iface}/flags 2>&1; + echo -n "gro_flush_timeout: "; cat /sys/class/net/${iface}/gro_flush_timeout 2>&1; + echo -n "ifalias: "; cat /sys/class/net/${iface}/ifalias 2>&1; + echo -n "ifindex: "; cat /sys/class/net/${iface}/ifindex 2>&1; + echo -n "iflink: "; cat /sys/class/net/${iface}/iflink 2>&1; + echo -n "link_mode: "; cat /sys/class/net/${iface}/link_mode 2>&1; + echo -n "mtu: "; cat /sys/class/net/${iface}/mtu 2>&1; + echo -n "netdev_group: "; cat /sys/class/net/${iface}/netdev_group 2>&1; + echo -n "operstate: "; cat /sys/class/net/${iface}/operstate 2>&1; + echo -n "proto_down: "; cat /sys/class/net/${iface}/proto_down 2>&1; + echo -n "speed: "; cat /sys/class/net/${iface}/speed 2>&1; + echo -n "tx_queue_len: "; cat /sys/class/net/${iface}/tx_queue_len 2>&1; + echo -n "type: "; cat /sys/class/net/${iface}/type 2>&1; + echo -n "wireless: "; cat /proc/net/wireless \| grep ${iface}; echo;`; + + let lines = execSync(cmd).toString().split('\n'); + duplex = util.getValue(lines, 'duplex'); + duplex = duplex.startsWith('cat') ? '' : duplex; + mtu = parseInt(util.getValue(lines, 'mtu'), 10); + let myspeed = parseInt(util.getValue(lines, 'speed'), 10); + speed = isNaN(myspeed) ? -1 : myspeed; + carrierChanges = parseInt(util.getValue(lines, 'carrier_changes'), 10); + operstate = util.getValue(lines, 'operstate'); + type = operstate === 'up' ? (util.getValue(lines, 'wireless').trim() ? 'wireless' : 'wired') : 'unknown'; + // rx_bytes = parseInt(util.getValue(lines, 'rx_bytes'), 10); + // rx_dropped = parseInt(util.getValue(lines, 'rx_dropped'), 10); + // rx_errors = parseInt(util.getValue(lines, 'rx_errors'), 10); + // tx_bytes = parseInt(util.getValue(lines, 'tx_bytes'), 10); + // tx_dropped = parseInt(util.getValue(lines, 'tx_dropped'), 10); + // tx_errors = parseInt(util.getValue(lines, 'tx_errors'), 10); + } + if (_windows) { + nics.forEach(detail => { + if (detail.mac === mac) { + ifaceName = detail.name; + operstate = detail.operstate; + speed = detail.speed; + type = detail.type; + } + }); + } + // if (_darwin) { + // + // } + let internal = (ifaces[dev] && ifaces[dev][0]) ? ifaces[dev][0].internal : null; + result.push({ + iface: dev, + ifaceName, + ip4, + ip6, + mac, + internal, + operstate, + type, + duplex, + mtu, + speed, + carrierChanges, + }); + } + } + if (callback) { callback(result); } + resolve(result); } - if (callback) { callback(result); } - resolve(result); }); }); } @@ -189,12 +317,16 @@ exports.networkInterfaces = networkInterfaces; // -------------------------- // NET - Speed -function calcNetworkSpeed(iface, rx, tx, operstate) { +function calcNetworkSpeed(iface, rx, tx, operstate, rx_dropped, rx_errors, tx_dropped, tx_errors) { let result = { - iface: iface, - operstate: operstate, - rx: rx, - tx: tx, + iface, + operstate, + rx, + rx_dropped, + rx_errors, + tx, + tx_dropped, + tx_errors, rx_sec: -1, tx_sec: -1, ms: 0 @@ -226,27 +358,6 @@ function calcNetworkSpeed(iface, rx, tx, operstate) { function networkStats(iface, callback) { - function parseLinesWindowsNics(sections) { - let nics = []; - for (let i in sections) { - if (sections.hasOwnProperty(i)) { - if (sections[i].trim() !== '') { - - let lines = sections[i].trim().split('\r\n'); - let netEnabled = util.getValue(lines, 'NetEnabled', '='); - if (netEnabled) { - nics.push({ - mac: util.getValue(lines, 'MACAddress', '=').toLowerCase(), - name: util.getValue(lines, 'Name', '=').replace(/[()\[\] ]+/g, '').toLowerCase(), - netEnabled: netEnabled === 'TRUE' - }); - } - } - } - } - return nics; - } - function parseLinesWindowsPerfData(sections) { let perfData = []; for (let i in sections) { @@ -257,7 +368,11 @@ function networkStats(iface, callback) { perfData.push({ name: util.getValue(lines, 'Name', '=').replace(/[()\[\] ]+/g, '').toLowerCase(), rx: parseInt(util.getValue(lines, 'BytesReceivedPersec', '='), 10), - tx: parseInt(util.getValue(lines, 'BytesSentPersec', '='), 10) + rx_errors: parseInt(util.getValue(lines, 'PacketsReceivedErrors', '='), 10), + rx_dropped: parseInt(util.getValue(lines, 'PacketsReceivedDiscarded', '='), 10), + tx: parseInt(util.getValue(lines, 'BytesSentPersec', '='), 10), + tx_errors: parseInt(util.getValue(lines, 'PacketsOutboundErrors', '='), 10), + tx_dropped: parseInt(util.getValue(lines, 'PacketsOutboundDiscarded', '='), 10) }); } } @@ -265,7 +380,6 @@ function networkStats(iface, callback) { return perfData; } - // fallback - if only callback is given if (util.isFunction(iface) && !callback) { callback = iface; @@ -281,7 +395,11 @@ function networkStats(iface, callback) { iface: iface, operstate: 'unknown', rx: 0, + rx_dropped: 0, + rx_errors: 0, tx: 0, + tx_dropped: 0, + tx_errors: 0, rx_sec: -1, tx_sec: -1, ms: 0 @@ -290,6 +408,10 @@ function networkStats(iface, callback) { let operstate = 'unknown'; let rx = 0; let tx = 0; + let rx_dropped = 0; + let rx_errors = 0; + let tx_dropped = 0; + let tx_errors = 0; let cmd, lines, stats; if (!_network[iface] || (_network[iface] && !_network[iface].ms) || (_network[iface] && _network[iface].ms && Date.now() - _network[iface].ms >= 500)) { @@ -298,15 +420,23 @@ function networkStats(iface, callback) { cmd = 'cat /sys/class/net/' + iface + '/operstate; ' + 'cat /sys/class/net/' + iface + '/statistics/rx_bytes; ' + - 'cat /sys/class/net/' + iface + '/statistics/tx_bytes; '; + 'cat /sys/class/net/' + iface + '/statistics/tx_bytes; ' + + 'cat /sys/class/net/' + iface + '/statistics/rx_dropped; ' + + 'cat /sys/class/net/' + iface + '/statistics/rx_errors; ' + + 'cat /sys/class/net/' + iface + '/statistics/rx_dropped; ' + + 'cat /sys/class/net/' + iface + '/statistics/tx_errors; '; exec(cmd, function (error, stdout) { if (!error) { lines = stdout.toString().split('\n'); operstate = lines[0].trim(); - rx = parseInt(lines[1]); - tx = parseInt(lines[2]); + rx = parseInt(lines[1], 10); + tx = parseInt(lines[2], 10); + rx_dropped = parseInt(lines[3], 10); + rx_errors = parseInt(lines[4], 10); + tx_dropped = parseInt(lines[5], 10); + tx_errors = parseInt(lines[6], 10); - result = calcNetworkSpeed(iface, rx, tx, operstate); + result = calcNetworkSpeed(iface, rx, tx, operstate, rx_dropped, rx_errors, tx_dropped, tx_errors); } if (callback) { callback(result); } @@ -318,7 +448,7 @@ function networkStats(iface, callback) { } } if (_freebsd || _openbsd) { - cmd = 'netstat -ibnI ' + iface; + cmd = 'netstat -ibndI ' + iface; exec(cmd, function (error, stdout) { if (!error) { lines = stdout.toString().split('\n'); @@ -326,11 +456,15 @@ function networkStats(iface, callback) { const line = lines[i].replace(/ +/g, ' ').split(' '); if (line && line[0] && line[7] && line[10]) { rx = rx + parseInt(line[7]); + if (stats[6].trim() !== '-') { rx_dropped = rx_dropped + parseInt(stats[6]); } + if (stats[5].trim() !== '-') { rx_errors = rx_errors + parseInt(stats[5]); } tx = tx + parseInt(line[10]); + if (stats[12].trim() !== '-') { tx_dropped = tx_dropped + parseInt(stats[12]); } + if (stats[9].trim() !== '-') { tx_errors = tx_errors + parseInt(stats[9]); } operstate = 'up'; } } - result = calcNetworkSpeed(iface, rx, tx, operstate); + result = calcNetworkSpeed(iface, rx, tx, operstate, rx_dropped, rx_errors, tx_dropped, tx_errors); } if (callback) { callback(result); } resolve(result); @@ -342,7 +476,7 @@ function networkStats(iface, callback) { result.operstate = (stdout.toString().split(':')[1] || '').trim(); result.operstate = (result.operstate || '').toLowerCase(); result.operstate = (result.operstate === 'active' ? 'up' : (result.operstate === 'inactive' ? 'down' : 'unknown')); - cmd = 'netstat -bI ' + iface; + cmd = 'netstat -bdI ' + iface; exec(cmd, function (error, stdout) { if (!error) { lines = stdout.toString().split('\n'); @@ -352,9 +486,13 @@ function networkStats(iface, callback) { // use the second line because it is tied to the NIC instead of the ipv4 or ipv6 address stats = lines[1].replace(/ +/g, ' ').split(' '); rx = parseInt(stats[6]); + rx_dropped = parseInt(stats[11]); + rx_errors = parseInt(stats[5]); tx = parseInt(stats[9]); + tx_dropped = parseInt(stats[11]); + tx_errors = parseInt(stats[8]); - result = calcNetworkSpeed(iface, rx, tx, result.operstate); + result = calcNetworkSpeed(iface, rx, tx, result.operstate, rx_dropped, rx_errors, tx_dropped, tx_errors); } } if (callback) { callback(result); } @@ -363,66 +501,42 @@ function networkStats(iface, callback) { }); } if (_windows) { - // NICs let perfData = []; - let nics = []; - cmd = util.getWmic() + ' nic get MACAddress, name, NetEnabled /value'; - try { - exec(cmd, util.execOptsWin, function (error, stdout) { - if (!error) { - const nsections = stdout.split(/\n\s*\n/); - nics = parseLinesWindowsNics(nsections); - // Performance Data - cmd = util.getWmic() + ' path Win32_PerfRawData_Tcpip_NetworkInterface Get name,BytesReceivedPersec,BytesSentPersec,BytesTotalPersec /value'; - exec(cmd, util.execOptsWin, function (error, stdout) { - if (!error) { - const psections = stdout.split(/\n\s*\n/); - perfData = parseLinesWindowsPerfData(psections); + // Performance Data + cmd = util.getWmic() + ' path Win32_PerfRawData_Tcpip_NetworkInterface Get name,BytesReceivedPersec,BytesSentPersec,BytesTotalPersec,PacketsOutboundDiscarded,PacketsOutboundErrors,PacketsReceivedDiscarded,PacketsReceivedErrors /value'; + exec(cmd, util.execOptsWin, function (error, stdout) { + if (!error) { + const psections = stdout.split(/\n\s*\n/); + perfData = parseLinesWindowsPerfData(psections); + } + + // Network Interfaces + networkInterfaces().then(interfaces => { + // get bytes sent, received from perfData by name + rx = 0; + tx = 0; + perfData.forEach(detail => { + interfaces.forEach(det => { + if (det.iface === iface && det.ifaceName.replace(/[()\[\] ]+/g, '').toLowerCase() === detail.name) { + rx = detail.rx; + rx_dropped = detail.rx_dropped; + rx_errors = detail.rx_errors; + tx = detail.tx; + tx_dropped = detail.tx_dropped; + tx_errors = detail.tx_errors; + operstate = det.operstate; } - - // Network Interfaces - networkInterfaces().then(interfaces => { - // get mac from 'interfaces' by interfacename - let mac = ''; - interfaces.forEach(detail => { - if (detail.iface === iface) { - mac = detail.mac; - } - }); - - // get name from 'nics' (by macadress) - let name = ''; - nics.forEach(detail => { - if (detail.mac === mac) { - name = detail.name; - operstate = (detail.netEnabled ? 'up' : 'down'); - } - }); - - // get bytes sent, received from perfData by name - rx = 0; - tx = 0; - perfData.forEach(detail => { - if (detail.name === name) { - rx = detail.rx; - tx = detail.tx; - } - }); - - if (rx && tx) { - result = calcNetworkSpeed(iface, parseInt(rx), parseInt(tx), operstate); - } - if (callback) { callback(result); } - resolve(result); - }); }); + }); + + if (rx && tx) { + result = calcNetworkSpeed(iface, parseInt(rx), parseInt(tx), operstate, rx_dropped, rx_errors, tx_dropped, tx_errors); } + if (callback) { callback(result); } + resolve(result); }); - } catch (e) { - if (callback) { callback(result); } - resolve(result); - } + }); } } else { result.rx = _network[iface].rx; diff --git a/lib/osinfo.js b/lib/osinfo.js index 3501c20..3074171 100644 --- a/lib/osinfo.js +++ b/lib/osinfo.js @@ -370,7 +370,7 @@ function versions(callback) { functionProcessed(); }); if (_darwin) { - const cmdLineToolsExists = fs.existsSync('/Library/Developer/CommandLineTools/'); + const cmdLineToolsExists = fs.existsSync('/Library/Developer/CommandLineTools/usr/bin/'); const xcodeAppExists = fs.existsSync('/Applications/Xcode.app/Contents/Developer/Tools'); const xcodeExists = fs.existsSync('/Library/Developer/Xcode/'); const gitHomebrewExists = fs.existsSync('/usr/local/Cellar/git'); diff --git a/package.json b/package.json index 494cd9f..723e856 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,10 @@ "block devices", "netstats", "network", + "network interfaces", "network connections", "network stats", + "iface", "processes", "users", "internet", @@ -55,6 +57,7 @@ "graphics", "graphic card", "graphic controller", + "gpu", "display", "smart", "disk layout"