blob: a6ab616b85f1450bd9dc8acfbeb9e470e9ecc22d [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2022 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
"""Collect compilation statistics from benchmark suites.
The benchmark suites need to be built with ninja and enable the CMake option
IREE_ENABLE_COMPILATION_BENCHMARKS.
"""
import argparse
import json
import os
import re
import zipfile
from dataclasses import asdict
from typing import BinaryIO, Dict, Optional, TextIO
from pathlib import PurePath
from common.benchmark_definition import CompilationInfo, CompilationResults, CompilationStatistics, ModuleComponentSizes, get_git_commit_hash
from common.benchmark_suite import BENCHMARK_SUITE_REL_PATH, BenchmarkSuite
BENCHMARK_FLAGFILE = "flagfile"
MODULE_DIR = "vmfb"
MODULE_FILE_EXTENSION = ".vmfb"
NINJA_LOG_HEADER = "ninja log v5"
NINJA_BUILD_LOG = ".ninja_log"
COMPILATION_STATS_MODULE_SUFFIX = "compile-stats"
VM_COMPONENT_NAME = "module.fb"
CONST_COMPONENT_NAME = "_const.bin"
DISPATCH_COMPONENT_PATTERNS = [
r".+_embedded_elf_.+\.so",
r".+_vulkan_spirv_fb\.fb",
r".+_cuda_nvptx_fb\.fb",
r".+_vmvx_bytecode_fb\.bin",
]
def match_module_cmake_target(module_path: str) -> Optional[str]:
# Get the last 4 parts of module path. They are expected to be:
# benchmark_suites/<category>/vmfb/<module filename>.vmfb
path_parts = PurePath(module_path).parts[-4:]
if len(path_parts) < 4:
return None
if path_parts[0] != BENCHMARK_SUITE_REL_PATH:
return None
if path_parts[2] != MODULE_DIR:
return None
if os.path.splitext(path_parts[3])[1] != MODULE_FILE_EXTENSION:
return None
return os.path.join(*path_parts)
def parse_compilation_time_from_ninja_log(log: TextIO) -> Dict[str, int]:
"""Retrieve the compilation time (ms) from the Ninja build log.
Returns:
Map of target name and compilation time in ms.
"""
target_build_time_map = {}
header = log.readline()
if NINJA_LOG_HEADER not in header:
raise NotImplementedError(f"Unsupported ninja log version: {header}")
for line in log:
start_time, end_time, _, target, _ = line.strip().split("\t")
cmake_target = match_module_cmake_target(target)
if cmake_target is None:
continue
start_time = int(start_time)
end_time = int(end_time)
target_build_time_map[cmake_target] = end_time - start_time
return target_build_time_map
def get_module_component_info(module: BinaryIO,
module_file_bytes: int) -> ModuleComponentSizes:
with zipfile.ZipFile(module) as module_zipfile:
size_map = dict(
(info.filename, info.file_size) for info in module_zipfile.infolist())
vm_component_bytes = size_map[VM_COMPONENT_NAME]
const_component_bytes = size_map[CONST_COMPONENT_NAME]
identified_names = {VM_COMPONENT_NAME, CONST_COMPONENT_NAME}
total_dispatch_component_bytes = 0
for filename, size in size_map.items():
for pattern in DISPATCH_COMPONENT_PATTERNS:
if re.match(pattern, filename):
total_dispatch_component_bytes += size
identified_names.add(filename)
break
if identified_names != set(size_map.keys()):
raise RuntimeError(
f"Unrecognized components in the module: {size_map.keys()}.")
return ModuleComponentSizes(
file_bytes=module_file_bytes,
vm_component_bytes=vm_component_bytes,
const_component_bytes=const_component_bytes,
total_dispatch_component_bytes=total_dispatch_component_bytes)
def get_module_path(flag_file: TextIO) -> Optional[str]:
"""Retrieve the module path for compilation statistics from the flag file."""
module_path = None
for line in flag_file:
match = re.match("--module_file=(.+)", line.strip())
if match:
module_name, module_ext = os.path.splitext(match.group(1))
module_path = f"{module_name}-{COMPILATION_STATS_MODULE_SUFFIX}{module_ext}"
break
return module_path
def parse_arguments():
"""Returns an argument parser with common options."""
def check_dir_path(path):
if os.path.isdir(path):
return path
else:
raise argparse.ArgumentTypeError(path)
parser = argparse.ArgumentParser()
parser.add_argument("--output",
required=True,
help="Path to output JSON file.")
parser.add_argument(
"build_dir",
metavar="<build-dir>",
type=check_dir_path,
help="Path to the build directory containing benchmark suites.")
parser.add_argument("--verbose",
action="store_true",
help="Print internal information during execution.")
return parser.parse_args()
def main(args: argparse.Namespace):
benchmark_suite_dir = os.path.join(args.build_dir, BENCHMARK_SUITE_REL_PATH)
benchmark_suite = BenchmarkSuite.load_from_benchmark_suite_dir(
benchmark_suite_dir)
with open(os.path.join(args.build_dir, NINJA_BUILD_LOG), "r") as log_file:
target_build_time_map = parse_compilation_time_from_ninja_log(log_file)
compilation_statistics_list = []
for category, _ in benchmark_suite.list_categories():
benchmark_cases = benchmark_suite.filter_benchmarks_for_category(
category=category)
for benchmark_case in benchmark_cases:
flag_file_path = os.path.join(benchmark_case.benchmark_case_dir,
BENCHMARK_FLAGFILE)
with open(flag_file_path, "r") as flag_file:
module_path = get_module_path(flag_file)
if module_path is None:
raise RuntimeError(
f"Can't find the module file in the flagfile: {flag_file_path}")
module_path = os.path.abspath(
os.path.join(benchmark_case.benchmark_case_dir, module_path))
with open(module_path, "rb") as module_file:
module_component_sizes = get_module_component_info(
module_file,
os.stat(module_path).st_size)
cmake_target = match_module_cmake_target(module_path)
if cmake_target is None:
raise RuntimeError(
f"Module path isn't a module cmake target: {module_path}")
compilation_time_ms = target_build_time_map[cmake_target]
compilation_info = CompilationInfo(model_name=benchmark_case.model_name,
model_tags=benchmark_case.model_tags,
model_source=category,
target_arch=benchmark_case.target_arch,
bench_mode=benchmark_case.bench_mode)
compilation_statistics = CompilationStatistics(
compilation_info=compilation_info,
module_component_sizes=module_component_sizes,
compilation_time_ms=compilation_time_ms)
compilation_statistics_list.append(compilation_statistics)
commit = get_git_commit_hash("HEAD")
compilation_results = CompilationResults(
commit=commit, compilation_statistics=compilation_statistics_list)
json_object = asdict(compilation_results)
with open(args.output, "w") as f:
json.dump(json_object, f)
if args.verbose:
print(json.dumps(json_object, indent=4))
if __name__ == "__main__":
main(parse_arguments())