Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #! /usr/bin/env python | 1 #! /usr/bin/env python |
| 2 # Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 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 """Pull a sandwich run's output directory's metrics from traces into a CSV. | 6 """Pull a sandwich run's output directory's metrics from traces into a CSV. |
| 7 | 7 |
| 8 python pull_sandwich_metrics.py -h | 8 python pull_sandwich_metrics.py -h |
| 9 """ | 9 """ |
| 10 | 10 |
| 11 import argparse | 11 import argparse |
| 12 import csv | 12 import csv |
| 13 import json | 13 import json |
| 14 import logging | 14 import logging |
| 15 import os | 15 import os |
| 16 import sys | 16 import sys |
| 17 | 17 |
| 18 import loading_trace as loading_trace_module | |
| 19 import tracing | |
| 20 | |
| 18 | 21 |
| 19 CATEGORIES = ['blink.user_timing', 'disabled-by-default-memory-infra'] | 22 CATEGORIES = ['blink.user_timing', 'disabled-by-default-memory-infra'] |
| 20 | 23 |
| 21 _CSV_FIELD_NAMES = [ | 24 _CSV_FIELD_NAMES = [ |
| 22 'id', | 25 'id', |
| 23 'url', | 26 'url', |
| 24 'total_load', | 27 'total_load', |
| 25 'onload', | 28 'onload', |
| 26 'browser_malloc_avg', | 29 'browser_malloc_avg', |
| 27 'browser_malloc_max'] | 30 'browser_malloc_max'] |
| 28 | 31 |
| 29 _TRACKED_EVENT_NAMES = set(['requestStart', 'loadEventStart', 'loadEventEnd']) | 32 _TRACKED_EVENT_NAMES = set(['requestStart', 'loadEventStart', 'loadEventEnd']) |
| 30 | 33 |
| 31 | 34 |
| 32 def _GetBrowserPID(trace): | 35 def _GetBrowserPID(tracing_track): |
| 33 """Get the browser PID from a trace. | 36 """Get the browser PID from a trace. |
| 34 | 37 |
| 35 Args: | 38 Args: |
| 36 trace: The cached trace. | 39 tracing_track: The tracing.TracingTrack. |
| 37 | 40 |
| 38 Returns: | 41 Returns: |
| 39 The browser's PID as an integer. | 42 The browser's PID as an integer. |
| 40 """ | 43 """ |
| 41 for event in trace['traceEvents']: | 44 assert type(tracing_track) == tracing.TracingTrack |
| 42 if event['cat'] != '__metadata' or event['name'] != 'process_name': | 45 for event in tracing_track.GetEvents(): |
| 46 if event.category != '__metadata' or event.name != 'process_name': | |
| 43 continue | 47 continue |
| 44 if event['args']['name'] == 'Browser': | 48 if event.args['name'] == 'Browser': |
| 45 return event['pid'] | 49 return event.pid |
| 46 raise ValueError('couldn\'t find browser\'s PID') | 50 raise ValueError('couldn\'t find browser\'s PID') |
| 47 | 51 |
| 48 | 52 |
| 49 def _GetBrowserDumpEvents(trace): | 53 def _GetBrowserDumpEvents(tracing_track): |
| 50 """Get the browser memory dump events from a trace. | 54 """Get the browser memory dump events from a tracing track. |
| 51 | 55 |
| 52 Args: | 56 Args: |
| 53 trace: The cached trace. | 57 tracing_track: The tracing.TracingTrack. |
| 54 | 58 |
| 55 Returns: | 59 Returns: |
| 56 List of memory dump events. | 60 List of memory dump events. |
| 57 """ | 61 """ |
| 58 browser_pid = _GetBrowserPID(trace) | 62 assert type(tracing_track) == tracing.TracingTrack |
| 63 browser_pid = _GetBrowserPID(tracing_track) | |
| 59 browser_dumps_events = [] | 64 browser_dumps_events = [] |
| 60 for event in trace['traceEvents']: | 65 for event in tracing_track.GetEvents(): |
| 61 if event['cat'] != 'disabled-by-default-memory-infra': | 66 if event.category != 'disabled-by-default-memory-infra': |
| 62 continue | 67 continue |
| 63 if event['ph'] != 'v' or event['name'] != 'periodic_interval': | 68 if event.type != 'v' or event.name != 'periodic_interval': |
| 64 continue | 69 continue |
| 65 # Ignore dump events for processes other than the browser process | 70 # Ignore dump events for processes other than the browser process |
| 66 if event['pid'] != browser_pid: | 71 if event.pid != browser_pid: |
| 67 continue | 72 continue |
| 68 browser_dumps_events.append(event) | 73 browser_dumps_events.append(event) |
| 69 if len(browser_dumps_events) == 0: | 74 if len(browser_dumps_events) == 0: |
| 70 raise ValueError('No browser dump events found.') | 75 raise ValueError('No browser dump events found.') |
| 71 return browser_dumps_events | 76 return browser_dumps_events |
| 72 | 77 |
| 73 | 78 |
| 74 def _GetWebPageTrackedEvents(trace): | 79 def _GetWebPageTrackedEvents(tracing_track): |
| 75 """Get the web page's tracked events from a trace. | 80 """Get the web page's tracked events from a tracing track. |
| 76 | 81 |
| 77 Args: | 82 Args: |
| 78 trace: The cached trace. | 83 tracing_track: The tracing.TracingTrack. |
| 79 | 84 |
| 80 Returns: | 85 Returns: |
| 81 Dictionary all tracked events. | 86 Dictionary all tracked events. |
| 82 """ | 87 """ |
| 88 assert type(tracing_track) == tracing.TracingTrack | |
| 83 main_frame = None | 89 main_frame = None |
| 84 tracked_events = {} | 90 tracked_events = {} |
| 85 for event in trace['traceEvents']: | 91 for event in tracing_track.GetEvents(): |
| 86 if event['cat'] != 'blink.user_timing': | 92 if event.category != 'blink.user_timing': |
| 87 continue | 93 continue |
| 88 event_name = event['name'] | 94 event_name = event.name |
| 89 # Ignore events until about:blank's unloadEventEnd that give the main | 95 # Ignore events until about:blank's unloadEventEnd that give the main |
| 90 # frame id. | 96 # frame id. |
| 91 if not main_frame: | 97 if not main_frame: |
| 92 if event_name == 'unloadEventEnd': | 98 if event_name == 'unloadEventEnd': |
| 93 main_frame = event['args']['frame'] | 99 main_frame = event.args['frame'] |
| 94 logging.info('found about:blank\'s event \'unloadEventEnd\'') | 100 logging.info('found about:blank\'s event \'unloadEventEnd\'') |
| 95 continue | 101 continue |
| 96 # Ignore sub-frames events. requestStart don't have the frame set but it | 102 # Ignore sub-frames events. requestStart don't have the frame set but it |
| 97 # is fine since tracking the first one after about:blank's unloadEventEnd. | 103 # is fine since tracking the first one after about:blank's unloadEventEnd. |
| 98 if 'frame' in event['args'] and event['args']['frame'] != main_frame: | 104 if 'frame' in event.args and event.args['frame'] != main_frame: |
| 99 continue | 105 continue |
| 100 if event_name in _TRACKED_EVENT_NAMES and event_name not in tracked_events: | 106 if event_name in _TRACKED_EVENT_NAMES and event_name not in tracked_events: |
| 101 logging.info('found url\'s event \'%s\'' % event_name) | 107 logging.info('found url\'s event \'%s\'' % event_name) |
| 102 tracked_events[event_name] = event | 108 tracked_events[event_name] = event |
| 103 assert len(tracked_events) == len(_TRACKED_EVENT_NAMES) | 109 assert len(tracked_events) == len(_TRACKED_EVENT_NAMES) |
| 104 return tracked_events | 110 return tracked_events |
| 105 | 111 |
| 106 | 112 |
| 107 def _PullMetricsFromTrace(trace): | 113 def _PullMetricsFromLoadingTrace(loading_trace): |
| 108 """Pulls all the metrics from a given trace. | 114 """Pulls all the metrics from a given trace. |
| 109 | 115 |
| 110 Args: | 116 Args: |
| 111 trace: The cached trace. | 117 loading_trace: loading_trace_module.LoadingTrace. |
| 112 | 118 |
| 113 Returns: | 119 Returns: |
| 114 Dictionary with all _CSV_FIELD_NAMES's field set (except the 'id'). | 120 Dictionary with all _CSV_FIELD_NAMES's field set (except the 'id'). |
| 115 """ | 121 """ |
| 116 browser_dump_events = _GetBrowserDumpEvents(trace) | 122 assert type(loading_trace) == loading_trace_module.LoadingTrace |
|
mattcary
2016/02/19 14:41:05
This sort of check seems overly fussy. Worse, it p
gabadie
2016/02/19 15:04:35
Done.
| |
| 117 web_page_tracked_events = _GetWebPageTrackedEvents(trace) | 123 browser_dump_events = _GetBrowserDumpEvents(loading_trace.tracing_track) |
| 124 web_page_tracked_events = _GetWebPageTrackedEvents( | |
| 125 loading_trace.tracing_track) | |
| 118 | 126 |
| 119 browser_malloc_sum = 0 | 127 browser_malloc_sum = 0 |
| 120 browser_malloc_max = 0 | 128 browser_malloc_max = 0 |
| 121 for dump_event in browser_dump_events: | 129 for dump_event in browser_dump_events: |
| 122 attr = dump_event['args']['dumps']['allocators']['malloc']['attrs']['size'] | 130 attr = dump_event.args['dumps']['allocators']['malloc']['attrs']['size'] |
| 123 assert attr['units'] == 'bytes' | 131 assert attr['units'] == 'bytes' |
| 124 size = int(attr['value'], 16) | 132 size = int(attr['value'], 16) |
| 125 browser_malloc_sum += size | 133 browser_malloc_sum += size |
| 126 browser_malloc_max = max(browser_malloc_max, size) | 134 browser_malloc_max = max(browser_malloc_max, size) |
| 127 | 135 |
| 128 return { | 136 return { |
| 129 'total_load': (web_page_tracked_events['loadEventEnd']['ts'] - | 137 'total_load': (web_page_tracked_events['loadEventEnd'].start_msec - |
| 130 web_page_tracked_events['requestStart']['ts']), | 138 web_page_tracked_events['requestStart'].start_msec), |
| 131 'onload': (web_page_tracked_events['loadEventEnd']['ts'] - | 139 'onload': (web_page_tracked_events['loadEventEnd'].start_msec - |
| 132 web_page_tracked_events['loadEventStart']['ts']), | 140 web_page_tracked_events['loadEventStart'].start_msec), |
| 133 'browser_malloc_avg': browser_malloc_sum / float(len(browser_dump_events)), | 141 'browser_malloc_avg': browser_malloc_sum / float(len(browser_dump_events)), |
| 134 'browser_malloc_max': browser_malloc_max | 142 'browser_malloc_max': browser_malloc_max |
| 135 } | 143 } |
| 136 | 144 |
| 137 | 145 |
| 138 def _PullMetricsFromOutputDirectory(output_directory_path): | 146 def _PullMetricsFromOutputDirectory(output_directory_path): |
| 139 """Pulls all the metrics from all the traces of a sandwich run directory. | 147 """Pulls all the metrics from all the traces of a sandwich run directory. |
| 140 | 148 |
| 141 Args: | 149 Args: |
| 142 output_directory_path: The sandwich run's output directory to pull the | 150 output_directory_path: The sandwich run's output directory to pull the |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 155 if not os.path.isdir(os.path.join(output_directory_path, node_name)): | 163 if not os.path.isdir(os.path.join(output_directory_path, node_name)): |
| 156 continue | 164 continue |
| 157 try: | 165 try: |
| 158 page_id = int(node_name) | 166 page_id = int(node_name) |
| 159 except ValueError: | 167 except ValueError: |
| 160 continue | 168 continue |
| 161 trace_path = os.path.join(output_directory_path, node_name, 'trace.json') | 169 trace_path = os.path.join(output_directory_path, node_name, 'trace.json') |
| 162 if not os.path.isfile(trace_path): | 170 if not os.path.isfile(trace_path): |
| 163 continue | 171 continue |
| 164 logging.info('processing \'%s\'' % trace_path) | 172 logging.info('processing \'%s\'' % trace_path) |
| 165 with open(trace_path) as trace_file: | 173 loading_trace = loading_trace_module.LoadingTrace.FromJsonFile(trace_path) |
| 166 trace = json.load(trace_file) | 174 trace_metrics = _PullMetricsFromLoadingTrace(loading_trace) |
| 167 trace_metrics = _PullMetricsFromTrace(trace) | 175 trace_metrics['id'] = page_id |
| 168 trace_metrics['id'] = page_id | 176 trace_metrics['url'] = run_infos['urls'][page_id] |
| 169 trace_metrics['url'] = run_infos['urls'][page_id] | 177 metrics.append(trace_metrics) |
| 170 metrics.append(trace_metrics) | |
| 171 assert len(metrics) > 0, ('Looks like \'{}\' was not a sandwich ' + | 178 assert len(metrics) > 0, ('Looks like \'{}\' was not a sandwich ' + |
| 172 'run directory.').format(output_directory_path) | 179 'run directory.').format(output_directory_path) |
| 173 return metrics | 180 return metrics |
| 174 | 181 |
| 175 | 182 |
| 176 def main(): | 183 def main(): |
| 177 logging.basicConfig(level=logging.INFO) | 184 logging.basicConfig(level=logging.INFO) |
| 178 | 185 |
| 179 parser = argparse.ArgumentParser() | 186 parser = argparse.ArgumentParser() |
| 180 parser.add_argument('output', type=str, | 187 parser.add_argument('output', type=str, |
| 181 help='Output directory of run_sandwich.py command.') | 188 help='Output directory of run_sandwich.py command.') |
| 182 args = parser.parse_args() | 189 args = parser.parse_args() |
| 183 | 190 |
| 184 trace_metrics_list = _PullMetricsFromOutputDirectory(args.output) | 191 trace_metrics_list = _PullMetricsFromOutputDirectory(args.output) |
| 185 trace_metrics_list.sort(key=lambda e: e['id']) | 192 trace_metrics_list.sort(key=lambda e: e['id']) |
| 186 cs_file_path = os.path.join(args.output, 'trace_analysis.csv') | 193 cs_file_path = os.path.join(args.output, 'trace_analysis.csv') |
| 187 with open(cs_file_path, 'w') as csv_file: | 194 with open(cs_file_path, 'w') as csv_file: |
| 188 writer = csv.DictWriter(csv_file, fieldnames=_CSV_FIELD_NAMES) | 195 writer = csv.DictWriter(csv_file, fieldnames=_CSV_FIELD_NAMES) |
| 189 writer.writeheader() | 196 writer.writeheader() |
| 190 for trace_metrics in trace_metrics_list: | 197 for trace_metrics in trace_metrics_list: |
| 191 writer.writerow(trace_metrics) | 198 writer.writerow(trace_metrics) |
| 192 return 0 | 199 return 0 |
| 193 | 200 |
| 194 | 201 |
| 195 if __name__ == '__main__': | 202 if __name__ == '__main__': |
| 196 sys.exit(main()) | 203 sys.exit(main()) |
| OLD | NEW |