Generalize AndroidDeviceInfo to DeviceInfo. (#8615)

* Generalize AndroidDeviceInfo to DeviceInfo.
diff --git a/build_tools/benchmarks/common/android_device_utils.py b/build_tools/benchmarks/common/android_device_utils.py
new file mode 100644
index 0000000..2a697c1
--- /dev/null
+++ b/build_tools/benchmarks/common/android_device_utils.py
@@ -0,0 +1,66 @@
+#!/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
+"""Utils for accessing Android devices."""
+
+import json
+import re
+
+from typing import Sequence
+from .benchmark_definition import (execute_cmd_and_get_output, DeviceInfo,
+                                   PlatformType)
+
+
+def get_android_device_model(verbose: bool = False) -> str:
+  """Returns the Android device model."""
+  model = execute_cmd_and_get_output(
+      ["adb", "shell", "getprop", "ro.product.model"], verbose=verbose)
+  model = re.sub(r"\W+", "-", model)
+  return model
+
+
+def get_android_cpu_abi(verbose: bool = False) -> str:
+  """Returns the CPU ABI for the Android device."""
+  return execute_cmd_and_get_output(
+      ["adb", "shell", "getprop", "ro.product.cpu.abi"], verbose=verbose)
+
+
+def get_android_cpu_features(verbose: bool = False) -> Sequence[str]:
+  """Returns the CPU features for the Android device."""
+  cpuinfo = execute_cmd_and_get_output(["adb", "shell", "cat", "/proc/cpuinfo"],
+                                       verbose=verbose)
+  features = []
+  for line in cpuinfo.splitlines():
+    if line.startswith("Features"):
+      _, features = line.split(":")
+      return features.strip().split()
+  return features
+
+
+def get_android_gpu_name(verbose: bool = False) -> str:
+  """Returns the GPU name for the Android device."""
+  vkjson = execute_cmd_and_get_output(["adb", "shell", "cmd", "gpu", "vkjson"],
+                                      verbose=verbose)
+  vkjson = json.loads(vkjson)
+  name = vkjson["devices"][0]["properties"]["deviceName"]
+
+  # Perform some canonicalization:
+
+  # - Adreno GPUs have raw names like "Adreno (TM) 650".
+  name = name.replace("(TM)", "")
+
+  # Replace all consecutive non-word characters with a single hypen.
+  name = re.sub(r"\W+", "-", name)
+
+  return name
+
+
+def get_android_device_info(verbose: bool = False) -> DeviceInfo:
+  """Returns device info for the Android device."""
+  return DeviceInfo(PlatformType.ANDROID, get_android_device_model(verbose),
+                    get_android_cpu_abi(verbose),
+                    get_android_cpu_features(verbose),
+                    get_android_gpu_name(verbose))
diff --git a/build_tools/benchmarks/common/benchmark_definition.py b/build_tools/benchmarks/common/benchmark_definition.py
index ee3b4a0..7bb85a3 100644
--- a/build_tools/benchmarks/common/benchmark_definition.py
+++ b/build_tools/benchmarks/common/benchmark_definition.py
@@ -11,30 +11,39 @@
 """
 
 import json
-import re
 import subprocess
 
 from dataclasses import dataclass
+from enum import Enum
 from typing import Any, Dict, Sequence
 
-__all__ = [
-    "AndroidDeviceInfo", "BenchmarkInfo", "BenchmarkResults", "BenchmarkRun",
-    "execute_cmd_and_get_output", "execute_cmd"
-]
+
+@dataclass
+class DriverInfo:
+  """An object describing a IREE HAL driver.
+
+  It includes the following characteristics:
+  - pretty_name: the pretty name, e.g., 'IREE-DyLib'
+  - device_type: the targeted device type, e.g., 'CPU'
+  """
+
+  pretty_name: str
+  device_type: str
+
 
 # A map for IREE driver names. This allows us to normalize driver names like
 # mapping to more friendly ones and detach to keep driver names used in
 # benchmark presentation stable.
-IREE_DRIVERS_TO_PRETTY_NAMES = {
-    "iree-dylib": "IREE-Dylib",
-    "iree-dylib-sync": "IREE-Dylib-Sync",
-    "iree-vmvx": "IREE-VMVX",
-    "iree-vmvx-sync": "IREE-VMVX-Sync",
-    "iree-vulkan": "IREE-Vulkan",
+IREE_DRIVERS_INFOS = {
+    "iree-dylib": DriverInfo("IREE-Dylib", "CPU"),
+    "iree-dylib-sync": DriverInfo("IREE-Dylib-Sync", "CPU"),
+    "iree-vmvx": DriverInfo("IREE-VMVX", "CPU"),
+    "iree-vmvx-sync": DriverInfo("IREE-VMVX-Sync", "CPU"),
+    "iree-vulkan": DriverInfo("IREE-Vulkan", "GPU"),
 }
 
 IREE_PRETTY_NAMES_TO_DRIVERS = {
-    v: k for k, v in IREE_DRIVERS_TO_PRETTY_NAMES.items()
+    v.pretty_name: k for k, v in IREE_DRIVERS_INFOS.items()
 }
 
 
@@ -75,61 +84,23 @@
                      **kwargs).stdout.strip()
 
 
-def get_android_device_model(verbose: bool = False) -> str:
-  """Returns the Android device model."""
-  model = execute_cmd_and_get_output(
-      ["adb", "shell", "getprop", "ro.product.model"], verbose=verbose)
-  model = re.sub(r"\W+", "-", model)
-  return model
-
-
-def get_android_cpu_abi(verbose: bool = False) -> str:
-  """Returns the CPU ABI for the Android device."""
-  return execute_cmd_and_get_output(
-      ["adb", "shell", "getprop", "ro.product.cpu.abi"], verbose=verbose)
-
-
-def get_android_cpu_features(verbose: bool = False) -> Sequence[str]:
-  """Returns the CPU features for the Android device."""
-  cpuinfo = execute_cmd_and_get_output(["adb", "shell", "cat", "/proc/cpuinfo"],
-                                       verbose=verbose)
-  features = []
-  for line in cpuinfo.splitlines():
-    if line.startswith("Features"):
-      _, features = line.split(":")
-      return features.strip().split()
-  return features
-
-
-def get_android_gpu_name(verbose: bool = False) -> str:
-  """Returns the GPU name for the Android device."""
-  vkjson = execute_cmd_and_get_output(["adb", "shell", "cmd", "gpu", "vkjson"],
-                                      verbose=verbose)
-  vkjson = json.loads(vkjson)
-  name = vkjson["devices"][0]["properties"]["deviceName"]
-
-  # Perform some canonicalization:
-
-  # - Adreno GPUs have raw names like "Adreno (TM) 650".
-  name = name.replace("(TM)", "")
-
-  # Replace all consecutive non-word characters with a single hypen.
-  name = re.sub(r"\W+", "-", name)
-
-  return name
+class PlatformType(Enum):
+  ANDROID = "Android"
 
 
 @dataclass
-class AndroidDeviceInfo:
-  """An object describing the current Android Device.
+class DeviceInfo:
+  """An object describing a device.
 
-  It includes the following phone characteristics:
+  It includes the following characteristics:
+  - platform_type: the OS platform, e.g., 'Android'
   - model: the product model, e.g., 'Pixel-4'
   - cpu_abi: the CPU ABI, e.g., 'arm64-v8a'
   - cpu_features: the detailed CPU features, e.g., ['fphp', 'sve']
   - gpu_name: the GPU name, e.g., 'Mali-G77'
   """
 
+  platform_type: PlatformType
   model: str
   cpu_abi: str
   cpu_features: Sequence[str]
@@ -144,12 +115,30 @@
         f"cpu_features=[{features}]",
     ]
     params = ", ".join(params)
-    return f"Android device <{params}>"
+    return f"{self.platform_type.value} device <{params}>"
 
-  def get_arm_arch_revision(self) -> str:
+  def get_cpu_arch_revision(self) -> str:
+    if self.cpu_abi == "arm64-v8a":
+      return self.__get_arm_cpu_arch_revision()
+    raise ValueError("Unrecognized CPU ABI; need to update the list")
+
+  def to_json_object(self) -> Dict[str, Any]:
+    return {
+        "platform_type": self.platform_type.value,
+        "model": self.model,
+        "cpu_abi": self.cpu_abi,
+        "cpu_features": self.cpu_features,
+        "gpu_name": self.gpu_name,
+    }
+
+  @staticmethod
+  def from_json_object(json_object: Dict[str, Any]):
+    return DeviceInfo(PlatformType(json_object["platform_type"]),
+                      json_object["model"], json_object["cpu_abi"],
+                      json_object["cpu_features"], json_object["gpu_name"])
+
+  def __get_arm_cpu_arch_revision(self) -> str:
     """Returns the ARM architecture revision."""
-    if self.cpu_abi != "arm64-v8a":
-      raise ValueError("Unrecognized ARM CPU ABI; need to update the list")
 
     # CPU features for ARMv8 revisions.
     # From https://en.wikichip.org/wiki/arm/armv8#ARMv8_Extensions_and_Processor_Features
@@ -165,27 +154,6 @@
       rev = "ARMv8.2-A"
     return rev
 
-  def to_json_object(self) -> Dict[str, Any]:
-    return {
-        "model": self.model,
-        "cpu_abi": self.cpu_abi,
-        "cpu_features": self.cpu_features,
-        "gpu_name": self.gpu_name,
-    }
-
-  @staticmethod
-  def from_json_object(json_object: Dict[str, Any]):
-    return AndroidDeviceInfo(json_object["model"], json_object["cpu_abi"],
-                             json_object["cpu_features"],
-                             json_object["gpu_name"])
-
-  @staticmethod
-  def from_adb(verbose: bool = False):
-    return AndroidDeviceInfo(get_android_device_model(verbose),
-                             get_android_cpu_abi(verbose),
-                             get_android_cpu_features(verbose),
-                             get_android_gpu_name(verbose))
-
 
 @dataclass
 class BenchmarkInfo:
@@ -199,8 +167,7 @@
   - bench_mode: a list of tags for benchmark mode,
       e.g., ['1-thread', 'big-core', 'full-inference']
   - runner: which runner is used for benchmarking, e.g., 'iree_vulkan', 'tflite'
-  - device_info: an AndroidDeviceInfo object describing the phone where
-      benchmarks run
+  - device_info: an DeviceInfo object describing the device where benchmarks run
   """
 
   model_name: str
@@ -208,35 +175,37 @@
   model_source: str
   bench_mode: Sequence[str]
   runner: str
-  device_info: AndroidDeviceInfo
+  device_info: DeviceInfo
 
   def __str__(self):
     # Get the target architecture and better driver name depending on the runner.
-    target_arch = ""
-    driver = ""
-    if self.runner == "iree-vulkan":
-      target_arch = "GPU-" + self.device_info.gpu_name
-      driver = IREE_DRIVERS_TO_PRETTY_NAMES[self.runner]
-    elif (self.runner == "iree-dylib" or self.runner == "iree-dylib-sync" or
-          self.runner == "iree-vmvx" or self.runner == "iree-vmvx-sync"):
-      target_arch = "CPU-" + self.device_info.get_arm_arch_revision()
-      driver = IREE_DRIVERS_TO_PRETTY_NAMES[self.runner]
-    else:
+    driver_info = IREE_DRIVERS_INFOS.get(self.runner)
+    if not driver_info:
       raise ValueError(
           f"Unrecognized runner '{self.runner}'; need to update the list")
 
+    target_arch = None
+    if driver_info.device_type == 'GPU':
+      target_arch = "GPU-" + self.device_info.gpu_name
+    elif driver_info.device_type == 'CPU':
+      target_arch = "CPU-" + self.device_info.get_cpu_arch_revision()
+    else:
+      raise ValueError(
+          f"Unrecognized device type '{driver_info.device_type}' of the runner '{self.runner}'"
+      )
+
     if self.model_tags:
       tags = ",".join(self.model_tags)
       model_part = f"{self.model_name} [{tags}] ({self.model_source})"
     else:
       model_part = f"{self.model_name} ({self.model_source})"
-    phone_part = f"{self.device_info.model} ({target_arch})"
+    device_part = f"{self.device_info.model} ({target_arch})"
     mode = ",".join(self.bench_mode)
 
-    return f"{model_part} {mode} with {driver} @ {phone_part}"
+    return f"{model_part} {mode} with {driver_info.pretty_name} @ {device_part}"
 
   @staticmethod
-  def from_device_info_and_name(device_info: AndroidDeviceInfo, name: str):
+  def from_device_info_and_name(device_info: DeviceInfo, name: str):
     (
         model_name,
         model_tags,
@@ -283,7 +252,7 @@
                          model_source=json_object["model_source"],
                          bench_mode=json_object["bench_mode"],
                          runner=json_object["runner"],
-                         device_info=AndroidDeviceInfo.from_json_object(
+                         device_info=DeviceInfo.from_json_object(
                              json_object["device_info"]))
 
 
diff --git a/build_tools/benchmarks/common/benchmark_suite.py b/build_tools/benchmarks/common/benchmark_suite.py
index 3e09831..bd72617 100644
--- a/build_tools/benchmarks/common/benchmark_suite.py
+++ b/build_tools/benchmarks/common/benchmark_suite.py
@@ -34,17 +34,17 @@
 
 from typing import List, Optional, Sequence
 
-from .benchmark_definition import AndroidDeviceInfo, BenchmarkInfo
+from .benchmark_definition import DeviceInfo, BenchmarkInfo
 
 # All benchmarks' relative path against root build directory.
 BENCHMARK_SUITE_REL_PATH = "benchmark_suites"
 
-def compose_info_object(device_info: AndroidDeviceInfo,
-                        benchmark_category_dir: str,
+
+def compose_info_object(device_info: DeviceInfo, benchmark_category_dir: str,
                         benchmark_case_dir: str) -> BenchmarkInfo:
   """Creates an BenchmarkInfo object to describe the benchmark.
   Args:
-    device_info: an AndroidDeviceInfo object.
+    device_info: an DeviceInfo object.
     benchmark_category_dir: the directory to a specific benchmark category.
     benchmark_case_dir: a directory containing the benchmark case.
   Returns:
diff --git a/build_tools/benchmarks/run_benchmarks_on_android.py b/build_tools/benchmarks/run_benchmarks_on_android.py
index 7763cf2..a86b7e2 100755
--- a/build_tools/benchmarks/run_benchmarks_on_android.py
+++ b/build_tools/benchmarks/run_benchmarks_on_android.py
@@ -40,15 +40,18 @@
 import shutil
 import sys
 
-from typing import Any, Dict, List, Optional, Sequence, Tuple, TextIO, Set
+from typing import List, Optional, Sequence, Tuple, Set
 
-from common.benchmark_definition import (
-    AndroidDeviceInfo, BenchmarkInfo, BenchmarkResults, BenchmarkRun,
-    execute_cmd, execute_cmd_and_get_output, get_android_device_model,
-    get_android_gpu_name, IREE_PRETTY_NAMES_TO_DRIVERS)
+from common.benchmark_definition import (DeviceInfo, BenchmarkInfo,
+                                         BenchmarkResults, BenchmarkRun,
+                                         execute_cmd,
+                                         execute_cmd_and_get_output)
 from common.benchmark_suite import (BENCHMARK_SUITE_REL_PATH,
                                     compose_info_object,
                                     filter_benchmarks_for_category)
+from common.android_device_utils import (get_android_device_model,
+                                         get_android_device_info,
+                                         get_android_gpu_name)
 
 # The flagfile/toolfile's filename for compiled benchmark artifacts.
 MODEL_FLAGFILE_NAME = "flagfile"
@@ -223,7 +226,7 @@
 
 
 def run_benchmarks_for_category(
-    device_info: AndroidDeviceInfo,
+    device_info: DeviceInfo,
     root_benchmark_dir: str,
     benchmark_category_dir: str,
     benchmark_case_dirs: Sequence[str],
@@ -241,7 +244,7 @@
   """Runs all benchmarks on the Android device and reports results and captures.
 
   Args:
-    device_info: an AndroidDeviceInfo object.
+    device_info: an DeviceInfo object.
     root_benchmark_dir: path to the benchmark suite within the root build dir
     benchmark_category_dir: the directory to a specific benchmark category.
     benchmark_case_dirs: a list of benchmark case directories.
@@ -449,7 +452,7 @@
 
 
 def filter_and_run_benchmarks(
-    device_info: AndroidDeviceInfo,
+    device_info: DeviceInfo,
     root_build_dir: str,
     driver_filter: Optional[str],
     model_name_filter: Optional[str],
@@ -467,7 +470,7 @@
   """Filters and runs benchmarks in all categories for the given device.
 
   Args:
-    device_info: an AndroidDeviceInfo object.
+    device_info: an DeviceInfo object.
     root_build_dir: the root build directory containing the built benchmark
       suites.
     driver_filter: filter benchmarks to those whose driver matches this regex
@@ -695,7 +698,7 @@
 
 
 def main(args):
-  device_info = AndroidDeviceInfo.from_adb()
+  device_info = get_android_device_info()
   if args.verbose:
     print(device_info)