1"""util.py - General utilities for running, loading, and processing benchmarks 2""" 3import json 4import os 5import re 6import subprocess 7import sys 8import tempfile 9 10 11# Input file type enumeration 12IT_Invalid = 0 13IT_JSON = 1 14IT_Executable = 2 15 16_num_magic_bytes = 2 if sys.platform.startswith('win') else 4 17 18 19def is_executable_file(filename): 20 """ 21 Return 'True' if 'filename' names a valid file which is likely 22 an executable. A file is considered an executable if it starts with the 23 magic bytes for a EXE, Mach O, or ELF file. 24 """ 25 if not os.path.isfile(filename): 26 return False 27 with open(filename, mode='rb') as f: 28 magic_bytes = f.read(_num_magic_bytes) 29 if sys.platform == 'darwin': 30 return magic_bytes in [ 31 b'\xfe\xed\xfa\xce', # MH_MAGIC 32 b'\xce\xfa\xed\xfe', # MH_CIGAM 33 b'\xfe\xed\xfa\xcf', # MH_MAGIC_64 34 b'\xcf\xfa\xed\xfe', # MH_CIGAM_64 35 b'\xca\xfe\xba\xbe', # FAT_MAGIC 36 b'\xbe\xba\xfe\xca' # FAT_CIGAM 37 ] 38 elif sys.platform.startswith('win'): 39 return magic_bytes == b'MZ' 40 else: 41 return magic_bytes == b'\x7FELF' 42 43 44def is_json_file(filename): 45 """ 46 Returns 'True' if 'filename' names a valid JSON output file. 47 'False' otherwise. 48 """ 49 try: 50 with open(filename, 'r') as f: 51 json.load(f) 52 return True 53 except BaseException: 54 pass 55 return False 56 57 58def classify_input_file(filename): 59 """ 60 Return a tuple (type, msg) where 'type' specifies the classified type 61 of 'filename'. If 'type' is 'IT_Invalid' then 'msg' is a human readable 62 string representing the error. 63 """ 64 ftype = IT_Invalid 65 err_msg = None 66 if not os.path.exists(filename): 67 err_msg = "'%s' does not exist" % filename 68 elif not os.path.isfile(filename): 69 err_msg = "'%s' does not name a file" % filename 70 elif is_executable_file(filename): 71 ftype = IT_Executable 72 elif is_json_file(filename): 73 ftype = IT_JSON 74 else: 75 err_msg = "'%s' does not name a valid benchmark executable or JSON file" % filename 76 return ftype, err_msg 77 78 79def check_input_file(filename): 80 """ 81 Classify the file named by 'filename' and return the classification. 82 If the file is classified as 'IT_Invalid' print an error message and exit 83 the program. 84 """ 85 ftype, msg = classify_input_file(filename) 86 if ftype == IT_Invalid: 87 print("Invalid input file: %s" % msg) 88 sys.exit(1) 89 return ftype 90 91 92def find_benchmark_flag(prefix, benchmark_flags): 93 """ 94 Search the specified list of flags for a flag matching `<prefix><arg>` and 95 if it is found return the arg it specifies. If specified more than once the 96 last value is returned. If the flag is not found None is returned. 97 """ 98 assert prefix.startswith('--') and prefix.endswith('=') 99 result = None 100 for f in benchmark_flags: 101 if f.startswith(prefix): 102 result = f[len(prefix):] 103 return result 104 105 106def remove_benchmark_flags(prefix, benchmark_flags): 107 """ 108 Return a new list containing the specified benchmark_flags except those 109 with the specified prefix. 110 """ 111 assert prefix.startswith('--') and prefix.endswith('=') 112 return [f for f in benchmark_flags if not f.startswith(prefix)] 113 114 115def load_benchmark_results(fname, benchmark_filter): 116 """ 117 Read benchmark output from a file and return the JSON object. 118 119 Apply benchmark_filter, a regular expression, with nearly the same 120 semantics of the --benchmark_filter argument. May be None. 121 Note: the Python regular expression engine is used instead of the 122 one used by the C++ code, which may produce different results 123 in complex cases. 124 125 REQUIRES: 'fname' names a file containing JSON benchmark output. 126 """ 127 def benchmark_wanted(benchmark): 128 if benchmark_filter is None: 129 return True 130 name = benchmark.get('run_name', None) or benchmark['name'] 131 if re.search(benchmark_filter, name): 132 return True 133 return False 134 135 with open(fname, 'r') as f: 136 results = json.load(f) 137 if 'benchmarks' in results: 138 results['benchmarks'] = list(filter(benchmark_wanted, 139 results['benchmarks'])) 140 return results 141 142 143def sort_benchmark_results(result): 144 benchmarks = result['benchmarks'] 145 146 # From inner key to the outer key! 147 benchmarks = sorted( 148 benchmarks, key=lambda benchmark: benchmark['repetition_index'] if 'repetition_index' in benchmark else -1) 149 benchmarks = sorted( 150 benchmarks, key=lambda benchmark: 1 if 'run_type' in benchmark and benchmark['run_type'] == "aggregate" else 0) 151 benchmarks = sorted( 152 benchmarks, key=lambda benchmark: benchmark['per_family_instance_index'] if 'per_family_instance_index' in benchmark else -1) 153 benchmarks = sorted( 154 benchmarks, key=lambda benchmark: benchmark['family_index'] if 'family_index' in benchmark else -1) 155 156 result['benchmarks'] = benchmarks 157 return result 158 159 160def run_benchmark(exe_name, benchmark_flags): 161 """ 162 Run a benchmark specified by 'exe_name' with the specified 163 'benchmark_flags'. The benchmark is run directly as a subprocess to preserve 164 real time console output. 165 RETURNS: A JSON object representing the benchmark output 166 """ 167 output_name = find_benchmark_flag('--benchmark_out=', 168 benchmark_flags) 169 is_temp_output = False 170 if output_name is None: 171 is_temp_output = True 172 thandle, output_name = tempfile.mkstemp() 173 os.close(thandle) 174 benchmark_flags = list(benchmark_flags) + \ 175 ['--benchmark_out=%s' % output_name] 176 177 cmd = [exe_name] + benchmark_flags 178 print("RUNNING: %s" % ' '.join(cmd)) 179 exitCode = subprocess.call(cmd) 180 if exitCode != 0: 181 print('TEST FAILED...') 182 sys.exit(exitCode) 183 json_res = load_benchmark_results(output_name, None) 184 if is_temp_output: 185 os.unlink(output_name) 186 return json_res 187 188 189def run_or_load_benchmark(filename, benchmark_flags): 190 """ 191 Get the results for a specified benchmark. If 'filename' specifies 192 an executable benchmark then the results are generated by running the 193 benchmark. Otherwise 'filename' must name a valid JSON output file, 194 which is loaded and the result returned. 195 """ 196 ftype = check_input_file(filename) 197 if ftype == IT_JSON: 198 benchmark_filter = find_benchmark_flag('--benchmark_filter=', 199 benchmark_flags) 200 return load_benchmark_results(filename, benchmark_filter) 201 if ftype == IT_Executable: 202 return run_benchmark(filename, benchmark_flags) 203 raise ValueError('Unknown file type %s' % ftype) 204