11#!/usr/bin/env python3
2- """Emit host- local GPU process state for scheduler reconciliation."""
2+ """Emit local or SSH-polled GPU process state for scheduler reconciliation."""
33
44from __future__ import annotations
55
@@ -30,6 +30,12 @@ def run_capture(argv: list[str], *, timeout: float) -> subprocess.CompletedProce
3030 )
3131
3232
33+ def nvidia_smi_command (args : argparse .Namespace , nvidia_smi_args : list [str ]) -> list [str ]:
34+ if args .ssh_host :
35+ return ["ssh" , args .ssh_host , args .nvidia_smi , * nvidia_smi_args ]
36+ return [args .nvidia_smi , * nvidia_smi_args ]
37+
38+
3339def parse_compute_apps (stdout : str ) -> list [dict [str , Any ]]:
3440 apps : list [dict [str , Any ]] = []
3541 for row in csv .reader (io .StringIO (stdout )):
@@ -92,15 +98,17 @@ def as_int(value: str) -> int | None:
9298 return rows
9399
94100
95- def query_compute_apps (nvidia_smi : str , timeout : float ) -> dict [str , Any ]:
101+ def query_compute_apps (args : argparse . Namespace ) -> dict [str , Any ]:
96102 try :
97103 proc = run_capture (
98- [
99- nvidia_smi ,
100- "--query-compute-apps=pid,process_name,used_gpu_memory" ,
101- "--format=csv,noheader,nounits" ,
102- ],
103- timeout = timeout ,
104+ nvidia_smi_command (
105+ args ,
106+ [
107+ "--query-compute-apps=pid,process_name,used_gpu_memory" ,
108+ "--format=csv,noheader,nounits" ,
109+ ],
110+ ),
111+ timeout = args .timeout_sec ,
104112 )
105113 except FileNotFoundError as exc :
106114 return {"ok" : False , "reason" : "nvidia_smi_not_found" , "error" : str (exc ), "apps" : []}
@@ -115,15 +123,17 @@ def query_compute_apps(nvidia_smi: str, timeout: float) -> dict[str, Any]:
115123 }
116124
117125
118- def query_gpus (nvidia_smi : str , timeout : float ) -> dict [str , Any ]:
126+ def query_gpus (args : argparse . Namespace ) -> dict [str , Any ]:
119127 try :
120128 proc = run_capture (
121- [
122- nvidia_smi ,
123- "--query-gpu=index,uuid,pci.bus_id,name,memory.total,memory.used,memory.free,utilization.gpu" ,
124- "--format=csv,noheader,nounits" ,
125- ],
126- timeout = timeout ,
129+ nvidia_smi_command (
130+ args ,
131+ [
132+ "--query-gpu=index,uuid,pci.bus_id,name,memory.total,memory.used,memory.free,utilization.gpu" ,
133+ "--format=csv,noheader,nounits" ,
134+ ],
135+ ),
136+ timeout = args .timeout_sec ,
127137 )
128138 except FileNotFoundError as exc :
129139 return {"ok" : False , "reason" : "nvidia_smi_not_found" , "error" : str (exc ), "gpus" : []}
@@ -146,22 +156,24 @@ def proc_cmdline(pid: int) -> str:
146156 return ""
147157
148158
149- def enrich_apps (apps : list [dict [str , Any ]]) -> list [dict [str , Any ]]:
159+ def enrich_apps (apps : list [dict [str , Any ]], * , include_cmdline : bool ) -> list [dict [str , Any ]]:
150160 enriched = []
151161 for app in apps :
152162 row = dict (app )
153163 pid = row .get ("pid" )
154- if isinstance (pid , int ):
164+ if include_cmdline and isinstance (pid , int ):
155165 row ["cmdline" ] = proc_cmdline (pid )
156166 enriched .append (row )
157167 return enriched
158168
159169
160170def build_payload (args : argparse .Namespace ) -> dict [str , Any ]:
161- gpu_query = query_gpus (args . nvidia_smi , args . timeout_sec )
162- compute_query = query_compute_apps (args . nvidia_smi , args . timeout_sec )
163- apps = enrich_apps (compute_query .get ("apps" , []))
171+ gpu_query = query_gpus (args )
172+ compute_query = query_compute_apps (args )
173+ apps = enrich_apps (compute_query .get ("apps" , []), include_cmdline = not args . ssh_host )
164174 ok = bool (gpu_query .get ("ok" )) and bool (compute_query .get ("ok" ))
175+ poller_host = socket .gethostname ()
176+ worker_host = args .ssh_host or poller_host
165177 reasons = []
166178 if not gpu_query .get ("ok" ):
167179 reasons .append (str (gpu_query .get ("reason" ) or "gpu_query_failed" ))
@@ -172,7 +184,9 @@ def build_payload(args: argparse.Namespace) -> dict[str, Any]:
172184 "ok" : ok ,
173185 "reason" : "ok" if ok else "," .join (reasons ),
174186 "checked_at_unix" : time .time (),
175- "worker_host" : socket .gethostname (),
187+ "worker_host" : worker_host ,
188+ "poller_host" : poller_host ,
189+ "ssh_host" : args .ssh_host ,
176190 "resource" : args .resource ,
177191 "nvidia_smi" : args .nvidia_smi ,
178192 "gpus" : gpu_query .get ("gpus" , []),
@@ -188,6 +202,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
188202 parser = argparse .ArgumentParser (description = __doc__ )
189203 parser .add_argument ("--resource" , default = "cuda" )
190204 parser .add_argument ("--nvidia-smi" , default = "nvidia-smi" )
205+ parser .add_argument ("--ssh-host" , default = "" )
191206 parser .add_argument ("--timeout-sec" , type = float , default = 10.0 )
192207 parser .add_argument ("--json" , action = "store_true" )
193208 args = parser .parse_args (argv )
0 commit comments