From 0da06759cbe0b331b0e3fdde4d35ea39bc759493 Mon Sep 17 00:00:00 2001 From: Sebastian Hildebrandt Date: Thu, 17 Dec 2020 18:33:01 +0100 Subject: [PATCH] graphics() adding nvidia-smi support (linux, win) --- README.md | 2 +- docs/security.html | 2 +- lib/graphics.js | 164 ++++++++++++++++++++++++++++++++++++--------- lib/index.d.ts | 1 + lib/util.js | 1 + 5 files changed, 138 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 7c9943d..27b668c 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ This is amazing. Started as a small project just for myself, it now has > 10,000 ## Upcoming -**MacOS on ARM (Apple silicon support), Windows on ARM**: November 11th 2020 - We will have a closer look on that! I just ordered new hardware (any support is highly appreciated - [Buy me a coffe](https://www.buymeacoffee.com/systeminfo)). As soon as I have the new hardware here, I work very hard on it to get full native support for those platforms! +**MacOS on ARM (Apple silicon support), Windows on ARM**: November 11th 2020 - We will have a closer look on that! I just ordered new hardware (any support is highly appreciated - [Buy me a coffee](https://www.buymeacoffee.com/systeminfo)). As soon as I have the new hardware here, I work very hard on it to get full native support for those platforms! **Version 5**: we are planning a new major version with some minor breaking changes and some additional features. Will try to make this available Q1 of 2021. diff --git a/docs/security.html b/docs/security.html index 387e83d..3e4d649 100644 --- a/docs/security.html +++ b/docs/security.html @@ -46,7 +46,7 @@

Affected versions: < 4.31.1
Date: 2020-12-11
- CVE indentifier CVE-2020-28448 + CVE indentifier CVE-2020-26274, CVE-2020-28448

Impact

diff --git a/lib/graphics.js b/lib/graphics.js index c1f0a6c..566e6c8 100644 --- a/lib/graphics.js +++ b/lib/graphics.js @@ -14,11 +14,13 @@ // ---------------------------------------------------------------------------------- const os = require('os'); +const fs = require('fs'); const exec = require('child_process').exec; const execSync = require('child_process').execSync; const util = require('./util'); let _platform = process.platform; +let _nvidiaSmiPath = ''; const _linux = (_platform === 'linux'); const _darwin = (_platform === 'darwin'); @@ -204,7 +206,8 @@ function graphics(callback) { bus: '', busAddress: '', vram: -1, - vramDynamic: false + vramDynamic: false, + pciID: '' }; let isGraphicsController = false; // PCI bus IDs @@ -238,9 +241,14 @@ function graphics(callback) { bus: '', busAddress: '', vram: -1, - vramDynamic: false + vramDynamic: false, }; } + + const pciIDCandidate = lines[i].split(' ')[0]; + if (/[\da-fA-F]{2}:[\da-fA-F]{2}\.[\da-fA-F]/.test(pciIDCandidate)) { + currentController.busAddress = pciIDCandidate; + } isGraphicsController = true; let endpos = lines[i].search(/\[[0-9a-f]{4}:[0-9a-f]{4}]|$/); let parts = lines[i].substr(vgapos, endpos - vgapos).split(':'); @@ -310,7 +318,7 @@ function graphics(callback) { return devices }, {}); for (let deviceId in devices) { - const device = devices[deviceId] + const device = devices[deviceId]; if (device['CL_DEVICE_TYPE'] === 'CL_DEVICE_TYPE_GPU') { let busAddress; if (device['CL_DEVICE_TOPOLOGY_AMD']) { @@ -354,9 +362,114 @@ function graphics(callback) { } } } - return controllers + return controllers; } + function getNvidiaSmi() { + if (_nvidiaSmiPath) { + return _nvidiaSmiPath; + } + + if (_windows) { + try { + const basePath = util.WINDIR + '\\System32\\DriverStore\\FileRepository'; + const dirContent = fs.readdirSync(basePath); + const candidateDirs = dirContent.filter(dir => dir.startsWith('nv')); + const targetDir = candidateDirs.find(dir => { + const content = fs.readdirSync([basePath, dir].join('/')); + return content.includes('nvidia-smi.exe'); + }); + + if (targetDir) { + _nvidiaSmiPath = [basePath, targetDir, 'nvidia-smi.exe'].join('/'); + } + } catch (e) { + util.noop() + } + } else if (_linux) { + _nvidiaSmiPath = 'nvidia-smi'; + } + return _nvidiaSmiPath; + } + + function nvidiaSmi(options) { + const nvidiaSmiExe = getNvidiaSmi(); + if (nvidiaSmiExe) { + const nvidiaSmiOpts = '--query-gpu=driver_version,pci.sub_device_id,name,pci.bus_id,fan.speed,memory.total,memory.used,memory.free,utilization.gpu,utilization.memory,temperature.gpu,temperature.memory,power.draw,power.limit,clocks.gr,clocks.mem --format=csv,noheader,nounits' + try { + const res = execSync(nvidiaSmiExe + ' ' + nvidiaSmiOpts, options); + return res; + } catch (e) { + util.noop(); + } + } + return ''; + } + + function nvidiaDevices() { + + function safeParseNumber(value) { + if ([null, undefined].includes(value)) { + return value; + } + return parseFloat(value); + } + + const stdout = nvidiaSmi(); + if (!stdout) { + return []; + } + + const gpus = stdout.split('\n').filter(Boolean); + const results = gpus.map(gpu => { + const splittedData = gpu.split(', ').map(value => value.includes('N/A') ? undefined : value); + if (splittedData.length === 16) { + return { + driverVersion: splittedData[0], + subDeviceId: splittedData[1], + name: splittedData[2], + pciBus: splittedData[3], + fanSpeed: safeParseNumber(splittedData[4]), + memoryTotal: safeParseNumber(splittedData[5]), + memoryUsed: safeParseNumber(splittedData[6]), + memoryFree: safeParseNumber(splittedData[7]), + utilizationGpu: safeParseNumber(splittedData[8]), + utilizationMemory: safeParseNumber(splittedData[9]), + temperatureGpu: safeParseNumber(splittedData[10]), + temperatureMemory: safeParseNumber(splittedData[11]), + powerDraw: safeParseNumber(splittedData[12]), + powerLimit: safeParseNumber(splittedData[13]), + clockCore: safeParseNumber(splittedData[14]), + clockMemory: safeParseNumber(splittedData[15]), + } + } + }); + + return results; + } + + function mergeControllerNvidia(controller, nvidia) { + if (nvidia.driverVersion) { controller.driverVersion = nvidia.driverVersion; } + if (nvidia.subDeviceId) { controller.subDeviceId = nvidia.subDeviceId; } + if (nvidia.name) { controller.name = nvidia.name; } + if (nvidia.pciBus) { controller.pciBus = nvidia.pciBus; } + if (nvidia.fanSpeed) { controller.fanSpeed = nvidia.fanSpeed; } + if (nvidia.memoryTotal) { controller.memoryTotal = nvidia.memoryTotal; } + if (nvidia.memoryUsed) { controller.memoryUsed = nvidia.memoryUsed; } + if (nvidia.memoryFree) { controller.memoryFree = nvidia.memoryFree; } + if (nvidia.utilizationGpu) { controller.utilizationGpu = nvidia.utilizationGpu; } + if (nvidia.utilizationMemory) { controller.utilizationMemory = nvidia.utilizationMemory; } + if (nvidia.temperatureGpu) { controller.temperatureGpu = nvidia.temperatureGpu; } + if (nvidia.temperatureMemory) { controller.temperatureMemory = nvidia.temperatureMemory; } + if (nvidia.powerDraw) { controller.powerDraw = nvidia.powerDraw; } + if (nvidia.powerLimit) { controller.powerLimit = nvidia.powerLimit; } + if (nvidia.clockCore) { controller.clockCore = nvidia.clockCore; } + if (nvidia.clockMemory) { controller.clockMemory = nvidia.clockMemory; } + return controller + } + + + function parseLinesLinuxEdid(edid) { // parsen EDID // --> model @@ -592,6 +705,11 @@ function graphics(callback) { if (!error) { let lines = stdout.toString().split('\n'); result.controllers = parseLinesLinuxControllers(lines); + const nvidiaData = nvidiaDevices(); + // needs to be rewritten ... using no spread operators + result.controllers = result.controllers.map(( controller ) => { // match by busAddress + return mergeControllerNvidia(controller, nvidiaData.find(({ pciBus }) => pciBus.endsWith(controller.busAddress)) || {} ); + }) } let cmd = "clinfo --raw"; exec(cmd, function (error, stdout) { @@ -643,13 +761,18 @@ function graphics(callback) { workload.push(util.powerShell('Get-CimInstance -Namespace root\\wmi -ClassName WmiMonitorConnectionParams | fl')); workload.push(util.powerShell('gwmi WmiMonitorID -Namespace root\\wmi | ForEach-Object {(($_.ManufacturerName -notmatch 0 | foreach {[char]$_}) -join "") + "|" + (($_.ProductCodeID -notmatch 0 | foreach {[char]$_}) -join "") + "|" + (($_.UserFriendlyName -notmatch 0 | foreach {[char]$_}) -join "") + "|" + (($_.SerialNumberID -notmatch 0 | foreach {[char]$_}) -join "") + "|" + $_.InstanceName}')); + const nvidiaData = nvidiaDevices(); + Promise.all( workload ).then(data => { // controller let csections = data[0].split(/\n\s*\n/); result.controllers = parseLinesWindowsControllers(csections); - + // needs to be rewritten ... using no spread operators + result.controllers = result.controllers.map((controller) => { // match by subDeviceId + return mergeControllerNvidia(controller, nvidiaData.find(device => controller.subDeviceId.toLowerCase() === device.subDeviceId.split('x')[1].toLowerCase()) || {}); + }) // displays let dsections = data[1].split(/\n\s*\n/); // result.displays = parseLinesWindowsDisplays(dsections); @@ -732,12 +855,17 @@ function graphics(callback) { if (sections[i].trim() !== '') { let lines = sections[i].trim().split('\r\n'); + let subDeviceId = util.getValue(lines, 'PNPDeviceID', '=').match(/SUBSYS_[a-fA-F\d]{8}/)[0]; + if (subDeviceId) { + subDeviceId = subDeviceId.split('_')[1]; + } controllers.push({ vendor: util.getValue(lines, 'AdapterCompatibility', '='), model: util.getValue(lines, 'name', '='), bus: util.getValue(lines, 'PNPDeviceID', '=').startsWith('PCI') ? 'PCI' : '', vram: parseInt(util.getValue(lines, 'AdapterRAM', '='), 10) / 1024 / 1024, - vramDynamic: (util.getValue(lines, 'VideoMemoryType', '=') === '2') + vramDynamic: (util.getValue(lines, 'VideoMemoryType', '=') === '2'), + subDeviceId }); _resolutionx = util.toInt(util.getValue(lines, 'CurrentHorizontalResolution', '=')) || _resolutionx; _resolutiony = util.toInt(util.getValue(lines, 'CurrentVerticalResolution', '=')) || _resolutiony; @@ -749,30 +877,6 @@ function graphics(callback) { return controllers; } - // function parseLinesWindowsDisplays(sections) { - // let displays = []; - // for (let i in sections) { - // if (sections.hasOwnProperty(i)) { - // if (sections[i].trim() !== '') { - // let lines = sections[i].trim().split('\r\n'); - // displays.push({ - // vendor: util.getValue(lines, 'MonitorManufacturer', '='), - // model: util.getValue(lines, 'Name', '='), - // main: false, - // builtin: false, - // connection: '', - // sizex: -1, - // sizey: -1, - // pixeldepth: -1, - // resolutionx: util.toInt(util.getValue(lines, 'ScreenWidth', '=')), - // resolutiony: util.toInt(util.getValue(lines, 'ScreenHeight', '=')), - // }); - // } - // } - // } - // return displays; - // } - function parseLinesWindowsDisplaysPowershell(ssections, msections, dsections, tsections, isections) { let displays = []; let vendor = ''; diff --git a/lib/index.d.ts b/lib/index.d.ts index 76cb327..8179aec 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -255,6 +255,7 @@ export namespace Systeminformation { busAddress?: string; vram: number; vramDynamic: boolean; + subDeviceId?: string; } interface GraphicsDisplayData { diff --git a/lib/util.js b/lib/util.js index 1931a88..3a9b51f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -830,3 +830,4 @@ exports.stringToLower = stringToLower; exports.stringToString = stringToString; exports.stringSubstr = stringSubstr; exports.stringTrim = stringTrim; +exports.WINDIR = WINDIR;