• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""Performs bisection on LLVM based off a .JSON file."""
8
9
10import enum
11import json
12import os
13import subprocess
14import sys
15import time
16import traceback
17
18import chroot
19import llvm_bisection
20from llvm_bisection import BisectionExitStatus
21import update_tryjob_status
22
23
24# Used to re-try for 'llvm_bisection.py' to attempt to launch more tryjobs.
25BISECTION_RETRY_TIME_SECS = 10 * 60
26
27# Wait time to then poll each tryjob whose 'status' value is 'pending'.
28POLL_RETRY_TIME_SECS = 30 * 60
29
30# The number of attempts for 'llvm_bisection.py' to launch more tryjobs.
31#
32# It is reset (break out of the `for` loop/ exit the program) if successfully
33# launched more tryjobs or bisection is finished (no more revisions between
34# start and end of the bisection).
35BISECTION_ATTEMPTS = 3
36
37# The limit for updating all tryjobs whose 'status' is 'pending'.
38#
39# If the time that has passed for polling exceeds this value, then the program
40# will exit with the appropriate exit code.
41POLLING_LIMIT_SECS = 18 * 60 * 60
42
43
44class BuilderStatus(enum.Enum):
45    """Actual values given via 'cros buildresult'."""
46
47    PASS = "pass"
48    FAIL = "fail"
49    RUNNING = "running"
50
51
52builder_status_mapping = {
53    BuilderStatus.PASS.value: update_tryjob_status.TryjobStatus.GOOD.value,
54    BuilderStatus.FAIL.value: update_tryjob_status.TryjobStatus.BAD.value,
55    BuilderStatus.RUNNING.value: update_tryjob_status.TryjobStatus.PENDING.value,
56}
57
58
59def GetBuildResult(chroot_path, buildbucket_id):
60    """Returns the conversion of the result of 'cros buildresult'."""
61
62    # Calls 'cros buildresult' to get the status of the tryjob.
63    try:
64        tryjob_json = subprocess.check_output(
65            [
66                "cros_sdk",
67                "--",
68                "cros",
69                "buildresult",
70                "--buildbucket-id",
71                str(buildbucket_id),
72                "--report",
73                "json",
74            ],
75            cwd=chroot_path,
76            stderr=subprocess.STDOUT,
77            encoding="UTF-8",
78        )
79    except subprocess.CalledProcessError as err:
80        if "No build found. Perhaps not started" not in err.output:
81            raise
82        return None
83
84    tryjob_content = json.loads(tryjob_json)
85
86    build_result = str(tryjob_content["%d" % buildbucket_id]["status"])
87
88    # The string returned by 'cros buildresult' might not be in the mapping.
89    if build_result not in builder_status_mapping:
90        raise ValueError(
91            '"cros buildresult" return value is invalid: %s' % build_result
92        )
93
94    return builder_status_mapping[build_result]
95
96
97def main():
98    """Bisects LLVM using the result of `cros buildresult` of each tryjob.
99
100    Raises:
101      AssertionError: The script was run inside the chroot.
102    """
103
104    chroot.VerifyOutsideChroot()
105
106    args_output = llvm_bisection.GetCommandLineArgs()
107
108    if os.path.isfile(args_output.last_tested):
109        print("Resuming bisection for %s" % args_output.last_tested)
110    else:
111        print("Starting a new bisection for %s" % args_output.last_tested)
112
113    while True:
114        # Update the status of existing tryjobs
115        if os.path.isfile(args_output.last_tested):
116            update_start_time = time.time()
117            with open(args_output.last_tested) as json_file:
118                json_dict = json.load(json_file)
119            while True:
120                print(
121                    '\nAttempting to update all tryjobs whose "status" is '
122                    '"pending":'
123                )
124                print("-" * 40)
125
126                completed = True
127                for tryjob in json_dict["jobs"]:
128                    if (
129                        tryjob["status"]
130                        == update_tryjob_status.TryjobStatus.PENDING.value
131                    ):
132                        status = GetBuildResult(
133                            args_output.chroot_path, tryjob["buildbucket_id"]
134                        )
135                        if status:
136                            tryjob["status"] = status
137                        else:
138                            completed = False
139
140                print("-" * 40)
141
142                # Proceed to the next step if all the existing tryjobs have completed.
143                if completed:
144                    break
145
146                delta_time = time.time() - update_start_time
147
148                if delta_time > POLLING_LIMIT_SECS:
149                    # Something is wrong with updating the tryjobs's 'status' via
150                    # `cros buildresult` (e.g. network issue, etc.).
151                    sys.exit("Failed to update pending tryjobs.")
152
153                print("-" * 40)
154                print("Sleeping for %d minutes." % (POLL_RETRY_TIME_SECS // 60))
155                time.sleep(POLL_RETRY_TIME_SECS)
156
157            # There should always be update from the tryjobs launched in the
158            # last iteration.
159            temp_filename = "%s.new" % args_output.last_tested
160            with open(temp_filename, "w") as temp_file:
161                json.dump(
162                    json_dict, temp_file, indent=4, separators=(",", ": ")
163                )
164            os.rename(temp_filename, args_output.last_tested)
165
166        # Launch more tryjobs.
167        for cur_try in range(1, BISECTION_ATTEMPTS + 1):
168            try:
169                print("\nAttempting to launch more tryjobs if possible:")
170                print("-" * 40)
171
172                bisection_ret = llvm_bisection.main(args_output)
173
174                print("-" * 40)
175
176                # Stop if the bisection has completed.
177                if (
178                    bisection_ret
179                    == BisectionExitStatus.BISECTION_COMPLETE.value
180                ):
181                    sys.exit(0)
182
183                # Successfully launched more tryjobs.
184                break
185            except Exception:
186                traceback.print_exc()
187
188                print("-" * 40)
189
190                # Exceeded the number of times to launch more tryjobs.
191                if cur_try == BISECTION_ATTEMPTS:
192                    sys.exit("Unable to continue bisection.")
193
194                num_retries_left = BISECTION_ATTEMPTS - cur_try
195
196                print(
197                    "Retries left to continue bisection %d." % num_retries_left
198                )
199
200                print(
201                    "Sleeping for %d minutes."
202                    % (BISECTION_RETRY_TIME_SECS // 60)
203                )
204                time.sleep(BISECTION_RETRY_TIME_SECS)
205
206
207if __name__ == "__main__":
208    main()
209