Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2893)

Unified Diff: Tools/Scripts/webkitpy/thirdparty/webpagereplay/replay.py

Issue 18418010: Check in the thirdparty libs needed for webkitpy. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: Tools/Scripts/webkitpy/thirdparty/webpagereplay/replay.py
diff --git a/Tools/Scripts/webkitpy/thirdparty/webpagereplay/replay.py b/Tools/Scripts/webkitpy/thirdparty/webpagereplay/replay.py
new file mode 100755
index 0000000000000000000000000000000000000000..63b7307a642c1baced37d15302417e5118e9b825
--- /dev/null
+++ b/Tools/Scripts/webkitpy/thirdparty/webpagereplay/replay.py
@@ -0,0 +1,455 @@
+#!/usr/bin/env python
+# Copyright 2010 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Replays web pages under simulated network conditions.
+
+Must be run as administrator (sudo).
+
+To record web pages:
+ 1. Start the program in record mode.
+ $ sudo ./replay.py --record archive.wpr
+ 2. Load the web pages you want to record in a web browser. It is important to
+ clear browser caches before this so that all subresources are requested
+ from the network.
+ 3. Kill the process to stop recording.
+
+To replay web pages:
+ 1. Start the program in replay mode with a previously recorded archive.
+ $ sudo ./replay.py archive.wpr
+ 2. Load recorded pages in a web browser. A 404 will be served for any pages or
+ resources not in the recorded archive.
+
+Network simulation examples:
+ # 128KByte/s uplink bandwidth, 4Mbps/s downlink bandwidth with 100ms RTT time
+ $ sudo ./replay.py --up 128KByte/s --down 4Mbit/s --delay_ms=100 archive.wpr
+
+ # 1% packet loss rate
+ $ sudo ./replay.py --packet_loss_rate=0.01 archive.wpr
+"""
+
+import logging
+import optparse
+import os
+import sys
+import traceback
+
+import cachemissarchive
+import customhandlers
+import dnsproxy
+import httparchive
+import httpclient
+import httpproxy
+import platformsettings
+import replayspdyserver
+import servermanager
+import trafficshaper
+
+if sys.version < '2.6':
+ print 'Need Python 2.6 or greater.'
+ sys.exit(1)
+
+
+def configure_logging(platform_settings, log_level_name, log_file_name=None):
+ """Configure logging level and format.
+
+ Args:
+ log_level_name: 'debug', 'info', 'warning', 'error', or 'critical'.
+ log_file_name: a file name
+ """
+ if logging.root.handlers:
+ logging.critical('A logging method (e.g. "logging.warn(...)")'
+ ' was called before logging was configured.')
+ log_level = getattr(logging, log_level_name.upper())
+ log_format = '%(asctime)s %(levelname)s %(message)s'
+ logging.basicConfig(level=log_level, format=log_format)
+ logger = logging.getLogger()
+ if log_file_name:
+ fh = logging.FileHandler(log_file_name)
+ fh.setLevel(log_level)
+ fh.setFormatter(logging.Formatter(log_format))
+ logger.addHandler(fh)
+ system_handler = platform_settings.get_system_logging_handler()
+ if system_handler:
+ logger.addHandler(system_handler)
+
+
+def AddDnsForward(server_manager, platform_settings, host):
+ """Forward DNS traffic."""
+ server_manager.AppendStartStopFunctions(
+ [platform_settings.set_primary_dns, host],
+ [platform_settings.restore_primary_dns])
+
+def AddDnsProxy(server_manager, options, host, real_dns_lookup, http_archive):
+ dns_lookup = None
+ if options.dns_private_passthrough:
+ dns_lookup = dnsproxy.PrivateIpDnsLookup(
+ host, real_dns_lookup, http_archive)
+ server_manager.AppendRecordCallback(dns_lookup.InitializeArchiveHosts)
+ server_manager.AppendReplayCallback(dns_lookup.InitializeArchiveHosts)
+ server_manager.Append(dnsproxy.DnsProxyServer, dns_lookup, host)
+
+
+def AddWebProxy(server_manager, options, host, real_dns_lookup, http_archive,
+ cache_misses):
+ inject_script = httpclient.GetInjectScript(options.inject_scripts.split(','))
+ http_custom_handlers = customhandlers.CustomHandlers(options.screenshot_dir)
+ if options.spdy:
+ assert not options.record, 'spdy cannot be used with --record.'
+ http_archive_fetch = httpclient.ReplayHttpArchiveFetch(
+ http_archive,
+ inject_script,
+ options.diff_unknown_requests,
+ cache_misses=cache_misses,
+ use_closest_match=options.use_closest_match)
+ server_manager.Append(
+ replayspdyserver.ReplaySpdyServer, http_archive_fetch,
+ http_custom_handlers, host=host, port=options.port,
+ certfile=options.certfile)
+ else:
+ http_custom_handlers.add_server_manager_handler(server_manager)
+ http_archive_fetch = httpclient.ControllableHttpArchiveFetch(
+ http_archive, real_dns_lookup,
+ inject_script,
+ options.diff_unknown_requests, options.record,
+ cache_misses=cache_misses, use_closest_match=options.use_closest_match)
+ server_manager.AppendRecordCallback(http_archive_fetch.SetRecordMode)
+ server_manager.AppendReplayCallback(http_archive_fetch.SetReplayMode)
+ server_manager.Append(
+ httpproxy.HttpProxyServer, http_archive_fetch, http_custom_handlers,
+ host=host, port=options.port, use_delays=options.use_server_delay)
+ if options.ssl:
+ server_manager.Append(
+ httpproxy.HttpsProxyServer, http_archive_fetch,
+ http_custom_handlers, options.certfile,
+ host=host, port=options.ssl_port, use_delays=options.use_server_delay)
+
+
+def AddTrafficShaper(server_manager, options, host):
+ if options.HasTrafficShaping():
+ server_manager.Append(
+ trafficshaper.TrafficShaper, host=host, port=options.shaping_port,
+ ssl_port=(options.ssl_shaping_port if options.ssl else None),
+ up_bandwidth=options.up, down_bandwidth=options.down,
+ delay_ms=options.delay_ms, packet_loss_rate=options.packet_loss_rate,
+ init_cwnd=options.init_cwnd, use_loopback=not options.server_mode)
+
+
+class OptionsWrapper(object):
+ """Add checks, updates, and methods to option values.
+
+ Example:
+ options, args = option_parser.parse_args()
+ options = OptionsWrapper(options, option_parser) # run checks and updates
+ if options.record and options.HasTrafficShaping():
+ [...]
+ """
+ _TRAFFICSHAPING_OPTIONS = set(
+ ['down', 'up', 'delay_ms', 'packet_loss_rate', 'init_cwnd', 'net'])
+ _CONFLICTING_OPTIONS = (
+ ('record', ('down', 'up', 'delay_ms', 'packet_loss_rate', 'net',
+ 'spdy', 'use_server_delay')),
+ ('net', ('down', 'up', 'delay_ms')),
+ ('server', ('server_mode',)),
+ )
+ # The --net values come from http://www.webpagetest.org/.
+ # https://sites.google.com/a/webpagetest.org/docs/other-resources/2011-fcc-broadband-data
+ _NET_CONFIGS = (
+ # key --down --up --delay_ms
+ ('dsl', ('1536Kbit/s', '384Kbit/s', '50')),
+ ('cable', ( '5Mbit/s', '1Mbit/s', '28')),
+ ('fios', ( '20Mbit/s', '5Mbit/s', '4')),
+ )
+ NET_CHOICES = [key for key, values in _NET_CONFIGS]
+
+ def __init__(self, options, parser):
+ self._options = options
+ self._parser = parser
+ self._nondefaults = set([
+ name for name, value in parser.defaults.items()
+ if getattr(options, name) != value])
+ self._CheckConflicts()
+ self._MassageValues()
+
+ def _CheckConflicts(self):
+ """Give an error if mutually exclusive options are used."""
+ for option, bad_options in self._CONFLICTING_OPTIONS:
+ if option in self._nondefaults:
+ for bad_option in bad_options:
+ if bad_option in self._nondefaults:
+ self._parser.error('Option --%s cannot be used with --%s.' %
+ (bad_option, option))
+
+ def _MassageValues(self):
+ """Set options that depend on the values of other options."""
+ for net_choice, values in self._NET_CONFIGS:
+ if net_choice == self.net:
+ self._options.down, self._options.up, self._options.delay_ms = values
+ if not self.shaping_port:
+ self._options.shaping_port = self.port
+ if not self.ssl_shaping_port:
+ self._options.ssl_shaping_port = self.ssl_port
+ if not self.ssl:
+ self._options.certfile = None
+
+ def __getattr__(self, name):
+ """Make the original option values available."""
+ return getattr(self._options, name)
+
+ def HasTrafficShaping(self):
+ """Returns True iff the options require traffic shaping."""
+ return bool(self._TRAFFICSHAPING_OPTIONS & self._nondefaults)
+
+ def IsRootRequired(self):
+ """Returns True iff the options require root access."""
+ return (self.HasTrafficShaping() or
+ self.dns_forwarding or
+ self.port < 1024 or
+ self.ssl_port < 1024)
+
+
+def replay(options, replay_filename):
+ platform_settings = platformsettings.get_platform_settings()
+ if options.IsRootRequired():
+ platform_settings.rerun_as_administrator()
+ configure_logging(platform_settings, options.log_level, options.log_file)
+ server_manager = servermanager.ServerManager(options.record)
+ cache_misses = None
+ if options.cache_miss_file:
+ if os.path.exists(options.cache_miss_file):
+ logging.warning('Cache Miss Archive file %s already exists; '
+ 'replay will load and append entries to archive file',
+ options.cache_miss_file)
+ cache_misses = cachemissarchive.CacheMissArchive.Load(
+ options.cache_miss_file)
+ else:
+ cache_misses = cachemissarchive.CacheMissArchive(
+ options.cache_miss_file)
+ if options.server:
+ AddDnsForward(server_manager, platform_settings, options.server)
+ else:
+ host = platform_settings.get_server_ip_address(options.server_mode)
+ real_dns_lookup = dnsproxy.RealDnsLookup(
+ name_servers=[platform_settings.get_original_primary_dns()])
+ if options.record:
+ http_archive = httparchive.HttpArchive()
+ http_archive.AssertWritable(replay_filename)
+ else:
+ http_archive = httparchive.HttpArchive.Load(replay_filename)
+ logging.info('Loaded %d responses from %s',
+ len(http_archive), replay_filename)
+ server_manager.AppendRecordCallback(real_dns_lookup.ClearCache)
+ server_manager.AppendRecordCallback(http_archive.clear)
+
+ if options.dns_forwarding:
+ if not options.server_mode:
+ AddDnsForward(server_manager, platform_settings, host)
+ AddDnsProxy(server_manager, options, host, real_dns_lookup, http_archive)
+ if options.ssl and options.certfile is None:
+ options.certfile = platform_settings.get_certfile_name()
+ server_manager.AppendStartStopFunctions(
+ [platform_settings.create_certfile, options.certfile],
+ [os.unlink, options.certfile])
+ AddWebProxy(server_manager, options, host, real_dns_lookup,
+ http_archive, cache_misses)
+ AddTrafficShaper(server_manager, options, host)
+
+ exit_status = 0
+ try:
+ server_manager.Run()
+ except KeyboardInterrupt:
+ logging.info('Shutting down.')
+ except (dnsproxy.DnsProxyException,
+ trafficshaper.TrafficShaperException,
+ platformsettings.NotAdministratorError,
+ platformsettings.DnsUpdateError) as e:
+ logging.critical('%s: %s', e.__class__.__name__, e)
+ exit_status = 1
+ except:
+ logging.critical(traceback.format_exc())
+ exit_status = 2
+
+ if options.record:
+ http_archive.Persist(replay_filename)
+ logging.info('Saved %d responses to %s', len(http_archive), replay_filename)
+ if cache_misses:
+ cache_misses.Persist()
+ logging.info('Saved %d cache misses and %d requests to %s',
+ cache_misses.get_total_cache_misses(),
+ len(cache_misses.request_counts.keys()),
+ options.cache_miss_file)
+ return exit_status
+
+
+def main():
+ class PlainHelpFormatter(optparse.IndentedHelpFormatter):
+ def format_description(self, description):
+ if description:
+ return description + '\n'
+ else:
+ return ''
+ option_parser = optparse.OptionParser(
+ usage='%prog [options] replay_file',
+ formatter=PlainHelpFormatter(),
+ description=__doc__,
+ epilog='http://code.google.com/p/web-page-replay/')
+
+ option_parser.add_option('--spdy', default=False,
+ action='store_true',
+ help='Replay via SPDY. (Can be combined with --no-ssl).')
+ option_parser.add_option('-r', '--record', default=False,
+ action='store_true',
+ help='Download real responses and record them to replay_file')
+ option_parser.add_option('-l', '--log_level', default='debug',
+ action='store',
+ type='choice',
+ choices=('debug', 'info', 'warning', 'error', 'critical'),
+ help='Minimum verbosity level to log')
+ option_parser.add_option('-f', '--log_file', default=None,
+ action='store',
+ type='string',
+ help='Log file to use in addition to writting logs to stderr.')
+ option_parser.add_option('-e', '--cache_miss_file', default=None,
+ action='store',
+ dest='cache_miss_file',
+ type='string',
+ help='Archive file to record cache misses as pickled objects.'
+ 'Cache misses occur when a request cannot be served in replay mode.')
+
+ network_group = optparse.OptionGroup(option_parser,
+ 'Network Simulation Options',
+ 'These options configure the network simulation in replay mode')
+ network_group.add_option('-u', '--up', default='0',
+ action='store',
+ type='string',
+ help='Upload Bandwidth in [K|M]{bit/s|Byte/s}. Zero means unlimited.')
+ network_group.add_option('-d', '--down', default='0',
+ action='store',
+ type='string',
+ help='Download Bandwidth in [K|M]{bit/s|Byte/s}. Zero means unlimited.')
+ network_group.add_option('-m', '--delay_ms', default='0',
+ action='store',
+ type='string',
+ help='Propagation delay (latency) in milliseconds. Zero means no delay.')
+ network_group.add_option('-p', '--packet_loss_rate', default='0',
+ action='store',
+ type='string',
+ help='Packet loss rate in range [0..1]. Zero means no loss.')
+ network_group.add_option('-w', '--init_cwnd', default='0',
+ action='store',
+ type='string',
+ help='Set initial cwnd (linux only, requires kernel patch)')
+ network_group.add_option('--net', default=None,
+ action='store',
+ type='choice',
+ choices=OptionsWrapper.NET_CHOICES,
+ help='Select a set of network options: %s.' % ', '.join(
+ OptionsWrapper.NET_CHOICES))
+ option_parser.add_option_group(network_group)
+
+ harness_group = optparse.OptionGroup(option_parser,
+ 'Replay Harness Options',
+ 'These advanced options configure various aspects of the replay harness')
+ harness_group.add_option('-S', '--server', default=None,
+ action='store',
+ type='string',
+ help='IP address of host running "replay.py --server_mode". '
+ 'This only changes the primary DNS nameserver to use the given IP.')
+ harness_group.add_option('-M', '--server_mode', default=False,
+ action='store_true',
+ help='Run replay DNS & http proxies, and trafficshaping on --port '
+ 'without changing the primary DNS nameserver. '
+ 'Other hosts may connect to this using "replay.py --server" '
+ 'or by pointing their DNS to this server.')
+ harness_group.add_option('-i', '--inject_scripts', default='deterministic.js',
+ action='store',
+ dest='inject_scripts',
+ help='A comma separated list of JavaScript sources to inject in all '
+ 'pages. By default a script is injected that eliminates sources '
+ 'of entropy such as Date() and Math.random() deterministic. '
+ 'CAUTION: Without deterministic.js, many pages will not replay.')
+ harness_group.add_option('-D', '--no-diff_unknown_requests', default=True,
+ action='store_false',
+ dest='diff_unknown_requests',
+ help='During replay, do not show a diff of unknown requests against '
+ 'their nearest match in the archive.')
+ harness_group.add_option('-C', '--use_closest_match', default=False,
+ action='store_true',
+ dest='use_closest_match',
+ help='During replay, if a request is not found, serve the closest match'
+ 'in the archive instead of giving a 404.')
+ harness_group.add_option('-U', '--use_server_delay', default=False,
+ action='store_true',
+ dest='use_server_delay',
+ help='During replay, simulate server delay by delaying response time to'
+ 'requests.')
+ harness_group.add_option('-I', '--screenshot_dir', default=None,
+ action='store',
+ type='string',
+ help='Save PNG images of the loaded page in the given directory.')
+ harness_group.add_option('-P', '--no-dns_private_passthrough', default=True,
+ action='store_false',
+ dest='dns_private_passthrough',
+ help='Don\'t forward DNS requests that resolve to private network '
+ 'addresses. CAUTION: With this option important services like '
+ 'Kerberos will resolve to the HTTP proxy address.')
+ harness_group.add_option('-x', '--no-dns_forwarding', default=True,
+ action='store_false',
+ dest='dns_forwarding',
+ help='Don\'t forward DNS requests to the local replay server. '
+ 'CAUTION: With this option an external mechanism must be used to '
+ 'forward traffic to the replay server.')
+ harness_group.add_option('-o', '--port', default=80,
+ action='store',
+ type='int',
+ help='Port number to listen on.')
+ harness_group.add_option('--ssl_port', default=443,
+ action='store',
+ type='int',
+ help='SSL port number to listen on.')
+ harness_group.add_option('--shaping_port', default=None,
+ action='store',
+ type='int',
+ help='Port on which to apply traffic shaping. Defaults to the '
+ 'listen port (--port)')
+ harness_group.add_option('--ssl_shaping_port', default=None,
+ action='store',
+ type='int',
+ help='SSL port on which to apply traffic shaping. Defaults to the '
+ 'SSL listen port (--ssl_port)')
+ harness_group.add_option('-c', '--certfile', default=None,
+ action='store',
+ type='string',
+ help='Certificate file to use with SSL (gets auto-generated if needed).')
+ harness_group.add_option('--no-ssl', default=True,
+ action='store_false',
+ dest='ssl',
+ help='Do not setup an SSL proxy.')
+ option_parser.add_option_group(harness_group)
+
+ options, args = option_parser.parse_args()
+ options = OptionsWrapper(options, option_parser)
+
+ if options.server:
+ replay_filename = None
+ elif len(args) != 1:
+ option_parser.error('Must specify a replay_file')
+ else:
+ replay_filename = args[0]
+
+ return replay(options, replay_filename)
+
+
+if __name__ == '__main__':
+ sys.exit(main())

Powered by Google App Engine
This is Rietveld 408576698