1#!/usr/bin/env python 2# 3# Copyright (C) 2019 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17# Sample Usage: 18# $ python update_profiles.py 500000 ALL --profdata-suffix 2019-04-15 19# 20# Additional/frequently-used arguments: 21# -b BUG adds a 'Bug: <BUG>' to the commit message when adding the profiles. 22# --do-not-merge adds a 'DO NOT MERGE' tag to the commit message to restrict 23# automerge of profiles from release branches. 24# 25# Try '-h' for a full list of command line arguments. 26 27import argparse 28import os 29import shutil 30import subprocess 31import sys 32import tempfile 33import zipfile 34 35import utils 36 37from android_build_client import AndroidBuildClient 38 39 40class Benchmark(object): 41 42 def __init__(self, name): 43 self.name = name 44 45 def apct_test_tag(self): 46 raise NotImplementedError() 47 48 def profdata_file(self, suffix=''): 49 profdata = os.path.join(self.name, '{}.profdata'.format(self.name)) 50 if suffix: 51 profdata += '.' + suffix 52 return profdata 53 54 def profraw_files(self): 55 raise NotImplementedError() 56 57 def merge_profraws(self, profile_dir, output): 58 profraws = [ 59 os.path.join(profile_dir, p) 60 for p in self.profraw_files(profile_dir) 61 ] 62 utils.run_llvm_profdata(profraws, output) 63 64 65class NativeExeBenchmark(Benchmark): 66 67 def apct_test_tag(self): 68 return 'apct/perf/pgo/profile-collector' 69 70 def profraw_files(self, profile_dir): 71 if self.name == 'hwui': 72 return [ 73 'hwuimacro.profraw', 'hwuimacro_64.profraw', 74 'hwuimicro.profraw', 'hwuimicro_64.profraw', 75 'skia_nanobench.profraw', 'skia_nanobench_64.profraw' 76 ] 77 elif self.name == 'hwbinder': 78 return ['hwbinder.profraw', 'hwbinder_64.profraw'] 79 80 81class APKBenchmark(Benchmark): 82 83 def apct_test_tag(self): 84 return 'apct/perf/pgo/apk-profile-collector' 85 86 def profdata_file(self, suffix=''): 87 profdata = os.path.join('art', 88 '{}_arm_arm64.profdata'.format(self.name)) 89 if suffix: 90 profdata += '.' + suffix 91 return profdata 92 93 def profraw_files(self, profile_dir): 94 return os.listdir(profile_dir) 95 96 97def BenchmarkFactory(benchmark_name): 98 if benchmark_name == 'dex2oat': 99 return APKBenchmark(benchmark_name) 100 elif benchmark_name in ['hwui', 'hwbinder']: 101 return NativeExeBenchmark(benchmark_name) 102 else: 103 raise RuntimeError('Unknown benchmark ' + benchmark_name) 104 105 106def extract_profiles(build, test_tag, build_client, output_dir): 107 pgo_zip = build_client.download_pgo_zip(build, test_tag, output_dir) 108 109 zipfile_name = os.path.join(pgo_zip) 110 zip_ref = zipfile.ZipFile(zipfile_name) 111 zip_ref.extractall(output_dir) 112 zip_ref.close() 113 114 115KNOWN_BENCHMARKS = ['ALL', 'dex2oat', 'hwui', 'hwbinder'] 116 117 118def parse_args(): 119 """Parses and returns command line arguments.""" 120 parser = argparse.ArgumentParser() 121 122 parser.add_argument( 123 'build', 124 metavar='BUILD', 125 help='Build number to pull from the build server.') 126 127 parser.add_argument( 128 '-b', '--bug', type=int, help='Bug to reference in commit message.') 129 130 parser.add_argument( 131 '--use-current-branch', 132 action='store_true', 133 help='Do not repo start a new branch for the update.') 134 135 parser.add_argument( 136 '--add-do-not-merge', 137 action='store_true', 138 help='Add \'DO NOT MERGE\' to the commit message.') 139 140 parser.add_argument( 141 '--profdata-suffix', 142 type=str, 143 default='', 144 help='Suffix to append to merged profdata file') 145 146 parser.add_argument( 147 'benchmark', 148 metavar='BENCHMARK', 149 help='Update profiles for BENCHMARK. Choices are {}'.format( 150 KNOWN_BENCHMARKS)) 151 152 parser.add_argument( 153 '--skip-cleanup', 154 '-sc', 155 action='store_true', 156 default=False, 157 help='Skip the cleanup, and leave intermediate files (in /tmp/pgo-profiles-*)' 158 ) 159 160 return parser.parse_args() 161 162 163def get_current_profile(benchmark): 164 profile = benchmark.profdata_file() 165 dirname, basename = os.path.split(profile) 166 167 old_profiles = [f for f in os.listdir(dirname) if f.startswith(basename)] 168 if len(old_profiles) == 0: 169 return '' 170 return os.path.join(dirname, old_profiles[0]) 171 172 173def main(): 174 args = parse_args() 175 176 if args.benchmark == 'ALL': 177 worklist = KNOWN_BENCHMARKS[1:] 178 else: 179 worklist = [args.benchmark] 180 181 profiles_project = os.path.join(utils.android_build_top(), 'toolchain', 182 'pgo-profiles') 183 os.chdir(profiles_project) 184 185 if not args.use_current_branch: 186 branch_name = 'update-profiles-' + args.build 187 utils.check_call(['repo', 'start', branch_name, '.']) 188 189 build_client = AndroidBuildClient() 190 191 for benchmark_name in worklist: 192 benchmark = BenchmarkFactory(benchmark_name) 193 194 # Existing profile file, which gets 'rm'-ed from 'git' down below. 195 current_profile = get_current_profile(benchmark) 196 197 # Extract profiles to a temporary directory. After extraction, we 198 # expect to find one subdirectory with profraw files under the temporary 199 # directory. 200 extract_dir = tempfile.mkdtemp(prefix='pgo-profiles-' + benchmark_name) 201 extract_profiles(args.build, benchmark.apct_test_tag(), build_client, 202 extract_dir) 203 204 extract_subdirs = [ 205 os.path.join(extract_dir, sub) 206 for sub in os.listdir(extract_dir) 207 if os.path.isdir(os.path.join(extract_dir, sub)) 208 ] 209 if len(extract_subdirs) != 1: 210 raise RuntimeError( 211 'Expected one subdir under {}'.format(extract_dir)) 212 213 # Merge profiles. 214 profdata = benchmark.profdata_file(args.profdata_suffix) 215 benchmark.merge_profraws(extract_subdirs[0], profdata) 216 217 # Construct 'git' commit message. 218 message_lines = [ 219 'Update PGO profiles for {}'.format(benchmark_name), '', 220 'The profiles are from build {}.'.format(args.build), '' 221 ] 222 223 if args.add_do_not_merge: 224 message_lines[0] = '[DO NOT MERGE] ' + message_lines[0] 225 226 if args.bug: 227 message_lines.append('') 228 message_lines.append('Bug: http://b/{}'.format(args.bug)) 229 message_lines.append('Test: Build (TH)') 230 message = '\n'.join(message_lines) 231 232 # Invoke git: Delete current profile, add new profile and commit these 233 # changes. 234 if current_profile: 235 utils.check_call(['git', 'rm', current_profile]) 236 utils.check_call(['git', 'add', profdata]) 237 utils.check_call(['git', 'commit', '-m', message]) 238 239 if not args.skip_cleanup: 240 shutil.rmtree(extract_dir) 241 242 243if __name__ == '__main__': 244 main() 245