blob: 8b1828726c8d44cea057d8022745aea818b66dca [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (C) 2022 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
from dataclasses import dataclass
import gzip
import json
from pathlib import Path
from typing import TextIO
from paths import TOOLCHAIN_MODULE_LIST_PATH
from utils import ResolvedPath, ms_to_hms
SUFFIXES_RUST = [
".rs",
".rlib",
".rlib.clippy",
".dylib.so",
]
#
# Classes
#
@dataclass
class TraceInfo:
targets_total: int = 0
targets_rust: int = 0
duration_total_ms: int = 0
duration_rust_ms: int = 0
#
# Helper functions
#
def open_trace(trace_path: Path) -> TextIO:
if not trace_path.exists():
print(f"Trace file does not exist: {trace_path.as_posix()}")
exit(-1)
if trace_path.suffix == ".gz":
return gzip.open(trace_path, mode="rt")
else:
return open(trace_path, mode="r")
def is_rust_target(target_name: str, module_prefixes: list[str]) -> bool:
target_path = Path(target_name)
if target_path.suffix != "":
for suffix in SUFFIXES_RUST:
if target_path.name.endswith(suffix):
return True
return False
else:
for prefix in module_prefixes:
if target_name.startswith(prefix):
return True
return False
def target_in_known_rust_module(target_name: str, module_prefixes: list[str]) -> bool:
for prefix in module_prefixes:
if target_name.startswith(prefix):
return True
return False
#
# Program logic
#
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Produce a summary of Rust-related information from a Soong build trace")
parser.add_argument("trace", metavar="TRACE", type=ResolvedPath, help="Soong trace file to process")
return parser.parse_args()
# The module list is generated using the Bazel query command:
#
# $ m queryview
# $ bazel query --config=queryview 'kind("rust_(?!default).*", //...)' --output label_kind
#
# `rust_defaults` targets are excluded as they are abstract and used
# to avoid duplicating common properties among targets.
def load_target_prefixes() -> list[str]:
rust_target_prefixes = []
TRACE_PATH_PREFIX = "out/soong/.intermediates"
if TOOLCHAIN_MODULE_LIST_PATH.exists():
with open(TOOLCHAIN_MODULE_LIST_PATH, "r") as ml:
# Example Line:
# rust_binary_host rule //external/rust/crates/grpcio-compiler:grpc_rust_plugin--linux_glibc_x86_64
#
# comps = ["rust_binary_host", "rule", "//external/rust/crates/grpcio-compiler:grpc_rust_plugin--linux_glibc_x86_64"]
# mod_path = "//external/rust/crates/grpcio-compiler"
# rest = "grpc_rust_plugin--linux_glibc_x86_64"
# mod_name = "grpc_rust_plugin"
# variant = "linux_glibc_x86_64"
#
# target_prefix = out/soong/.intermediates/external/rust/crates/grpcio-compiler/grpc_rust_plugin/linux_glibc_x86_64
for line in ml.readlines():
comps = line.strip().split(" ", 3)
mod_path, rest = comps[2].split(":")
mod_name, variant = rest.split("--")
target_prefix = f"{TRACE_PATH_PREFIX}/{mod_path[2:]}/{mod_name}/{variant}"
rust_target_prefixes.append(target_prefix)
return rust_target_prefixes
def process_trace(trace_path: Path, rust_target_prefixes: list[str]) -> TraceInfo:
info = TraceInfo()
with open_trace(trace_path) as fd:
for item in json.load(fd):
if "dur" in item:
info.targets_total += 1
info.duration_total_ms += item["dur"]
if is_rust_target(item["name"], rust_target_prefixes):
info.targets_rust += 1
info.duration_rust_ms += item["dur"]
return info
def main() -> None:
args = parse_args()
# Load list of known Rust targets if it exists.
rust_target_prefixes = load_target_prefixes()
trace_info = process_trace(args.trace, rust_target_prefixes)
rust_dur_parts = ms_to_hms(trace_info.duration_rust_ms)
total_dur_parts = ms_to_hms(trace_info.duration_total_ms)
print(f"Rust targets: {trace_info.targets_rust}")
print(f"Rust duration (ms): {trace_info.duration_rust_ms}")
print(f"Rust duration (dd hh:mm:ss.ms): {rust_dur_parts[0]} {rust_dur_parts[1]}:{rust_dur_parts[2]}:{rust_dur_parts[3]:02.2f}")
print(f"Total targets: {trace_info.targets_total}")
print(f"Total duration (ms): {trace_info.duration_total_ms}")
print(f"Total duration (dd hh:mm:ss.ms): {total_dur_parts[0]} {total_dur_parts[1]}:{total_dur_parts[2]}:{total_dur_parts[3]:02.2f}")
print(f"Rust percent: {(float(trace_info.duration_rust_ms) / float(trace_info.duration_total_ms) * 100):02.2f}%")
print()
if __name__ == '__main__':
main()