Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(16)

Side by Side Diff: Tools/Scripts/webkitpy/tool/commands/rebaseline.py

Issue 308793004: Optimize baselines in parallel. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: remove stray edits Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 # Copyright (c) 2010 Google Inc. All rights reserved. 1 # Copyright (c) 2010 Google Inc. All rights reserved.
2 # 2 #
3 # Redistribution and use in source and binary forms, with or without 3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are 4 # modification, are permitted provided that the following conditions are
5 # met: 5 # met:
6 # 6 #
7 # * Redistributions of source code must retain the above copyright 7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer. 8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above 9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer 10 # copyright notice, this list of conditions and the following disclaimer
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
65 platform_options = factory.platform_options(use_globs=True) 65 platform_options = factory.platform_options(use_globs=True)
66 66
67 results_directory_option = optparse.make_option("--results-directory", help= "Local results directory to use") 67 results_directory_option = optparse.make_option("--results-directory", help= "Local results directory to use")
68 68
69 suffixes_option = optparse.make_option("--suffixes", default=','.join(BASELI NE_SUFFIX_LIST), action="store", 69 suffixes_option = optparse.make_option("--suffixes", default=','.join(BASELI NE_SUFFIX_LIST), action="store",
70 help="Comma-separated-list of file types to rebaseline") 70 help="Comma-separated-list of file types to rebaseline")
71 71
72 def __init__(self, options=None): 72 def __init__(self, options=None):
73 super(AbstractRebaseliningCommand, self).__init__(options=options) 73 super(AbstractRebaseliningCommand, self).__init__(options=options)
74 self._baseline_suffix_list = BASELINE_SUFFIX_LIST 74 self._baseline_suffix_list = BASELINE_SUFFIX_LIST
75 self._scm_changes = {'add': [], 'delete': [], 'remove-lines': []}
76
77 def _add_to_scm(self, path):
Dirk Pranke 2014/05/30 23:35:11 I would probably use different names, like "rememb
ojan 2014/05/31 00:02:56 I went with add_to_scm_later.
78 self._scm_changes['add'].append(path)
79
80 def _delete_from_scm(self, path):
81 self._scm_changes['delete'].append(path)
75 82
76 83
77 class BaseInternalRebaselineCommand(AbstractRebaseliningCommand): 84 class BaseInternalRebaselineCommand(AbstractRebaseliningCommand):
78 def __init__(self): 85 def __init__(self):
79 super(BaseInternalRebaselineCommand, self).__init__(options=[ 86 super(BaseInternalRebaselineCommand, self).__init__(options=[
80 self.results_directory_option, 87 self.results_directory_option,
81 self.suffixes_option, 88 self.suffixes_option,
82 optparse.make_option("--builder", help="Builder to pull new baseline s from"), 89 optparse.make_option("--builder", help="Builder to pull new baseline s from"),
83 optparse.make_option("--test", help="Test to rebaseline"), 90 optparse.make_option("--test", help="Test to rebaseline"),
84 ]) 91 ])
85 self._scm_changes = {'add': [], 'remove-lines': []}
86
87 def _add_to_scm(self, path):
88 self._scm_changes['add'].append(path)
89 92
90 def _baseline_directory(self, builder_name): 93 def _baseline_directory(self, builder_name):
91 port = self._tool.port_factory.get_from_builder_name(builder_name) 94 port = self._tool.port_factory.get_from_builder_name(builder_name)
92 override_dir = builders.rebaseline_override_dir(builder_name) 95 override_dir = builders.rebaseline_override_dir(builder_name)
93 if override_dir: 96 if override_dir:
94 return self._tool.filesystem.join(port.layout_tests_dir(), 'platform ', override_dir) 97 return self._tool.filesystem.join(port.layout_tests_dir(), 'platform ', override_dir)
95 return port.baseline_version_dir() 98 return port.baseline_version_dir()
96 99
97 def _test_root(self, test_name): 100 def _test_root(self, test_name):
98 return self._tool.filesystem.splitext(test_name)[0] 101 return self._tool.filesystem.splitext(test_name)[0]
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after
224 print json.dumps(self._scm_changes) 227 print json.dumps(self._scm_changes)
225 228
226 229
227 class OptimizeBaselines(AbstractRebaseliningCommand): 230 class OptimizeBaselines(AbstractRebaseliningCommand):
228 name = "optimize-baselines" 231 name = "optimize-baselines"
229 help_text = "Reshuffles the baselines for the given tests to use as litte sp ace on disk as possible." 232 help_text = "Reshuffles the baselines for the given tests to use as litte sp ace on disk as possible."
230 show_in_main_help = True 233 show_in_main_help = True
231 argument_names = "TEST_NAMES" 234 argument_names = "TEST_NAMES"
232 235
233 def __init__(self): 236 def __init__(self):
234 super(OptimizeBaselines, self).__init__(options=[self.suffixes_option] + self.platform_options) 237 super(OptimizeBaselines, self).__init__(options=[
238 self.suffixes_option,
239 optparse.make_option('--no-modify-scm', action='store_true', default =False, help='Dump SCM commands as JSON instead of '),
240 ] + self.platform_options)
235 241
236 def _optimize_baseline(self, optimizer, test_name): 242 def _optimize_baseline(self, optimizer, test_name):
237 for suffix in self._baseline_suffix_list: 243 for suffix in self._baseline_suffix_list:
238 baseline_name = _baseline_name(self._tool.filesystem, test_name, suf fix) 244 baseline_name = _baseline_name(self._tool.filesystem, test_name, suf fix)
239 if not optimizer.optimize(baseline_name): 245 succeeded, files_to_delete, files_to_add = optimizer.optimize(baseli ne_name)
246 if not succeeded:
240 print "Heuristics failed to optimize %s" % baseline_name 247 print "Heuristics failed to optimize %s" % baseline_name
248 return files_to_delete, files_to_add
241 249
242 def execute(self, options, args, tool): 250 def execute(self, options, args, tool):
243 self._baseline_suffix_list = options.suffixes.split(',') 251 self._baseline_suffix_list = options.suffixes.split(',')
244 port_names = tool.port_factory.all_port_names(options.platform) 252 port_names = tool.port_factory.all_port_names(options.platform)
245 if not port_names: 253 if not port_names:
246 print "No port names match '%s'" % options.platform 254 print "No port names match '%s'" % options.platform
247 return 255 return
248 256
249 optimizer = BaselineOptimizer(tool, port_names) 257 optimizer = BaselineOptimizer(tool, port_names, skip_scm_commands=option s.no_modify_scm)
250 port = tool.port_factory.get(port_names[0]) 258 port = tool.port_factory.get(port_names[0])
251 for test_name in port.tests(args): 259 for test_name in port.tests(args):
252 _log.info("Optimizing %s" % test_name) 260 _log.info("Optimizing %s" % test_name)
253 self._optimize_baseline(optimizer, test_name) 261 files_to_delete, files_to_add = self._optimize_baseline(optimizer, t est_name)
262 for path in files_to_delete:
263 self._delete_from_scm(path)
264 for path in files_to_add:
265 self._add_to_scm(path)
266
267 print json.dumps(self._scm_changes)
254 268
255 269
256 class AnalyzeBaselines(AbstractRebaseliningCommand): 270 class AnalyzeBaselines(AbstractRebaseliningCommand):
257 name = "analyze-baselines" 271 name = "analyze-baselines"
258 help_text = "Analyzes the baselines for the given tests and prints results t hat are identical." 272 help_text = "Analyzes the baselines for the given tests and prints results t hat are identical."
259 show_in_main_help = True 273 show_in_main_help = True
260 argument_names = "TEST_NAMES" 274 argument_names = "TEST_NAMES"
261 275
262 def __init__(self): 276 def __init__(self):
263 super(AnalyzeBaselines, self).__init__(options=[ 277 super(AnalyzeBaselines, self).__init__(options=[
(...skipping 17 matching lines...) Expand all
281 elif options.missing: 295 elif options.missing:
282 self._write("%s: (no baselines found)" % baseline_name) 296 self._write("%s: (no baselines found)" % baseline_name)
283 297
284 def execute(self, options, args, tool): 298 def execute(self, options, args, tool):
285 self._baseline_suffix_list = options.suffixes.split(',') 299 self._baseline_suffix_list = options.suffixes.split(',')
286 port_names = tool.port_factory.all_port_names(options.platform) 300 port_names = tool.port_factory.all_port_names(options.platform)
287 if not port_names: 301 if not port_names:
288 print "No port names match '%s'" % options.platform 302 print "No port names match '%s'" % options.platform
289 return 303 return
290 304
291 self._baseline_optimizer = self._optimizer_class(tool, port_names) 305 self._baseline_optimizer = self._optimizer_class(tool, port_names, skip_ scm_commands=False)
292 self._port = tool.port_factory.get(port_names[0]) 306 self._port = tool.port_factory.get(port_names[0])
293 for test_name in self._port.tests(args): 307 for test_name in self._port.tests(args):
294 self._analyze_baseline(options, test_name) 308 self._analyze_baseline(options, test_name)
295 309
296 310
297 class AbstractParallelRebaselineCommand(AbstractRebaseliningCommand): 311 class AbstractParallelRebaselineCommand(AbstractRebaseliningCommand):
298 # not overriding execute() - pylint: disable=W0223 312 # not overriding execute() - pylint: disable=W0223
299 313
300 def __init__(self, options=None): 314 def __init__(self, options=None):
301 super(AbstractParallelRebaselineCommand, self).__init__(options=options) 315 super(AbstractParallelRebaselineCommand, self).__init__(options=options)
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
366 suffixes = ','.join(actual_failures_suffixes) 380 suffixes = ','.join(actual_failures_suffixes)
367 cmd_line = ['--suffixes', suffixes, '--builder', builder, '- -test', test] 381 cmd_line = ['--suffixes', suffixes, '--builder', builder, '- -test', test]
368 if options.results_directory: 382 if options.results_directory:
369 cmd_line.extend(['--results-directory', options.results_ directory]) 383 cmd_line.extend(['--results-directory', options.results_ directory])
370 if options.verbose: 384 if options.verbose:
371 cmd_line.append('--verbose') 385 cmd_line.append('--verbose')
372 copy_baseline_commands.append(tuple([[path_to_webkit_patch, 'copy-existing-baselines-internal'] + cmd_line, cwd])) 386 copy_baseline_commands.append(tuple([[path_to_webkit_patch, 'copy-existing-baselines-internal'] + cmd_line, cwd]))
373 rebaseline_commands.append(tuple([[path_to_webkit_patch, 're baseline-test-internal'] + cmd_line, cwd])) 387 rebaseline_commands.append(tuple([[path_to_webkit_patch, 're baseline-test-internal'] + cmd_line, cwd]))
374 return copy_baseline_commands, rebaseline_commands 388 return copy_baseline_commands, rebaseline_commands
375 389
376 def _files_to_add(self, command_results): 390 def _files_to_add(self, command_results):
Dirk Pranke 2014/05/30 23:35:11 since we're now returning the files to delete as w
ojan 2014/05/31 00:02:56 I called it _serial_commands.
377 files_to_add = set() 391 files_to_add = set()
392 files_to_delete = set()
378 lines_to_remove = {} 393 lines_to_remove = {}
379 for output in [result[1].split('\n') for result in command_results]: 394 for output in [result[1].split('\n') for result in command_results]:
380 file_added = False 395 file_added = False
381 for line in output: 396 for line in output:
382 try: 397 try:
383 if line: 398 if line:
384 parsed_line = json.loads(line) 399 parsed_line = json.loads(line)
385 if 'add' in parsed_line: 400 if 'add' in parsed_line:
386 files_to_add.update(parsed_line['add']) 401 files_to_add.update(parsed_line['add'])
402 if 'delete' in parsed_line:
403 files_to_delete.update(parsed_line['delete'])
387 if 'remove-lines' in parsed_line: 404 if 'remove-lines' in parsed_line:
388 for line_to_remove in parsed_line['remove-lines']: 405 for line_to_remove in parsed_line['remove-lines']:
389 test = line_to_remove['test'] 406 test = line_to_remove['test']
390 builder = line_to_remove['builder'] 407 builder = line_to_remove['builder']
391 if test not in lines_to_remove: 408 if test not in lines_to_remove:
392 lines_to_remove[test] = [] 409 lines_to_remove[test] = []
393 lines_to_remove[test].append(builder) 410 lines_to_remove[test].append(builder)
394 file_added = True 411 file_added = True
395 except ValueError: 412 except ValueError:
396 _log.debug('"%s" is not a JSON object, ignoring' % line) 413 _log.debug('"%s" is not a JSON object, ignoring' % line)
397 414
398 if not file_added: 415 if not file_added:
399 _log.debug('Could not add file based off output "%s"' % output) 416 _log.debug('Could not add file based off output "%s"' % output)
400 417
401 return list(files_to_add), lines_to_remove 418 return list(files_to_add), list(files_to_delete), lines_to_remove
402 419
403 def _optimize_baselines(self, test_prefix_list, verbose=False): 420 def _optimize_baselines(self, test_prefix_list, verbose=False):
404 # We don't run this in parallel because modifying the SCM in parallel is unreliable. 421 optimize_commands = []
405 for test in test_prefix_list: 422 for test in test_prefix_list:
406 all_suffixes = set() 423 all_suffixes = set()
407 for builder in self._builders_to_fetch_from(test_prefix_list[test]): 424 for builder in self._builders_to_fetch_from(test_prefix_list[test]):
408 all_suffixes.update(self._suffixes_for_actual_failures(test, bui lder, test_prefix_list[test][builder])) 425 all_suffixes.update(self._suffixes_for_actual_failures(test, bui lder, test_prefix_list[test][builder]))
426
409 # FIXME: We should propagate the platform options as well. 427 # FIXME: We should propagate the platform options as well.
410 self._run_webkit_patch(['optimize-baselines', '--suffixes', ','.join (all_suffixes), test], verbose) 428 cmd_line = ['--no-modify-scm', '--suffixes', ','.join(all_suffixes), test]
429 if verbose:
430 cmd_line.append('--verbose')
431
432 path_to_webkit_patch = self._tool.path()
433 cwd = self._tool.scm().checkout_root
434 optimize_commands.append(tuple([[path_to_webkit_patch, 'optimize-bas elines'] + cmd_line, cwd]))
435
436 # TODO: add test that shows 'delete' getting populated.
Dirk Pranke 2014/05/30 23:35:11 can we actually write this test now, rather than l
ojan 2014/05/31 00:02:56 Whoops. I meant to do this. I use TODO instead of
437 return optimize_commands
411 438
412 def _update_expectations_files(self, lines_to_remove): 439 def _update_expectations_files(self, lines_to_remove):
413 # FIXME: This routine is way too expensive. We're creating N ports and N TestExpectations 440 # FIXME: This routine is way too expensive. We're creating N ports and N TestExpectations
414 # objects and (re-)writing the actual expectations file N times, for eac h test we update. 441 # objects and (re-)writing the actual expectations file N times, for eac h test we update.
415 # We should be able to update everything in memory, once, and then write the file out a single time. 442 # We should be able to update everything in memory, once, and then write the file out a single time.
416 for test in lines_to_remove: 443 for test in lines_to_remove:
417 for builder in lines_to_remove[test]: 444 for builder in lines_to_remove[test]:
418 port = self._tool.port_factory.get_from_builder_name(builder) 445 port = self._tool.port_factory.get_from_builder_name(builder)
419 path = port.path_to_generic_test_expectations_file() 446 path = port.path_to_generic_test_expectations_file()
420 expectations = TestExpectations(port, include_overrides=False) 447 expectations = TestExpectations(port, include_overrides=False)
(...skipping 23 matching lines...) Expand all
444 return (SKIP in full_expectations.get_expectations(test) and 471 return (SKIP in full_expectations.get_expectations(test) and
445 SKIP not in generic_expectations.get_expectations(test)) 472 SKIP not in generic_expectations.get_expectations(test))
446 473
447 def _run_in_parallel_and_update_scm(self, commands): 474 def _run_in_parallel_and_update_scm(self, commands):
448 command_results = self._tool.executive.run_in_parallel(commands) 475 command_results = self._tool.executive.run_in_parallel(commands)
449 log_output = '\n'.join(result[2] for result in command_results).replace( '\n\n', '\n') 476 log_output = '\n'.join(result[2] for result in command_results).replace( '\n\n', '\n')
450 for line in log_output.split('\n'): 477 for line in log_output.split('\n'):
451 if line: 478 if line:
452 print >> sys.stderr, line # FIXME: Figure out how to log proper ly. 479 print >> sys.stderr, line # FIXME: Figure out how to log proper ly.
453 480
454 files_to_add, lines_to_remove = self._files_to_add(command_results) 481 files_to_add, files_to_delete, lines_to_remove = self._files_to_add(comm and_results)
Dirk Pranke 2014/05/30 23:35:11 see note on the naming of "self._files_to_add()",
ojan 2014/05/31 00:02:56 Done.
482 if files_to_delete:
483 self._tool.scm().delete_list(files_to_delete)
455 if files_to_add: 484 if files_to_add:
456 self._tool.scm().add_list(list(files_to_add)) 485 self._tool.scm().add_list(files_to_add)
457 if lines_to_remove: 486 if lines_to_remove:
458 self._update_expectations_files(lines_to_remove) 487 self._update_expectations_files(lines_to_remove)
459 488
460 def _rebaseline(self, options, test_prefix_list): 489 def _rebaseline(self, options, test_prefix_list):
461 for test, builders_to_check in sorted(test_prefix_list.items()): 490 for test, builders_to_check in sorted(test_prefix_list.items()):
462 _log.info("Rebaselining %s" % test) 491 _log.info("Rebaselining %s" % test)
463 for builder, suffixes in sorted(builders_to_check.items()): 492 for builder, suffixes in sorted(builders_to_check.items()):
464 _log.debug(" %s: %s" % (builder, ",".join(suffixes))) 493 _log.debug(" %s: %s" % (builder, ",".join(suffixes)))
465 494
466 copy_baseline_commands, rebaseline_commands = self._rebaseline_commands( test_prefix_list, options) 495 copy_baseline_commands, rebaseline_commands = self._rebaseline_commands( test_prefix_list, options)
467 if copy_baseline_commands: 496 if copy_baseline_commands:
468 self._run_in_parallel_and_update_scm(copy_baseline_commands) 497 self._run_in_parallel_and_update_scm(copy_baseline_commands)
469 if rebaseline_commands: 498 if rebaseline_commands:
470 self._run_in_parallel_and_update_scm(rebaseline_commands) 499 self._run_in_parallel_and_update_scm(rebaseline_commands)
471
472 if options.optimize: 500 if options.optimize:
473 self._optimize_baselines(test_prefix_list, options.verbose) 501 self._run_in_parallel_and_update_scm(self._optimize_baselines(test_p refix_list, options.verbose))
474 502
475 def _suffixes_for_actual_failures(self, test, builder_name, existing_suffixe s): 503 def _suffixes_for_actual_failures(self, test, builder_name, existing_suffixe s):
476 actual_results = self.builder_data()[builder_name].actual_results(test) 504 actual_results = self.builder_data()[builder_name].actual_results(test)
477 if not actual_results: 505 if not actual_results:
478 return set() 506 return set()
479 return set(existing_suffixes) & TestExpectations.suffixes_for_actual_exp ectations_string(actual_results) 507 return set(existing_suffixes) & TestExpectations.suffixes_for_actual_exp ectations_string(actual_results)
480 508
481 509
482 class RebaselineJson(AbstractParallelRebaselineCommand): 510 class RebaselineJson(AbstractParallelRebaselineCommand):
483 name = "rebaseline-json" 511 name = "rebaseline-json"
(...skipping 397 matching lines...) Expand 10 before | Expand all | Expand 10 after
881 if options.verbose: 909 if options.verbose:
882 rebaseline_command.append('--verbose') 910 rebaseline_command.append('--verbose')
883 # Use call instead of run_command so that stdout doesn't get swa llowed. 911 # Use call instead of run_command so that stdout doesn't get swa llowed.
884 tool.executive.call(rebaseline_command) 912 tool.executive.call(rebaseline_command)
885 except: 913 except:
886 traceback.print_exc(file=sys.stderr) 914 traceback.print_exc(file=sys.stderr)
887 # Sometimes git crashes and leaves us on a detached head. 915 # Sometimes git crashes and leaves us on a detached head.
888 tool.scm().checkout_branch(old_branch_name) 916 tool.scm().checkout_branch(old_branch_name)
889 917
890 time.sleep(self.SLEEP_TIME_IN_SECONDS) 918 time.sleep(self.SLEEP_TIME_IN_SECONDS)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698