mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-04-12 21:52:57 +00:00
Add device system information to state endpoint patch
Change-type: minor Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
parent
f918910067
commit
f305a333ba
34
package-lock.json
generated
34
package-lock.json
generated
@ -613,6 +613,12 @@
|
||||
"integrity": "sha1-qIc1gLOoS2msHmhzI7Ffu+uQR5o=",
|
||||
"dev": true
|
||||
},
|
||||
"@types/os-utils": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/os-utils/-/os-utils-0.0.1.tgz",
|
||||
"integrity": "sha512-r5a1I4xr8e25GFiLdpHV+jHUawtbmtuCQHR1P3SH/Y1gmRy0KLjBVGe5uUkODU/6GntoGPhOu05jv+sBdAVpog==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
@ -1311,7 +1317,7 @@
|
||||
},
|
||||
"util": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||
"resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -3370,7 +3376,7 @@
|
||||
},
|
||||
"stream-combiner": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
|
||||
"resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
|
||||
"integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -3716,7 +3722,7 @@
|
||||
},
|
||||
"event-stream": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
|
||||
"resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
|
||||
"integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -6204,7 +6210,7 @@
|
||||
},
|
||||
"map-stream": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
|
||||
"integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=",
|
||||
"dev": true
|
||||
},
|
||||
@ -6393,7 +6399,7 @@
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||
"dev": true
|
||||
},
|
||||
@ -6544,7 +6550,7 @@
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
|
||||
"dev": true
|
||||
},
|
||||
@ -7151,6 +7157,11 @@
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
|
||||
},
|
||||
"os-utils": {
|
||||
"version": "0.0.14",
|
||||
"resolved": "https://registry.npmjs.org/os-utils/-/os-utils-0.0.14.tgz",
|
||||
"integrity": "sha1-KeURaXsZgrjGJ3Ihdf45eX72QVY="
|
||||
},
|
||||
"osenv": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
|
||||
@ -7705,7 +7716,7 @@
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
@ -8584,7 +8595,7 @@
|
||||
},
|
||||
"split": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
|
||||
"resolved": "http://registry.npmjs.org/split/-/split-0.3.3.tgz",
|
||||
"integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -8692,7 +8703,7 @@
|
||||
},
|
||||
"stream-combiner": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
|
||||
"resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
|
||||
"integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -8842,6 +8853,11 @@
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"systeminformation": {
|
||||
"version": "4.27.3",
|
||||
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-4.27.3.tgz",
|
||||
"integrity": "sha512-0Nc8AYEK818h7FI+bbe/kj7xXsMD5zOHvO9alUqQH/G4MHXu5tHQfWqC/bzWOk4JtoQPhnyLgxMYncDA2eeSBw=="
|
||||
},
|
||||
"tapable": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz",
|
||||
|
@ -29,8 +29,10 @@
|
||||
"dependencies": {
|
||||
"dbus": "^1.0.7",
|
||||
"mdns-resolver": "^1.0.0",
|
||||
"os-utils": "0.0.14",
|
||||
"semver": "^7.3.2",
|
||||
"sqlite3": "^4.1.1"
|
||||
"sqlite3": "^4.1.1",
|
||||
"systeminformation": "^4.27.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.16.2",
|
||||
@ -56,6 +58,7 @@
|
||||
"@types/morgan": "^1.9.0",
|
||||
"@types/mz": "0.0.32",
|
||||
"@types/node": "^12.12.54",
|
||||
"@types/os-utils": "0.0.1",
|
||||
"@types/request": "^2.48.5",
|
||||
"@types/rimraf": "^2.0.4",
|
||||
"@types/rwlock": "^5.0.2",
|
||||
|
@ -11,6 +11,8 @@ import DeviceState from '../device-state';
|
||||
import { CoreOptions } from 'request';
|
||||
import * as url from 'url';
|
||||
|
||||
import * as sysInfo from '../lib/system-info';
|
||||
|
||||
// The exponential backoff starts at 15s
|
||||
const MINIMUM_BACKOFF_DELAY = 15000;
|
||||
|
||||
@ -121,6 +123,39 @@ const getStateDiff = (): DeviceStatus => {
|
||||
.value(),
|
||||
};
|
||||
|
||||
const shouldReportSysInfo = (type: string, past: number, now: number) => {
|
||||
if (!past) {
|
||||
return true;
|
||||
}
|
||||
// TODO: Deduplicate this code
|
||||
switch (type) {
|
||||
case 'cpu_usage':
|
||||
// The bucket size of cpu usage is 20
|
||||
return Math.floor(past / 20) !== Math.floor(now / 20);
|
||||
case 'cpu_temp':
|
||||
return Math.floor(past / 5) !== Math.floor(now / 5);
|
||||
case 'memory_usage':
|
||||
return Math.floor(past / 10) !== Math.floor(now / 10);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const toOmit: string[] = [];
|
||||
_.each(diff.local, (value, key) => {
|
||||
// if we have some system information that has changed, we check that it's
|
||||
// within a certain range before reporting
|
||||
if (
|
||||
!shouldReportSysInfo(
|
||||
key,
|
||||
(lastReportedLocal as any)[key],
|
||||
value as number,
|
||||
)
|
||||
) {
|
||||
toOmit.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
diff.local = _.omit(diff.local, toOmit);
|
||||
return _.omitBy(diff, _.isEmpty);
|
||||
};
|
||||
|
||||
@ -171,8 +206,16 @@ const reportCurrentState = (): null => {
|
||||
reportPending = true;
|
||||
try {
|
||||
const currentDeviceState = await deviceState.getStatus();
|
||||
_.assign(stateForReport.local, currentDeviceState.local);
|
||||
_.assign(stateForReport.dependent, currentDeviceState.dependent);
|
||||
const info = await sysInfo.getSysInfoToReport();
|
||||
stateForReport.local = {
|
||||
...stateForReport.local,
|
||||
...currentDeviceState.local,
|
||||
...info,
|
||||
};
|
||||
stateForReport.dependent = {
|
||||
...stateForReport.dependent,
|
||||
...currentDeviceState.dependent,
|
||||
};
|
||||
|
||||
const stateDiff = getStateDiff();
|
||||
if (_.size(stateDiff) === 0) {
|
||||
|
113
src/lib/system-info.ts
Normal file
113
src/lib/system-info.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import * as systeminformation from 'systeminformation';
|
||||
import * as osUtils from 'os-utils';
|
||||
import { fs, child_process } from 'mz';
|
||||
|
||||
export function getCpuUsage() {
|
||||
return new Promise((resolve) => {
|
||||
osUtils.cpuUsage((percent) => {
|
||||
resolve(Math.round(percent * 100));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const blockDeviceRegex = /(\/dev\/.*)p\d+/;
|
||||
export async function getStorageInfo() {
|
||||
const fsInfo = await systeminformation.fsSize();
|
||||
let mainFs: string | undefined;
|
||||
let total = 0;
|
||||
// First we find the block device which the data partition is part of
|
||||
for (const partition of fsInfo) {
|
||||
if (partition.mount === '/data') {
|
||||
const match = partition.fs.match(blockDeviceRegex);
|
||||
if (match == null) {
|
||||
mainFs = undefined;
|
||||
} else {
|
||||
mainFs = match[1];
|
||||
total = partition.size;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mainFs) {
|
||||
return {
|
||||
blockDevice: '',
|
||||
storageUsed: undefined,
|
||||
storageTotal: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
let used = 0;
|
||||
for (const partition of fsInfo) {
|
||||
if (partition.fs.startsWith(mainFs)) {
|
||||
used += partition.used;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
blockDevice: mainFs,
|
||||
storageUsed: bytesToMb(used),
|
||||
storageTotal: bytesToMb(total),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getMemoryInformation() {
|
||||
const mem = await systeminformation.mem();
|
||||
return {
|
||||
used: bytesToMb(mem.used),
|
||||
total: bytesToMb(mem.total),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getCpuTemp() {
|
||||
return Math.round((await systeminformation.cpuTemperature()).main);
|
||||
}
|
||||
|
||||
export async function getCpuId() {
|
||||
// Read /proc/device-tree/serial-number
|
||||
// if it's not there, return undefined
|
||||
try {
|
||||
const buffer = await fs.readFile('/proc/device-tree/serial-number');
|
||||
// Remove the null byte at the end
|
||||
return buffer.toString('utf-8').substr(0, buffer.length - 2);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const undervoltageRegex = /under.*voltage/;
|
||||
export async function undervoltageDetected() {
|
||||
try {
|
||||
const [dmesgStdout] = await child_process.exec('dmesg');
|
||||
return undervoltageRegex.test(dmesgStdout.toString());
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSysInfoToReport() {
|
||||
const [cpu, mem, temp, cpuid, storage, undervoltage] = await Promise.all([
|
||||
getCpuUsage(),
|
||||
getMemoryInformation(),
|
||||
getCpuTemp(),
|
||||
getCpuId(),
|
||||
getStorageInfo(),
|
||||
undervoltageDetected(),
|
||||
]);
|
||||
|
||||
return {
|
||||
cpu_usage: cpu,
|
||||
memory_usage: mem.used,
|
||||
memory_total: mem.total,
|
||||
storage_usage: storage.storageUsed,
|
||||
storage_total: storage.storageTotal,
|
||||
storage_block_device: storage.blockDevice,
|
||||
cpu_temp: temp,
|
||||
cpu_id: cpuid,
|
||||
is_undervolted: undervoltage,
|
||||
};
|
||||
}
|
||||
|
||||
function bytesToMb(bytes: number) {
|
||||
return Math.floor(bytes / 1024 / 1024);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user