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