[llvm-exegesis][NFC] Improve parsing of the YAML files
[llvm-core.git] / utils / benchmark / tools / gbench / util.py
blob07c237727549899458c0667e05903c79a489b0ed
1 """util.py - General utilities for running, loading, and processing benchmarks
2 """
3 import json
4 import os
5 import tempfile
6 import subprocess
7 import sys
9 # Input file type enumeration
10 IT_Invalid = 0
11 IT_JSON = 1
12 IT_Executable = 2
14 _num_magic_bytes = 2 if sys.platform.startswith('win') else 4
15 def is_executable_file(filename):
16 """
17 Return 'True' if 'filename' names a valid file which is likely
18 an executable. A file is considered an executable if it starts with the
19 magic bytes for a EXE, Mach O, or ELF file.
20 """
21 if not os.path.isfile(filename):
22 return False
23 with open(filename, mode='rb') as f:
24 magic_bytes = f.read(_num_magic_bytes)
25 if sys.platform == 'darwin':
26 return magic_bytes in [
27 b'\xfe\xed\xfa\xce', # MH_MAGIC
28 b'\xce\xfa\xed\xfe', # MH_CIGAM
29 b'\xfe\xed\xfa\xcf', # MH_MAGIC_64
30 b'\xcf\xfa\xed\xfe', # MH_CIGAM_64
31 b'\xca\xfe\xba\xbe', # FAT_MAGIC
32 b'\xbe\xba\xfe\xca' # FAT_CIGAM
34 elif sys.platform.startswith('win'):
35 return magic_bytes == b'MZ'
36 else:
37 return magic_bytes == b'\x7FELF'
40 def is_json_file(filename):
41 """
42 Returns 'True' if 'filename' names a valid JSON output file.
43 'False' otherwise.
44 """
45 try:
46 with open(filename, 'r') as f:
47 json.load(f)
48 return True
49 except:
50 pass
51 return False
54 def classify_input_file(filename):
55 """
56 Return a tuple (type, msg) where 'type' specifies the classified type
57 of 'filename'. If 'type' is 'IT_Invalid' then 'msg' is a human readable
58 string represeting the error.
59 """
60 ftype = IT_Invalid
61 err_msg = None
62 if not os.path.exists(filename):
63 err_msg = "'%s' does not exist" % filename
64 elif not os.path.isfile(filename):
65 err_msg = "'%s' does not name a file" % filename
66 elif is_executable_file(filename):
67 ftype = IT_Executable
68 elif is_json_file(filename):
69 ftype = IT_JSON
70 else:
71 err_msg = "'%s' does not name a valid benchmark executable or JSON file" % filename
72 return ftype, err_msg
75 def check_input_file(filename):
76 """
77 Classify the file named by 'filename' and return the classification.
78 If the file is classified as 'IT_Invalid' print an error message and exit
79 the program.
80 """
81 ftype, msg = classify_input_file(filename)
82 if ftype == IT_Invalid:
83 print("Invalid input file: %s" % msg)
84 sys.exit(1)
85 return ftype
87 def find_benchmark_flag(prefix, benchmark_flags):
88 """
89 Search the specified list of flags for a flag matching `<prefix><arg>` and
90 if it is found return the arg it specifies. If specified more than once the
91 last value is returned. If the flag is not found None is returned.
92 """
93 assert prefix.startswith('--') and prefix.endswith('=')
94 result = None
95 for f in benchmark_flags:
96 if f.startswith(prefix):
97 result = f[len(prefix):]
98 return result
100 def remove_benchmark_flags(prefix, benchmark_flags):
102 Return a new list containing the specified benchmark_flags except those
103 with the specified prefix.
105 assert prefix.startswith('--') and prefix.endswith('=')
106 return [f for f in benchmark_flags if not f.startswith(prefix)]
108 def load_benchmark_results(fname):
110 Read benchmark output from a file and return the JSON object.
111 REQUIRES: 'fname' names a file containing JSON benchmark output.
113 with open(fname, 'r') as f:
114 return json.load(f)
117 def run_benchmark(exe_name, benchmark_flags):
119 Run a benchmark specified by 'exe_name' with the specified
120 'benchmark_flags'. The benchmark is run directly as a subprocess to preserve
121 real time console output.
122 RETURNS: A JSON object representing the benchmark output
124 output_name = find_benchmark_flag('--benchmark_out=',
125 benchmark_flags)
126 is_temp_output = False
127 if output_name is None:
128 is_temp_output = True
129 thandle, output_name = tempfile.mkstemp()
130 os.close(thandle)
131 benchmark_flags = list(benchmark_flags) + \
132 ['--benchmark_out=%s' % output_name]
134 cmd = [exe_name] + benchmark_flags
135 print("RUNNING: %s" % ' '.join(cmd))
136 exitCode = subprocess.call(cmd)
137 if exitCode != 0:
138 print('TEST FAILED...')
139 sys.exit(exitCode)
140 json_res = load_benchmark_results(output_name)
141 if is_temp_output:
142 os.unlink(output_name)
143 return json_res
146 def run_or_load_benchmark(filename, benchmark_flags):
148 Get the results for a specified benchmark. If 'filename' specifies
149 an executable benchmark then the results are generated by running the
150 benchmark. Otherwise 'filename' must name a valid JSON output file,
151 which is loaded and the result returned.
153 ftype = check_input_file(filename)
154 if ftype == IT_JSON:
155 return load_benchmark_results(filename)
156 elif ftype == IT_Executable:
157 return run_benchmark(filename, benchmark_flags)
158 else:
159 assert False # This branch is unreachable