diff --git a/treble/combined_build_test.sh b/treble/combined_build_test.sh old mode 100644 new mode 100755 index bcdea9faf..a6133cd8b --- a/treble/combined_build_test.sh +++ b/treble/combined_build_test.sh @@ -15,15 +15,17 @@ # limitations under the License. usage() { - echo "usage: "$0" -t TARGET -v VARIANT [-d DIST_OUT] [-a ALTER_TARGET] [-c] [GOALS ...]" + echo "usage: ${0} -t TARGET -v VARIANT [-d DIST_OUT] [-a ALTER_TARGET] [-c] [-o] [-r] [GOALS ...]" echo " -t TARGET : Primay target to build" echo " -v VARIANT : Build variant (ex. user, userdebug)" echo " -d DIST_OUT : Path for dist out" echo " -a ALTER_TARGET: The secondary target that shares the build artifacts with the primary target" - echo " -c : Installclean between each build" + echo " -c : Run the target build again after installclean for reference" + echo ' -o : Write build time results to "build_time_results.txt" file in "${OUT_DIR}" or "${DIST_OUT}/logs" if -d defined' + echo " -r : Dryrun to see the commands without actually building the targets" } -while getopts ha:cd:t:v: opt; do +while getopts ha:cd:ort:v: opt; do case "${opt}" in h) usage @@ -32,11 +34,17 @@ while getopts ha:cd:t:v: opt; do alter_target="${OPTARG}" ;; c) - installclean=true + installclean="true" ;; d) dist_dir="${OPTARG}" ;; + o) + result_out="build_time_results.txt" + ;; + r) + dry_run="true" + ;; t) target="${OPTARG}" ;; @@ -49,48 +57,84 @@ while getopts ha:cd:t:v: opt; do esac done -if [[ -z ${target} ]]; then +if [[ -z "${target}" ]]; then echo "-t must set for the primary target" + usage exit 1 fi -if [[ -z ${variant} ]]; then +if [[ -z "${variant}" ]]; then echo "-v must set for build variant" + usage exit 1 fi - goals="${@:OPTIND}" +readonly ANDROID_TOP="$(cd $(dirname $0)/../..; pwd)" +cd "${ANDROID_TOP}" + +out_dir="${OUT_DIR:-out}" +if [[ -n "${dist_dir}" ]]; then + out_dir="${dist_dir}/logs" +fi + base_command="build/soong/soong_ui.bash --make-mode" -if [[ ! -z ${dist_dir} ]]; then +if [[ -n "${dist_dir}" ]]; then base_command="${base_command} DIST_DIR=${dist_dir} dist" fi +run_command() { + if [[ -z "${dry_run}" ]]; then + $1 + else + echo "$1" + fi +} + +write_output() { + if [[ -z "${result_out}" || -n "${dry_run}" ]]; then + echo "Output: $1" + else + echo "$1" >> "${out_dir}/${result_out}" + fi +} + +get_build_trace() { + run_command "cp -f ${out_dir}/build.trace.gz ${out_dir}/${1}" + if [[ -n "${result_out}" ]]; then + write_output "$(python3 development/treble/read_build_trace_gz.py ${out_dir}/${1})" + fi +} + +if [[ -n "${result_out}" ]]; then + run_command "rm -f ${out_dir}/${result_out}" + write_output "target, soong, kati, ninja, total" +fi + # Build the target first. echo "Initial build..." -${base_command} TARGET_PRODUCT=${target} TARGET_BUILD_VARIANT=${variant} ${goals} +run_command "${base_command} TARGET_PRODUCT=${target} TARGET_BUILD_VARIANT=${variant} ${goals}" -if [[ ! -z ${alter_target} ]]; then - # Building two targets with a single artifacts - if [[ ! -z ${installclean} ]]; then - echo "Installclean for the alternative target..." - ${base_command} TARGET_PRODUCT=${alter_target} TARGET_BUILD_VARIANT=${variant} installclean - fi - echo "Build the alternative target..." - ${base_command} TARGET_PRODUCT=${alter_target} TARGET_BUILD_VARIANT=${variant} ${goals} - if [[ ! -z ${installclean} ]]; then - echo "Installclean for the primary target..." - ${base_command} TARGET_PRODUCT=${target} TARGET_BUILD_VARIANT=${variant} installclean - fi - echo "Build the primary target again..." - ${base_command} TARGET_PRODUCT=${target} TARGET_BUILD_VARIANT=${variant} ${goals} -else - # Build again just after installclean for reference - if [[ ! -z ${installclean} ]]; then - echo "Installclean..." - ${base_command} TARGET_PRODUCT=${target} TARGET_BUILD_VARIANT=${variant} installclean - fi - echo "Build the target again..." - ${base_command} TARGET_PRODUCT=${target} TARGET_BUILD_VARIANT=${variant} ${goals} +if [[ -n "${installclean}" ]]; then + # Run the same build after installclean + echo "Installclean..." + run_command "${base_command} TARGET_PRODUCT=${target} TARGET_BUILD_VARIANT=${variant} installclean" + echo "Build the same initial build..." + run_command "${base_command} TARGET_PRODUCT=${target} TARGET_BUILD_VARIANT=${variant} ${goals}" + get_build_trace "build_${target}_installclean.trace.gz" fi +if [[ -n "${alter_target}" ]]; then + # Building two targets with a single artifacts + echo "Installclean for the alternative target..." + run_command "${base_command} TARGET_PRODUCT=${alter_target} TARGET_BUILD_VARIANT=${variant} installclean" + echo "Build the alternative target..." + run_command "${base_command} TARGET_PRODUCT=${alter_target} TARGET_BUILD_VARIANT=${variant} ${goals}" + get_build_trace "build_${alter_target}_ab.trace.gz" + + echo "Installclean for the primary target..." + run_command "${base_command} TARGET_PRODUCT=${target} TARGET_BUILD_VARIANT=${variant} installclean" + echo "Build the primary target again..." + run_command "${base_command} TARGET_PRODUCT=${target} TARGET_BUILD_VARIANT=${variant} ${goals}" + get_build_trace "build_${target}_aba.trace.gz" +fi diff --git a/treble/read_build_trace_gz.py b/treble/read_build_trace_gz.py new file mode 100644 index 000000000..6431876e1 --- /dev/null +++ b/treble/read_build_trace_gz.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 gzip +import json +import os +import sys + +from collections import defaultdict + +READ_DURATION = [ + 'soong', + 'kati build', + 'ninja', + 'total', +] + +class Trace: + def __init__(self, trace_file): + self.duration = dict() + self._queue = defaultdict(list) + self.target = os.path.splitext(os.path.basename(trace_file))[0] + if not os.path.isfile(trace_file): + return + self._trace_file = gzip.open(trace_file, 'r') + self._trace_data = json.load(self._trace_file) + for t in self._trace_data: + if 'ph' not in t: + continue + if t['ph'] == 'X': + self.duration[t['name']] = t['dur'] + continue + if t['ph'] == 'B': + self._queue[(t['pid'], t['pid'])].append((t['name'], t['ts'])) + continue + if t['ph'] == 'E': + queue = self._queue[(t['pid'], t['pid'])] + if not queue: + raise Exception('pid:{}, tid:{} not started'.format(t['pid'], t['pid'])) + name, ts = queue.pop() + self.duration[name] = t['ts'] - ts + continue + + def out_durations(self): + out_str = self.target + for name in READ_DURATION: + if name not in self.duration: + continue + out_str = '{}, {}'.format(out_str, self.duration[name]) + out_str += '\n' + sys.stdout.write(out_str) + + +def main(argv): + trace = Trace(argv[1]) + trace.out_durations() + +if __name__ == '__main__': + main(sys.argv)