OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2013 The Chromium Authors. All rights reserved. | 2 # Copyright 2013 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Parses CSV output from the loading_measurement and outputs interesting stats. | 6 """Parses CSV output from the loading_measurement and outputs interesting stats. |
7 | 7 |
8 Example usage: | 8 Example usage: |
9 $ tools/perf/run_measurement --browser=release \ | 9 $ tools/perf/run_measurement --browser=release \ |
10 --output-format=csv --output=/path/to/loading_measurement_output.csv \ | 10 --output-format=csv --output=/path/to/loading_measurement_output.csv \ |
(...skipping 11 matching lines...) Expand all Loading... | |
22 import sys | 22 import sys |
23 | 23 |
24 | 24 |
25 class LoadingMeasurementAnalyzer(object): | 25 class LoadingMeasurementAnalyzer(object): |
26 | 26 |
27 def __init__(self, input_file, options): | 27 def __init__(self, input_file, options): |
28 self.ranks = {} | 28 self.ranks = {} |
29 self.totals = collections.defaultdict(list) | 29 self.totals = collections.defaultdict(list) |
30 self.maxes = collections.defaultdict(list) | 30 self.maxes = collections.defaultdict(list) |
31 self.avgs = collections.defaultdict(list) | 31 self.avgs = collections.defaultdict(list) |
32 self.load_times = [] | |
33 self.cpu_times = [] | |
34 self.network_percents = [] | |
32 self.num_rows_parsed = 0 | 35 self.num_rows_parsed = 0 |
33 self.num_slowest_urls = options.num_slowest_urls | 36 self.num_slowest_urls = options.num_slowest_urls |
34 if options.rank_csv_file: | 37 if options.rank_csv_file: |
35 self._ParseRankCsvFile(os.path.expanduser(options.rank_csv_file)) | 38 self._ParseRankCsvFile(os.path.expanduser(options.rank_csv_file)) |
36 self._ParseInputFile(input_file, options) | 39 self._ParseInputFile(input_file, options) |
37 | 40 |
38 def _ParseInputFile(self, input_file, options): | 41 def _ParseInputFile(self, input_file, options): |
39 with open(input_file, 'r') as csvfile: | 42 with open(input_file, 'r') as csvfile: |
40 row_dict = csv.DictReader(csvfile) | 43 row_dict = csv.DictReader(csvfile) |
41 for row in row_dict: | 44 for row in row_dict: |
42 if (options.rank_limit and | 45 if (options.rank_limit and |
43 self._GetRank(row['url']) > options.rank_limit): | 46 self._GetRank(row['url']) > options.rank_limit): |
44 continue | 47 continue |
48 cpu_time = 0 | |
49 load_time = float(row['load_time (ms)']) | |
50 if load_time < 0: | |
51 print 'Skipping %s due to negative load time' % row['url'] | |
52 continue | |
45 for key, value in row.iteritems(): | 53 for key, value in row.iteritems(): |
46 if key in ('url', 'dom_content_loaded_time (ms)', 'load_time (ms)'): | 54 if key in ('url', 'load_time (ms)', 'dom_content_loaded_time (ms)'): |
47 continue | 55 continue |
48 if not value or value == '-': | 56 if not value or value == '-': |
49 continue | 57 continue |
58 value = float(value) | |
50 if '_avg' in key: | 59 if '_avg' in key: |
51 self.avgs[key].append((float(value), row['url'])) | 60 self.avgs[key].append((value, row['url'])) |
52 elif '_max' in key: | 61 elif '_max' in key: |
53 self.maxes[key].append((float(value), row['url'])) | 62 self.maxes[key].append((value, row['url'])) |
54 else: | 63 else: |
55 self.totals[key].append((float(value), row['url'])) | 64 self.totals[key].append((value, row['url'])) |
nduca
2013/07/22 23:37:00
so you define network time as the unaccounted-for
| |
65 cpu_time += value | |
66 self.load_times.append((load_time, row['url'])) | |
67 self.cpu_times.append((cpu_time, row['url'])) | |
68 if options.show_network: | |
69 network_time = load_time - cpu_time | |
70 self.totals['Network (ms)'].append((network_time, row['url'])) | |
71 self.network_percents.append((network_time / load_time, row['url'])) | |
56 self.num_rows_parsed += 1 | 72 self.num_rows_parsed += 1 |
57 if options.max_rows and self.num_rows_parsed == int(options.max_rows): | 73 if options.max_rows and self.num_rows_parsed == int(options.max_rows): |
58 break | 74 break |
59 | 75 |
60 def _ParseRankCsvFile(self, input_file): | 76 def _ParseRankCsvFile(self, input_file): |
61 with open(input_file, 'r') as csvfile: | 77 with open(input_file, 'r') as csvfile: |
62 for row in csv.reader(csvfile): | 78 for row in csv.reader(csvfile): |
63 assert len(row) == 2 | 79 assert len(row) == 2 |
64 self.ranks[row[1]] = int(row[0]) | 80 self.ranks[row[1]] = int(row[0]) |
65 | 81 |
66 def _GetRank(self, url): | 82 def _GetRank(self, url): |
67 url = url.replace('http://', '') | 83 url = url.replace('http://', '') |
68 if url in self.ranks: | 84 if url in self.ranks: |
69 return self.ranks[url] | 85 return self.ranks[url] |
70 return len(self.ranks) | 86 return len(self.ranks) |
71 | 87 |
72 def PrintSummary(self): | 88 def PrintSummary(self): |
73 sum_totals = {} | 89 sum_totals = {} |
74 for key, values in self.totals.iteritems(): | 90 for key, values in self.totals.iteritems(): |
75 sum_totals[key] = sum([v[0] for v in values]) | 91 sum_totals[key] = sum([v[0] for v in values]) |
76 total_time = sum(sum_totals.values()) | 92 total_cpu_time = sum([v[0] for v in self.cpu_times]) |
93 total_page_load_time = sum([v[0] for v in self.load_times]) | |
77 | 94 |
78 print | 95 print |
79 print 'Total URLs: ', self.num_rows_parsed | 96 print 'Total URLs: ', self.num_rows_parsed |
80 print 'Total time: %ds' % int(round(total_time / 1000)) | 97 print 'Total CPU time: %ds' % int(round(total_cpu_time / 1000)) |
98 print 'Total page load time: %ds' % int(round(total_page_load_time / 1000)) | |
99 print 'Average CPU time: %dms' % int(round( | |
100 total_cpu_time / self.num_rows_parsed)) | |
101 print 'Average page load time: %dms' % int(round( | |
102 total_page_load_time / self.num_rows_parsed)) | |
81 print | 103 print |
82 for key, value in sorted(sum_totals.iteritems(), reverse=True, | 104 for key, value in sorted(sum_totals.iteritems(), reverse=True, |
83 key=lambda i: i[1]): | 105 key=lambda i: i[1]): |
84 output_key = '%30s: ' % key.replace(' (ms)', '') | 106 output_key = '%30s: ' % key.replace(' (ms)', '') |
85 output_value = '%10ds ' % (value / 1000) | 107 output_value = '%10ds ' % (value / 1000) |
86 output_percent = '%.1f%%' % (100 * value / total_time) | 108 output_percent = '%.1f%%' % (100 * value / total_page_load_time) |
87 print output_key, output_value, output_percent | 109 print output_key, output_value, output_percent |
88 | 110 |
89 if not self.num_slowest_urls: | 111 if not self.num_slowest_urls: |
90 return | 112 return |
91 | 113 |
92 for key, values in sorted(self.totals.iteritems(), reverse=True, | 114 for key, values in sorted(self.totals.iteritems(), reverse=True, |
93 key=lambda i: sum_totals[i[0]]): | 115 key=lambda i: sum_totals[i[0]]): |
94 print | 116 print |
95 print 'Top %d slowest %s:' % (self.num_slowest_urls, | 117 print 'Top %d slowest %s:' % (self.num_slowest_urls, |
96 key.replace(' (ms)', '')) | 118 key.replace(' (ms)', '')) |
97 slowest = heapq.nlargest(self.num_slowest_urls, values) | 119 slowest = heapq.nlargest(self.num_slowest_urls, values) |
98 for value, url in slowest: | 120 for value, url in slowest: |
99 print '\t', '%dms\t' % value, url, '(#%s)' % self._GetRank(url) | 121 print '\t', '%dms\t' % value, url, '(#%s)' % self._GetRank(url) |
100 | 122 |
123 if self.network_percents: | |
124 print | |
125 print 'Top %d highest network to CPU time ratios:' % self.num_slowest_urls | |
126 for percent, url in sorted( | |
127 self.network_percents, reverse=True)[:self.num_slowest_urls]: | |
128 percent *= 100 | |
129 print '\t', '%.1f%%' % percent, url, '(#%s)' % self._GetRank(url) | |
130 | |
131 | |
101 def main(argv): | 132 def main(argv): |
102 prog_desc = 'Parses CSV output from the loading_measurement' | 133 prog_desc = 'Parses CSV output from the loading_measurement' |
103 parser = optparse.OptionParser(usage=('%prog [options]' + '\n\n' + prog_desc)) | 134 parser = optparse.OptionParser(usage=('%prog [options]' + '\n\n' + prog_desc)) |
104 | 135 |
105 parser.add_option('--max-rows', type='int', | 136 parser.add_option('--max-rows', type='int', |
106 help='Only process this many rows') | 137 help='Only process this many rows') |
107 parser.add_option('--num-slowest-urls', type='int', | 138 parser.add_option('--num-slowest-urls', type='int', |
108 help='Output this many slowest URLs for each category') | 139 help='Output this many slowest URLs for each category') |
109 parser.add_option('--rank-csv-file', help='A CSV file of <rank,url>') | 140 parser.add_option('--rank-csv-file', help='A CSV file of <rank,url>') |
110 parser.add_option('--rank-limit', type='int', | 141 parser.add_option('--rank-limit', type='int', |
111 help='Only process pages higher than this rank') | 142 help='Only process pages higher than this rank') |
143 parser.add_option('--show-network', action='store_true', | |
144 help='Whether to display Network as a category') | |
112 | 145 |
113 options, args = parser.parse_args(argv[1:]) | 146 options, args = parser.parse_args(argv[1:]) |
114 | 147 |
115 assert len(args) == 1, 'Must pass exactly one CSV file to analyze' | 148 assert len(args) == 1, 'Must pass exactly one CSV file to analyze' |
116 if options.rank_limit and not options.rank_csv_file: | 149 if options.rank_limit and not options.rank_csv_file: |
117 print 'Must pass --rank-csv-file with --rank-limit' | 150 print 'Must pass --rank-csv-file with --rank-limit' |
118 return 1 | 151 return 1 |
119 | 152 |
120 LoadingMeasurementAnalyzer(args[0], options).PrintSummary() | 153 LoadingMeasurementAnalyzer(args[0], options).PrintSummary() |
121 | 154 |
122 return 0 | 155 return 0 |
123 | 156 |
124 | 157 |
125 if __name__ == '__main__': | 158 if __name__ == '__main__': |
126 sys.exit(main(sys.argv)) | 159 sys.exit(main(sys.argv)) |
OLD | NEW |