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+ }
0 commit comments