OLD | NEW |
---|---|
(Empty) | |
1 #! /usr/bin/env python | |
2 # Copyright 2016 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 """Pull a sandwich run's output directory's metrics from traces into a CSV. | |
7 | |
8 python pull_sandwich_metrics.py -h | |
mattcary
2016/02/12 09:59:47
With the #! line, this can be run directly without
gabadie
2016/02/12 10:39:11
Oups yes indeed... Done.
| |
9 """ | |
10 | |
11 import argparse | |
12 import csv | |
13 import json | |
14 import logging | |
15 import os | |
16 import sys | |
17 | |
18 | |
19 CATEGORIES = ['blink.user_timing', 'disabled-by-default-memory-infra'] | |
20 _CSV_FIELD_NAMES = [ | |
21 'id', | |
22 'total_load', | |
23 'onload', | |
24 'browser_malloc_avg', | |
25 'browser_malloc_max'] | |
26 | |
27 _TRACKED_EVENT_NAMES = set(['requestStart', 'loadEventStart', 'loadEventEnd']) | |
28 | |
29 def _GetBrowserPID(trace): | |
Benoit L
2016/02/12 10:26:41
nit: Here and below, 2 lines between top-level dec
gabadie
2016/02/12 10:39:11
Done.
| |
30 """Get the browser PID from a trace. | |
31 | |
32 Args: | |
33 trace: The cached trace. | |
34 | |
35 Returns: | |
36 The browser's PID as an integer. | |
37 """ | |
38 for event in trace['traceEvents']: | |
39 if event['cat'] != '__metadata' or event['name'] != 'process_name': | |
40 continue | |
41 if event['args']['name'] == 'Browser': | |
42 return event['pid'] | |
43 raise Exception('couldn\'t find browser\'s PID') | |
44 | |
45 def _GetBrowserDumpEvents(trace): | |
46 """Get the browser memory dump events from a trace. | |
47 | |
48 Args: | |
49 trace: The cached trace. | |
50 | |
51 Returns: | |
52 List of memory dump events. | |
53 """ | |
54 browser_pid = _GetBrowserPID(trace) | |
55 browser_dumps_events = [] | |
56 for event in trace['traceEvents']: | |
57 if event['cat'] != 'disabled-by-default-memory-infra': | |
58 continue | |
59 if event['ph'] != 'v' or event['name'] != 'periodic_interval': | |
60 continue | |
61 # Ignore dumps event for processes other than the browser process | |
Benoit L
2016/02/12 10:26:41
s/dumps event/dump events/
gabadie
2016/02/12 10:39:11
Done.
| |
62 if event['pid'] != browser_pid: | |
63 continue | |
64 browser_dumps_events.append(event) | |
65 if len(browser_dumps_events) == 0: | |
66 raise Exception('No browser dump events found.') | |
Benoit L
2016/02/12 10:26:41
ValueError, as pointed out below.
gabadie
2016/02/12 10:39:11
Done.
| |
67 return browser_dumps_events | |
68 | |
69 def _GetWebPageTrackedEvents(trace): | |
70 """Get the web page's tracked events from a trace. | |
71 | |
72 Args: | |
73 trace: The cached trace. | |
74 | |
75 Returns: | |
76 Dictionary all tracked events. | |
77 """ | |
78 main_frame = None | |
79 tracked_events = {} | |
80 for event in trace['traceEvents']: | |
81 if event['cat'] != 'blink.user_timing': | |
82 continue | |
83 event_name = event['name'] | |
84 # Ignore events until about:blank's unloadEventEnd that give the main | |
85 # frame id. | |
86 if not main_frame: | |
87 if event_name == 'unloadEventEnd': | |
88 main_frame = event['args']['frame'] | |
89 logging.info('found about:blank\'s event \'unloadEventEnd\'') | |
90 continue | |
91 # Ignore sub-frames events. requestStart don't have the frame set but it | |
92 # is fine since tracking the first one after about:blank's unloadEventEnd. | |
93 if 'frame' in event['args'] and event['args']['frame'] != main_frame: | |
94 continue | |
95 if event_name in _TRACKED_EVENT_NAMES and event_name not in tracked_events: | |
96 logging.info('found url\'s event \'%s\'' % event_name) | |
97 tracked_events[event_name] = event | |
98 assert len(tracked_events) == len(_TRACKED_EVENT_NAMES) | |
99 return tracked_events | |
100 | |
101 def _PullMetricsFromTrace(trace): | |
102 """Pulls all the metrics from a given trace. | |
103 | |
104 Args: | |
105 trace: The cached trace. | |
106 | |
107 Returns: | |
108 Dictionary with all _CSV_FIELD_NAMES's field set (except the 'id'). | |
109 """ | |
110 browser_dumps_events = _GetBrowserDumpEvents(trace) | |
Benoit L
2016/02/12 10:26:41
s/browser_dumps_events/browser_dump_events/
gabadie
2016/02/12 10:39:11
Done.
| |
111 web_page_tracked_events = _GetWebPageTrackedEvents(trace) | |
112 | |
113 browser_malloc_avg = 0 | |
Benoit L
2016/02/12 10:26:41
Arguably, this is not an average, but OK.
gabadie
2016/02/12 10:39:11
Renaming as *_sum. Done.
| |
114 browser_malloc_max = 0 | |
115 for dump_event in browser_dumps_events: | |
116 attr = dump_event['args']['dumps']['allocators']['malloc']['attrs']['size'] | |
117 assert attr['units'] == 'bytes' | |
118 size = int(attr['value'], 16) | |
Benoit L
2016/02/12 10:26:41
size is serialized as an hex string? Weird.
gabadie
2016/02/12 10:39:11
Totally agree.
| |
119 browser_malloc_avg += size | |
120 browser_malloc_max = max(browser_malloc_max, size) | |
121 | |
122 return { | |
123 'total_load': (web_page_tracked_events['loadEventEnd']['ts'] - | |
124 web_page_tracked_events['requestStart']['ts']), | |
125 'onload': (web_page_tracked_events['loadEventEnd']['ts'] - | |
126 web_page_tracked_events['loadEventStart']['ts']), | |
127 'browser_malloc_avg': browser_malloc_avg / float(len(browser_dumps_events)), | |
128 'browser_malloc_max': browser_malloc_max | |
129 } | |
130 | |
131 def _PullMetricsFromOutputDirectory(output_directory_path): | |
132 """Pulls all the metrics from all the traces of a sandwich run directory. | |
133 | |
134 Args: | |
135 output_directory_path: The sandwich run's output directory to pull the | |
136 metrics from. | |
137 | |
138 Returns: | |
139 List of dictionaries with all _CSV_FIELD_NAMES's field set. | |
140 """ | |
141 assert os.path.isdir(output_directory_path) | |
142 metrics = [] | |
143 for node_name in os.listdir(output_directory_path): | |
144 if not os.path.isdir(os.path.join(output_directory_path, node_name)): | |
145 continue | |
146 try: | |
147 page_id = int(node_name) | |
148 except Exception: | |
mattcary
2016/02/12 09:59:47
ValueError would be better than catching all excep
Benoit L
2016/02/12 10:26:41
Pokemon exception handling: gotta catch'em all!
gabadie
2016/02/12 10:39:11
Done.
mattcary
2016/02/12 10:41:11
But continuing isn't actually catching them... it'
| |
149 continue | |
150 trace_path = os.path.join(output_directory_path, node_name, 'trace.json') | |
151 if not os.path.isfile(trace_path): | |
152 continue | |
153 logging.info('processing \'%s\'' % trace_path) | |
154 with open(trace_path) as trace_file: | |
155 trace = json.load(trace_file) | |
156 trace_metrics = _PullMetricsFromTrace(trace) | |
157 trace_metrics['id'] = page_id | |
158 metrics.append(trace_metrics) | |
159 assert len(metrics) > 0, ('Looks like \'{}\' was not a sandwich ' + | |
160 'run directory.').format(output_directory_path) | |
161 return metrics | |
162 | |
163 def main(): | |
164 logging.basicConfig(level=logging.INFO) | |
165 | |
166 parser = argparse.ArgumentParser() | |
167 parser.add_argument('output', type=str, | |
168 help='Output directory of run_sandwich.py command.') | |
169 args = parser.parse_args() | |
170 | |
171 trace_metrics_list = _PullMetricsFromOutputDirectory(args.output) | |
172 trace_metrics_list.sort(key=lambda e: e['id']) | |
173 cs_file_path = os.path.join(args.output, 'trace_analysis.csv') | |
174 with open(cs_file_path, 'w') as csv_file: | |
175 writer = csv.DictWriter(csv_file, fieldnames=_CSV_FIELD_NAMES) | |
176 writer.writeheader() | |
177 for trace_metrics in trace_metrics_list: | |
178 writer.writerow(trace_metrics) | |
179 return 0 | |
180 | |
181 if __name__ == '__main__': | |
182 sys.exit(main()) | |
OLD | NEW |