Skip to content

Commit cac1e59

Browse files
committed
Added GPU and temperature readings
1 parent eb53e45 commit cac1e59

File tree

10 files changed

+307
-15
lines changed

10 files changed

+307
-15
lines changed

README.md

Lines changed: 28 additions & 1 deletion
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.getGPUs().then(gpus => console.log("GPU Info: " + gpus));
1819
osUtils.getTemperatures().then(temps => console.log("Temperatures: " + temps));
1920
```
2021

@@ -26,6 +27,7 @@ import osUtils from 'lup-system';
2627
console.log("CPU Utilization: " + await osUtils.getCpuUtilization());
2728
console.log("Drives: ", await osUtils.getDrives()); // Array of drive objects
2829
console.log("Network Interfaces: ", await osUtils.getNetworkInterfaces());
30+
console.log("GPU Info: ", await osUtils.getGPUs());
2931
console.log("Temperatures: ", await osUtils.getTemperatures());
3032
})();
3133
```
@@ -72,8 +74,33 @@ Network Interfaces: [
7274
}
7375
}
7476
]
77+
GPU Info: [
78+
{
79+
name: 'NVIDIA GeForce RTX 3060 Ti',
80+
status: 'ok',
81+
id: 'PCI\\VEN_10DE&DEV_2489&SUBSYS_884F1043&REV_A1\\4&2130FF93&0&0008',
82+
processor: 'NVIDIA GeForce RTX 3060 Ti',
83+
memory: 8589934592,
84+
driverDate: '14.05.2025 02:00:00',
85+
driverVersion: '32.0.15.7652',
86+
displayAttached: true,
87+
displayActive: true,
88+
fanSpeed: 0.53,
89+
utilization: 0.03,
90+
memoryUtilization: 0.01,
91+
temperature: 52,
92+
powerDraw: 48.32
93+
}
94+
]
7595
Temperatures: {
7696
cpu: 45.2,
7797
gpu: 60.8,
7898
}
79-
```
99+
```
100+
101+
102+
## Considerations
103+
104+
### GPU Readings
105+
For more detailed information on GPUs it is recommended to
106+
install the [nvidia-smi](https://developer.nvidia.com/nvidia-system-management-interface) tool.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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.1.0",
3+
"version": "1.2.0",
44
"description": "NodeJS library to retrieve system information and utilization.",
55
"files": [
66
"lib/**/*"

src/__tests__/GPU.test.ts

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

src/__tests__/Net.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getNetworkInterfaces, stopNetworkUtilizationComputation } from '../net'
33
test('getNetworkInterfaces', async () => {
44
const nics = await getNetworkInterfaces();
55
console.log(nics); // TODO REMOVE
6-
});
6+
}, 10000);
77

88
afterAll(() => {
99
stopNetworkUtilizationComputation();

src/cpu.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,45 @@
11
import os from 'os';
22
import { sleep } from './utils';
33

4+
5+
export type CPUUtilization = {
6+
7+
/** Overall CPU utilization as a percentage (0.0-1.0). */
8+
overall: number;
9+
10+
/** Utilization of each CPU core as a percentage (0.0-1.0). */
11+
cores: number[];
12+
13+
};
14+
15+
export type CPU = {
16+
17+
/** Name of the CPU. */
18+
name: string;
19+
20+
/** Number of CPU cores. */
21+
coreCount: number;
22+
23+
/** CPU architecture (e.g., x64, arm64). */
24+
architecture: string;
25+
26+
/** CPU model (e.g., Intel Core i7-9700K). */
27+
model: string;
28+
29+
/** CPU speed in MHz. */
30+
speed: number;
31+
32+
/** CPU utilization data. */
33+
utilization: CPUUtilization;
34+
};
35+
36+
37+
38+
439
/** Intervall in milliseconds at which CPU utilization is computed. */
540
export let CPU_COMPUTE_UTILIZATION_INTERVAL = 1000;
641

42+
743
const CPU_COMPUTE_UTILIZATION_INITIAL_DELAY = 50;
844

945
let PREV_CPU_CORES: os.CpuInfo[] = [];
@@ -58,6 +94,7 @@ export function stopCpuUtilizationComputation() {
5894
CPU_COMPUTE_RUNNING = false;
5995
}
6096

97+
6198
/**
6299
* Returns the number of CPU cores available on the system.
63100
*

src/gpu.ts

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { execCommand } from "./utils";
2+
3+
export type GPU = {
4+
/** Unique ID of the GPU device. */
5+
id: string;
6+
7+
/** Name of the GPU device. */
8+
name: string;
9+
10+
/** Name of the processor built into the GPU. */
11+
processor?: string;
12+
13+
/** Status of the GPU device. */
14+
status: 'ok' | 'error' | 'unknown' | string;
15+
16+
/** Last update date of the GPU driver, if available. */
17+
driverDate?: string;
18+
19+
/** Version of the GPU driver, if available. */
20+
driverVersion?: string;
21+
22+
/** Memory size of the GPU in bytes. */
23+
memory?: number;
24+
25+
/** Memory utilization of the GPU in percentage (0.0-1.0) */
26+
memoryUtilization?: number;
27+
28+
/** Temperature of the memory in Celsius. */
29+
memoryTemperature?: number;
30+
31+
/** GPU processing utilization in percentage (0.0-1.0) */
32+
utilization?: number;
33+
34+
/** Index of the GPU device. */
35+
index?: number;
36+
37+
/** If a physical display/monitor is attached to one of the GPU's connector.s */
38+
displayAttached?: boolean;
39+
40+
/** If memory is allocated inside the GPU for display purposes. Can be true even if no physical display is attached. */
41+
displayActive?: boolean;
42+
43+
/** Fan speed in percentage (0.0-1.0). */
44+
fanSpeed?: number;
45+
46+
/** Temperature of the GPU in Celsius. */
47+
temperature?: number;
48+
49+
/** Power draw of the GPU in watts. */
50+
powerDraw?: number;
51+
};
52+
53+
export async function getGPUs(): Promise<GPU[]> {
54+
const gpus: GPU[] = [];
55+
switch(process.platform){
56+
57+
case 'linux': {
58+
const output = await execCommand('lspci -k | grep -i -A3 "\[vga\]\|\[3d\]"');
59+
const lines = output.split('\n');
60+
let curr: GPU | null = null;
61+
// tslint:disable-next-line:prefer-for-of
62+
for(let i=0; i < lines.length; i++){
63+
const line = lines[i].trim();
64+
if(!line) continue; // skip empty lines
65+
if(line.startsWith('00:')){
66+
// new GPU found, push current if exists
67+
if(curr) gpus.push(curr);
68+
curr = {} as GPU;
69+
curr.id = line.split(' ')[0];
70+
curr.name = line.split(':')[2].trim();
71+
curr.status = 'ok'; // assume ok, will update later if needed
72+
}
73+
}
74+
break;
75+
}
76+
77+
78+
case 'win32': {
79+
const output = await execCommand('powershell -Command "Get-CimInstance -ClassName Win32_VideoController | Format-List"');
80+
const lines = output.split('\n');
81+
let curr: GPU | null = null;
82+
// tslint:disable-next-line:prefer-for-of
83+
for(let i=0; i < lines.length; i++){
84+
const [key, value] = lines[i].split(' : ').map(s => s.trim());
85+
if(!key && !value){
86+
// empty line, push current GPU if exists
87+
if(curr) gpus.push(curr);
88+
curr = null;
89+
continue;
90+
}
91+
if(!value) continue; // skip lines without value
92+
93+
if(key === 'Name'){
94+
if(!curr) curr = {} as any;
95+
curr!.name = value;
96+
} else
97+
if(key === 'Status'){
98+
if(!curr) curr = {} as any;
99+
curr!.status = value.toLowerCase() || 'unknown';
100+
} else
101+
if(key === 'PNPDeviceID'){
102+
if(!curr) curr = {} as any;
103+
curr!.id = value;
104+
} else
105+
if(key === 'VideoProcessor'){
106+
if(!curr) curr = {} as any;
107+
curr!.processor = value;
108+
} else
109+
if(key === 'AdapterRAM'){
110+
const memory = parseInt(value.trim(), 10);
111+
if(Number.isNaN(memory)) continue; // skip invalid memory values
112+
if(!curr) curr = {} as any;
113+
curr!.memory = memory; // not accurate, but gives an idea of the GPU memory
114+
} else
115+
if(key === 'DriverDate'){
116+
if(!curr) curr = {} as any;
117+
curr!.driverDate = value;
118+
} else
119+
if(key === 'DriverVersion'){
120+
if(!curr) curr = {} as any;
121+
curr!.driverVersion = value;
122+
}
123+
}
124+
if(curr) gpus.push(curr);
125+
break;
126+
}
127+
128+
}
129+
130+
// nvidia-smi for more detailed info
131+
{
132+
const output = await execCommand('nvidia-smi --query-gpu=name,display_attached,display_active,fan.speed,memory.total,utilization.gpu,utilization.memory,temperature.gpu,temperature.memory,power.draw --format=csv,nounits,noheader').catch(() => '');
133+
const lines = output.split('\n').filter(line => line.trim() !== '');
134+
const updatedIndexes = new Set<number>();
135+
// tslint:disable-next-line:prefer-for-of
136+
for(let i = 0; i < lines.length; i++){
137+
const [name, displayAttached, displayActive, fanSpeed, memoryTotal, utilizationGPU, utilizationMemory, temperatureGPU, temperatureMemory, powerDraw] = lines[i].split(',').map(s => s.trim());
138+
let currIdx = gpus.length;
139+
let currGpu: GPU | undefined = undefined;
140+
for(let g = 0; g < gpus.length; g++){
141+
if(updatedIndexes.has(g)) continue; // already updated this GPU
142+
const gpu = gpus[g];
143+
if(gpu.name !== name) continue;
144+
updatedIndexes.add(g);
145+
currIdx = g;
146+
currGpu = gpus[g];
147+
break;
148+
}
149+
if(!currGpu){
150+
currGpu = { id: name, name } as GPU;
151+
updatedIndexes.add(gpus.length);
152+
gpus.push(currGpu);
153+
}
154+
if(displayAttached) currGpu.displayAttached = ['yes', 'enabled', '1' ].includes(displayAttached.toLowerCase());
155+
if(displayActive) currGpu.displayActive = ['yes', 'enabled', '1' ].includes(displayActive.toLowerCase());
156+
if(fanSpeed && !Number.isNaN(parseFloat(fanSpeed))) currGpu.fanSpeed = parseFloat(fanSpeed) / 100;
157+
if(memoryTotal && !Number.isNaN(parseInt(memoryTotal, 10))) currGpu.memory = parseInt(memoryTotal, 10) * 1024 * 1024; // convert MiB to bytes
158+
if(utilizationGPU && !Number.isNaN(parseFloat(utilizationGPU))) currGpu.utilization = parseFloat(utilizationGPU) / 100;
159+
if(utilizationMemory && !Number.isNaN(parseFloat(utilizationMemory))) currGpu.memoryUtilization = parseFloat(utilizationMemory) / 100;
160+
if(temperatureGPU && !Number.isNaN(parseFloat(temperatureGPU))) currGpu.temperature = parseFloat(temperatureGPU);
161+
if(temperatureMemory && !Number.isNaN(parseFloat(temperatureMemory))) currGpu.memoryTemperature = parseFloat(temperatureMemory);
162+
if(powerDraw && !Number.isNaN(parseFloat(powerDraw))) currGpu.powerDraw = parseFloat(powerDraw);
163+
gpus[currIdx] = currGpu;
164+
}
165+
166+
}
167+
168+
return gpus;
169+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as cpu from './cpu';
22
import * as drive from './drive';
3+
import * as gpu from './gpu';
34
import * as net from './net';
45
import * as temperatures from './temperature';
56
import * as utils from './utils';
@@ -10,6 +11,7 @@ import * as utils from './utils';
1011
const osUtils = {
1112
...cpu,
1213
...drive,
14+
...gpu,
1315
...net,
1416
...temperatures,
1517
...utils,

src/memory.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export type Memory = {
2+
3+
/** Memory size in bytes. */
4+
size: number; // Size in bytes
5+
6+
/** Memory used in bytes. */
7+
used: number;
8+
9+
/** Memory free in bytes. */
10+
free: number;
11+
12+
/** Utilization of available memory in percentage (0.0-1.0). */
13+
utilization: number;
14+
15+
/** Memory type (e.g., DDR4, LPDDR4X). */
16+
type: string;
17+
18+
/** Memory clock speed in MHz. */
19+
frequency: number;
20+
21+
/** Memory speed in bytes per second. */
22+
speed: number;
23+
};

0 commit comments

Comments
 (0)