1# Copyright (C) 2013 Google Inc. All rights reserved. 2# 3# Redistribution and use in source and binary forms, with or without 4# modification, are permitted provided that the following conditions are 5# met: 6# 7# * Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above 10# copyright notice, this list of conditions and the following disclaimer 11# in the documentation and/or other materials provided with the 12# distribution. 13# * Neither the name of Google Inc. nor the names of its 14# contributors may be used to endorse or promote products derived from 15# this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29import json 30import optparse 31 32from webkitpy.layout_tests.port import Port 33 34 35def main(host, argv): 36 parser = optparse.OptionParser(usage='%prog [times_ms.json]') 37 parser.add_option('-f', '--forward', action='store', type='int', 38 help='group times by first N directories of test') 39 parser.add_option('-b', '--backward', action='store', type='int', 40 help='group times by last N directories of test') 41 parser.add_option('--fastest', action='store', type='float', 42 help='print a list of tests that will take N % of the time') 43 44 epilog = """ 45 You can print out aggregate times per directory using the -f and -b 46 flags. The value passed to each flag indicates the "depth" of the flag, 47 similar to positive and negative arguments to python arrays. 48 49 For example, given fast/forms/week/week-input-type.html, -f 1 50 truncates to 'fast', -f 2 and -b 2 truncates to 'fast/forms', and -b 1 51 truncates to fast/forms/week . -f 0 truncates to '', which can be used 52 to produce a single total time for the run.""" 53 parser.epilog = '\n'.join(s.lstrip() for s in epilog.splitlines()) 54 55 options, args = parser.parse_args(argv) 56 57 port = host.port_factory.get() 58 if args and args[0]: 59 times_ms_path = args[0] 60 else: 61 times_ms_path = host.filesystem.join(port.results_directory(), 'times_ms.json') 62 63 times_trie = json.loads(host.filesystem.read_text_file(times_ms_path)) 64 65 times = convert_trie_to_flat_paths(times_trie) 66 67 if options.fastest: 68 if options.forward is None and options.backward is None: 69 options.forward = 0 70 print_fastest(host, port, options, times) 71 else: 72 print_times(host, options, times) 73 74 75def print_times(host, options, times): 76 by_key = times_by_key(times, options.forward, options.backward) 77 for key in sorted(by_key): 78 if key: 79 host.print_("%s %d" % (key, by_key[key])) 80 else: 81 host.print_("%d" % by_key[key]) 82 83 84def print_fastest(host, port, options, times): 85 total = times_by_key(times, 0, None)[''] 86 by_key = times_by_key(times, options.forward, options.backward) 87 keys_by_time = sorted(by_key, key=lambda k: (by_key[k], k)) 88 89 tests_by_key = {} 90 for test_name in sorted(times): 91 key = key_for(test_name, options.forward, options.backward) 92 if key in sorted(tests_by_key): 93 tests_by_key[key].append(test_name) 94 else: 95 tests_by_key[key] = [test_name] 96 97 fast_tests_by_key = {} 98 total_so_far = 0 99 per_key = total * options.fastest / (len(keys_by_time) * 100.0) 100 budget = 0 101 while keys_by_time: 102 budget += per_key 103 key = keys_by_time.pop(0) 104 tests_by_time = sorted(tests_by_key[key], key=lambda t: (times[t], t)) 105 fast_tests_by_key[key] = [] 106 while tests_by_time and total_so_far <= budget: 107 test = tests_by_time.pop(0) 108 test_time = times[test] 109 # Make sure test time > 0 so we don't include tests that are skipped. 110 if test_time and total_so_far + test_time <= budget: 111 fast_tests_by_key[key].append(test) 112 total_so_far += test_time 113 114 for k in sorted(fast_tests_by_key): 115 for t in fast_tests_by_key[k]: 116 host.print_("%s %d" % (t, times[t])) 117 return 118 119 120def key_for(path, forward, backward): 121 sep = Port.TEST_PATH_SEPARATOR 122 if forward is not None: 123 return sep.join(path.split(sep)[:-1][:forward]) 124 if backward is not None: 125 return sep.join(path.split(sep)[:-backward]) 126 return path 127 128 129def times_by_key(times, forward, backward): 130 by_key = {} 131 for test_name in times: 132 key = key_for(test_name, forward, backward) 133 if key in by_key: 134 by_key[key] += times[test_name] 135 else: 136 by_key[key] = times[test_name] 137 return by_key 138 139 140def convert_trie_to_flat_paths(trie, prefix=None): 141 result = {} 142 for name, data in trie.iteritems(): 143 if prefix: 144 name = prefix + "/" + name 145 if isinstance(data, int): 146 result[name] = data 147 else: 148 result.update(convert_trie_to_flat_paths(data, name)) 149 150 return result 151