aosp12/toolchain/pgo-profiles/scripts/update_profiles.py

246 lines
7.3 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright (C) 2019 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.
#
# Sample Usage:
# $ python update_profiles.py 500000 ALL --profdata-suffix 2019-04-15
#
# Additional/frequently-used arguments:
# -b BUG adds a 'Bug: <BUG>' to the commit message when adding the profiles.
# --do-not-merge adds a 'DO NOT MERGE' tag to the commit message to restrict
# automerge of profiles from release branches.
#
# Try '-h' for a full list of command line arguments.
import argparse
import os
import shutil
import subprocess
import sys
import tempfile
import zipfile
import utils
from android_build_client import AndroidBuildClient
class Benchmark(object):
def __init__(self, name):
self.name = name
def apct_test_tag(self):
raise NotImplementedError()
def profdata_file(self, suffix=''):
profdata = os.path.join(self.name, '{}.profdata'.format(self.name))
if suffix:
profdata += '.' + suffix
return profdata
def profraw_files(self):
raise NotImplementedError()
def merge_profraws(self, profile_dir, output):
profraws = [
os.path.join(profile_dir, p)
for p in self.profraw_files(profile_dir)
]
utils.run_llvm_profdata(profraws, output)
class NativeExeBenchmark(Benchmark):
def apct_test_tag(self):
return 'apct/perf/pgo/profile-collector'
def profraw_files(self, profile_dir):
if self.name == 'hwui':
return [
'hwuimacro.profraw', 'hwuimacro_64.profraw',
'hwuimicro.profraw', 'hwuimicro_64.profraw',
'skia_nanobench.profraw', 'skia_nanobench_64.profraw'
]
elif self.name == 'hwbinder':
return ['hwbinder.profraw', 'hwbinder_64.profraw']
class APKBenchmark(Benchmark):
def apct_test_tag(self):
return 'apct/perf/pgo/apk-profile-collector'
def profdata_file(self, suffix=''):
profdata = os.path.join('art',
'{}_arm_arm64.profdata'.format(self.name))
if suffix:
profdata += '.' + suffix
return profdata
def profraw_files(self, profile_dir):
return os.listdir(profile_dir)
def BenchmarkFactory(benchmark_name):
if benchmark_name == 'dex2oat':
return APKBenchmark(benchmark_name)
elif benchmark_name in ['hwui', 'hwbinder']:
return NativeExeBenchmark(benchmark_name)
else:
raise RuntimeError('Unknown benchmark ' + benchmark_name)
def extract_profiles(build, test_tag, build_client, output_dir):
pgo_zip = build_client.download_pgo_zip(build, test_tag, output_dir)
zipfile_name = os.path.join(pgo_zip)
zip_ref = zipfile.ZipFile(zipfile_name)
zip_ref.extractall(output_dir)
zip_ref.close()
KNOWN_BENCHMARKS = ['ALL', 'dex2oat', 'hwui', 'hwbinder']
def parse_args():
"""Parses and returns command line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument(
'build',
metavar='BUILD',
help='Build number to pull from the build server.')
parser.add_argument(
'-b', '--bug', type=int, help='Bug to reference in commit message.')
parser.add_argument(
'--use-current-branch',
action='store_true',
help='Do not repo start a new branch for the update.')
parser.add_argument(
'--add-do-not-merge',
action='store_true',
help='Add \'DO NOT MERGE\' to the commit message.')
parser.add_argument(
'--profdata-suffix',
type=str,
default='',
help='Suffix to append to merged profdata file')
parser.add_argument(
'benchmark',
metavar='BENCHMARK',
help='Update profiles for BENCHMARK. Choices are {}'.format(
KNOWN_BENCHMARKS))
parser.add_argument(
'--skip-cleanup',
'-sc',
action='store_true',
default=False,
help='Skip the cleanup, and leave intermediate files (in /tmp/pgo-profiles-*)'
)
return parser.parse_args()
def get_current_profile(benchmark):
profile = benchmark.profdata_file()
dirname, basename = os.path.split(profile)
old_profiles = [f for f in os.listdir(dirname) if f.startswith(basename)]
if len(old_profiles) == 0:
return ''
return os.path.join(dirname, old_profiles[0])
def main():
args = parse_args()
utils.check_gcertstatus()
if args.benchmark == 'ALL':
worklist = KNOWN_BENCHMARKS[1:]
else:
worklist = [args.benchmark]
profiles_project = os.path.join(utils.android_build_top(), 'toolchain',
'pgo-profiles')
os.chdir(profiles_project)
if not args.use_current_branch:
branch_name = 'update-profiles-' + args.build
utils.check_call(['repo', 'start', branch_name, '.'])
build_client = AndroidBuildClient()
for benchmark_name in worklist:
benchmark = BenchmarkFactory(benchmark_name)
# Existing profile file, which gets 'rm'-ed from 'git' down below.
current_profile = get_current_profile(benchmark)
# Extract profiles to a temporary directory. After extraction, we
# expect to find one subdirectory with profraw files under the temporary
# directory.
extract_dir = tempfile.mkdtemp(prefix='pgo-profiles-' + benchmark_name)
extract_profiles(args.build, benchmark.apct_test_tag(), build_client,
extract_dir)
extract_subdirs = [
os.path.join(extract_dir, sub)
for sub in os.listdir(extract_dir)
if os.path.isdir(os.path.join(extract_dir, sub))
]
if len(extract_subdirs) != 1:
raise RuntimeError(
'Expected one subdir under {}'.format(extract_dir))
# Merge profiles.
profdata = benchmark.profdata_file(args.profdata_suffix)
benchmark.merge_profraws(extract_subdirs[0], profdata)
# Construct 'git' commit message.
message_lines = [
'Update PGO profiles for {}'.format(benchmark_name), '',
'The profiles are from build {}.'.format(args.build), ''
]
if args.add_do_not_merge:
message_lines[0] = '[DO NOT MERGE] ' + message_lines[0]
if args.bug:
message_lines.append('')
message_lines.append('Bug: http://b/{}'.format(args.bug))
message_lines.append('Test: Build (TH)')
message = '\n'.join(message_lines)
# Invoke git: Delete current profile, add new profile and commit these
# changes.
if current_profile:
utils.check_call(['git', 'rm', current_profile])
utils.check_call(['git', 'add', profdata])
utils.check_call(['git', 'commit', '-m', message])
if not args.skip_cleanup:
shutil.rmtree(extract_dir)
if __name__ == '__main__':
main()