Chromium Code Reviews| Index: tools/android/loading/pull_sandwich_metrics.py |
| diff --git a/tools/android/loading/pull_sandwich_metrics.py b/tools/android/loading/pull_sandwich_metrics.py |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..81fa619a938f14063d6bced6951250c27402aed0 |
| --- /dev/null |
| +++ b/tools/android/loading/pull_sandwich_metrics.py |
| @@ -0,0 +1,182 @@ |
| +#! /usr/bin/env python |
| +# Copyright 2016 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +"""Pull a sandwich run's output directory's metrics from traces into a CSV. |
| + |
| +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.
|
| +""" |
| + |
| +import argparse |
| +import csv |
| +import json |
| +import logging |
| +import os |
| +import sys |
| + |
| + |
| +CATEGORIES = ['blink.user_timing', 'disabled-by-default-memory-infra'] |
| +_CSV_FIELD_NAMES = [ |
| + 'id', |
| + 'total_load', |
| + 'onload', |
| + 'browser_malloc_avg', |
| + 'browser_malloc_max'] |
| + |
| +_TRACKED_EVENT_NAMES = set(['requestStart', 'loadEventStart', 'loadEventEnd']) |
| + |
| +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.
|
| + """Get the browser PID from a trace. |
| + |
| + Args: |
| + trace: The cached trace. |
| + |
| + Returns: |
| + The browser's PID as an integer. |
| + """ |
| + for event in trace['traceEvents']: |
| + if event['cat'] != '__metadata' or event['name'] != 'process_name': |
| + continue |
| + if event['args']['name'] == 'Browser': |
| + return event['pid'] |
| + raise Exception('couldn\'t find browser\'s PID') |
| + |
| +def _GetBrowserDumpEvents(trace): |
| + """Get the browser memory dump events from a trace. |
| + |
| + Args: |
| + trace: The cached trace. |
| + |
| + Returns: |
| + List of memory dump events. |
| + """ |
| + browser_pid = _GetBrowserPID(trace) |
| + browser_dumps_events = [] |
| + for event in trace['traceEvents']: |
| + if event['cat'] != 'disabled-by-default-memory-infra': |
| + continue |
| + if event['ph'] != 'v' or event['name'] != 'periodic_interval': |
| + continue |
| + # 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.
|
| + if event['pid'] != browser_pid: |
| + continue |
| + browser_dumps_events.append(event) |
| + if len(browser_dumps_events) == 0: |
| + 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.
|
| + return browser_dumps_events |
| + |
| +def _GetWebPageTrackedEvents(trace): |
| + """Get the web page's tracked events from a trace. |
| + |
| + Args: |
| + trace: The cached trace. |
| + |
| + Returns: |
| + Dictionary all tracked events. |
| + """ |
| + main_frame = None |
| + tracked_events = {} |
| + for event in trace['traceEvents']: |
| + if event['cat'] != 'blink.user_timing': |
| + continue |
| + event_name = event['name'] |
| + # Ignore events until about:blank's unloadEventEnd that give the main |
| + # frame id. |
| + if not main_frame: |
| + if event_name == 'unloadEventEnd': |
| + main_frame = event['args']['frame'] |
| + logging.info('found about:blank\'s event \'unloadEventEnd\'') |
| + continue |
| + # Ignore sub-frames events. requestStart don't have the frame set but it |
| + # is fine since tracking the first one after about:blank's unloadEventEnd. |
| + if 'frame' in event['args'] and event['args']['frame'] != main_frame: |
| + continue |
| + if event_name in _TRACKED_EVENT_NAMES and event_name not in tracked_events: |
| + logging.info('found url\'s event \'%s\'' % event_name) |
| + tracked_events[event_name] = event |
| + assert len(tracked_events) == len(_TRACKED_EVENT_NAMES) |
| + return tracked_events |
| + |
| +def _PullMetricsFromTrace(trace): |
| + """Pulls all the metrics from a given trace. |
| + |
| + Args: |
| + trace: The cached trace. |
| + |
| + Returns: |
| + Dictionary with all _CSV_FIELD_NAMES's field set (except the 'id'). |
| + """ |
| + 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.
|
| + web_page_tracked_events = _GetWebPageTrackedEvents(trace) |
| + |
| + 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.
|
| + browser_malloc_max = 0 |
| + for dump_event in browser_dumps_events: |
| + attr = dump_event['args']['dumps']['allocators']['malloc']['attrs']['size'] |
| + assert attr['units'] == 'bytes' |
| + 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.
|
| + browser_malloc_avg += size |
| + browser_malloc_max = max(browser_malloc_max, size) |
| + |
| + return { |
| + 'total_load': (web_page_tracked_events['loadEventEnd']['ts'] - |
| + web_page_tracked_events['requestStart']['ts']), |
| + 'onload': (web_page_tracked_events['loadEventEnd']['ts'] - |
| + web_page_tracked_events['loadEventStart']['ts']), |
| + 'browser_malloc_avg': browser_malloc_avg / float(len(browser_dumps_events)), |
| + 'browser_malloc_max': browser_malloc_max |
| + } |
| + |
| +def _PullMetricsFromOutputDirectory(output_directory_path): |
| + """Pulls all the metrics from all the traces of a sandwich run directory. |
| + |
| + Args: |
| + output_directory_path: The sandwich run's output directory to pull the |
| + metrics from. |
| + |
| + Returns: |
| + List of dictionaries with all _CSV_FIELD_NAMES's field set. |
| + """ |
| + assert os.path.isdir(output_directory_path) |
| + metrics = [] |
| + for node_name in os.listdir(output_directory_path): |
| + if not os.path.isdir(os.path.join(output_directory_path, node_name)): |
| + continue |
| + try: |
| + page_id = int(node_name) |
| + 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'
|
| + continue |
| + trace_path = os.path.join(output_directory_path, node_name, 'trace.json') |
| + if not os.path.isfile(trace_path): |
| + continue |
| + logging.info('processing \'%s\'' % trace_path) |
| + with open(trace_path) as trace_file: |
| + trace = json.load(trace_file) |
| + trace_metrics = _PullMetricsFromTrace(trace) |
| + trace_metrics['id'] = page_id |
| + metrics.append(trace_metrics) |
| + assert len(metrics) > 0, ('Looks like \'{}\' was not a sandwich ' + |
| + 'run directory.').format(output_directory_path) |
| + return metrics |
| + |
| +def main(): |
| + logging.basicConfig(level=logging.INFO) |
| + |
| + parser = argparse.ArgumentParser() |
| + parser.add_argument('output', type=str, |
| + help='Output directory of run_sandwich.py command.') |
| + args = parser.parse_args() |
| + |
| + trace_metrics_list = _PullMetricsFromOutputDirectory(args.output) |
| + trace_metrics_list.sort(key=lambda e: e['id']) |
| + cs_file_path = os.path.join(args.output, 'trace_analysis.csv') |
| + with open(cs_file_path, 'w') as csv_file: |
| + writer = csv.DictWriter(csv_file, fieldnames=_CSV_FIELD_NAMES) |
| + writer.writeheader() |
| + for trace_metrics in trace_metrics_list: |
| + writer.writerow(trace_metrics) |
| + return 0 |
| + |
| +if __name__ == '__main__': |
| + sys.exit(main()) |