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