1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2019 The Chromium OS Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Spawns off an AFDO tryjob. 8 9This tryjob will cause perf profiles to be collected as though we were running 10our benchmark AFDO pipeline. Depending on the set of flags that you use, 11different things will happen. Any artifacts will land in 12gs://chromeos-localmirror/distfiles/afdo/experimental/approximation 13 14This tryjob will generate *either* a full (AFDO profile, perf.data, 15chrome.debug) combo, or just a perf.data, depending on the arguments you feed 16it. 17 18The thing to be careful of is that our localmirror bucket is shared between 19everyone, so it's super easy for two AFDO profile runs to 'collide'. Hence, if 20you provide the --tag_profiles_with_current_time flag, the script will generate 21*only* a perf.data, but that perf.data will have a timestamp (with second 22resolution) on it. This makes collisions super unlikely. 23 24If you'd like to know which perf profile was yours: 25 - Go to the tryjob output page 26 - Look for 'HWTest [AFDO_Record]' 27 - Click on its stdout 28 - Find "Links to test logs:" in the stdout 29 - Follow the link by telemetry_AFDOGenerate 30 - Find and click the link to debug/autoserv.DEBUG 31 - Look for a gs:// link ending in `.perf.data` with a compression suffix 32 (currently `.bz2`; maybe `.xz` eventually). That's the gs:// path to your 33 perf profile. 34 35The downside to this option is that there's no (reliable + trivial to 36implement) way for the bot that converts AFDO profiles into perf profiles to 37know the profile to choose. So, you're stuck generating a profile on your own. 38We have a tool for just that. Please see `generate_afdo_from_tryjob.py`. 39 40If you don't like that tool, generating your own profile isn't super difficult. 41Just grab the perf profile that your logs note from gs://, grab a copy of 42chrome.debug from your tryjob, and use `create_llvm_prof` to create a profile. 43 44On the other hand, if you're 100% sure that your profile won't collide, you can 45make your life easier by providing --use_afdo_generation_stage. 46 47If you provide neither --use_afdo_generation_stage nor 48--tag_profiles_with_current_time, --tag_profiles_with_current_time is implied, 49since it's safer. 50""" 51 52from __future__ import print_function 53 54import argparse 55import collections 56import pipes 57import subprocess 58import sys 59import time 60 61 62def main(): 63 parser = argparse.ArgumentParser( 64 description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) 65 parser.add_argument( 66 '--force_no_patches', 67 action='store_true', 68 help='Run even if no patches are provided') 69 parser.add_argument( 70 '--tag_profiles_with_current_time', 71 action='store_true', 72 help='Perf profile names will have the current time added to them.') 73 parser.add_argument( 74 '--use_afdo_generation_stage', 75 action='store_true', 76 help='Perf profiles will be automatically converted to AFDO profiles.') 77 parser.add_argument( 78 '-g', 79 '--patch', 80 action='append', 81 default=[], 82 help='A patch to add to the AFDO run') 83 parser.add_argument( 84 '-n', 85 '--dry_run', 86 action='store_true', 87 help='Just print the command that would be run') 88 args = parser.parse_args() 89 90 dry_run = args.dry_run 91 force_no_patches = args.force_no_patches 92 tag_profiles_with_current_time = args.tag_profiles_with_current_time 93 use_afdo_generation_stage = args.use_afdo_generation_stage 94 user_patches = args.patch 95 96 if tag_profiles_with_current_time and use_afdo_generation_stage: 97 raise ValueError("You can't tag profiles with the time + have " 98 'afdo-generate') 99 100 if not tag_profiles_with_current_time and not use_afdo_generation_stage: 101 print('Neither current_time nor afdo_generate asked for. Assuming you ' 102 'prefer current time tagging.') 103 print('You have 5 seconds to cancel and try again.') 104 print() 105 if not dry_run: 106 time.sleep(5) 107 tag_profiles_with_current_time = True 108 109 patches = [ 110 # Send profiles to localmirror instead of chromeos-prebuilt. This should 111 # always be done, since sending profiles into production is bad. :) 112 # https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/1436158 113 1436158, 114 # Force profile generation. Otherwise, we'll decide to not spawn off the 115 # perf hwtests. 116 # https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1313291 117 1313291, 118 ] 119 120 if tag_profiles_with_current_time: 121 # Tags the profiles with the current time of day. As detailed in the 122 # docstring, this is desirable unless you're sure that this is the only 123 # experimental profile that will be generated today. 124 # https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/1436157 125 patches.append(1436157) 126 127 if use_afdo_generation_stage: 128 # Make the profile generation stage look in localmirror, instead of having 129 # it look in chromeos-prebuilt. Without this, we'll never upload 130 # chrome.debug or try to generate an AFDO profile. 131 # https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1436583 132 patches.append(1436583) 133 134 if not user_patches and not force_no_patches: 135 raise ValueError('No patches given; pass --force_no_patches to force a ' 136 'tryjob') 137 138 for patch in user_patches: 139 # We accept two formats. Either a URL that ends with a number, or a number. 140 if patch.startswith('http'): 141 patch = patch.split('/')[-1] 142 patches.append(int(patch)) 143 144 count = collections.Counter(patches) 145 too_many = [k for k, v in count.items() if v > 1] 146 if too_many: 147 too_many.sort() 148 raise ValueError( 149 'Patch(es) asked for application more than once: %s' % too_many) 150 151 args = [ 152 'cros', 153 'tryjob', 154 ] 155 156 for patch in patches: 157 args += ['-g', str(patch)] 158 159 args += [ 160 '--nochromesdk', 161 '--hwtest', 162 'chell-chrome-pfq-tryjob', 163 ] 164 165 print(' '.join(pipes.quote(a) for a in args)) 166 if not dry_run: 167 sys.exit(subprocess.call(args)) 168 169 170if __name__ == '__main__': 171 main() 172