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