Chromium Code Reviews| 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 |