1# Copyright 2011 Google Inc. All Rights Reserved. 2"""Script to build chrome with FDO and compare performance against no FDO.""" 3 4import getpass 5import optparse 6import os 7import sys 8 9import image_chromeos 10import setup_chromeos 11from cros_utils import command_executer 12from cros_utils import misc 13from cros_utils import logger 14 15 16class Patcher(object): 17 18 def __init__(self, dir_to_patch, patch_file): 19 self._dir_to_patch = dir_to_patch 20 self._patch_file = patch_file 21 self._base_patch_command = 'patch -p0 %%s < %s' % patch_file 22 self._ce = command_executer.GetCommandExecuter() 23 24 def _RunPatchCommand(self, args): 25 patch_command = self._base_patch_command % args 26 command = ('cd %s && %s' % (self._dir_to_patch, patch_command)) 27 return self._ce.RunCommand(command) 28 29 def _ApplyPatch(self, args): 30 full_args = '%s --dry-run' % args 31 ret = self._RunPatchCommand(full_args) 32 if ret: 33 raise RuntimeError('Patch dry run failed!') 34 ret = self._RunPatchCommand(args) 35 if ret: 36 raise RuntimeError('Patch application failed!') 37 38 def __enter__(self): 39 self._ApplyPatch('') 40 41 def __exit__(self, type, value, traceback): 42 self._ApplyPatch('-R') 43 44 45class FDOComparator(object): 46 47 def __init__(self, board, remotes, ebuild_version, plus_pgo, minus_pgo, 48 update_pgo, chromeos_root): 49 self._board = board 50 self._remotes = remotes 51 self._ebuild_version = ebuild_version 52 self._remote = remotes.split(',')[0] 53 self._chromeos_root = chromeos_root 54 self._profile_dir = 'profile_dir' 55 self._profile_path = os.path.join(self._chromeos_root, 'src', 'scripts', 56 os.path.basename(self._profile_dir)) 57 self._plus_pgo = plus_pgo 58 self._minus_pgo = minus_pgo 59 self._update_pgo = update_pgo 60 61 self._ce = command_executer.GetCommandExecuter() 62 self._l = logger.GetLogger() 63 64 def _CheckoutChromeOS(self): 65 if not os.path.exists(self._chromeos_root): 66 setup_chromeos_args = [setup_chromeos.__file__, 67 '--dir=%s' % self._chromeos_root, '--minilayout'] 68 setup_chromeos.Main(setup_chromeos_args) 69 70 def _BuildChromeOSUsingBinaries(self): 71 image_dir = misc.GetImageDir(self._chromeos_root, self._board) 72 command = 'equery-%s l chromeos' % self._board 73 ret = self._ce.ChrootRunCommand(self._chromeos_root, command) 74 if ret: 75 command = misc.GetSetupBoardCommand(self._board, usepkg=True) 76 ret = self._ce.ChrootRunCommand(self._chromeos_root, command) 77 if ret: 78 raise RuntimeError("Couldn't run setup_board!") 79 command = misc.GetBuildPackagesCommand(self._board, True) 80 ret = self._ce.ChrootRunCommand(self._chromeos_root, command) 81 if ret: 82 raise RuntimeError("Couldn't run build_packages!") 83 84 def _ReportMismatches(self, build_log): 85 mismatch_signature = '-Wcoverage-mismatch' 86 mismatches = build_log.count(mismatch_signature) 87 self._l.LogOutput('Total mismatches: %s' % mismatches) 88 stale_files = set([]) 89 for line in build_log.splitlines(): 90 if mismatch_signature in line: 91 filename = line.split(':')[0] 92 stale_files.add(filename) 93 self._l.LogOutput('Total stale files: %s' % len(stale_files)) 94 95 def _BuildChromeAndImage(self, 96 ebuild_version='', 97 env_dict={}, 98 cflags='', 99 cxxflags='', 100 ldflags='', 101 label='', 102 build_image_args=''): 103 env_string = misc.GetEnvStringFromDict(env_dict) 104 if not label: 105 label = ' '.join([env_string, cflags, cxxflags, ldflags, ebuild_version]) 106 label = label.strip() 107 label = misc.GetFilenameFromString(label) 108 if not misc.DoesLabelExist(self._chromeos_root, self._board, label): 109 build_chrome_browser_args = ['--clean', '--chromeos_root=%s' % 110 self._chromeos_root, '--board=%s' % 111 self._board, '--env=%r' % env_string, 112 '--cflags=%r' % cflags, '--cxxflags=%r' % 113 cxxflags, '--ldflags=%r' % ldflags, 114 '--ebuild_version=%s' % ebuild_version, 115 '--build_image_args=%s' % build_image_args] 116 117 build_chrome_browser = os.path.join( 118 os.path.dirname(__file__), '..', 'build_chrome_browser.py') 119 command = 'python %s %s' % (build_chrome_browser, 120 ' '.join(build_chrome_browser_args)) 121 ret, out, err = self._ce.RunCommandWOutput(command) 122 if '-fprofile-use' in cxxflags: 123 self._ReportMismatches(out) 124 125 if ret: 126 raise RuntimeError("Couldn't build chrome browser!") 127 misc.LabelLatestImage(self._chromeos_root, self._board, label) 128 return label 129 130 def _TestLabels(self, labels): 131 experiment_file = 'pgo_experiment.txt' 132 experiment_header = """ 133 board: %s 134 remote: %s 135 """ % (self._board, self._remotes) 136 experiment_tests = """ 137 benchmark: desktopui_PyAutoPerfTests { 138 iterations: 1 139 } 140 """ 141 142 with open(experiment_file, 'w') as f: 143 print >> f, experiment_header 144 print >> f, experiment_tests 145 for label in labels: 146 # TODO(asharif): Fix crosperf so it accepts labels with symbols 147 crosperf_label = label 148 crosperf_label = crosperf_label.replace('-', 'minus') 149 crosperf_label = crosperf_label.replace('+', 'plus') 150 experiment_image = """ 151 %s { 152 chromeos_image: %s 153 } 154 """ % (crosperf_label, os.path.join( 155 misc.GetImageDir(self._chromeos_root, self._board), label, 156 'chromiumos_test_image.bin')) 157 print >> f, experiment_image 158 crosperf = os.path.join( 159 os.path.dirname(__file__), '..', 'crosperf', 'crosperf') 160 command = '%s %s' % (crosperf, experiment_file) 161 ret = self._ce.RunCommand(command) 162 if ret: 163 raise RuntimeError("Couldn't run crosperf!") 164 165 def _ImageRemote(self, label): 166 image_path = os.path.join( 167 misc.GetImageDir(self._chromeos_root, 168 self._board), label, 'chromiumos_test_image.bin') 169 image_chromeos_args = [image_chromeos.__file__, '--chromeos_root=%s' % 170 self._chromeos_root, '--image=%s' % image_path, 171 '--remote=%s' % self._remote, 172 '--board=%s' % self._board] 173 image_chromeos.Main(image_chromeos_args) 174 175 def _ProfileRemote(self): 176 profile_cycler = os.path.join( 177 os.path.dirname(__file__), 'profile_cycler.py') 178 profile_cycler_args = ['--chromeos_root=%s' % self._chromeos_root, 179 '--cycler=all', '--board=%s' % self._board, 180 '--profile_dir=%s' % self._profile_path, 181 '--remote=%s' % self._remote] 182 command = 'python %s %s' % (profile_cycler, ' '.join(profile_cycler_args)) 183 ret = self._ce.RunCommand(command) 184 if ret: 185 raise RuntimeError("Couldn't profile cycler!") 186 187 def _BuildGenerateImage(self): 188 # TODO(asharif): add cflags as well. 189 labels_list = ['fprofile-generate', self._ebuild_version] 190 label = '_'.join(labels_list) 191 generate_label = self._BuildChromeAndImage( 192 env_dict={'USE': 'chrome_internal -pgo pgo_generate'}, 193 label=label, 194 ebuild_version=self._ebuild_version, 195 build_image_args='--rootfs_boost_size=400') 196 return generate_label 197 198 def _BuildUseImage(self): 199 ctarget = misc.GetCtargetFromBoard(self._board, self._chromeos_root) 200 chroot_profile_dir = os.path.join('/home/%s/trunk' % getpass.getuser(), 201 'src', 'scripts', self._profile_dir, 202 ctarget) 203 cflags = ('-fprofile-use ' 204 '-fprofile-correction ' 205 '-Wno-error ' 206 '-fdump-tree-optimized-blocks-lineno ' 207 '-fdump-ipa-profile-blocks-lineno ' 208 '-fno-vpt ' 209 '-fprofile-dir=%s' % chroot_profile_dir) 210 labels_list = ['updated_pgo', self._ebuild_version] 211 label = '_'.join(labels_list) 212 pgo_use_label = self._BuildChromeAndImage( 213 env_dict={'USE': 'chrome_internal -pgo'}, 214 cflags=cflags, 215 cxxflags=cflags, 216 ldflags=cflags, 217 label=label, 218 ebuild_version=self._ebuild_version) 219 return pgo_use_label 220 221 def DoAll(self): 222 self._CheckoutChromeOS() 223 self._BuildChromeOSUsingBinaries() 224 labels = [] 225 226 if self._minus_pgo: 227 minus_pgo = self._BuildChromeAndImage( 228 env_dict={'USE': 'chrome_internal -pgo'}, 229 ebuild_version=self._ebuild_version) 230 labels.append(minus_pgo) 231 if self._plus_pgo: 232 plus_pgo = self._BuildChromeAndImage( 233 env_dict={'USE': 'chrome_internal pgo'}, 234 ebuild_version=self._ebuild_version) 235 labels.append(plus_pgo) 236 237 if self._update_pgo: 238 if not os.path.exists(self._profile_path): 239 # Build Chrome with -fprofile-generate 240 generate_label = self._BuildGenerateImage() 241 # Image to the remote box. 242 self._ImageRemote(generate_label) 243 # Profile it using all page cyclers. 244 self._ProfileRemote() 245 246 # Use the profile directory to rebuild it. 247 updated_pgo_label = self._BuildUseImage() 248 labels.append(updated_pgo_label) 249 250 # Run crosperf on all images now. 251 self._TestLabels(labels) 252 return 0 253 254 255def Main(argv): 256 """The main function.""" 257 # Common initializations 258 ### command_executer.InitCommandExecuter(True) 259 command_executer.InitCommandExecuter() 260 parser = optparse.OptionParser() 261 parser.add_option('--remote', 262 dest='remote', 263 help='Remote machines to run tests on.') 264 parser.add_option('--board', 265 dest='board', 266 default='x86-zgb', 267 help='The target board.') 268 parser.add_option('--ebuild_version', 269 dest='ebuild_version', 270 default='', 271 help='The Chrome ebuild version to use.') 272 parser.add_option('--plus_pgo', 273 dest='plus_pgo', 274 action='store_true', 275 default=False, 276 help='Build USE=+pgo.') 277 parser.add_option('--minus_pgo', 278 dest='minus_pgo', 279 action='store_true', 280 default=False, 281 help='Build USE=-pgo.') 282 parser.add_option('--update_pgo', 283 dest='update_pgo', 284 action='store_true', 285 default=False, 286 help='Update pgo and build Chrome with the update.') 287 parser.add_option('--chromeos_root', 288 dest='chromeos_root', 289 default=False, 290 help='The chromeos root directory') 291 options, _ = parser.parse_args(argv) 292 if not options.board: 293 print 'Please give a board.' 294 return 1 295 if not options.remote: 296 print 'Please give at least one remote machine.' 297 return 1 298 if not options.chromeos_root: 299 print 'Please provide the chromeos root directory.' 300 return 1 301 if not any((options.minus_pgo, options.plus_pgo, options.update_pgo)): 302 print 'Please provide at least one build option.' 303 return 1 304 fc = FDOComparator(options.board, options.remote, options.ebuild_version, 305 options.plus_pgo, options.minus_pgo, options.update_pgo, 306 os.path.expanduser(options.chromeos_root)) 307 return fc.DoAll() 308 309 310if __name__ == '__main__': 311 retval = Main(sys.argv) 312 sys.exit(retval) 313