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