xref: /aosp_15_r20/external/google-benchmark/tools/gbench/util.py (revision dbb99499c3810fa1611fa2242a2fc446be01a57c)
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