Skip to content

Commit eb53e45

Browse files
committed
Added temperature readings
1 parent 4c33e51 commit eb53e45

File tree

7 files changed

+191
-40
lines changed

7 files changed

+191
-40
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const osUtils = require('lup-system');
1515
osUtils.getCpuUtilization().then(utilization => console.log("CPU Utilization: " + utilization));
1616
osUtils.getDrives().then(drives => console.log("Drives: " + drives)); // Array of drive objects
1717
osUtils.getNetworkInterfaces().then(interfaces => console.log("Network Interfaces: " + interfaces));
18+
osUtils.getTemperatures().then(temps => console.log("Temperatures: " + temps));
1819
```
1920

2021
TypeScript:
@@ -25,6 +26,7 @@ import osUtils from 'lup-system';
2526
console.log("CPU Utilization: " + await osUtils.getCpuUtilization());
2627
console.log("Drives: ", await osUtils.getDrives()); // Array of drive objects
2728
console.log("Network Interfaces: ", await osUtils.getNetworkInterfaces());
29+
console.log("Temperatures: ", await osUtils.getTemperatures());
2830
})();
2931
```
3032

@@ -70,4 +72,8 @@ Network Interfaces: [
7072
}
7173
}
7274
]
75+
Temperatures: {
76+
cpu: 45.2,
77+
gpu: 60.8,
78+
}
7379
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lup-system",
3-
"version": "1.0.2",
3+
"version": "1.1.0",
44
"description": "NodeJS library to retrieve system information and utilization.",
55
"files": [
66
"lib/**/*"

src/__tests__/CPU.test.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
import { getCpuTemperature, getCpuUtilization, stopCpuUtilizationComputation } from '../cpu';
2-
3-
test('getCpuTemperature', async () => {
4-
const temp = await getCpuTemperature();
5-
console.log('CPU temperature:', temp); // TODO REMOVE
6-
});
1+
import { getCpuUtilization, stopCpuUtilizationComputation } from '../cpu';
72

83
test('getCpuUtilization', async () => {
9-
const utilization = await getCpuUtilization();
10-
console.log('CPU utilization:', utilization); // TODO REMOVE
4+
const utilization = await getCpuUtilization();
5+
console.log('CPU utilization:', utilization); // TODO REMOVE
116
});
127

138
afterAll(() => {

src/__tests__/Temperature.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { getTemperatures } from '../temperature';
2+
3+
test('getTemperatures', async () => {
4+
const temperatures = await getTemperatures();
5+
console.log(temperatures); // TODO REMOVE
6+
});

src/cpu.ts

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { clear } from 'console';
21
import os from 'os';
3-
import { execCommand, sleep } from './utils';
2+
import { sleep } from './utils';
43

54
/** Intervall in milliseconds at which CPU utilization is computed. */
65
export let CPU_COMPUTE_UTILIZATION_INTERVAL = 1000;
@@ -95,32 +94,3 @@ export async function getCpuUtilization(): Promise<number> {
9594
}
9695
return CPU_UTILIZATION;
9796
}
98-
99-
/**
100-
* Returns the temeprature of the CPU in degrees Celsius (°C).
101-
*
102-
* @returns CPU temperature in degrees Celsius (°C) or null if not available.
103-
*/
104-
export async function getCpuTemperature(): Promise<number | null> {
105-
switch (process.platform) {
106-
case 'win32': {
107-
const output = await execCommand(
108-
'powershell -Command "Get-CimInstance MSAcpi_ThermalZoneTemperature -Namespace "root/wmi" | Select CurrentTemperature | Format-List"',
109-
).catch(() => ''); // only successful if administrator rights are available
110-
const lines = output.split('\n');
111-
// tslint:disable-next-line:prefer-for-of
112-
for (let i = 0; i < lines.length; i++) {
113-
const [key, value] = lines[i].split(' : ').map((s) => s.trim());
114-
if (key === 'CurrentTemperature') {
115-
const temperature = parseInt(value, 10);
116-
if (!Number.isNaN(temperature)) {
117-
return (temperature - 2732) / 10; // Convert from Kelvin to Celsius
118-
}
119-
}
120-
}
121-
break;
122-
}
123-
}
124-
125-
return null;
126-
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as cpu from './cpu';
22
import * as drive from './drive';
33
import * as net from './net';
4+
import * as temperatures from './temperature';
45
import * as utils from './utils';
56

67
/**
@@ -10,6 +11,7 @@ const osUtils = {
1011
...cpu,
1112
...drive,
1213
...net,
14+
...temperatures,
1315
...utils,
1416
};
1517
export default osUtils;

src/temperature.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import fs from 'fs/promises';
2+
import { execCommand } from './utils';
3+
4+
export type Temperatures = {
5+
/** Overall battery temperature in degrees Celsius (°C). */
6+
battery?: number;
7+
8+
/** Overall CPU temperature in degrees Celsius (°C). */
9+
cpu?: number;
10+
11+
/**
12+
* Temperature of each CPU core in degrees Celsius (°C).
13+
* If present contains at least one value.
14+
*/
15+
cpuCores?: number[];
16+
17+
/**
18+
* Temperature of each CPU socket in degrees Celsius (°C).
19+
* If present contains at least one value.
20+
*/
21+
cpuSockets?: number[];
22+
23+
/** Overall GPU temperature in degrees Celsius (°C). */
24+
gpu?: number;
25+
26+
/**
27+
* Temperature of each GPU in degrees Celsius (°C).
28+
* If present contains at least one value.
29+
*/
30+
gpus?: number[];
31+
32+
/** Temperature of the motherboard in degrees Celsius (°C). */
33+
motherboard?: number;
34+
35+
/** Temperature of the Wi-Fi adapter in degrees Celsius (°C). */
36+
wifi?: number;
37+
};
38+
39+
export async function getTemperatures(): Promise<Temperatures> {
40+
const temperatures: Temperatures = {};
41+
42+
switch (process.platform) {
43+
case 'linux': {
44+
const thermalFiles = await fs.readdir('/sys/class/thermal');
45+
const hwmonFiles = await fs.readdir('/sys/class/hwmon');
46+
await Promise.allSettled([
47+
...thermalFiles.map(async (file) => {
48+
const temp = await fs
49+
.readFile(`/sys/class/thermal/${file}/temp`, 'utf8')
50+
.then((tempStr) => {
51+
const t = parseInt(tempStr, 10);
52+
return !Number.isNaN(t) ? t / 1000 : null; // millidegrees Celsius to degrees Celsius
53+
})
54+
.catch(() => null);
55+
if (temp === null) return;
56+
const type = (await fs.readFile(`/sys/class/thermal/${file}/type`, 'utf8').catch(() => '')).toLowerCase();
57+
if (type.includes('core')) {
58+
temperatures.cpuCores = temperatures.cpuCores || [];
59+
temperatures.cpuCores.push(temp);
60+
} else if (type.includes('x86') || type.includes('soc_thermal')) {
61+
temperatures.cpuSockets = temperatures.cpuSockets || [];
62+
temperatures.cpuSockets.push(temp);
63+
} else if (type.startsWith('acp') || type.startsWith('pch')) {
64+
temperatures.motherboard = temp;
65+
} else if (type.includes('gpu') || type.includes('graphics')) {
66+
temperatures.gpus = temperatures.gpus || [];
67+
temperatures.gpus.push(temp);
68+
} else if (type.includes('wifi')) {
69+
temperatures.wifi = temp;
70+
} else if (type.includes('battery')) {
71+
temperatures.battery = temp;
72+
}
73+
}),
74+
...hwmonFiles.map(async (file) => {
75+
const subFiles = await fs.readdir('/sys/class/hwmon/' + file);
76+
let defaultName: string | null = null;
77+
const names: { [key: string]: string } = {};
78+
const temps: { [key: string]: number } = {};
79+
80+
await Promise.allSettled(
81+
subFiles.map(async (subFile) => {
82+
if (subFile === 'name')
83+
defaultName = await fs.readFile('/sys/class/hwmon/' + file + '/' + subFile, 'utf8').catch(() => null);
84+
if (subFile.endsWith('_label')) {
85+
const key = subFile.slice(0, -6);
86+
names[key] = await fs.readFile('/sys/class/hwmon/' + file + '/' + subFile, 'utf8').catch(() => '');
87+
}
88+
if (subFile.endsWith('_input')) {
89+
const key = subFile.slice(0, -6);
90+
const temp = await fs
91+
.readFile('/sys/class/hwmon/' + file + '/' + subFile, 'utf8')
92+
.then((tempStr) => {
93+
const t = parseInt(tempStr, 10);
94+
return !Number.isNaN(t) ? t / 1000 : null; // millidegrees Celsius to degrees Celsius
95+
})
96+
.catch(() => null);
97+
if (temp !== null) temps[key] = temp;
98+
}
99+
}),
100+
);
101+
102+
for (const [key, temp] of Object.entries(temps)) {
103+
const name = (names[key] || defaultName || '').toLowerCase();
104+
if (name.includes('core')) {
105+
temperatures.cpuCores = temperatures.cpuCores || [];
106+
temperatures.cpuCores.push(temp);
107+
} else if (name.includes('socket') || name.includes('package')) {
108+
temperatures.cpuSockets = temperatures.cpuSockets || [];
109+
temperatures.cpuSockets.push(temp);
110+
} else if (name.includes('gpu') || name.includes('graphics')) {
111+
temperatures.gpus = temperatures.gpus || [];
112+
temperatures.gpus.push(temp);
113+
} else if (name.includes('motherboard') || name.includes('mainboard') || name.includes('mb')) {
114+
temperatures.motherboard = temp;
115+
} else if (name.includes('wifi')) {
116+
temperatures.wifi = temp;
117+
} else if (name.includes('battery')) {
118+
temperatures.battery = temp;
119+
}
120+
}
121+
}),
122+
]);
123+
break;
124+
}
125+
126+
case 'win32': {
127+
const output = await execCommand(
128+
'powershell -Command "Get-CimInstance MSAcpi_ThermalZoneTemperature -Namespace "root/wmi" | Select CurrentTemperature | Format-List"',
129+
).catch(() => ''); // only successful if administrator rights are available
130+
const lines = output.split('\n');
131+
// tslint:disable-next-line:prefer-for-of
132+
for (let i = 0; i < lines.length; i++) {
133+
const [key, value] = lines[i].split(' : ').map((s) => s.trim());
134+
if (key === 'CurrentTemperature') {
135+
const temperature = parseInt(value, 10);
136+
if (!Number.isNaN(temperature)) {
137+
temperatures.cpu = (temperature - 2732) / 10; // Convert from Kelvin to Celsius
138+
}
139+
}
140+
}
141+
break;
142+
}
143+
}
144+
145+
// backup nvidia-smi
146+
if (temperatures.gpus === undefined) {
147+
const output = await execCommand('nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader').catch(() => '');
148+
const lines = output.split('\n').filter((line) => line.trim() !== '');
149+
if (lines.length > 0) {
150+
temperatures.gpus = lines
151+
.map((line) => {
152+
const temp = parseInt(line.trim(), 10);
153+
return !Number.isNaN(temp) ? temp : null;
154+
})
155+
.filter((temp) => temp !== null);
156+
}
157+
}
158+
159+
// post processing
160+
if (temperatures.cpu === undefined) {
161+
if (temperatures.cpuSockets && temperatures.cpuSockets.length > 0) {
162+
temperatures.cpu = temperatures.cpuSockets.reduce((a, b) => a + b, 0) / temperatures.cpuSockets.length;
163+
} else if (temperatures.cpuCores && temperatures.cpuCores.length > 0) {
164+
temperatures.cpu = temperatures.cpuCores.reduce((a, b) => a + b, 0) / temperatures.cpuCores.length;
165+
}
166+
}
167+
if (temperatures.gpu === undefined && temperatures.gpus && temperatures.gpus.length > 0) {
168+
temperatures.gpu = temperatures.gpus.reduce((a, b) => a + b, 0) / temperatures.gpus.length;
169+
}
170+
171+
return temperatures;
172+
}

0 commit comments

Comments
 (0)