blob: 57ef0cc6bdff1de5bdeb1673ca18f16ef7005a4e [file] [log] [blame]
#! /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.
"""Instructs Chrome to load series of web pages and reports results.
When running Chrome is sandwiched between preprocessed disk caches and
WepPageReplay serving all connections.
"""
import argparse
import json
import logging
import os
import shutil
import sys
from urlparse import urlparse
_SRC_DIR = os.path.abspath(os.path.join(
os.path.dirname(__file__), '..', '..', '..'))
sys.path.append(os.path.join(_SRC_DIR, 'third_party', 'catapult', 'devil'))
from devil.android import device_utils
sys.path.append(os.path.join(_SRC_DIR, 'build', 'android'))
from pylib import constants
import devil_chromium
import chrome_cache
import common_util
import csv_util
import device_setup
import emulation
import options
import sandwich_metrics
import sandwich_prefetch
import sandwich_runner
import sandwich_swr
import sandwich_utils
import task_manager
from trace_test.webserver_test import WebServer
# Use options layer to access constants.
OPTIONS = options.OPTIONS
_SPEED_INDEX_MEASUREMENT = 'speed-index'
_MEMORY_MEASUREMENT = 'memory'
_CORPUS_DIR = 'sandwich_corpuses'
def ReadUrlsFromCorpus(corpus_path):
"""Retrieves the list of URLs associated with the corpus name."""
try:
# Attempt to read by regular file name.
json_file_name = corpus_path
with open(json_file_name) as f:
json_data = json.load(f)
except IOError:
# Extra sugar: attempt to load from _CORPUS_DIR.
json_file_name = os.path.join(
os.path.dirname(__file__), _CORPUS_DIR, corpus_path)
with open(json_file_name) as f:
json_data = json.load(f)
key = 'urls'
if json_data and key in json_data:
url_list = json_data[key]
if isinstance(url_list, list) and len(url_list) > 0:
return url_list
raise Exception(
'File {} does not define a list named "urls"'.format(json_file_name))
def _ArgumentParser():
"""Build a command line argument's parser."""
task_parser = task_manager.CommandLineParser()
# Command parser when dealing with SandwichRunner.
sandwich_runner_parser = argparse.ArgumentParser(add_help=False)
sandwich_runner_parser.add_argument('--android', default=None, type=str,
dest='android_device_serial',
help='Android device\'s serial to use.')
# Plumbing parser to configure OPTIONS.
plumbing_parser = OPTIONS.GetParentParser('plumbing options')
# Main parser
parser = argparse.ArgumentParser(parents=[plumbing_parser],
fromfile_prefix_chars=task_manager.FROMFILE_PREFIX_CHARS)
subparsers = parser.add_subparsers(dest='subcommand', help='subcommand line')
# Record test trace subcommand.
record_trace_parser = subparsers.add_parser('record-test-trace',
parents=[sandwich_runner_parser],
help='Record a test trace using the trace_test.webserver_test.')
record_trace_parser.add_argument('--source-dir', type=str, required=True,
help='Base path where the files are opened '
'by the web server.')
record_trace_parser.add_argument('--page', type=str, required=True,
help='Source page in source-dir to navigate '
'to.')
record_trace_parser.add_argument('-o', '--output', type=str, required=True,
help='Output path of the generated trace.')
# Run subcommand.
run_parser = subparsers.add_parser('run',
parents=[sandwich_runner_parser, task_parser],
help='Run all steps using the task manager infrastructure.')
run_parser.add_argument('-g', '--gen-full', action='store_true',
help='Generate the full graph with all possible '
'benchmarks.')
run_parser.add_argument('-c', '--corpus', required=True,
help='Path to a JSON file with a corpus such as in %s/.' % _CORPUS_DIR)
run_parser.add_argument('-m', '--measure', default=[], nargs='+',
choices=[_SPEED_INDEX_MEASUREMENT, _MEMORY_MEASUREMENT],
dest='optional_measures', help='Enable optional measurements.')
run_parser.add_argument('--wpr-archive', default=None, type=str,
dest='wpr_archive_path',
help='WebPageReplay archive to use, instead of '
'generating one.')
run_parser.add_argument('-r', '--url-repeat', default=1, type=int,
help='How many times to repeat the urls.')
run_parser.add_argument('--swr-benchmark', action='store_true',
help='Run the Stale-While-Revalidate benchmarks.')
# Collect subcommand.
collect_csv_parser = subparsers.add_parser('collect-csv',
help='Collects all CSVs from Sandwich output directory into a single '
'CSV.')
collect_csv_parser.add_argument('output_dir', type=str,
help='Path to the run output directory.')
collect_csv_parser.add_argument('output_csv', type=argparse.FileType('w'),
help='Path to the output CSV.')
return parser
def _GetAndroidDeviceFromArgs(args):
if args.android_device_serial:
return device_setup.GetDeviceFromSerial(args.android_device_serial)
return None
def _RecordWebServerTestTrace(args):
with common_util.TemporaryDirectory() as out_path:
runner = sandwich_runner.SandwichRunner()
runner.android_device = _GetAndroidDeviceFromArgs(args)
# Reuse the WPR's forwarding to access the webpage from Android.
runner.wpr_record = True
runner.wpr_archive_path = os.path.join(out_path, 'wpr')
runner.output_dir = os.path.join(out_path, 'run')
with WebServer.Context(
source_dir=args.source_dir, communication_dir=out_path) as server:
address = server.Address()
runner.url = 'http://%s/%s' % (address, args.page)
runner.Run()
trace_path = os.path.join(
out_path, 'run', '0', sandwich_runner.TRACE_FILENAME)
shutil.copy(trace_path, args.output)
return 0
def _GenerateBenchmarkTasks(args, android_device, url, output_subdirectory):
MAIN_TRANSFORMER_LIST_NAME = 'no-network-emulation'
common_builder = sandwich_utils.SandwichCommonBuilder(
android_device=android_device,
url=url,
output_directory=args.output,
output_subdirectory=output_subdirectory)
if args.wpr_archive_path:
common_builder.OverridePathToWprArchive(args.wpr_archive_path)
else:
common_builder.PopulateWprRecordingTask()
def MainTransformer(runner):
runner.record_video = _SPEED_INDEX_MEASUREMENT in args.optional_measures
runner.record_memory_dumps = _MEMORY_MEASUREMENT in args.optional_measures
runner.repeat = args.url_repeat
if not args.swr_benchmark:
builder = sandwich_prefetch.PrefetchBenchmarkBuilder(common_builder)
builder.PopulateLoadBenchmark(sandwich_prefetch.EMPTY_CACHE_DISCOVERER,
MAIN_TRANSFORMER_LIST_NAME,
transformer_list=[MainTransformer])
builder.PopulateLoadBenchmark(sandwich_prefetch.FULL_CACHE_DISCOVERER,
MAIN_TRANSFORMER_LIST_NAME,
transformer_list=[MainTransformer])
if args.gen_full:
for network_condition in ['Regular4G', 'Regular3G', 'Regular2G']:
transformer_list_name = network_condition.lower()
network_transformer = \
sandwich_utils.NetworkSimulationTransformer(
network_condition)
transformer_list = [MainTransformer, network_transformer]
for subresource_discoverer in sandwich_prefetch.SUBRESOURCE_DISCOVERERS:
if subresource_discoverer == sandwich_prefetch.FULL_CACHE_DISCOVERER:
continue
builder.PopulateLoadBenchmark(subresource_discoverer,
transformer_list_name, transformer_list)
else:
builder = sandwich_swr.StaleWhileRevalidateBenchmarkBuilder(common_builder)
for enable_swr in [False, True]:
builder.PopulateBenchmark(enable_swr, MAIN_TRANSFORMER_LIST_NAME,
transformer_list=[MainTransformer])
for network_condition in ['Regular3G', 'Regular2G']:
transformer_list_name = network_condition.lower()
network_transformer = \
sandwich_utils.NetworkSimulationTransformer(
network_condition)
transformer_list = [MainTransformer, network_transformer]
builder.PopulateBenchmark(enable_swr, transformer_list_name,
transformer_list)
return common_builder.default_final_tasks
def _RunAllMain(args):
urls = ReadUrlsFromCorpus(args.corpus)
domain_times_encountered_per_domain = {}
default_final_tasks = []
android_device = _GetAndroidDeviceFromArgs(args)
for url in urls:
domain = '.'.join(urlparse(url).netloc.split('.')[-2:])
domain_times_encountered = domain_times_encountered_per_domain.get(
domain, 0)
output_subdirectory = '{}.{}'.format(domain, domain_times_encountered)
domain_times_encountered_per_domain[domain] = domain_times_encountered + 1
default_final_tasks.extend(
_GenerateBenchmarkTasks(args, android_device, url, output_subdirectory))
return task_manager.ExecuteWithCommandLine(args, default_final_tasks)
def main(command_line_args):
logging.basicConfig(level=logging.INFO)
devil_chromium.Initialize()
args = _ArgumentParser().parse_args(command_line_args)
OPTIONS.SetParsedArgs(args)
if args.subcommand == 'record-test-trace':
return _RecordWebServerTestTrace(args)
if args.subcommand == 'run':
return _RunAllMain(args)
if args.subcommand == 'collect-csv':
with args.output_csv as output_file:
if not csv_util.CollectCSVsFromDirectory(args.output_dir, output_file):
return 1
return 0
assert False
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))