binary_search_perforce.py revision b860c4b6d6bda8bda32bcbf27adf3d8118c81ed7
1#!/usr/bin/python2 2"""Module of binary serch for perforce.""" 3from __future__ import print_function 4 5import math 6import optparse 7import os 8import re 9import sys 10import tempfile 11 12from cros_utils import command_executer 13from cros_utils import logger 14 15def _GetP4ClientSpec(client_name, p4_paths): 16 p4_string = "" 17 for p4_path in p4_paths: 18 if " " not in p4_path: 19 p4_string += " -a %s" % p4_path 20 else: 21 p4_string += " -a \"" + (" //" + client_name + "/").join(p4_path) + "\"" 22 23 return p4_string 24 25 26def GetP4Command(client_name, p4_port, p4_paths, checkoutdir, 27 p4_snapshot=""): 28 command = "" 29 30 if p4_snapshot: 31 command += "mkdir -p " + checkoutdir 32 for p4_path in p4_paths: 33 real_path = p4_path[1] 34 if real_path.endswith("..."): 35 real_path = real_path.replace("/...", "") 36 command += ("; mkdir -p " + checkoutdir + "/" + 37 os.path.dirname(real_path)) 38 command += ("&& rsync -lr " + p4_snapshot + "/" + real_path + 39 " " + checkoutdir + "/" + os.path.dirname(real_path)) 40 return command 41 42 command += " export P4CONFIG=.p4config" 43 command += " && mkdir -p " + checkoutdir 44 command += " && cd " + checkoutdir 45 command += " && cp ${HOME}/.p4config ." 46 command += " && chmod u+w .p4config" 47 command += " && echo \"P4PORT=" + p4_port + "\" >> .p4config" 48 command += " && echo \"P4CLIENT=" + client_name + "\" >> .p4config" 49 command += (" && g4 client " + 50 _GetP4ClientSpec(client_name, p4_paths)) 51 command += " && g4 sync " 52 command += " && cd -" 53 return command 54 55class BinarySearchPoint(object): 56 """Class of binary search point.""" 57 def __init__(self, revision, status, tag=None): 58 self.revision = revision 59 self.status = status 60 self.tag = tag 61 62class BinarySearcher(object): 63 """Class of binary searcher.""" 64 def __init__(self): 65 self.sorted_list = [] 66 self.index_log = [] 67 self.status_log = [] 68 self.skipped_indices = [] 69 self.current = 0 70 self.points = {} 71 self.lo = 0 72 self.hi = 0 73 74 def SetSortedList(self, sorted_list): 75 assert len(sorted_list) > 0 76 self.sorted_list = sorted_list 77 self.index_log = [] 78 self.hi = len(sorted_list) - 1 79 self.lo = 0 80 self.points = {} 81 for i in range(len(self.sorted_list)): 82 bsp = BinarySearchPoint(self.sorted_list[i], -1, "Not yet done.") 83 self.points[i] = bsp 84 85 def SetStatus(self, status, tag=None): 86 message = ("Revision: %s index: %d returned: %d" % 87 (self.sorted_list[self.current], 88 self.current, 89 status)) 90 logger.GetLogger().LogOutput(message) 91 assert status == 0 or status == 1 or status == 2 92 self.index_log.append(self.current) 93 self.status_log.append(status) 94 bsp = BinarySearchPoint(self.sorted_list[self.current], status, tag) 95 self.points[self.current] = bsp 96 97 if status == 2: 98 self.skipped_indices.append(self.current) 99 100 if status == 0 or status == 1: 101 if status == 0: 102 self.lo = self.current + 1 103 elif status == 1: 104 self.hi = self.current 105 logger.GetLogger().LogOutput("lo: %d hi: %d\n" % (self.lo, self.hi)) 106 self.current = (self.lo + self.hi)/2 107 108 if self.lo == self.hi: 109 message = ("Search complete. First bad version: %s" 110 " at index: %d" % 111 (self.sorted_list[self.current], 112 self.lo)) 113 logger.GetLogger().LogOutput(message) 114 return True 115 116 for index in range(self.lo, self.hi): 117 if index not in self.skipped_indices: 118 return False 119 logger.GetLogger().LogOutput( 120 "All skipped indices between: %d and %d\n" % (self.lo, self.hi)) 121 return True 122 123 # Does a better job with chromeos flakiness. 124 def GetNextFlakyBinary(self): 125 t = (self.lo, self.current, self.hi) 126 q = [t] 127 while len(q): 128 element = q.pop(0) 129 if element[1] in self.skipped_indices: 130 # Go top 131 to_add = (element[0], (element[0] + element[1]) / 2, element[1]) 132 q.append(to_add) 133 # Go bottom 134 to_add = (element[1], (element[1] + element[2]) / 2, element[2]) 135 q.append(to_add) 136 else: 137 self.current = element[1] 138 return 139 assert len(q), "Queue should never be 0-size!" 140 141 def GetNextFlakyLinear(self): 142 current_hi = self.current 143 current_lo = self.current 144 while True: 145 if current_hi < self.hi and current_hi not in self.skipped_indices: 146 self.current = current_hi 147 break 148 if current_lo >= self.lo and current_lo not in self.skipped_indices: 149 self.current = current_lo 150 break 151 if current_lo < self.lo and current_hi >= self.hi: 152 break 153 154 current_hi += 1 155 current_lo -= 1 156 157 def GetNext(self): 158 self.current = (self.hi + self.lo) / 2 159 # Try going forward if current is skipped. 160 if self.current in self.skipped_indices: 161 self.GetNextFlakyBinary() 162 163 # TODO: Add an estimated time remaining as well. 164 message = ("Estimated tries: min: %d max: %d\n" % 165 (1 + math.log(self.hi - self.lo, 2), 166 self.hi - self.lo - len(self.skipped_indices))) 167 logger.GetLogger().LogOutput(message) 168 message = ("lo: %d hi: %d current: %d version: %s\n" % 169 (self.lo, self.hi, self.current, 170 self.sorted_list[self.current])) 171 logger.GetLogger().LogOutput(message) 172 logger.GetLogger().LogOutput(str(self)) 173 return self.sorted_list[self.current] 174 175 def SetLoRevision(self, lo_revision): 176 self.lo = self.sorted_list.index(lo_revision) 177 def SetHiRevision(self, hi_revision): 178 self.hi = self.sorted_list.index(hi_revision) 179 def GetAllPoints(self): 180 to_return = "" 181 for i in range(len(self.sorted_list)): 182 to_return += ("%d %d %s\n" % 183 (self.points[i].status, 184 i, 185 self.points[i].revision)) 186 187 return to_return 188 def __str__(self): 189 to_return = "" 190 to_return += "Current: %d\n" % self.current 191 to_return += str(self.index_log) + "\n" 192 revision_log = [] 193 for index in self.index_log: 194 revision_log.append(self.sorted_list[index]) 195 to_return += str(revision_log) + "\n" 196 to_return += str(self.status_log) + "\n" 197 to_return += "Skipped indices:\n" 198 to_return += str(self.skipped_indices) + "\n" 199 to_return += self.GetAllPoints() 200 return to_return 201 202class RevisionInfo(object): 203 """Class of reversion info.""" 204 def __init__(self, date, client, description): 205 self.date = date 206 self.client = client 207 self.description = description 208 self.status = -1 209 210class VCSBinarySearcher(object): 211 """Class of VCS binary searcher.""" 212 def __init__(self): 213 self.bs = BinarySearcher() 214 self.rim = {} 215 self.current_ce = None 216 self.checkout_dir = None 217 self.current_revision = None 218 219 def Initialize(self): 220 pass 221 def GetNextRevision(self): 222 pass 223 def CheckoutRevision(self, revision): 224 pass 225 def SetStatus(self, status): 226 pass 227 def Cleanup(self): 228 pass 229 def SetGoodRevision(self, revision): 230 if revision is None: 231 return 232 assert revision in self.bs.sorted_list 233 self.bs.SetLoRevision(revision) 234 def SetBadRevision(self, revision): 235 if revision is None: 236 return 237 assert revision in self.bs.sorted_list 238 self.bs.SetHiRevision(revision) 239 240class P4BinarySearcher(VCSBinarySearcher): 241 """Class of P4 binary searcher.""" 242 def __init__(self, p4_port, p4_paths, test_command): 243 VCSBinarySearcher.__init__(self) 244 self.p4_port = p4_port 245 self.p4_paths = p4_paths 246 self.test_command = test_command 247 self.checkout_dir = tempfile.mkdtemp() 248 self.ce = command_executer.GetCommandExecuter() 249 self.client_name = "binary-searcher-$HOSTNAME-$USER" 250 self.job_log_root = "/home/asharif/www/coreboot_triage/" 251 self.changes = None 252 253 def Initialize(self): 254 self.Cleanup() 255 command = GetP4Command(self.client_name, self.p4_port, 256 self.p4_paths, 1, self.checkout_dir) 257 self.ce.RunCommand(command) 258 command = "cd %s && g4 changes ..." % self.checkout_dir 259 _, out, _ = self.ce.RunCommandWOutput(command) 260 self.changes = re.findall(r"Change (\d+)", out) 261 change_infos = re.findall(r"Change (\d+) on ([\d/]+) by " 262 r"([^\s]+) ('[^']*')", out) 263 for change_info in change_infos: 264 ri = RevisionInfo(change_info[1], change_info[2], change_info[3]) 265 self.rim[change_info[0]] = ri 266 # g4 gives changes in reverse chronological order. 267 self.changes.reverse() 268 self.bs.SetSortedList(self.changes) 269 def SetStatus(self, status): 270 self.rim[self.current_revision].status = status 271 return self.bs.SetStatus(status) 272 def GetNextRevision(self): 273 next_revision = self.bs.GetNext() 274 self.current_revision = next_revision 275 return next_revision 276 def CleanupCLs(self): 277 if not os.path.isfile(self.checkout_dir + "/.p4config"): 278 command = "cd %s" % self.checkout_dir 279 command += " && cp ${HOME}/.p4config ." 280 command += " && echo \"P4PORT=" + self.p4_port + "\" >> .p4config" 281 command += " && echo \"P4CLIENT=" + self.client_name + "\" >> .p4config" 282 self.ce.RunCommand(command) 283 command = "cd %s" % self.checkout_dir 284 command += "; g4 changes -c %s" % self.client_name 285 _, out, _ = self.ce.RunCommandWOUTPUOT(command) 286 changes = re.findall(r"Change (\d+)", out) 287 if len(changes) != 0: 288 command = "cd %s" % self.checkout_dir 289 for change in changes: 290 command += "; g4 revert -c %s" % change 291 self.ce.RunCommand(command) 292 293 def CleanupClient(self): 294 command = "cd %s" % self.checkout_dir 295 command += "; g4 revert ..." 296 command += "; g4 client -d %s" % self.client_name 297 self.ce.RunCommand(command) 298 299 def Cleanup(self): 300 self.CleanupCLs() 301 self.CleanupClient() 302 303 def __str__(self): 304 to_return = "" 305 for change in self.changes: 306 ri = self.rim[change] 307 if ri.status == -1: 308 to_return = "%s\t%d\n" % (change, ri.status) 309 else: 310 to_return += ("%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\n" % 311 (change, 312 ri.status, 313 ri.date, 314 ri.client, 315 ri.description, 316 self.job_log_root + change + ".cmd", 317 self.job_log_root + change + ".out", 318 self.job_log_root + change + ".err")) 319 return to_return 320 321 322 323class P4GCCBinarySearcher(P4BinarySearcher): 324 """Class of P4 gcc binary searcher.""" 325 # TODO: eventually get these patches from g4 instead of creating them manually 326 def HandleBrokenCLs(self, current_revision): 327 cr = int(current_revision) 328 problematic_ranges = [] 329 problematic_ranges.append([44528, 44539]) 330 problematic_ranges.append([44528, 44760]) 331 problematic_ranges.append([44335, 44882]) 332 command = "pwd" 333 for pr in problematic_ranges: 334 if cr in range(pr[0], pr[1]): 335 patch_file = "/home/asharif/triage_tool/%d-%d.patch" % (pr[0], pr[1]) 336 f = open(patch_file) 337 patch = f.read() 338 f.close() 339 files = re.findall("--- (//.*)", patch) 340 command += "; cd %s" % self.checkout_dir 341 for f in files: 342 command += "; g4 open %s" % f 343 command += "; patch -p2 < %s" % patch_file 344 self.current_ce.RunCommand(command) 345 346 def CheckoutRevision(self, current_revision): 347 job_logger = logger.Logger(self.job_log_root, 348 current_revision, 349 True, subdir="") 350 self.current_ce = command_executer.GetCommandExecuter(job_logger) 351 352 self.CleanupCLs() 353 # Change the revision of only the gcc part of the toolchain. 354 command = ("cd %s/gcctools/google_vendor_src_branch/gcc " 355 "&& g4 revert ...; g4 sync @%s" % 356 (self.checkout_dir, current_revision)) 357 self.current_ce.RunCommand(command) 358 359 self.HandleBrokenCLs(current_revision) 360 361 362def Main(argv): 363 """The main function.""" 364 # Common initializations 365### command_executer.InitCommandExecuter(True) 366 ce = command_executer.GetCommandExecuter() 367 368 parser = optparse.OptionParser() 369 parser.add_option("-n", "--num_tries", dest="num_tries", 370 default="100", 371 help="Number of tries.") 372 parser.add_option("-g", "--good_revision", dest="good_revision", 373 help="Last known good revision.") 374 parser.add_option("-b", "--bad_revision", dest="bad_revision", 375 help="Last known bad revision.") 376 parser.add_option("-s", 377 "--script", 378 dest="script", 379 help="Script to run for every version.") 380 [options, _] = parser.parse_args(argv) 381 # First get all revisions 382 p4_paths = ["//depot2/gcctools/google_vendor_src_branch/gcc/gcc-4.4.3/...", 383 "//depot2/gcctools/google_vendor_src_branch/binutils/" 384 "binutils-2.20.1-mobile/...", 385 "//depot2/gcctools/google_vendor_src_branch/" 386 "binutils/binutils-20100303/..."] 387 p4gccbs = P4GCCBinarySearcher("perforce2:2666", p4_paths, "") 388 389 390 # Main loop: 391 terminated = False 392 num_tries = int(options.num_tries) 393 script = os.path.expanduser(options.script) 394 395 try: 396 p4gccbs.Initialize() 397 p4gccbs.SetGoodRevision(options.good_revision) 398 p4gccbs.SetBadRevision(options.bad_revision) 399 while not terminated and num_tries > 0: 400 current_revision = p4gccbs.GetNextRevision() 401 402 # Now run command to get the status 403 ce = command_executer.GetCommandExecuter() 404 command = "%s %s" % (script, p4gccbs.checkout_dir) 405 status = ce.RunCommand(command) 406 message = ("Revision: %s produced: %d status\n" % 407 (current_revision, status)) 408 logger.GetLogger().LogOutput(message) 409 terminated = p4gccbs.SetStatus(status) 410 num_tries -= 1 411 logger.GetLogger().LogOutput(str(p4gccbs)) 412 413 if not terminated: 414 logger.GetLogger().LogOutput("Tries: %d expired." % num_tries) 415 logger.GetLogger().LogOutput(str(p4gccbs.bs)) 416 except (KeyboardInterrupt, SystemExit): 417 logger.GetLogger().LogOutput("Cleaning up...") 418 finally: 419 logger.GetLogger().LogOutput(str(p4gccbs.bs)) 420 status = p4gccbs.Cleanup() 421 422 423if __name__ == "__main__": 424 Main(sys.argv) 425