1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2019 The Chromium OS Authors. All rights reserved.
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
9from __future__ import print_function
10
11import os
12import subprocess
13import sys
14import time
15import traceback
16
17import chroot
18from llvm_bisection import BisectionExitStatus
19import llvm_bisection
20from update_all_tryjobs_with_auto import GetPathToUpdateAllTryjobsWithAutoScript
21
22# Used to re-try for 'llvm_bisection.py' to attempt to launch more tryjobs.
23BISECTION_RETRY_TIME_SECS = 10 * 60
24
25# Wait time to then poll each tryjob whose 'status' value is 'pending'.
26POLL_RETRY_TIME_SECS = 30 * 60
27
28# The number of attempts for 'llvm_bisection.py' to launch more tryjobs.
29#
30# It is reset (break out of the `for` loop/ exit the program) if successfully
31# launched more tryjobs or bisection is finished (no more revisions between
32# start and end of the bisection).
33BISECTION_ATTEMPTS = 3
34
35# The limit for updating all tryjobs whose 'status' is 'pending'.
36#
37# If the time that has passed for polling exceeds this value, then the program
38# will exit with the appropriate exit code.
39POLLING_LIMIT_SECS = 18 * 60 * 60
40
41
42def main():
43  """Bisects LLVM using the result of `cros buildresult` of each tryjob.
44
45  Raises:
46    AssertionError: The script was run inside the chroot.
47  """
48
49  chroot.VerifyOutsideChroot()
50
51  args_output = llvm_bisection.GetCommandLineArgs()
52
53  exec_update_tryjobs = [
54      GetPathToUpdateAllTryjobsWithAutoScript(), '--chroot_path',
55      args_output.chroot_path, '--last_tested', args_output.last_tested
56  ]
57
58  if os.path.isfile(args_output.last_tested):
59    print('Resuming bisection for %s' % args_output.last_tested)
60  else:
61    print('Starting a new bisection for %s' % args_output.last_tested)
62
63  while True:
64    if os.path.isfile(args_output.last_tested):
65      update_start_time = time.time()
66
67      # Update all tryjobs whose status is 'pending' to the result of `cros
68      # buildresult`.
69      while True:
70        print('\nAttempting to update all tryjobs whose "status" is '
71              '"pending":')
72        print('-' * 40)
73
74        update_ret = subprocess.call(exec_update_tryjobs)
75
76        print('-' * 40)
77
78        # Successfully updated all tryjobs whose 'status' was 'pending'/ no
79        # updates were needed (all tryjobs already have been updated).
80        if update_ret == 0:
81          break
82
83        delta_time = time.time() - update_start_time
84
85        if delta_time > POLLING_LIMIT_SECS:
86          print('Unable to update tryjobs whose status is "pending" to '
87                'the result of `cros buildresult`.')
88
89          # Something is wrong with updating the tryjobs's 'status' via
90          # `cros buildresult` (e.g. network issue, etc.).
91          sys.exit(1)
92
93        print('Sleeping for %d minutes.' % (POLL_RETRY_TIME_SECS // 60))
94        time.sleep(POLL_RETRY_TIME_SECS)
95
96    # Launch more tryjobs if possible to narrow down the bad commit/revision or
97    # terminate the bisection because the bad commit/revision was found.
98    for cur_try in range(1, BISECTION_ATTEMPTS + 1):
99      try:
100        print('\nAttempting to launch more tryjobs if possible:')
101        print('-' * 40)
102
103        bisection_ret = llvm_bisection.main(args_output)
104
105        print('-' * 40)
106
107        # Exit code 126 means that there are no more revisions to test between
108        # 'start' and 'end', so bisection is complete.
109        if bisection_ret == BisectionExitStatus.BISECTION_COMPLETE.value:
110          sys.exit(0)
111
112        # Successfully launched more tryjobs.
113        break
114      except Exception:
115        traceback.print_exc()
116
117        print('-' * 40)
118
119        # Exceeded the number of times to launch more tryjobs.
120        if cur_try == BISECTION_ATTEMPTS:
121          print('Unable to continue bisection.')
122
123          sys.exit(1)
124
125        num_retries_left = BISECTION_ATTEMPTS - cur_try
126
127        print('Retries left to continue bisection %d.' % num_retries_left)
128
129        print('Sleeping for %d minutes.' % (BISECTION_RETRY_TIME_SECS // 60))
130        time.sleep(BISECTION_RETRY_TIME_SECS)
131
132
133if __name__ == '__main__':
134  main()
135