mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-04-19 08:36:14 +00:00
Merge pull request #1864 from balena-os/dmidecode
Use dmidecode to read cpuid in non ARM devices
This commit is contained in:
commit
ec263d3028
@ -33,6 +33,7 @@ RUN apk add --no-cache \
|
||||
libuv \
|
||||
sqlite-libs \
|
||||
sqlite-dev \
|
||||
dmidecode \
|
||||
dbus-dev
|
||||
|
||||
COPY build-utils/node-sums.txt .
|
||||
@ -99,6 +100,7 @@ RUN apk add --no-cache \
|
||||
avahi \
|
||||
dbus \
|
||||
libstdc++ \
|
||||
dmidecode \
|
||||
sqlite-libs
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as systeminformation from 'systeminformation';
|
||||
import * as _ from 'lodash';
|
||||
import * as memoizee from 'memoizee';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
import { exec } from './fs-utils';
|
||||
@ -67,16 +68,96 @@ export async function getCpuTemp(): Promise<number> {
|
||||
return Math.round(tempInfo.main);
|
||||
}
|
||||
|
||||
export async function getCpuId(): Promise<string | undefined> {
|
||||
export async function getSystemId(): Promise<string | undefined> {
|
||||
try {
|
||||
// This will work on arm devices
|
||||
const buffer = await fs.readFile('/proc/device-tree/serial-number');
|
||||
// Remove the null byte at the end
|
||||
return buffer.toString('utf-8').replace(/\0/g, '');
|
||||
} catch {
|
||||
return undefined;
|
||||
// Otherwise use dmidecode
|
||||
const [baseBoardInfo] = (
|
||||
await dmidecode('baseboard').catch(() => [] as DmiDecodeInfo[])
|
||||
).filter((entry) => entry.type === 'Base Board Information');
|
||||
return baseBoardInfo?.values?.['Serial Number'] || undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSystemModel(): Promise<string | undefined> {
|
||||
try {
|
||||
const buffer = await fs.readFile('/proc/device-tree/model');
|
||||
// Remove the null byte at the end
|
||||
return buffer.toString('utf-8').replace(/\0/g, '');
|
||||
} catch {
|
||||
const [baseBoardInfo] = (
|
||||
await dmidecode('baseboard').catch(() => [] as DmiDecodeInfo[])
|
||||
).filter((entry) => entry.type === 'Base Board Information');
|
||||
|
||||
// Join manufacturer and product name in a single string
|
||||
return (
|
||||
[
|
||||
baseBoardInfo?.values?.['Manufacturer'],
|
||||
baseBoardInfo?.values?.['Product Name'],
|
||||
]
|
||||
.filter((s) => !!s)
|
||||
.join(' ') || undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export type DmiDecodeInfo = { type: string; values: { [key: string]: string } };
|
||||
|
||||
/**
|
||||
* Parse the output of dmidecode and return an array of
|
||||
* objects {type: string, values: string[]}
|
||||
*
|
||||
* This only parses simple key,value pairs from the output
|
||||
* of dmidecode, multiline strings and arrays are ignored
|
||||
*
|
||||
* The output of the command is memoized
|
||||
*/
|
||||
export const dmidecode = memoizee(
|
||||
async (t: string): Promise<DmiDecodeInfo[]> => {
|
||||
const { stdout: info } = await exec(`dmidecode -t ${t}`);
|
||||
return (
|
||||
info
|
||||
.toString()
|
||||
.split(/\r?\n/) // Split by line jumps
|
||||
// Split into groups by looking for empty lines
|
||||
.reduce((groups, line) => {
|
||||
const currentGroup = groups.pop() || [];
|
||||
if (/^\s*$/.test(line)) {
|
||||
// For each empty line create a new group
|
||||
groups.push(currentGroup);
|
||||
groups.push([]);
|
||||
} else {
|
||||
// Otherwise append the line to the group
|
||||
currentGroup.push(line);
|
||||
groups.push(currentGroup);
|
||||
}
|
||||
return groups;
|
||||
}, [] as string[][])
|
||||
// Only select the handles
|
||||
.filter((group) => group.length > 1 && /^Handle/.test(group[0]))
|
||||
.map(([, type, ...lines]) => ({
|
||||
type,
|
||||
values: lines
|
||||
// Only select lines that match 'key: value', this will exclude multiline strings
|
||||
// and arrays (we don't care about those for these purposes)
|
||||
.filter((line) => /^\s+[^:]+: .+$/.test(line))
|
||||
.map((line) => {
|
||||
const [key, value] = line.split(':').map((s) => s.trim());
|
||||
// Finally convert the lines into key value pairs
|
||||
return { [key]: value };
|
||||
})
|
||||
// And merge
|
||||
.reduce((vals, v) => ({ ...vals, ...v }), {}),
|
||||
}))
|
||||
);
|
||||
},
|
||||
{ promise: true },
|
||||
);
|
||||
|
||||
const undervoltageRegex = /under.*voltage/i;
|
||||
export async function undervoltageDetected(): Promise<boolean> {
|
||||
try {
|
||||
@ -110,7 +191,7 @@ export async function getSystemMetrics() {
|
||||
getCpuUsage(),
|
||||
getMemoryInformation(),
|
||||
getCpuTemp(),
|
||||
getCpuId(),
|
||||
getSystemId(),
|
||||
getStorageInfo(),
|
||||
]);
|
||||
|
||||
|
@ -95,11 +95,58 @@ describe('System information', () => {
|
||||
// Make sure it's the right number given the mocked data
|
||||
expect(tempInfo).to.equal(51);
|
||||
});
|
||||
});
|
||||
|
||||
it('gets CPU ID', async () => {
|
||||
const cpuId = await sysInfo.getCpuId();
|
||||
describe('baseboard information', () => {
|
||||
afterEach(() => {
|
||||
(fs.readFile as SinonStub).reset();
|
||||
(fsUtils.exec as SinonStub).reset();
|
||||
});
|
||||
|
||||
// Do these two tests first so the dmidecode call is not memoized yet
|
||||
it('returns undefined system model if dmidecode throws', async () => {
|
||||
(fs.readFile as SinonStub).rejects('Not found');
|
||||
(fsUtils.exec as SinonStub).rejects('Something bad happened');
|
||||
const systemModel = await sysInfo.getSystemModel();
|
||||
expect(systemModel).to.be.undefined;
|
||||
});
|
||||
|
||||
it('returns undefined system ID if dmidecode throws', async () => {
|
||||
(fs.readFile as SinonStub).rejects('Not found');
|
||||
(fsUtils.exec as SinonStub).rejects('Something bad happened');
|
||||
const systemId = await sysInfo.getSystemId();
|
||||
expect(systemId).to.be.undefined;
|
||||
});
|
||||
|
||||
it('gets system ID', async () => {
|
||||
(fs.readFile as SinonStub).resolves(mockCPU.idBuffer);
|
||||
const cpuId = await sysInfo.getSystemId();
|
||||
expect(cpuId).to.equal('1000000001b93f3f');
|
||||
});
|
||||
|
||||
it('gets system ID from dmidecode if /proc/device-tree/serial-number is not available', async () => {
|
||||
(fs.readFile as SinonStub).rejects('Not found');
|
||||
(fsUtils.exec as SinonStub).resolves({
|
||||
stdout: mockCPU.dmidecode,
|
||||
});
|
||||
const cpuId = await sysInfo.getSystemId();
|
||||
expect(cpuId).to.equal('GEBN94600PWW');
|
||||
});
|
||||
|
||||
it('gets system model', async () => {
|
||||
(fs.readFile as SinonStub).resolves('Raspberry PI 4');
|
||||
const systemModel = await sysInfo.getSystemModel();
|
||||
expect(systemModel).to.equal('Raspberry PI 4');
|
||||
});
|
||||
|
||||
it('gets system model from dmidecode if /proc/device-tree/model is not available', async () => {
|
||||
(fs.readFile as SinonStub).rejects('Not found');
|
||||
(fsUtils.exec as SinonStub).resolves({
|
||||
stdout: mockCPU.dmidecode,
|
||||
});
|
||||
const systemModel = await sysInfo.getSystemModel();
|
||||
expect(systemModel).to.equal('Intel Corporation NUC7i5BNB');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMemoryInformation', async () => {
|
||||
@ -154,6 +201,51 @@ describe('System information', () => {
|
||||
expect(await sysInfo.undervoltageDetected()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('dmidecode', () => {
|
||||
it('parses dmidecode output into json', async () => {
|
||||
(fsUtils.exec as SinonStub).resolves({
|
||||
stdout: mockCPU.dmidecode,
|
||||
});
|
||||
|
||||
expect(await sysInfo.dmidecode('baseboard')).to.deep.equal([
|
||||
{
|
||||
type: 'Base Board Information',
|
||||
values: {
|
||||
Manufacturer: 'Intel Corporation',
|
||||
'Product Name': 'NUC7i5BNB',
|
||||
Version: 'J31144-313',
|
||||
'Serial Number': 'GEBN94600PWW',
|
||||
'Location In Chassis': 'Default string',
|
||||
'Chassis Handle': '0x0003',
|
||||
Type: 'Motherboard',
|
||||
'Contained Object Handles': '0',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'On Board Device 1 Information',
|
||||
values: {
|
||||
Type: 'Sound',
|
||||
Status: 'Enabled',
|
||||
Description: 'Realtek High Definition Audio Device',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Onboard Device',
|
||||
values: {
|
||||
'Reference Designation': 'Onboard - Other',
|
||||
Type: 'Other',
|
||||
Status: 'Enabled',
|
||||
'Type Instance': '1',
|
||||
'Bus Address': '0000',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Reset the stub
|
||||
(fsUtils.exec as SinonStub).reset();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const mockCPU = {
|
||||
@ -236,25 +328,48 @@ const mockCPU = {
|
||||
},
|
||||
],
|
||||
},
|
||||
idBuffer: Buffer.from([
|
||||
0x31,
|
||||
0x30,
|
||||
0x30,
|
||||
0x30,
|
||||
0x30,
|
||||
0x30,
|
||||
0x30,
|
||||
0x30,
|
||||
0x30,
|
||||
0x31,
|
||||
0x62,
|
||||
0x39,
|
||||
0x33,
|
||||
0x66,
|
||||
0x33,
|
||||
0x66,
|
||||
0x00,
|
||||
]),
|
||||
idBuffer: Buffer.from('1000000001b93f3f'),
|
||||
dmidecode: Buffer.from(`# dmidecode 3.3
|
||||
Getting SMBIOS data from sysfs.
|
||||
SMBIOS 3.1.1 present.
|
||||
|
||||
Handle 0x0002, DMI type 2, 15 bytes
|
||||
Base Board Information
|
||||
Manufacturer: Intel Corporation
|
||||
Product Name: NUC7i5BNB
|
||||
Version: J31144-313
|
||||
Serial Number: GEBN94600PWW
|
||||
Asset Tag:
|
||||
Features:
|
||||
Board is a hosting board
|
||||
Board is replaceable
|
||||
Location In Chassis: Default string
|
||||
Chassis Handle: 0x0003
|
||||
Type: Motherboard
|
||||
Contained Object Handles: 0
|
||||
|
||||
Handle 0x000F, DMI type 10, 20 bytes
|
||||
On Board Device 1 Information
|
||||
Type: Video
|
||||
Status: Enabled
|
||||
Description: Intel(R) HD Graphics Device
|
||||
On Board Device 2 Information
|
||||
Type: Ethernet
|
||||
Status: Enabled
|
||||
Description: Intel(R) I219-V Gigabit Network Device
|
||||
On Board Device 3 Information
|
||||
Type: Sound
|
||||
Status: Enabled
|
||||
Description: Realtek High Definition Audio Device
|
||||
|
||||
Handle 0x003F, DMI type 41, 11 bytes
|
||||
Onboard Device
|
||||
Reference Designation: Onboard - Other
|
||||
Type: Other
|
||||
Status: Enabled
|
||||
Type Instance: 1
|
||||
Bus Address: 0000:00:00.0
|
||||
`),
|
||||
};
|
||||
const mockFS = [
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user