1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2019 The ChromiumOS Authors 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 52 53import argparse 54import collections 55import pipes 56import subprocess 57import sys 58import time 59 60 61def main(): 62 parser = argparse.ArgumentParser( 63 description=__doc__, 64 formatter_class=argparse.RawDescriptionHelpFormatter, 65 ) 66 parser.add_argument( 67 "--force_no_patches", 68 action="store_true", 69 help="Run even if no patches are provided", 70 ) 71 parser.add_argument( 72 "--tag_profiles_with_current_time", 73 action="store_true", 74 help="Perf profile names will have the current time added to them.", 75 ) 76 parser.add_argument( 77 "--use_afdo_generation_stage", 78 action="store_true", 79 help="Perf profiles will be automatically converted to AFDO profiles.", 80 ) 81 parser.add_argument( 82 "-g", 83 "--patch", 84 action="append", 85 default=[], 86 help="A patch to add to the AFDO run", 87 ) 88 parser.add_argument( 89 "-n", 90 "--dry_run", 91 action="store_true", 92 help="Just print the command that would be run", 93 ) 94 args = parser.parse_args() 95 96 dry_run = args.dry_run 97 force_no_patches = args.force_no_patches 98 tag_profiles_with_current_time = args.tag_profiles_with_current_time 99 use_afdo_generation_stage = args.use_afdo_generation_stage 100 user_patches = args.patch 101 102 if tag_profiles_with_current_time and use_afdo_generation_stage: 103 raise ValueError( 104 "You can't tag profiles with the time + have " "afdo-generate" 105 ) 106 107 if not tag_profiles_with_current_time and not use_afdo_generation_stage: 108 print( 109 "Neither current_time nor afdo_generate asked for. Assuming you " 110 "prefer current time tagging." 111 ) 112 print("You have 5 seconds to cancel and try again.") 113 print() 114 if not dry_run: 115 time.sleep(5) 116 tag_profiles_with_current_time = True 117 118 patches = [ 119 # Send profiles to localmirror instead of chromeos-prebuilt. This should 120 # always be done, since sending profiles into production is bad. :) 121 # https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/1436158 122 1436158, 123 # Force profile generation. Otherwise, we'll decide to not spawn off the 124 # perf hwtests. 125 # https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1313291 126 1313291, 127 ] 128 129 if tag_profiles_with_current_time: 130 # Tags the profiles with the current time of day. As detailed in the 131 # docstring, this is desirable unless you're sure that this is the only 132 # experimental profile that will be generated today. 133 # https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/1436157 134 patches.append(1436157) 135 136 if use_afdo_generation_stage: 137 # Make the profile generation stage look in localmirror, instead of having 138 # it look in chromeos-prebuilt. Without this, we'll never upload 139 # chrome.debug or try to generate an AFDO profile. 140 # https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1436583 141 patches.append(1436583) 142 143 if not user_patches and not force_no_patches: 144 raise ValueError( 145 "No patches given; pass --force_no_patches to force a " "tryjob" 146 ) 147 148 for patch in user_patches: 149 # We accept two formats. Either a URL that ends with a number, or a number. 150 if patch.startswith("http"): 151 patch = patch.split("/")[-1] 152 patches.append(int(patch)) 153 154 count = collections.Counter(patches) 155 too_many = [k for k, v in count.items() if v > 1] 156 if too_many: 157 too_many.sort() 158 raise ValueError( 159 "Patch(es) asked for application more than once: %s" % too_many 160 ) 161 162 args = [ 163 "cros", 164 "tryjob", 165 ] 166 167 for patch in patches: 168 args += ["-g", str(patch)] 169 170 args += [ 171 "--nochromesdk", 172 "--hwtest", 173 "chell-chrome-pfq-tryjob", 174 ] 175 176 print(" ".join(pipes.quote(a) for a in args)) 177 if not dry_run: 178 sys.exit(subprocess.call(args)) 179 180 181if __name__ == "__main__": 182 main() 183