| #!/usr/bin/env python3 | 
 | # Copyright 2021 The IREE Authors | 
 | # | 
 | # Licensed under the Apache License v2.0 with LLVM Exceptions. | 
 | # See https://llvm.org/LICENSE.txt for license information. | 
 | # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
 | """Runs all matched benchmark suites on an Android device. | 
 |  | 
 | This script probes the Android phone via `adb` and uses the device information | 
 | to filter and run suitable benchmarks and optionally captures Tracy traces on | 
 | the Android phone. | 
 |  | 
 | It expects that `adb` is installed, and there is an `iree-benchmark-module` | 
 | tool cross-compiled towards Android. If to capture traces, another | 
 | tracing-enabled `iree-benchmark-module` and the Tracy `capture` tool should be | 
 | cross-compiled towards Android. | 
 |  | 
 | It also expects the benchmark artifacts are generated by building the | 
 | `iree-benchmark-suites` target in the following directory structure: | 
 |  | 
 | <root-build-dir>/benchmark_suites | 
 | └── <benchmark-category> (e.g., TensorFlow) | 
 |     ├── <benchmark-suite> (e.g., MobileBertSquad-fp32) | 
 |     │   ├── <benchmark-case> (e.g., iree-vulkan__GPU-Mali-Valhall__kernel-execution) | 
 |     │   │   └── flagfile | 
 |     │   ├── ... | 
 |     │   │   └── flagfile | 
 |     │   └── <benchmark_case> | 
 |     │       └── flagfile | 
 |     └── vmfb | 
 |         ├── compiled-<sha1>.vmfb | 
 |         ├── ... | 
 |         └── compiled-<sha1>.vmfb | 
 |  | 
 | Example usages: | 
 |  | 
 |   # Without trace generation | 
 |   python3 run_benchmarks.py \ | 
 |     --normal_benchmark_tool=/path/to/android/target/iree-benchmark_module \ | 
 |     /path/to/host/build/dir | 
 |  | 
 |   # With trace generation | 
 |   python3 run_benchmarks.py \ | 
 |     --normal_benchmark_tool=/path/to/normal/android/target/iree-benchmark_module \ | 
 |     --traced_benchmark_tool=/path/to/tracy/android/target/iree-benchmark_module \ | 
 |     --trace_capture_tool=/path/to/host/build/tracy/capture \ | 
 |     /path/to/host/build/dir | 
 | """ | 
 |  | 
 | import argparse | 
 | import atexit | 
 | import json | 
 | import os | 
 | import re | 
 | import subprocess | 
 | import tarfile | 
 | import time | 
 | import shutil | 
 | import sys | 
 |  | 
 | from typing import Any, Dict, List, Optional, Sequence, Tuple, TextIO, Set | 
 |  | 
 | from common.benchmark_definition import (AndroidDeviceInfo, BenchmarkInfo, | 
 |                                          BenchmarkResults, BenchmarkRun, | 
 |                                          execute_cmd, | 
 |                                          execute_cmd_and_get_output, | 
 |                                          get_android_device_model, | 
 |                                          IREE_PRETTY_NAMES_TO_DRIVERS) | 
 |  | 
 | # All benchmarks' relative path against root build directory. | 
 | BENCHMARK_SUITE_REL_PATH = "benchmark_suites" | 
 | # VMFB files' relative path against a benchmark category directory. | 
 | VMFB_REL_PATH = "vmfb" | 
 |  | 
 | # The flagfile's filename for compiled benchmark artifacts. | 
 | MODEL_FLAGFILE_NAME = "flagfile" | 
 |  | 
 | # Root directory to perform benchmarks in on the Android device. | 
 | ANDROID_TMP_DIR = "/data/local/tmp/iree-benchmarks" | 
 |  | 
 | # A map from Android CPU ABI to IREE's benchmark target architecture. | 
 | CPU_ABI_TO_TARGET_ARCH_MAP = { | 
 |     "arm64-v8a": "cpu-arm64-v8a", | 
 | } | 
 |  | 
 | # A map from Android GPU name to IREE's benchmark target architecture. | 
 | GPU_NAME_TO_TARGET_ARCH_MAP = { | 
 |     "adreno-640": "gpu-adreno", | 
 |     "adreno-650": "gpu-adreno", | 
 |     "adreno-660": "gpu-adreno", | 
 |     "mali-g77": "gpu-mali-valhall", | 
 |     "mali-g78": "gpu-mali-valhall", | 
 | } | 
 |  | 
 |  | 
 | def get_benchmark_repetition_count(runner: str) -> int: | 
 |   """Returns the benchmark repetition count for the given runner.""" | 
 |   if runner == "iree-vmvx": | 
 |     # VMVX is very unoptimized for now and can take a long time to run. | 
 |     # Decrease the repetition for it until it's reasonably fast. | 
 |     return 3 | 
 |   return 10 | 
 |  | 
 |  | 
 | def get_git_commit_hash(commit: str) -> str: | 
 |   return execute_cmd_and_get_output(['git', 'rev-parse', commit], | 
 |                                     cwd=os.path.dirname( | 
 |                                         os.path.realpath(__file__))) | 
 |  | 
 |  | 
 | def adb_push_to_tmp_dir(content: str, | 
 |                         relative_dir: str = "", | 
 |                         verbose: bool = False) -> str: | 
 |   """Pushes content onto the Android device. | 
 |  | 
 |   Args: | 
 |     content: the full path to the source file. | 
 |     relative_dir: the directory to push to; relative to ANDROID_TMP_DIR. | 
 |  | 
 |   Returns: | 
 |     The full path to the content on the Android device. | 
 |   """ | 
 |   filename = os.path.basename(content) | 
 |   android_path = os.path.join(ANDROID_TMP_DIR, relative_dir, filename) | 
 |   execute_cmd(["adb", "push", "-p", | 
 |                os.path.abspath(content), android_path], | 
 |               verbose=verbose) | 
 |   return android_path | 
 |  | 
 |  | 
 | def adb_execute_and_get_output(cmd_args: Sequence[str], | 
 |                                relative_dir: str = "", | 
 |                                verbose: bool = False) -> str: | 
 |   """Executes command with adb shell. | 
 |  | 
 |   Switches to `relative_dir` relative to the android tmp directory before | 
 |   executing. Waits for completion and returns the command stdout. | 
 |  | 
 |   Args: | 
 |     cmd_args: a list containing the command to execute and its parameters | 
 |     relative_dir: the directory to execute the command in; relative to | 
 |       ANDROID_TMP_DIR. | 
 |  | 
 |   Returns: | 
 |     A string for the command output. | 
 |   """ | 
 |   cmd = ["adb", "shell"] | 
 |   cmd.extend(["cd", os.path.join(ANDROID_TMP_DIR, relative_dir)]) | 
 |   cmd.append("&&") | 
 |   cmd.extend(cmd_args) | 
 |  | 
 |   return execute_cmd_and_get_output(cmd, verbose=verbose) | 
 |  | 
 |  | 
 | def adb_execute(cmd_args: Sequence[str], | 
 |                 relative_dir: str = "", | 
 |                 verbose: bool = False) -> subprocess.CompletedProcess: | 
 |   """Executes command with adb shell. | 
 |  | 
 |   Switches to `relative_dir` relative to the android tmp directory before | 
 |   executing. Waits for completion. Output is streamed to the terminal. | 
 |  | 
 |   Args: | 
 |     cmd_args: a list containing the command to execute and its parameters | 
 |     relative_dir: the directory to execute the command in; relative to | 
 |       ANDROID_TMP_DIR. | 
 |  | 
 |   Returns: | 
 |     The completed process. | 
 |   """ | 
 |   cmd = ["adb", "shell"] | 
 |   cmd.extend(["cd", os.path.join(ANDROID_TMP_DIR, relative_dir)]) | 
 |   cmd.append("&&") | 
 |   cmd.extend(cmd_args) | 
 |  | 
 |   return execute_cmd(cmd, verbose=verbose) | 
 |  | 
 |  | 
 | def adb_start_cmd(cmd_args: Sequence[str], | 
 |                   relative_dir: str, | 
 |                   verbose: bool = False) -> subprocess.Popen: | 
 |   """Executes command with adb shell in a directory and returns the handle | 
 |   without waiting for completion. | 
 |  | 
 |   Args: | 
 |     cmd_args: a list containing the command to execute and its parameters | 
 |     relative_dir: the directory to execute the command in; relative to | 
 |       ANDROID_TMP_DIR. | 
 |  | 
 |   Returns: | 
 |     A Popen object for the started command. | 
 |   """ | 
 |   cmd = ["adb", "shell"] | 
 |   cmd.extend(["cd", f"{ANDROID_TMP_DIR}/{relative_dir}"]) | 
 |   cmd.append("&&") | 
 |   cmd.extend(cmd_args) | 
 |  | 
 |   if verbose: | 
 |     cmd_str = " ".join(cmd) | 
 |     print(f"cmd: {cmd_str}") | 
 |   return subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True) | 
 |  | 
 |  | 
 | def compose_benchmark_info_object(device_info: AndroidDeviceInfo, | 
 |                                   benchmark_category_dir: str, | 
 |                                   benchmark_case_dir: str) -> BenchmarkInfo: | 
 |   """Creates an BenchmarkInfo object to describe the benchmark. | 
 |  | 
 |   Args: | 
 |     device_info: an AndroidDeviceInfo object. | 
 |     benchmark_category_dir: the directory to a specific benchmark category. | 
 |     benchmark_case_dir: a directory containing the benchmark case. | 
 |  | 
 |   Returns: | 
 |     A BenchmarkInfo object. | 
 |   """ | 
 |   # Extract the model name from the directory path. This uses the relative | 
 |   # path under the root model directory. If there are multiple segments, | 
 |   # additional ones will be placed in parentheses. | 
 |   model_name = os.path.relpath(benchmark_case_dir, benchmark_category_dir) | 
 |   # Now we have <model-name>/.../<iree-driver>__<target-arch>__<bench_mode>, | 
 |   # Remove the last segment. | 
 |   model_name = os.path.dirname(model_name) | 
 |   main, rest = os.path.split(model_name) | 
 |   if main: | 
 |     # Tags coming from directory structure. | 
 |     model_name = main | 
 |     model_tags = [re.sub(r"\W+", "-", rest)] | 
 |   else: | 
 |     # Tags coming from the name itself. | 
 |     model_name, rest = rest.split("-", 1) | 
 |     model_tags = rest.split(",") | 
 |  | 
 |   # Extract benchmark info from the directory path following convention: | 
 |   #   <iree-driver>__<target-architecture>__<benchmark_mode> | 
 |   root_immediate_dir = os.path.basename(benchmark_case_dir) | 
 |   iree_driver, target_arch, bench_mode = root_immediate_dir.split("__") | 
 |  | 
 |   model_source = os.path.basename(benchmark_category_dir) | 
 |  | 
 |   return BenchmarkInfo(model_name=model_name, | 
 |                        model_tags=model_tags, | 
 |                        model_source=model_source, | 
 |                        bench_mode=bench_mode.split(","), | 
 |                        runner=iree_driver, | 
 |                        device_info=device_info) | 
 |  | 
 |  | 
 | def filter_benchmarks_for_category(benchmark_category_dir: str, | 
 |                                    cpu_target_arch: str, | 
 |                                    gpu_target_arch: str, | 
 |                                    driver_filter: Optional[str], | 
 |                                    verbose: bool = False) -> Sequence[str]: | 
 |   """Filters benchmarks in a specific category for the given device. | 
 |  | 
 |   Args: | 
 |     benchmark_category_dir: the directory to a specific benchmark category. | 
 |     cpu_target_arch: CPU target architecture. | 
 |     gpu_target_arch: GPU target architecture. | 
 |     driver_filter: only run benchmarks for the given driver if not None. | 
 |     verbose: whether to print additional debug info. | 
 |  | 
 |   Returns: | 
 |     A list containing all matched benchmark cases' directories. | 
 |   """ | 
 |   matched_benchmarks = [] | 
 |  | 
 |   # Go over all benchmarks in the model directory to find those matching the | 
 |   # current Android device's CPU/GPU architecture. | 
 |   for root, dirs, _ in os.walk(benchmark_category_dir): | 
 |     # Take the immediate directory name and try to see if it contains compiled | 
 |     # models and flagfiles. This relies on the following directory naming | 
 |     # convention: | 
 |     #   <iree-driver>__<target-architecture>__<benchmark_mode> | 
 |     root_immediate_dir = os.path.basename(root) | 
 |     segments = root_immediate_dir.split("__") | 
 |     if len(segments) != 3 or not segments[0].startswith("iree-"): | 
 |       continue | 
 |  | 
 |     iree_driver, target_arch, bench_mode = segments | 
 |     iree_driver = iree_driver[len("iree-"):].lower() | 
 |     target_arch = target_arch.lower() | 
 |  | 
 |     # We can choose this benchmark if it matches the driver and CPU/GPU | 
 |     # architecture. | 
 |     matched_driver = (driver_filter is None or | 
 |                       iree_driver == driver_filter.lower()) | 
 |     matched_arch = (target_arch == cpu_target_arch or | 
 |                     target_arch == gpu_target_arch) | 
 |     should_choose = matched_driver and matched_arch | 
 |     if should_choose: | 
 |       matched_benchmarks.append(root) | 
 |  | 
 |     if verbose: | 
 |       print(f"dir: {root}") | 
 |       print(f"  iree_driver: {iree_driver}") | 
 |       print(f"  target_arch: {target_arch}") | 
 |       print(f"  bench_mode: {bench_mode}") | 
 |       print(f"  chosen: {should_choose}") | 
 |  | 
 |   return matched_benchmarks | 
 |  | 
 |  | 
 | def run_benchmarks_for_category( | 
 |     device_info: AndroidDeviceInfo, | 
 |     benchmark_category_dir: str, | 
 |     benchmark_case_dirs: Sequence[str], | 
 |     tmp_dir: str, | 
 |     normal_benchmark_tool: str, | 
 |     traced_benchmark_tool: Optional[str] = None, | 
 |     trace_capture_tool: Optional[str] = None, | 
 |     skip_benchmarks: Optional[Set[str]] = None, | 
 |     skip_captures: Optional[Set[str]] = None, | 
 |     do_capture: bool = False, | 
 |     keep_going: bool = False, | 
 |     verbose: bool = False, | 
 | ) -> Tuple[Sequence[Tuple[Optional[str], Optional[str]]], Sequence[Exception]]: | 
 |   """Runs all benchmarks on the Android device and reports results and captures. | 
 |  | 
 |   Args: | 
 |     device_info: an AndroidDeviceInfo object. | 
 |     benchmark_category_dir: the directory to a specific benchmark category. | 
 |     benchmark_case_dirs: a list of benchmark case directories. | 
 |     tmp_dir: path to temporary directory to which intermediate outputs should be | 
 |       stored. Separate "benchmark-results" and "captures" subdirectories will be | 
 |       created as necessary. | 
 |     normal_benchmark_tool: the path to the normal benchmark tool. | 
 |     traced_benchmark_tool: the path to the tracing-enabled benchmark tool. | 
 |     trace_capture_tool: the path to the tool for collecting captured traces. | 
 |     skip_benchmarks: names of benchmarks that should be skipped. Note that | 
 |       captures will still be run for these benchmarks if do_capture is true and | 
 |       they are not also in skip_captures. | 
 |     skip_captures: names of benchmark captures that should be skipped. | 
 |     do_capture: whether captures should be collected. | 
 |     keep_going: whether to proceed if an individual run fails. Exceptions will | 
 |       logged and returned. | 
 |     verbose: whether to print additional debug information. | 
 |  | 
 |   Returns: | 
 |     A tuple with a list containing (benchmark-filename, capture-filename) tuples | 
 |     and a list containing raised exceptions (only if keep_going is true) | 
 |   """ | 
 |   # Push the benchmark vmfb and tool files to the Android device first. | 
 |   adb_push_to_tmp_dir(os.path.join(benchmark_category_dir, VMFB_REL_PATH), | 
 |                       relative_dir=os.path.basename(benchmark_category_dir), | 
 |                       verbose=verbose) | 
 |   normal_benchmark_tool_path = adb_push_to_tmp_dir(normal_benchmark_tool, | 
 |                                                    relative_dir="normal-tools", | 
 |                                                    verbose=verbose) | 
 |   # Create directories on the host to store results from each benchmark run. | 
 |   benchmark_results_dir = os.path.join(tmp_dir, "benchmark-results") | 
 |   os.makedirs(benchmark_results_dir, exist_ok=True) | 
 |  | 
 |   # And the same for captures, if we are collecting them. | 
 |   captures_dir = os.path.join(tmp_dir, "captures") | 
 |   if do_capture: | 
 |     os.makedirs(captures_dir, exist_ok=True) | 
 |     traced_benchmark_tool_path = adb_push_to_tmp_dir( | 
 |         traced_benchmark_tool, relative_dir="traced-tools", verbose=verbose) | 
 |  | 
 |   results = [] | 
 |   errors = [] | 
 |   skip_benchmarks = skip_benchmarks if skip_benchmarks else set() | 
 |   skip_captures = skip_captures if skip_captures else set() | 
 |  | 
 |   # Push all model artifacts to the device and run them. | 
 |   root_benchmark_dir = os.path.dirname(benchmark_category_dir) | 
 |   for benchmark_case_dir in benchmark_case_dirs: | 
 |     benchmark_info = compose_benchmark_info_object(device_info, | 
 |                                                    benchmark_category_dir, | 
 |                                                    benchmark_case_dir) | 
 |     benchmark_key = str(benchmark_info) | 
 |     # If we're not running the benchmark or the capture, just skip ahead. No | 
 |     # need to push files. | 
 |     if benchmark_key in skip_benchmarks and (not do_capture or | 
 |                                              benchmark_key in skip_captures): | 
 |       continue | 
 |     print(f"--> benchmark: {benchmark_info} <--") | 
 |     # Now try to actually run benchmarks and collect captures. If keep_going is | 
 |     # True then errors in the underlying commands will be logged and returned. | 
 |     try: | 
 |       android_relative_dir = os.path.relpath(benchmark_case_dir, | 
 |                                              root_benchmark_dir) | 
 |       adb_push_to_tmp_dir(os.path.join(benchmark_case_dir, MODEL_FLAGFILE_NAME), | 
 |                           android_relative_dir, | 
 |                           verbose=verbose) | 
 |  | 
 |       benchmark_result_filename = None | 
 |       if benchmark_key not in skip_benchmarks: | 
 |         repetitions = get_benchmark_repetition_count(benchmark_info.runner) | 
 |         benchmark_results_basename = f"{benchmark_key}.json" | 
 |         cmd = [ | 
 |             "taskset", | 
 |             benchmark_info.deduce_taskset(), | 
 |             normal_benchmark_tool_path, | 
 |             f"--flagfile={MODEL_FLAGFILE_NAME}", | 
 |             f"--benchmark_repetitions={repetitions}", | 
 |             "--benchmark_format=json", | 
 |             "--benchmark_out_format=json", | 
 |             f"--benchmark_out='{benchmark_results_basename}'", | 
 |         ] | 
 |         result_json = adb_execute_and_get_output(cmd, | 
 |                                                  android_relative_dir, | 
 |                                                  verbose=verbose) | 
 |  | 
 |         # Pull the result file back onto the host and set the filename for later | 
 |         # return. | 
 |         benchmark_result_filename = os.path.join(benchmark_results_dir, | 
 |                                                  benchmark_results_basename) | 
 |         pull_cmd = [ | 
 |             "adb", "pull", | 
 |             os.path.join(ANDROID_TMP_DIR, android_relative_dir, | 
 |                          benchmark_results_basename), benchmark_result_filename | 
 |         ] | 
 |         execute_cmd_and_get_output(pull_cmd, verbose=verbose) | 
 |  | 
 |         if verbose: | 
 |           print(result_json) | 
 |  | 
 |       capture_filename = None | 
 |       if do_capture and benchmark_key not in skip_captures: | 
 |         run_cmd = [ | 
 |             "TRACY_NO_EXIT=1", "taskset", | 
 |             benchmark_info.deduce_taskset(), traced_benchmark_tool_path, | 
 |             f"--flagfile={MODEL_FLAGFILE_NAME}" | 
 |         ] | 
 |  | 
 |         # Just launch the traced benchmark tool with TRACY_NO_EXIT=1 without | 
 |         # waiting for the adb command to complete as that won't happen. | 
 |         process = adb_start_cmd(run_cmd, android_relative_dir, verbose=verbose) | 
 |         # But we do need to wait for its start; otherwise will see connection | 
 |         # failure when opening the catpure tool. Here we cannot just sleep a | 
 |         # certain amount of seconds---Pixel 4 seems to have an issue that will | 
 |         # make the trace collection step get stuck. Instead wait for the | 
 |         # benchmark result to be available. | 
 |         while True: | 
 |           line = process.stdout.readline()  # pytype: disable=attribute-error | 
 |           if line == "" and process.poll() is not None:  # Process completed | 
 |             raise ValueError("Cannot find benchmark result line in the log!") | 
 |           if verbose: | 
 |             print(line.strip()) | 
 |           # Result available | 
 |           if re.match(r"^BM_.+/real_time", line) is not None: | 
 |             break | 
 |  | 
 |         # Now it's okay to collect the trace via the capture tool. This will | 
 |         # send the signal to let the previously waiting benchmark tool to | 
 |         # complete. | 
 |         capture_filename = os.path.join(captures_dir, f"{benchmark_key}.tracy") | 
 |         capture_cmd = [trace_capture_tool, "-f", "-o", capture_filename] | 
 |         capture_log = execute_cmd_and_get_output(capture_cmd, verbose=verbose) | 
 |         if verbose: | 
 |           print(capture_log) | 
 |  | 
 |       print("...benchmark completed") | 
 |  | 
 |       results.append((benchmark_result_filename, capture_filename)) | 
 |       time.sleep(1)  # Some grace time. | 
 |  | 
 |     except subprocess.CalledProcessError as e: | 
 |       if keep_going: | 
 |         print(f"Processing of benchmark failed with: {e}") | 
 |         errors.append(e) | 
 |         continue | 
 |       raise e | 
 |  | 
 |   return (results, errors) | 
 |  | 
 |  | 
 | def filter_and_run_benchmarks( | 
 |     device_info: AndroidDeviceInfo, | 
 |     root_build_dir: str, | 
 |     driver_filter: Optional[str], | 
 |     tmp_dir: str, | 
 |     normal_benchmark_tool: str, | 
 |     traced_benchmark_tool: Optional[str], | 
 |     trace_capture_tool: Optional[str], | 
 |     skip_benchmarks: Optional[Set[str]], | 
 |     skip_captures: Optional[Set[str]], | 
 |     do_capture: bool = False, | 
 |     keep_going: bool = False, | 
 |     verbose: bool = False) -> Tuple[List[str], List[str], List[Exception]]: | 
 |   """Filters and runs benchmarks in all categories for the given device. | 
 |  | 
 |   Args: | 
 |     device_info: an AndroidDeviceInfo object. | 
 |     root_build_dir: the root build directory containing the built benchmark | 
 |       suites. | 
 |     driver_filter: filter benchmarks to those with the given driver (or all if | 
 |       this is None). | 
 |     tmp_dir: path to temporary directory to which intermediate outputs should be | 
 |       stored. Separate "benchmark-results" and "captures" subdirectories will be | 
 |       created as necessary. | 
 |     normal_benchmark_tool: the path to the normal benchmark tool. | 
 |     traced_benchmark_tool: the path to the tracing-enabled benchmark tool. | 
 |     trace_capture_tool: the path to the tool for collecting captured traces. | 
 |     skip_benchmarks: names of benchmarks that should be skipped. Note that | 
 |       captures will still be run for these benchmarks if do_capture is true and | 
 |       they are not also in skip_captures. | 
 |     skip_captures: names of benchmark captures that should be skipped. | 
 |     do_capture: whether captures should be collected. | 
 |     keep_going: whether to proceed if an individual run fails. Exceptions will | 
 |       logged and returned. | 
 |     verbose: whether to print additional debug information. | 
 |  | 
 |   Returns: | 
 |     Lists of benchmark file paths, capture file paths, and exceptions raise | 
 |     (only if keep_going is True). | 
 |   """ | 
 |   cpu_target_arch = CPU_ABI_TO_TARGET_ARCH_MAP[device_info.cpu_abi.lower()] | 
 |   gpu_target_arch = GPU_NAME_TO_TARGET_ARCH_MAP[device_info.gpu_name.lower()] | 
 |  | 
 |   root_benchmark_dir = os.path.join(root_build_dir, BENCHMARK_SUITE_REL_PATH) | 
 |  | 
 |   benchmark_files = [] | 
 |   captures = [] | 
 |   errors = [] | 
 |  | 
 |   skip_benchmarks = skip_benchmarks if skip_benchmarks else set() | 
 |  | 
 |   for directory in sorted(os.listdir(root_benchmark_dir)): | 
 |     benchmark_category_dir = os.path.join(root_benchmark_dir, directory) | 
 |     matched_benchmarks = filter_benchmarks_for_category( | 
 |         benchmark_category_dir=benchmark_category_dir, | 
 |         cpu_target_arch=cpu_target_arch, | 
 |         gpu_target_arch=gpu_target_arch, | 
 |         driver_filter=driver_filter, | 
 |         verbose=verbose) | 
 |     run_results, run_errors = run_benchmarks_for_category( | 
 |         device_info=device_info, | 
 |         benchmark_category_dir=benchmark_category_dir, | 
 |         benchmark_case_dirs=matched_benchmarks, | 
 |         tmp_dir=tmp_dir, | 
 |         normal_benchmark_tool=normal_benchmark_tool, | 
 |         traced_benchmark_tool=traced_benchmark_tool, | 
 |         skip_benchmarks=skip_benchmarks, | 
 |         trace_capture_tool=trace_capture_tool, | 
 |         do_capture=do_capture, | 
 |         keep_going=keep_going, | 
 |         verbose=verbose) | 
 |     errors.extend(run_errors) | 
 |     for benchmark_filename, capture_filename in run_results: | 
 |       if benchmark_filename is not None: | 
 |         benchmark_files.append(benchmark_filename) | 
 |       if capture_filename is not None: | 
 |         captures.append(capture_filename) | 
 |  | 
 |   return (benchmark_files, captures, errors) | 
 |  | 
 |  | 
 | def set_cpu_frequency_scaling_governor(governor: str): | 
 |   git_root = execute_cmd_and_get_output(["git", "rev-parse", "--show-toplevel"]) | 
 |   cpu_script = os.path.join( | 
 |       git_root, "build_tools/benchmarks/set_android_scaling_governor.sh") | 
 |   android_path = adb_push_to_tmp_dir(cpu_script) | 
 |   adb_execute(["su", "root", android_path, governor]) | 
 |  | 
 |  | 
 | def set_gpu_frequency_scaling_policy(policy: str): | 
 |   git_root = execute_cmd_and_get_output(["git", "rev-parse", "--show-toplevel"]) | 
 |   device_model = get_android_device_model() | 
 |   if device_model == "Pixel-6" or device_model == "Pixel-6-Pro": | 
 |     gpu_script = os.path.join( | 
 |         git_root, "build_tools/benchmarks/set_pixel6_gpu_scaling_policy.sh") | 
 |   else: | 
 |     raise RuntimeError( | 
 |         f"Unsupported device '{device_model}' for setting GPU scaling policy") | 
 |   android_path = adb_push_to_tmp_dir(gpu_script) | 
 |   adb_execute(["su", "root", android_path, policy]) | 
 |  | 
 |  | 
 | def parse_arguments(): | 
 |   """Parses command-line options.""" | 
 |  | 
 |   def check_dir_path(path): | 
 |     if os.path.isdir(path): | 
 |       return path | 
 |     else: | 
 |       raise argparse.ArgumentTypeError(path) | 
 |  | 
 |   def check_exe_path(path): | 
 |     if os.access(path, os.X_OK): | 
 |       return path | 
 |     else: | 
 |       raise argparse.ArgumentTypeError(f"'{path}' is not an executable") | 
 |  | 
 |   parser = argparse.ArgumentParser() | 
 |   parser.add_argument( | 
 |       "build_dir", | 
 |       metavar="<build-dir>", | 
 |       type=check_dir_path, | 
 |       help="Path to the build directory containing benchmark suites") | 
 |   parser.add_argument("--normal_benchmark_tool", | 
 |                       "--normal-benchmark-tool", | 
 |                       type=check_exe_path, | 
 |                       required=True, | 
 |                       help="Path to the normal iree-benchmark-module tool") | 
 |   parser.add_argument( | 
 |       "--traced_benchmark_tool", | 
 |       "--traced-benchmark-tool", | 
 |       type=check_exe_path, | 
 |       default=None, | 
 |       help="Path to the tracing-enabled iree-benchmark-module tool") | 
 |   parser.add_argument("--trace_capture_tool", | 
 |                       "--trace-capture-tool", | 
 |                       type=check_exe_path, | 
 |                       default=None, | 
 |                       help="Path to the tool for collecting captured traces") | 
 |   parser.add_argument( | 
 |       "--driver", | 
 |       type=str, | 
 |       default=None, | 
 |       help="Only run benchmarks for a specific driver, e.g., 'vulkan'") | 
 |   parser.add_argument("--output", | 
 |                       "-o", | 
 |                       default=None, | 
 |                       help="Path to the ouput file") | 
 |   parser.add_argument("--capture_tarball", | 
 |                       "--capture-tarball", | 
 |                       default=None, | 
 |                       help="Path to the tarball for captures") | 
 |   parser.add_argument("--no-clean", | 
 |                       action="store_true", | 
 |                       help="Do not clean up the temporary directory used for " | 
 |                       "benchmarking on the Android device") | 
 |   parser.add_argument("--verbose", | 
 |                       action="store_true", | 
 |                       help="Print internal information during execution") | 
 |   parser.add_argument( | 
 |       "--pin-cpu-freq", | 
 |       "--pin_cpu_freq", | 
 |       action="store_true", | 
 |       help="Pin CPU frequency for all cores to the maximum. Requires root") | 
 |   parser.add_argument("--pin-gpu-freq", | 
 |                       "--pin_gpu_freq", | 
 |                       action="store_true", | 
 |                       help="Pin GPU frequency to the maximum. Requires root") | 
 |   parser.add_argument( | 
 |       "--keep_going", | 
 |       "--keep-going", | 
 |       action="store_true", | 
 |       help="Continue running after a failed benchmark. The overall exit status" | 
 |       " will still indicate failure and all errors will be reported at the end." | 
 |   ) | 
 |   parser.add_argument( | 
 |       "--tmp_dir", | 
 |       "--tmp-dir", | 
 |       "--tmpdir", | 
 |       default="/tmp/iree-benchmarks", | 
 |       help="Base directory in which to store temporary files. A subdirectory" | 
 |       " with a name matching the git commit hash will be created.") | 
 |   parser.add_argument( | 
 |       "--continue_from_directory", | 
 |       "--continue-from-directory", | 
 |       default=None, | 
 |       help="Path to directory with previous benchmark temporary files. This" | 
 |       " should be for the specific commit (not the general tmp-dir). Previous" | 
 |       " benchmark and capture results from here will not be rerun and will be" | 
 |       " combined with the new runs.") | 
 |  | 
 |   args = parser.parse_args() | 
 |  | 
 |   return args | 
 |  | 
 |  | 
 | def main(args): | 
 |   device_info = AndroidDeviceInfo.from_adb() | 
 |   if args.verbose: | 
 |     print(device_info) | 
 |  | 
 |   if device_info.cpu_abi.lower() not in CPU_ABI_TO_TARGET_ARCH_MAP: | 
 |     raise ValueError(f"Unrecognized CPU ABI: '{device_info.cpu_abi}'; " | 
 |                      "need to update the map") | 
 |   if device_info.gpu_name.lower() not in GPU_NAME_TO_TARGET_ARCH_MAP: | 
 |     raise ValueError(f"Unrecognized GPU name: '{device_info.gpu_name}'; " | 
 |                      "need to update the map") | 
 |  | 
 |   if args.pin_cpu_freq: | 
 |     set_cpu_frequency_scaling_governor("performance") | 
 |     atexit.register(set_cpu_frequency_scaling_governor, "schedutil") | 
 |   if args.pin_gpu_freq: | 
 |     set_gpu_frequency_scaling_policy("always_on") | 
 |     atexit.register(set_gpu_frequency_scaling_policy, "coarse_demand") | 
 |  | 
 |   previous_benchmarks = None | 
 |   previous_captures = None | 
 |  | 
 |   do_capture = (args.traced_benchmark_tool is not None and | 
 |                 args.trace_capture_tool is not None) | 
 |  | 
 |   # Collect names of previous benchmarks and captures that should be skipped and | 
 |   # merged into the results. | 
 |   if args.continue_from_directory is not None: | 
 |     previous_benchmarks_dir = os.path.join(args.continue_from_directory, | 
 |                                            "benchmark-results") | 
 |     if os.path.isdir(previous_benchmarks_dir): | 
 |       previous_benchmarks = set( | 
 |           os.path.splitext(os.path.basename(p))[0] | 
 |           for p in os.listdir(previous_benchmarks_dir)) | 
 |     if do_capture: | 
 |       previous_captures_dir = os.path.join(args.continue_from_directory, | 
 |                                            "captures") | 
 |       if os.path.isdir(previous_captures_dir): | 
 |         previous_captures = set( | 
 |             os.path.splitext(os.path.basename(p))[0] | 
 |             for p in os.listdir(previous_captures_dir)) | 
 |  | 
 |   # Clear the benchmark directory on the Android device first just in case | 
 |   # there are leftovers from manual or failed runs. | 
 |   execute_cmd_and_get_output(["adb", "shell", "rm", "-rf", ANDROID_TMP_DIR], | 
 |                              verbose=args.verbose) | 
 |  | 
 |   if not args.no_clean: | 
 |     # Clear the benchmark directory on the Android device. | 
 |     atexit.register(execute_cmd_and_get_output, | 
 |                     ["adb", "shell", "rm", "-rf", ANDROID_TMP_DIR], | 
 |                     verbose=args.verbose) | 
 |  | 
 |   # Tracy client and server communicate over port 8086 by default. If we want | 
 |   # to capture traces along the way, forward port via adb. | 
 |   if do_capture: | 
 |     execute_cmd_and_get_output(["adb", "forward", "tcp:8086", "tcp:8086"]) | 
 |     atexit.register(execute_cmd_and_get_output, | 
 |                     ["adb", "forward", "--remove", "tcp:8086"]) | 
 |  | 
 |     args.traced_benchmark_tool = os.path.realpath(args.traced_benchmark_tool) | 
 |     args.trace_capture_tool = os.path.realpath(args.trace_capture_tool) | 
 |  | 
 |   results = BenchmarkResults() | 
 |   commit = get_git_commit_hash("HEAD") | 
 |   results.set_commit(commit) | 
 |  | 
 |   args.tmp_dir = os.path.join(args.tmp_dir, commit) | 
 |   os.makedirs(args.tmp_dir, exist_ok=True) | 
 |  | 
 |   benchmarks, captures, errors = filter_and_run_benchmarks( | 
 |       device_info=device_info, | 
 |       root_build_dir=args.build_dir, | 
 |       driver_filter=args.driver, | 
 |       tmp_dir=args.tmp_dir, | 
 |       normal_benchmark_tool=os.path.realpath(args.normal_benchmark_tool), | 
 |       traced_benchmark_tool=args.traced_benchmark_tool, | 
 |       trace_capture_tool=args.trace_capture_tool, | 
 |       skip_benchmarks=previous_benchmarks, | 
 |       skip_captures=previous_captures, | 
 |       do_capture=do_capture, | 
 |       keep_going=args.keep_going, | 
 |       verbose=args.verbose) | 
 |  | 
 |   # Merge in previous benchmarks and captures. | 
 |   if previous_benchmarks: | 
 |     benchmarks.extend(f"{os.path.join(previous_benchmarks_dir, b)}.json" | 
 |                       for b in previous_benchmarks) | 
 |   if do_capture and previous_captures: | 
 |     captures.extend(f"{os.path.join(previous_captures_dir, c)}.tracy" | 
 |                     for c in previous_captures) | 
 |  | 
 |   for b in benchmarks: | 
 |     with open(b) as f: | 
 |       result_json_object = json.loads(f.read()) | 
 |     benchmark_info = BenchmarkInfo.from_device_info_and_name( | 
 |         device_info, | 
 |         os.path.splitext(os.path.basename(b))[0]) | 
 |     benchmark_run = BenchmarkRun(benchmark_info, result_json_object["context"], | 
 |                                  result_json_object["benchmarks"]) | 
 |     results.benchmarks.append(benchmark_run) | 
 |  | 
 |   if args.output is not None: | 
 |     with open(args.output, "w") as f: | 
 |       f.write(results.to_json_str()) | 
 |  | 
 |   if args.verbose: | 
 |     print(results.commit) | 
 |     print(results.benchmarks) | 
 |  | 
 |   if captures: | 
 |     # Put all captures in a tarball and remove the origial files. | 
 |     with tarfile.open(args.capture_tarball, "w:gz") as tar: | 
 |       for capture_filename in captures: | 
 |         tar.add(capture_filename) | 
 |  | 
 |   # Delete all the temp files if everything completed successfully. | 
 |   if not args.no_clean and not errors: | 
 |     shutil.rmtree(args.tmp_dir) | 
 |  | 
 |   if errors: | 
 |     print("Benchmarking completed with errors", file=sys.stderr) | 
 |     raise RuntimeError(errors) | 
 |  | 
 |  | 
 | if __name__ == "__main__": | 
 |   main(parse_arguments()) |