xref: /aosp_15_r20/external/toolchain-utils/afdo_tools/run_afdo_tryjob.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
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