Add support to capture Tracy traces when benchmarking (#6971)

This commit adds logic to perform Tracy captures after running
each benchmark. It allows us to have more details regarding
the benchmark to investigate performance regressions, etc.

Also decreased the benchmark repetition count for VMVX.
VMVX is very unoptimized for now and can take a long time to run,
e.g., one inference of MobileNetV2 takes almost 1min. Decrease
the repetition for it until it's reasonably fast.
diff --git a/build_tools/android/run_benchmarks.py b/build_tools/android/run_benchmarks.py
index 306312b..6a73390 100755
--- a/build_tools/android/run_benchmarks.py
+++ b/build_tools/android/run_benchmarks.py
@@ -7,12 +7,16 @@
 """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 on it.
+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. It also expects the benchmark artifacts
-are generated by building the `iree-benchmark-suites` target in the following
-directory structure:
+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)
@@ -29,8 +33,17 @@
         └── compiled-<sha1>.vmfb
 
 Example usages:
+
+  # Without trace generation
   python3 run_benchmarks.py \
-    --benchmark_tool=/path/to/android/target/iree-benchmark_module \
+    --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
 """
 
@@ -39,8 +52,10 @@
 import os
 import re
 import subprocess
+import tarfile
+import time
 
-from typing import Any, Dict, Sequence, Tuple
+from typing import Any, Dict, Optional, Sequence, Tuple
 
 from common.benchmark_description import (AndroidDeviceInfo, BenchmarkInfo,
                                           BenchmarkResults, get_output)
@@ -56,8 +71,6 @@
 # Root directory to perform benchmarks in on the Android device.
 ANDROID_TMP_DIR = "/data/local/tmp/iree-benchmarks"
 
-BENCHMARK_REPETITIONS = 10
-
 # A map from Android CPU ABI to IREE's benchmark target architecture.
 CPU_ABI_TO_TARGET_ARCH_MAP = {
     "arm64-v8a": "cpu-arm64-v8a",
@@ -73,6 +86,15 @@
 }
 
 
+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 get_output(['git', 'rev-parse', commit],
                     cwd=os.path.dirname(os.path.realpath(__file__)))
@@ -100,7 +122,8 @@
 def adb_execute_in_dir(cmd_args: Sequence[str],
                        relative_dir: str,
                        verbose: bool = False) -> str:
-  """Executes command with adb shell in a directory.
+  """Executes command with adb shell in a directory, waits for completion,
+  and returns the output.
 
   Args:
   - cmd_args: a list containing the command to execute and its parameters
@@ -118,6 +141,31 @@
   return get_output(cmd, verbose=verbose)
 
 
+def adb_start_in_dir(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:
@@ -213,27 +261,35 @@
     device_info: AndroidDeviceInfo,
     benchmark_category_dir: str,
     benchmark_case_dirs: Sequence[str],
-    benchmark_tool: str,
+    normal_benchmark_tool: str,
+    traced_benchmark_tool: Optional[str],
+    trace_capture_tool: Optional[str],
     verbose: bool = False
-) -> Sequence[Tuple[BenchmarkInfo, Dict[str, Any], Dict[str, Any]]]:
-  """Runs all benchmarks on the Android device and reports results.
+) -> Sequence[Tuple[BenchmarkInfo, Dict[str, Any], Dict[str, Any],
+                    Optional[str]]]:
+  """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.
-  - benchmark_tool: the path to the benchmark tool.
+  - 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.
 
   Returns:
-  - A list containing (BenchmarkInfo, context, results) tuples.
+  - A list containing (BenchmarkInfo, context, results, capture-filename) tuples.
   """
   # 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)
-  android_tool_path = adb_push_to_tmp_dir(benchmark_tool,
-                                          relative_dir="tools",
-                                          verbose=verbose)
+  normal_benchmark_tool_path = adb_push_to_tmp_dir(normal_benchmark_tool,
+                                                   relative_dir="normal-tools",
+                                                   verbose=verbose)
+  if traced_benchmark_tool is not None:
+    traced_benchmark_tool_path = adb_push_to_tmp_dir(
+        traced_benchmark_tool, relative_dir="traced-tools", verbose=verbose)
 
   results = []
 
@@ -244,18 +300,20 @@
                                                    benchmark_category_dir,
                                                    benchmark_case_dir)
     print(f"--> benchmark: {benchmark_info} <--")
+
     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)
 
+    repetitions = get_benchmark_repetition_count(benchmark_info.runner)
     cmd = [
         "taskset",
         benchmark_info.deduce_taskset(),
-        android_tool_path,
+        normal_benchmark_tool_path,
         f"--flagfile={MODEL_FLAGFILE_NAME}",
-        f"--benchmark_repetitions={BENCHMARK_REPETITIONS}",
+        f"--benchmark_repetitions={repetitions}",
         "--benchmark_format=json",
     ]
     resultjson = adb_execute_in_dir(cmd, android_relative_dir, verbose=verbose)
@@ -267,22 +325,64 @@
       if previous_result[0] == benchmark_info:
         raise ValueError(f"Duplicated benchmark: {benchmark_info}")
 
-    results.append(
-        (benchmark_info, resultjson["context"], resultjson["benchmarks"]))
+    # If we have a tracing-enabled benchmark tool and the capture collecting
+    # tool, catpure a trace of the benchmark run.
+    catpure_filename = None
+    if traced_benchmark_tool is not None and trace_capture_tool is not None:
+      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_in_dir(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 next 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())
+        if re.match(r"^BM_.+/real_time", line) is not None:  # Result available
+          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 = re.sub(r" +", "-", str(benchmark_info)) + ".tracy"
+      capture_cmd = [trace_capture_tool, "-f", "-o", capture_filename]
+      capture_log = get_output(capture_cmd, verbose=verbose)
+      if verbose:
+        print(capture_log)
+
+      time.sleep(1)  # Some grace time.
+
+    results.append((benchmark_info, resultjson["context"],
+                    resultjson["benchmarks"], capture_filename))
 
   return results
 
 
-def filter_and_run_benchmarks(device_info: AndroidDeviceInfo,
-                              root_build_dir: str,
-                              benchmark_tool: str,
-                              verbose: bool = False) -> BenchmarkResults:
+def filter_and_run_benchmarks(
+    device_info: AndroidDeviceInfo,
+    root_build_dir: str,
+    normal_benchmark_tool: str,
+    traced_benchmark_tool: Optional[str],
+    trace_capture_tool: Optional[str],
+    verbose: bool = False) -> Tuple[BenchmarkResults, Sequence[str]]:
   """Filters and runs benchmarks in all categories for the given device.
 
   Args:
   - device_info: an AndroidDeviceInfo object.
   - root_build_dir: the root build directory.
-  - benchmark_tool: the path to the benchmark tool.
+  - 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.
   """
   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()]
@@ -290,6 +390,7 @@
   root_benchmark_dir = os.path.join(root_build_dir, BENCHMARK_SUITE_REL_PATH)
 
   results = BenchmarkResults()
+  captures = []
 
   for directory in os.listdir(root_benchmark_dir):
     benchmark_category_dir = os.path.join(root_benchmark_dir, directory)
@@ -302,15 +403,19 @@
         device_info=device_info,
         benchmark_category_dir=benchmark_category_dir,
         benchmark_case_dirs=matched_benchmarks,
-        benchmark_tool=benchmark_tool,
+        normal_benchmark_tool=normal_benchmark_tool,
+        traced_benchmark_tool=traced_benchmark_tool,
+        trace_capture_tool=trace_capture_tool,
         verbose=verbose)
-    for info, context, runs in run_results:
+    for info, context, runs, capture_filename in run_results:
       results.append_one_benchmark(info, context, runs)
+      if capture_filename is not None:
+        captures.append(capture_filename)
 
   # Attach commit information.
   results.set_commit(get_git_commit_hash("HEAD"))
 
-  return results
+  return (results, captures)
 
 
 def parse_arguments():
@@ -334,15 +439,26 @@
       metavar="<build-dir>",
       type=check_dir_path,
       help="Path to the build directory containing benchmark suites")
-  parser.add_argument("--benchmark_tool",
+  parser.add_argument("--normal_benchmark_tool",
+                      type=check_exe_path,
+                      required=True,
+                      help="Path to the normal iree-benchmark-module tool")
+  parser.add_argument(
+      "--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",
                       type=check_exe_path,
                       default=None,
-                      help="Path to the iree-benchmark-module tool (default to "
-                      "iree/tools/iree-benchmark-module under <build-dir>)")
+                      help="Path to the tool for collecting captured traces")
   parser.add_argument("-o",
                       dest="output",
                       default=None,
                       help="Path to the ouput file")
+  parser.add_argument("--capture_tarball",
+                      default=None,
+                      help="Path to the tarball for captures")
   parser.add_argument(
       "--no-clean",
       action="store_true",
@@ -355,10 +471,6 @@
 
   args = parser.parse_args()
 
-  if args.benchmark_tool is None:
-    args.benchmark_tool = os.path.join(args.build_dir, "iree", "tools",
-                                       "iree-benchmark-module")
-
   return args
 
 
@@ -379,8 +491,16 @@
   get_output(["adb", "shell", "rm", "-rf", ANDROID_TMP_DIR],
              verbose=args.verbose)
 
-  results = filter_and_run_benchmarks(device_info, args.build_dir,
-                                      args.benchmark_tool, 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 (args.traced_benchmark_tool is not None) and \
+          (args.trace_capture_tool is not None):
+    get_output(["adb", "forward", "tcp:8086", "tcp:8086"])
+
+  results, captures = filter_and_run_benchmarks(
+      device_info, args.build_dir, os.path.realpath(args.normal_benchmark_tool),
+      os.path.realpath(args.traced_benchmark_tool),
+      os.path.realpath(args.trace_capture_tool), args.verbose)
 
   if args.output is not None:
     with open(args.output, "w") as f:
@@ -389,6 +509,17 @@
     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)
+    for capture_filename in captures:
+      os.remove(capture_filename)
+
+    # Disable port forwarding.
+    get_output(["adb", "forward", "--remove", "tcp:8086"])
+
   if not args.no_clean:
     # Clear the benchmark directory on the Android device.
     get_output(["adb", "shell", "rm", "-rf", ANDROID_TMP_DIR],
diff --git a/build_tools/buildkite/cmake/android/arm64-v8a/benchmark2.yml b/build_tools/buildkite/cmake/android/arm64-v8a/benchmark2.yml
index 5298771..ed85be1 100644
--- a/build_tools/buildkite/cmake/android/arm64-v8a/benchmark2.yml
+++ b/build_tools/buildkite/cmake/android/arm64-v8a/benchmark2.yml
@@ -9,59 +9,67 @@
 steps:
   - label: "Build"
     commands:
-      - "docker run --user=$(id -u):$(id -g) --volume=\\$PWD:\\$IREE_DOCKER_WORKDIR --workdir=\\$IREE_DOCKER_WORKDIR --rm gcr.io/iree-oss/cmake-android@sha256:7d780787608474301e74e1b5cc2a1bfd1304a79ed9e0774c7ed422c0e4a38625 build_tools/kokoro/gcp_ubuntu/cmake/android/build.sh arm64-v8a"
-      - "tar --exclude='*.tar.gz' --exclude='*.tgz' --exclude='*.mlir' -czvf benchmark-suites.tgz build-host/benchmark_suites"
-      - "tar -czvf iree-android-tools.tgz build-android/iree/tools/iree-*-module"
+      - "docker run --user=$(id -u):$(id -g) --volume=\\$PWD:\\$IREE_DOCKER_WORKDIR --workdir=\\$IREE_DOCKER_WORKDIR --rm gcr.io/iree-oss/cmake-android@sha256:7d780787608474301e74e1b5cc2a1bfd1304a79ed9e0774c7ed422c0e4a38625 build_tools/cmake/build_android_benchmark.sh"
+      - "tar --exclude='*.tar.gz' --exclude='*.tgz' --exclude='*.mlir' -czvf benchmark-suites-${BUILDKITE_BUILD_NUMBER}.tgz build-host/benchmark_suites"
+      - "tar -czvf iree-android-tools-${BUILDKITE_BUILD_NUMBER}.tgz build-android/iree/tools/iree-benchmark-module build-android-trace/iree/tools/iree-benchmark-module"
     if: "build.pull_request.id == null || (build.pull_request.labels includes 'buildkite:benchmark')"
     agents:
       - "queue=build"
     env:
       IREE_DOCKER_WORKDIR: "/usr/src/github/iree"
     artifact_paths:
-      - "benchmark-suites.tgz"
-      - "iree-android-tools.tgz"
+      - "benchmark-suites-${BUILDKITE_BUILD_NUMBER}.tgz"
+      - "iree-android-tools-${BUILDKITE_BUILD_NUMBER}.tgz"
 
   - wait
 
   - label: "Benchmark on Pixel 4 (snapdragon-855, adreno-640)"
     commands:
-      - "buildkite-agent artifact download --step Build benchmark-suites.tgz ./"
-      - "buildkite-agent artifact download --step Build iree-android-tools.tgz ./"
-      - "tar -xzvf benchmark-suites.tgz"
-      - "tar -xzvf iree-android-tools.tgz"
-      - "python3 build_tools/android/run_benchmarks.py --benchmark_tool=build-android/iree/tools/iree-benchmark-module -o benchmark-results-pixel-4.json --verbose build-host/"
-      - "rm -rf build-host/ build-android/"
+      - "git clean -f"
+      - "buildkite-agent artifact download --step Build benchmark-suites-${BUILDKITE_BUILD_NUMBER}.tgz ./"
+      - "buildkite-agent artifact download --step Build iree-android-tools-${BUILDKITE_BUILD_NUMBER}.tgz ./"
+      - "wget https://storage.googleapis.com/iree-shared-files/tracy-capture.tgz"
+      - "tar -xzvf benchmark-suites-${BUILDKITE_BUILD_NUMBER}.tgz"
+      - "tar -xzvf iree-android-tools-${BUILDKITE_BUILD_NUMBER}.tgz"
+      - "tar -xzvf tracy-capture.tgz"
+      - "python3 build_tools/android/run_benchmarks.py --normal_benchmark_tool=build-android/iree/tools/iree-benchmark-module --traced_benchmark_tool=build-android-trace/iree/tools/iree-benchmark-module --trace_capture_tool=tracy-capture -o benchmark-results-pixel-4-${BUILDKITE_BUILD_NUMBER}.json --capture_tarball=trace-captures-pixel-4-${BUILDKITE_BUILD_NUMBER}.tgz --verbose build-host/"
     if: "build.pull_request.id == null || (build.pull_request.labels includes 'buildkite:benchmark')"
     agents:
       - "android-soc=snapdragon-855"
       - "android-version=11"
       - "queue=benchmark-android"
-    artifact_paths: "benchmark-results-pixel-4.json"
+    artifact_paths:
+      - "benchmark-results-pixel-4-${BUILDKITE_BUILD_NUMBER}.json"
+      - "trace-captures-pixel-4-${BUILDKITE_BUILD_NUMBER}.tgz"
     timeout_in_minutes: "40"
 
   - label: "Benchmark on Galaxy S20 (exynos-990, mali-g77)"
     commands:
-      - "buildkite-agent artifact download --step Build benchmark-suites.tgz ./"
-      - "buildkite-agent artifact download --step Build iree-android-tools.tgz ./"
-      - "tar -xzvf benchmark-suites.tgz"
-      - "tar -xzvf iree-android-tools.tgz"
-      - "python3 build_tools/android/run_benchmarks.py --benchmark_tool=build-android/iree/tools/iree-benchmark-module -o benchmark-results-galaxy-s20.json --verbose build-host/"
-      - "rm -rf build-host/ build-android/"
+      - "git clean -f"
+      - "buildkite-agent artifact download --step Build benchmark-suites-${BUILDKITE_BUILD_NUMBER}.tgz ./"
+      - "buildkite-agent artifact download --step Build iree-android-tools-${BUILDKITE_BUILD_NUMBER}.tgz ./"
+      - "wget https://storage.googleapis.com/iree-shared-files/tracy-capture.tgz"
+      - "tar -xzvf benchmark-suites-${BUILDKITE_BUILD_NUMBER}.tgz"
+      - "tar -xzvf iree-android-tools-${BUILDKITE_BUILD_NUMBER}.tgz"
+      - "tar -xzvf tracy-capture.tgz"
+      - "python3 build_tools/android/run_benchmarks.py --normal_benchmark_tool=build-android/iree/tools/iree-benchmark-module --traced_benchmark_tool=build-android-trace/iree/tools/iree-benchmark-module --trace_capture_tool=tracy-capture -o benchmark-results-galaxy-s20-${BUILDKITE_BUILD_NUMBER}.json --capture_tarball=trace-captures-galaxy-s20-${BUILDKITE_BUILD_NUMBER}.tgz --verbose build-host/"
     if: "build.pull_request.id == null || (build.pull_request.labels includes 'buildkite:benchmark')"
     agents:
       - "android-soc=exynos-990"
       - "android-version=11"
       - "queue=benchmark-android"
-    artifact_paths: "benchmark-results-galaxy-s20.json"
+    artifact_paths:
+      - "benchmark-results-galaxy-s20-${BUILDKITE_BUILD_NUMBER}.json"
+      - "trace-captures-galaxy-s20-${BUILDKITE_BUILD_NUMBER}.tgz"
     timeout_in_minutes: "40"
 
   - wait
 
   - label: "Comment benchmark results on pull request"
     commands:
+      - "git clean -f"
       - "buildkite-agent artifact download benchmark-results-*.json ./"
       - "python3 build_tools/android/post_benchmarks_as_pr_comment.py --verbose --query-base benchmark-results-*.json"
-      - "rm benchmark-results-*.json"
     key: "post-on-pr"
     if: "build.pull_request.id != null && (build.pull_request.labels includes 'buildkite:benchmark')"
     agents:
@@ -69,9 +77,9 @@
 
   - label: "Push benchmark results to dashboard"
     commands:
+      - "git clean -f"
       - "buildkite-agent artifact download benchmark-results-*.json ./"
       - "python3 build_tools/android/upload_benchmarks_to_dashboard.py --verbose benchmark-results-*.json"
-      - "rm benchmark-results-*.json"
     key: "upload-to-dashboard"
     branches: "main"
     agents:
diff --git a/build_tools/cmake/build_android_benchmark.sh b/build_tools/cmake/build_android_benchmark.sh
new file mode 100755
index 0000000..f26287c
--- /dev/null
+++ b/build_tools/cmake/build_android_benchmark.sh
@@ -0,0 +1,111 @@
+#!/bin/bash
+# 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
+
+# Cross-compile the IREE project towards Android with CMake. Designed for CI,
+# but can be run manually. This uses previously cached build results and does
+# not clear build directories.
+#
+# Host binaries (e.g. compiler tools) will be built and installed in build-host/
+# Android binaries (e.g. tests) will be built in build-android/.
+
+set -x
+set -e
+
+# Print the UTC time when set -x is on.
+export PS4='[$(date -u "+%T %Z")] '
+
+# Check these exist and print the versions for later debugging.
+export CMAKE_BIN="$(which cmake)"
+"${CMAKE_BIN?}" --version
+"${CC?}" --version
+"${CXX?}" --version
+ninja --version
+python3 --version
+echo "Android NDK path: ${ANDROID_NDK?}"
+
+ROOT_DIR=$(git rev-parse --show-toplevel)
+cd ${ROOT_DIR?}
+
+echo "Initializing submodules"
+./scripts/git/submodule_versions.py init
+
+# --------------------------------------------------------------------------- #
+# Build for the host.
+
+cd ${ROOT_DIR?}
+
+if [ -d "build-host" ]
+then
+  echo "build-host directory already exists. Will use cached results there."
+else
+  echo "build-host directory does not already exist. Creating a new one."
+  mkdir build-host
+fi
+cd build-host
+
+# Configure, build, install.
+"${CMAKE_BIN?}" -G Ninja .. \
+  -DCMAKE_INSTALL_PREFIX=./install \
+  -DIREE_BUILD_COMPILER=ON \
+  -DIREE_BUILD_TESTS=OFF \
+  -DIREE_BUILD_BENCHMARKS=ON \
+  -DIREE_BUILD_SAMPLES=OFF
+"${CMAKE_BIN?}" --build . --target install
+# Also generate artifacts for benchmarking on Android.
+"${CMAKE_BIN?}" --build . --target iree-benchmark-suites
+# --------------------------------------------------------------------------- #
+
+# --------------------------------------------------------------------------- #
+# Build for the target (Android).
+
+cd ${ROOT_DIR?}
+
+if [ -d "build-android" ]
+then
+  echo "build-android directory already exists. Will use cached results there."
+else
+  echo "build-android directory does not already exist. Creating a new one."
+  mkdir build-android
+fi
+cd build-android
+
+# Configure towards 64-bit Android 10, then build.
+"${CMAKE_BIN?}" -G Ninja .. \
+  -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK?}/build/cmake/android.toolchain.cmake \
+  -DANDROID_ABI=arm64-v8a \
+  -DANDROID_PLATFORM=android-29 \
+  -DIREE_HOST_BINARY_ROOT=$PWD/../build-host/install \
+  -DIREE_BUILD_COMPILER=OFF \
+  -DIREE_BUILD_TESTS=ON \
+  -DIREE_BUILD_SAMPLES=OFF
+"${CMAKE_BIN?}" --build . --target iree-benchmark-module
+
+# --------------------------------------------------------------------------- #
+# Build for the target (Android) with tracing.
+
+cd ${ROOT_DIR?}
+
+if [ -d "build-android-trace" ]
+then
+  echo "build-android-trace directory already exists. Will use cached results there."
+else
+  echo "build-android-trace directory does not already exist. Creating a new one."
+  mkdir build-android-trace
+fi
+cd build-android-trace
+
+# Configure towards 64-bit Android 10, then build.
+"${CMAKE_BIN?}" -G Ninja .. \
+  -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK?}/build/cmake/android.toolchain.cmake \
+  -DANDROID_ABI=arm64-v8a \
+  -DANDROID_PLATFORM=android-29 \
+  -DIREE_HOST_BINARY_ROOT=$PWD/../build-host/install \
+  -DIREE_ENABLE_RUNTIME_TRACING=ON \
+  -DIREE_BUILD_COMPILER=OFF \
+  -DIREE_BUILD_TESTS=ON \
+  -DIREE_BUILD_SAMPLES=OFF
+"${CMAKE_BIN?}" --build . --target iree-benchmark-module