44import json
55import subprocess
66import xmltodict
7+ from sys import platform
8+ from pathlib import Path
79
810from importlib .metadata import requires , version , PackageNotFoundError
911from packaging .requirements import Requirement
1416
1517
1618def output_file_schema (target ):
17- """ Get the requested JSON schema and the version number
19+ """Get the requested JSON schema and the version number
1820
1921 :param target: Name of the T4 schema to return, should be any of ['output', 'metadata']
2022 :type target: string
@@ -25,13 +27,13 @@ def output_file_schema(target):
2527 """
2628 current_version = "1.0.0"
2729 output_file = schema_dir + f"/T4/{ current_version } /{ target } -schema.json"
28- with open (output_file , 'r' ) as fh :
30+ with open (output_file , "r" ) as fh :
2931 json_string = json .load (fh )
3032 return current_version , json_string
3133
3234
3335def get_configuration_validity (objective ) -> str :
34- """ Convert internal Kernel Tuner error to string """
36+ """Convert internal Kernel Tuner error to string"""
3537 errorstring : str
3638 if not isinstance (objective , util .ErrorConfig ):
3739 errorstring = "correct"
@@ -40,24 +42,33 @@ def get_configuration_validity(objective) -> str:
4042 errorstring = "compile"
4143 elif isinstance (objective , util .RuntimeFailedConfig ):
4244 errorstring = "runtime"
43- else :
45+ elif isinstance ( objective , util . InvalidConfig ) :
4446 errorstring = "constraints"
47+ else :
48+ raise ValueError (f"Unkown objective type { type (objective )} , value { objective } " )
4549 return errorstring
4650
4751
4852def filename_ensure_json_extension (filename : str ) -> str :
49- """ Check if the filename has a .json extension, if not, add it """
53+ """Check if the filename has a .json extension, if not, add it"""
5054 if filename [- 5 :] != ".json" :
5155 filename += ".json"
5256 return filename
5357
5458
55- def store_output_file (output_filename , results , tune_params , objective = "time" ):
56- """ Store the obtained auto-tuning results in a JSON output file
59+ def make_filenamepath (filenamepath : Path ):
60+ """Create the given path to a filename if the path does not yet exist"""
61+ filepath = filenamepath .parents [0 ]
62+ if not filepath .exists ():
63+ filepath .mkdir ()
64+
65+
66+ def store_output_file (output_filename : str , results , tune_params , objective = "time" ):
67+ """Store the obtained auto-tuning results in a JSON output file
5768
5869 This function produces a JSON file that adheres to the T4 auto-tuning output JSON schema.
5970
60- :param output_filename: Name of the to be created output file
71+ :param output_filename: Name or 'path / name' of the to be created output file
6172 :type output_filename: string
6273
6374 :param results: Results list as return by tune_kernel
@@ -70,26 +81,20 @@ def store_output_file(output_filename, results, tune_params, objective="time"):
7081 :type objective: string
7182
7283 """
73- output_filename = filename_ensure_json_extension (output_filename )
84+ output_filenamepath = Path (filename_ensure_json_extension (output_filename ))
85+ make_filenamepath (output_filenamepath )
7486
75- timing_keys = [
76- "compile_time" , "benchmark_time" , "framework_time" , "strategy_time" ,
77- "verification_time"
78- ]
79- not_measurement_keys = list (
80- tune_params .keys ()) + timing_keys + ["timestamp" ] + ["times" ]
87+ timing_keys = ["compile_time" , "benchmark_time" , "framework_time" , "strategy_time" , "verification_time" ]
88+ not_measurement_keys = list (tune_params .keys ()) + timing_keys + ["timestamp" ] + ["times" ]
8189
8290 output_data = []
8391
8492 for result in results :
85-
8693 out = {}
8794
88- out ["timestamp" ] = result ["timestamp" ]
89- out ["configuration" ] = {
90- k : v
91- for k , v in result .items () if k in tune_params
92- }
95+ if "timestamp" in result :
96+ out ["timestamp" ] = result ["timestamp" ]
97+ out ["configuration" ] = {k : v for k , v in result .items () if k in tune_params }
9398
9499 # collect configuration specific timings
95100 timings = dict ()
@@ -98,7 +103,8 @@ def store_output_file(output_filename, results, tune_params, objective="time"):
98103 timings ["framework" ] = result ["framework_time" ]
99104 timings ["search_algorithm" ] = result ["strategy_time" ]
100105 timings ["validation" ] = result ["verification_time" ]
101- timings ["runtimes" ] = result ["times" ]
106+ if "times" in result :
107+ timings ["runtimes" ] = result ["times" ]
102108 out ["times" ] = timings
103109
104110 # encode the validity of the configuration
@@ -112,10 +118,7 @@ def store_output_file(output_filename, results, tune_params, objective="time"):
112118 measurements = []
113119 for key , value in result .items ():
114120 if key not in not_measurement_keys :
115- measurements .append (
116- dict (name = key ,
117- value = value ,
118- unit = "ms" if key .startswith ("time" ) else "" ))
121+ measurements .append (dict (name = key , value = value , unit = "ms" if key .startswith ("time" ) else "" ))
119122 out ["measurements" ] = measurements
120123
121124 # objectives
@@ -130,12 +133,12 @@ def store_output_file(output_filename, results, tune_params, objective="time"):
130133 # write output_data to a JSON file
131134 version , _ = output_file_schema ("results" )
132135 output_json = dict (results = output_data , schema_version = version )
133- with open (output_filename , 'w+' ) as fh :
136+ with open (output_filenamepath , "w+" ) as fh :
134137 json .dump (output_json , fh , cls = util .NpEncoder )
135138
136139
137- def get_dependencies (package = ' kernel_tuner' ):
138- """ Get the Python dependencies of Kernel Tuner currently installed and their version numbers """
140+ def get_dependencies (package = " kernel_tuner" ):
141+ """Get the Python dependencies of Kernel Tuner currently installed and their version numbers"""
139142 requirements = requires (package )
140143 deps = [Requirement (req ).name for req in requirements ]
141144 depends = []
@@ -150,10 +153,9 @@ def get_dependencies(package='kernel_tuner'):
150153
151154
152155def get_device_query (target ):
153- """ Get the information about GPUs in the current system, target is any of ['nvidia', 'amd'] """
156+ """Get the information about GPUs in the current system, target is any of ['nvidia', 'amd']"""
154157 if target == "nvidia" :
155- nvidia_smi_out = subprocess .run (["nvidia-smi" , "--query" , "-x" ],
156- capture_output = True )
158+ nvidia_smi_out = subprocess .run (["nvidia-smi" , "--query" , "-x" ], capture_output = True )
157159 nvidia_smi = xmltodict .parse (nvidia_smi_out .stdout )
158160 gpu_info = nvidia_smi ["nvidia_smi_log" ]["gpu" ]
159161 del_key = "processes"
@@ -162,61 +164,89 @@ def get_device_query(target):
162164 for gpu in gpu_info :
163165 del gpu [del_key ]
164166 elif isinstance (gpu_info , dict ) and del_key in gpu_info :
165- del gpu_info [del_key ]
167+ del gpu_info [del_key ]
166168 return nvidia_smi
167169 elif target == "amd" :
168- rocm_smi_out = subprocess .run (["rocm-smi" , "--showallinfo" , "--json" ],
169- capture_output = True )
170+ rocm_smi_out = subprocess .run (["rocm-smi" , "--showallinfo" , "--json" ], capture_output = True )
170171 return json .loads (rocm_smi_out .stdout )
171172 else :
172173 raise ValueError ("get_device_query target not supported" )
173174
174175
175- def store_metadata_file (metadata_filename ):
176- """ Store the metadata about the current hardware and software environment in a JSON output file
176+ def store_metadata_file (metadata_filename : str ):
177+ """Store the metadata about the current hardware and software environment in a JSON output file
177178
178179 This function produces a JSON file that adheres to the T4 auto-tuning metadata JSON schema.
179180
180- :param metadata_filename: Name of the to be created metadata file
181+ :param metadata_filename: Name or 'path / name' of the to be created metadata file
181182 :type metadata_filename: string
182183
183184 """
184- metadata_filename = filename_ensure_json_extension (metadata_filename )
185+ metadata_filenamepath = Path (filename_ensure_json_extension (metadata_filename ))
186+ make_filenamepath (metadata_filenamepath )
185187 metadata = {}
188+ supported_operating_systems = ["linux" , "win32" , "darwin" ]
186189
187- # lshw only works on Linux
188- try :
189- lshw_out = subprocess .run (["lshw" , "-json" ], capture_output = True )
190+ if all (platform != supported for supported in supported_operating_systems ):
191+ raise ValueError (f"Platform { platform } not supported for metadata collection" )
190192
191- # sometimes lshw outputs a list of length 1, sometimes just as a dict, schema wants a list
192- lshw_string = lshw_out .stdout .decode ('utf-8' ).strip ()
193- if lshw_string [0 ] == '{' and lshw_string [- 1 ] == '}' :
194- lshw_string = '[' + lshw_string + ']'
195- hardware_desc = dict (lshw = json .loads (lshw_string ))
193+ try :
194+ # differentiate between OSes, possible values: https://docs.python.org/3/library/sys.html#sys.platform
195+ if platform == "linux" :
196+ os_string = "Linux"
197+ hardware_description_out = subprocess .run (["lshw" , "-json" ], capture_output = True )
198+ elif platform == "win32" :
199+ os_string = "Windows"
200+ raise NotImplementedError ("Hardware specification not yet implemented for Windows" )
201+ elif platform == "darwin" :
202+ os_string = "Mac"
203+ hardware_description_out = subprocess .run (
204+ [
205+ "system_profiler" ,
206+ "-json" ,
207+ "-detailLevel" ,
208+ "mini" ,
209+ "SPSoftwareDataType" ,
210+ "SPHardwareDataType" ,
211+ "SPiBridgeDataType" ,
212+ "SPPCIDataType" ,
213+ "SPMemoryDataType" ,
214+ "SPNVMeDataType" ,
215+ ],
216+ capture_output = True ,
217+ )
218+ else :
219+ raise ValueError ("This code is supposed to be unreachable, the supported platform check has failed" )
220+
221+ # process the hardware description output
222+ hardware_description_string = hardware_description_out .stdout .decode ("utf-8" ).strip ()
223+ if hardware_description_string [0 ] == "{" and hardware_description_string [- 1 ] == "}" :
224+ # sometimes lshw outputs a list of length 1, sometimes just as a dict, schema wants a list
225+ hardware_description_string = "[" + hardware_description_string + "]"
226+ metadata ["operating_system" ] = os_string
196227 except :
197- hardware_desc = dict ( lshw = [ "lshw error" ])
198-
199- metadata ["hardware" ] = hardware_desc
228+ hardware_description_string = "[ error retrieving hardware description]"
229+ metadata [ "operating_system" ] = "unidentified OS"
230+ metadata ["hardware" ] = dict ( hardware_description = json . loads ( hardware_description_string ))
200231
201232 # attempts to use nvidia-smi or rocm-smi if present
202233 device_query = {}
203234 try :
204- device_query [' nvidia-smi' ] = get_device_query ("nvidia" )
235+ device_query [" nvidia-smi" ] = get_device_query ("nvidia" )
205236 except FileNotFoundError :
206237 # ignore if nvidia-smi is not found
207238 pass
208239
209240 try :
210- device_query [' rocm-smi' ] = get_device_query ("amd" )
241+ device_query [" rocm-smi" ] = get_device_query ("amd" )
211242 except FileNotFoundError :
212243 # ignore if rocm-smi is not found
213244 pass
214245
215- metadata ["environment" ] = dict (device_query = device_query ,
216- requirements = get_dependencies ())
246+ metadata ["environment" ] = dict (device_query = device_query , requirements = get_dependencies ())
217247
218248 # write metadata to JSON file
219249 version , _ = output_file_schema ("metadata" )
220250 metadata_json = dict (metadata = metadata , schema_version = version )
221- with open (metadata_filename , 'w+' ) as fh :
251+ with open (metadata_filenamepath , "w+" ) as fh :
222252 json .dump (metadata_json , fh , indent = " " )
0 commit comments