| 1 | /* |
| 2 | Copyright 2019-2021 Intel Corporation |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | var PDH_FMT_LONG = 0x00000100; |
| 18 | var PDH_FMT_DOUBLE = 0x00000200; |
| 19 | |
| 20 | var promise = require('promise'); |
| 21 | if (process.platform == 'win32') |
| 22 | { |
| 23 | var GM = require('_GenericMarshal'); |
| 24 | GM.kernel32 = GM.CreateNativeProxy('kernel32.dll'); |
| 25 | GM.kernel32.CreateMethod('GlobalMemoryStatusEx'); |
| 26 | |
| 27 | GM.pdh = GM.CreateNativeProxy('pdh.dll'); |
| 28 | GM.pdh.CreateMethod('PdhAddEnglishCounterA'); |
| 29 | GM.pdh.CreateMethod('PdhCloseQuery'); |
| 30 | GM.pdh.CreateMethod('PdhCollectQueryData'); |
| 31 | GM.pdh.CreateMethod('PdhGetFormattedCounterValue'); |
| 32 | GM.pdh.CreateMethod('PdhGetFormattedCounterArrayA'); |
| 33 | GM.pdh.CreateMethod('PdhOpenQueryA'); |
| 34 | GM.pdh.CreateMethod('PdhRemoveCounter'); |
| 35 | } |
| 36 | |
| 37 | function windows_cpuUtilization() |
| 38 | { |
| 39 | var p = new promise(function (res, rej) { this._res = res; this._rej = rej; }); |
| 40 | p.counter = GM.CreateVariable(16); |
| 41 | p.cpu = GM.CreatePointer(); |
| 42 | p.cpuTotal = GM.CreatePointer(); |
| 43 | var err = 0; |
| 44 | if ((err = GM.pdh.PdhOpenQueryA(0, 0, p.cpu).Val) != 0) { p._rej(err); return; } |
| 45 | |
| 46 | // This gets the CPU Utilization for each proc |
| 47 | if ((err = GM.pdh.PdhAddEnglishCounterA(p.cpu.Deref(), GM.CreateVariable('\\Processor(*)\\% Processor Time'), 0, p.cpuTotal).Val) != 0) { p._rej(err); return; } |
| 48 | |
| 49 | if ((err = GM.pdh.PdhCollectQueryData(p.cpu.Deref()).Val != 0)) { p._rej(err); return; } |
| 50 | p._timeout = setTimeout(function (po) |
| 51 | { |
| 52 | var u = { cpus: [] }; |
| 53 | var bufSize = GM.CreateVariable(4); |
| 54 | var itemCount = GM.CreateVariable(4); |
| 55 | var buffer, szName, item; |
| 56 | var e; |
| 57 | if ((e = GM.pdh.PdhCollectQueryData(po.cpu.Deref()).Val != 0)) { po._rej(e); return; } |
| 58 | |
| 59 | if ((e = GM.pdh.PdhGetFormattedCounterArrayA(po.cpuTotal.Deref(), PDH_FMT_DOUBLE, bufSize, itemCount, 0).Val) == -2147481646) |
| 60 | { |
| 61 | buffer = GM.CreateVariable(bufSize.toBuffer().readUInt32LE()); |
| 62 | } |
| 63 | else |
| 64 | { |
| 65 | po._rej(e); |
| 66 | return; |
| 67 | } |
| 68 | if ((e = GM.pdh.PdhGetFormattedCounterArrayA(po.cpuTotal.Deref(), PDH_FMT_DOUBLE, bufSize, itemCount, buffer).Val) != 0) { po._rej(e); return; } |
| 69 | for(var i=0;i<itemCount.toBuffer().readUInt32LE();++i) |
| 70 | { |
| 71 | item = buffer.Deref(i * 24, 24); |
| 72 | szName = item.Deref(0, GM.PointerSize).Deref(); |
| 73 | if (szName.String == '_Total') |
| 74 | { |
| 75 | u.total = item.Deref(16, 8).toBuffer().readDoubleLE(); |
| 76 | } |
| 77 | else |
| 78 | { |
| 79 | u.cpus[parseInt(szName.String)] = item.Deref(16, 8).toBuffer().readDoubleLE(); |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | GM.pdh.PdhRemoveCounter(po.cpuTotal.Deref()); |
| 84 | GM.pdh.PdhCloseQuery(po.cpu.Deref()); |
| 85 | p._res(u); |
| 86 | }, 100, p); |
| 87 | |
| 88 | return (p); |
| 89 | } |
| 90 | function windows_memUtilization() |
| 91 | { |
| 92 | var info = GM.CreateVariable(64); |
| 93 | info.Deref(0, 4).toBuffer().writeUInt32LE(64); |
| 94 | GM.kernel32.GlobalMemoryStatusEx(info); |
| 95 | |
| 96 | var ret = |
| 97 | { |
| 98 | MemTotal: require('bignum').fromBuffer(info.Deref(8, 8).toBuffer(), { endian: 'little' }), |
| 99 | MemFree: require('bignum').fromBuffer(info.Deref(16, 8).toBuffer(), { endian: 'little' }) |
| 100 | }; |
| 101 | |
| 102 | ret.percentFree = ((ret.MemFree.div(require('bignum')('1048576')).toNumber() / ret.MemTotal.div(require('bignum')('1048576')).toNumber()) * 100);//.toFixed(2); |
| 103 | ret.percentConsumed = ((ret.MemTotal.sub(ret.MemFree).div(require('bignum')('1048576')).toNumber() / ret.MemTotal.div(require('bignum')('1048576')).toNumber()) * 100);//.toFixed(2); |
| 104 | ret.MemTotal = ret.MemTotal.toString(); |
| 105 | ret.MemFree = ret.MemFree.toString(); |
| 106 | return (ret); |
| 107 | } |
| 108 | |
| 109 | var cpuLastIdle = []; |
| 110 | var cpuLastSum = []; |
| 111 | function linux_cpuUtilization() { |
| 112 | var ret = { cpus: [] }; |
| 113 | var info = require('fs').readFileSync('/proc/stat'); |
| 114 | var lines = info.toString().split('\n'); |
| 115 | var columns; |
| 116 | var x, y; |
| 117 | var cpuNo = 0; |
| 118 | var currSum, currIdle, utilization; |
| 119 | for (var i in lines) { |
| 120 | columns = lines[i].split(' '); |
| 121 | if (!columns[0].startsWith('cpu')) { break; } |
| 122 | |
| 123 | x = 0, currSum = 0; |
| 124 | while (columns[++x] == ''); |
| 125 | for (y = x; y < columns.length; ++y) { currSum += parseInt(columns[y]); } |
| 126 | currIdle = parseInt(columns[3 + x]); |
| 127 | |
| 128 | var diffIdle = currIdle - cpuLastIdle[cpuNo]; |
| 129 | var diffSum = currSum - cpuLastSum[cpuNo]; |
| 130 | |
| 131 | utilization = (100 - ((diffIdle / diffSum) * 100)); |
| 132 | |
| 133 | cpuLastSum[cpuNo] = currSum; |
| 134 | cpuLastIdle[cpuNo] = currIdle; |
| 135 | |
| 136 | if (!ret.total) { |
| 137 | ret.total = utilization; |
| 138 | } else { |
| 139 | ret.cpus.push(utilization); |
| 140 | } |
| 141 | ++cpuNo; |
| 142 | } |
| 143 | |
| 144 | var p = new promise(function (res, rej) { this._res = res; this._rej = rej; }); |
| 145 | p._res(ret); |
| 146 | return (p); |
| 147 | } |
| 148 | function linux_memUtilization() |
| 149 | { |
| 150 | var ret = {}; |
| 151 | |
| 152 | var info = require('fs').readFileSync('/proc/meminfo').toString().split('\n'); |
| 153 | var tokens; |
| 154 | for(var i in info) |
| 155 | { |
| 156 | tokens = info[i].split(' '); |
| 157 | switch(tokens[0]) |
| 158 | { |
| 159 | case 'MemTotal:': |
| 160 | ret.total = parseInt(tokens[tokens.length - 2]); |
| 161 | break; |
| 162 | case 'MemAvailable:': |
| 163 | ret.free = parseInt(tokens[tokens.length - 2]); |
| 164 | break; |
| 165 | } |
| 166 | } |
| 167 | ret.percentFree = ((ret.free / ret.total) * 100);//.toFixed(2); |
| 168 | ret.percentConsumed = (((ret.total - ret.free) / ret.total) * 100);//.toFixed(2); |
| 169 | return (ret); |
| 170 | } |
| 171 | |
| 172 | function macos_cpuUtilization() |
| 173 | { |
| 174 | var ret = new promise(function (res, rej) { this._res = res; this._rej = rej; }); |
| 175 | var child = require('child_process').execFile('/bin/sh', ['sh']); |
| 176 | child.stdout.str = ''; |
| 177 | child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); |
| 178 | child.stdin.write('top -l 1 | grep -E "^CPU"\nexit\n'); |
| 179 | child.waitExit(); |
| 180 | |
| 181 | var lines = child.stdout.str.split('\n'); |
| 182 | if (lines[0].length > 0) |
| 183 | { |
| 184 | var usage = lines[0].split(':')[1]; |
| 185 | var bdown = usage.split(','); |
| 186 | |
| 187 | var tot = parseFloat(bdown[0].split('%')[0].trim()) + parseFloat(bdown[1].split('%')[0].trim()); |
| 188 | ret._res({total: tot, cpus: []}); |
| 189 | } |
| 190 | else |
| 191 | { |
| 192 | ret._rej('parse error'); |
| 193 | } |
| 194 | |
| 195 | return (ret); |
| 196 | } |
| 197 | function macos_memUtilization() |
| 198 | { |
| 199 | var mem = { }; |
| 200 | var ret = new promise(function (res, rej) { this._res = res; this._rej = rej; }); |
| 201 | var child = require('child_process').execFile('/bin/sh', ['sh']); |
| 202 | child.stdout.str = ''; |
| 203 | child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); |
| 204 | child.stdin.write('top -l 1 | grep -E "^Phys"\nexit\n'); |
| 205 | child.waitExit(); |
| 206 | |
| 207 | var lines = child.stdout.str.split('\n'); |
| 208 | if (lines[0].length > 0) |
| 209 | { |
| 210 | var usage = lines[0].split(':')[1]; |
| 211 | var bdown = usage.split(','); |
| 212 | if (bdown.length > 2){ // new style - PhysMem: 5750M used (1130M wired, 634M compressor), 1918M unused. |
| 213 | mem.MemFree = parseInt(bdown[2].trim().split(' ')[0]); |
| 214 | } else { // old style - PhysMem: 6683M used (1606M wired), 9699M unused. |
| 215 | mem.MemFree = parseInt(bdown[1].trim().split(' ')[0]); |
| 216 | } |
| 217 | mem.MemUsed = parseInt(bdown[0].trim().split(' ')[0]); |
| 218 | mem.MemTotal = (mem.MemFree + mem.MemUsed); |
| 219 | mem.percentFree = ((mem.MemFree / mem.MemTotal) * 100);//.toFixed(2); |
| 220 | mem.percentConsumed = (((mem.MemTotal - mem.MemFree) / mem.MemTotal) * 100);//.toFixed(2); |
| 221 | return (mem); |
| 222 | } |
| 223 | else |
| 224 | { |
| 225 | throw ('Parse Error'); |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | function windows_thermals() |
| 230 | { |
| 231 | var ret = []; |
| 232 | try { |
| 233 | ret = require('win-wmi').query('ROOT\\WMI', 'SELECT CurrentTemperature,InstanceName FROM MSAcpi_ThermalZoneTemperature',['CurrentTemperature','InstanceName']); |
| 234 | if (ret[0]) { |
| 235 | for (var i = 0; i < ret.length; ++i) { |
| 236 | ret[i]['CurrentTemperature'] = ((parseFloat(ret[i]['CurrentTemperature']) / 10) - 273.15).toFixed(2); |
| 237 | } |
| 238 | } |
| 239 | } catch (ex) { } |
| 240 | return (ret); |
| 241 | } |
| 242 | |
| 243 | function linux_thermals() |
| 244 | { |
| 245 | var ret = []; |
| 246 | child = require('child_process').execFile('/bin/sh', ['sh']); |
| 247 | child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); |
| 248 | child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); }); |
| 249 | child.stdin.write("for folder in /sys/class/thermal/thermal_zone*/; do [ -e \"$folder/temp\" ] && echo \"$(cat \"$folder/temp\"),$(cat \"$folder/type\")\"; done\nexit\n"); |
| 250 | child.waitExit(); |
| 251 | if(child.stdout.str.trim()!='') |
| 252 | { |
| 253 | var lines = child.stdout.str.trim().split('\n'); |
| 254 | for (var i = 0; i < lines.length; ++i) |
| 255 | { |
| 256 | var line = lines[i].trim().split(','); |
| 257 | ret.push({CurrentTemperature: (parseFloat(line[0])/1000), InstanceName: line[1]}); |
| 258 | } |
| 259 | } |
| 260 | child = require('child_process').execFile('/bin/sh', ['sh']); |
| 261 | child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); |
| 262 | child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); }); |
| 263 | child.stdin.write("for mon in /sys/class/hwmon/hwmon*; do for label in \"$mon\"/temp*_label; do if [ -f $label ]; then echo $(cat \"$label\")___$(cat \"${label%_*}_input\"); fi; done; done;\nexit\n"); |
| 264 | child.waitExit(); |
| 265 | if(child.stdout.str.trim()!='') |
| 266 | { |
| 267 | var lines = child.stdout.str.trim().split('\n'); |
| 268 | for (var i = 0; i < lines.length; ++i) |
| 269 | { |
| 270 | var line = lines[i].trim().split('___'); |
| 271 | ret.push({ CurrentTemperature: (parseFloat(line[1])/1000), InstanceName: line[0] }); |
| 272 | } |
| 273 | } |
| 274 | return (ret); |
| 275 | } |
| 276 | |
| 277 | function macos_thermals() |
| 278 | { |
| 279 | var ret = []; |
| 280 | var child = require('child_process').execFile('/bin/sh', ['sh']); |
| 281 | child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); |
| 282 | child.stderr.on('data', function () { }); |
| 283 | child.stdin.write('powermetrics --help | grep SMC\nexit\n'); |
| 284 | child.waitExit(); |
| 285 | if (child.stdout.str.trim() != '') |
| 286 | { |
| 287 | child = require('child_process').execFile('/bin/sh', ['sh']); |
| 288 | child.stdout.str = ''; child.stdout.on('data', function (c) |
| 289 | { |
| 290 | this.str += c.toString(); |
| 291 | var tokens = this.str.trim().split('\n'); |
| 292 | for (var i in tokens) |
| 293 | { |
| 294 | if (tokens[i].split(' die temperature: ').length > 1) |
| 295 | { |
| 296 | ret.push({CurrentTemperature: tokens[i].split(' ')[3], InstanceName: tokens[i].split(' ')[0]}); |
| 297 | this.parent.kill(); |
| 298 | } |
| 299 | } |
| 300 | }); |
| 301 | child.stderr.on('data', function (c) { |
| 302 | if (c.toString().split('unable to get smc values').length > 1) { // error getting sensors so just kill |
| 303 | this.parent.kill(); |
| 304 | return; |
| 305 | } |
| 306 | }); |
| 307 | child.stdin.write('powermetrics -s smc -i 500 -n 1\n'); |
| 308 | child.waitExit(2000); |
| 309 | } |
| 310 | return (ret); |
| 311 | } |
| 312 | |
| 313 | switch(process.platform) |
| 314 | { |
| 315 | case 'linux': |
| 316 | module.exports = { cpuUtilization: linux_cpuUtilization, memUtilization: linux_memUtilization, thermals: linux_thermals }; |
| 317 | break; |
| 318 | case 'win32': |
| 319 | module.exports = { cpuUtilization: windows_cpuUtilization, memUtilization: windows_memUtilization, thermals: windows_thermals }; |
| 320 | break; |
| 321 | case 'darwin': |
| 322 | module.exports = { cpuUtilization: macos_cpuUtilization, memUtilization: macos_memUtilization, thermals: macos_thermals }; |
| 323 | break; |
| 324 | } |
| 325 |