OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 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 """Start and stop Web Page Replay.""" | 6 """Start and stop Web Page Replay. |
| 7 |
| 8 Of the public module names, the following ones are key: |
| 9 CHROME_FLAGS: Chrome options to make it work with Web Page Replay. |
| 10 ReplayServer: a class to start/stop Web Page Replay. |
| 11 """ |
7 | 12 |
8 import logging | 13 import logging |
9 import optparse | |
10 import os | 14 import os |
11 import shutil | |
12 import signal | 15 import signal |
13 import subprocess | 16 import subprocess |
14 import sys | |
15 import tempfile | |
16 import time | 17 import time |
17 import urllib | 18 import urllib |
18 | 19 |
19 | 20 |
20 USAGE = '%s [options] CHROME_EXE TEST_NAME' % os.path.basename(sys.argv[0]) | |
21 USER_DATA_DIR = '{TEMP}/webpagereplay_utils-chrome' | |
22 | |
23 # The port numbers must match those in chrome/test/perf/page_cycler_test.cc. | |
24 HTTP_PORT = 8080 | 21 HTTP_PORT = 8080 |
25 HTTPS_PORT = 8413 | 22 HTTPS_PORT = 8413 |
26 REPLAY_HOST='127.0.0.1' | 23 REPLAY_HOST='127.0.0.1' |
| 24 CHROME_FLAGS = [ |
| 25 '--host-resolver-rules=MAP * %s,EXCLUDE localhost' % REPLAY_HOST, |
| 26 '--testing-fixed-http-port=%s' % HTTP_PORT, |
| 27 '--testing-fixed-https-port=%s' % HTTPS_PORT, |
| 28 '--ignore-certificate-errors', |
| 29 ] |
| 30 |
| 31 _CHROME_BASE_DIR = os.path.abspath(os.path.join( |
| 32 os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, os.pardir)) |
| 33 REPLAY_DIR = os.path.join( |
| 34 _CHROME_BASE_DIR, 'src', 'third_party', 'webpagereplay') |
| 35 LOG_PATH = os.path.join( |
| 36 _CHROME_BASE_DIR, 'src', 'webpagereplay_logs', 'logs.txt') |
27 | 37 |
28 | 38 |
29 class ReplayError(Exception): | 39 class ReplayError(Exception): |
30 """Catch-all exception for the module.""" | 40 """Catch-all exception for the module.""" |
31 pass | 41 pass |
32 | 42 |
33 class ReplayNotFoundError(ReplayError): | 43 class ReplayNotFoundError(ReplayError): |
34 pass | 44 def __init__(self, label, path): |
| 45 self.args = (label, path) |
| 46 |
| 47 def __str__(self): |
| 48 label, path = self.args |
| 49 return 'Path does not exist for %s: %s' % (label, path) |
35 | 50 |
36 class ReplayNotStartedError(ReplayError): | 51 class ReplayNotStartedError(ReplayError): |
37 pass | 52 pass |
38 | 53 |
39 | 54 |
40 class ReplayServer(object): | 55 class ReplayServer(object): |
41 """Start and Stop Web Page Replay. | 56 """Start and Stop Web Page Replay. |
42 | 57 |
| 58 Web Page Replay is a proxy that can record and "replay" web pages with |
| 59 simulated network characteristics -- without having to edit the pages |
| 60 by hand. With WPR, tests can use "real" web content, and catch |
| 61 performance issues that may result from introducing network delays and |
| 62 bandwidth throttling. |
| 63 |
43 Example: | 64 Example: |
44 with ReplayServer(replay_dir, archive_path, log_dir, replay_options): | 65 with ReplayServer(archive_path): |
45 self.NavigateToURL(start_url) | 66 self.NavigateToURL(start_url) |
46 self.WaitUntil(...) | 67 self.WaitUntil(...) |
| 68 |
| 69 Environment Variables (for development): |
| 70 WPR_ARCHIVE_PATH: path to alternate archive file (e.g. '/tmp/foo.wpr'). |
| 71 WPR_RECORD: if set, puts Web Page Replay in record mode instead of replay. |
| 72 WPR_REPLAY_DIR: path to alternate Web Page Replay source. |
47 """ | 73 """ |
48 LOG_FILE = 'log.txt' | 74 def __init__(self, archive_path, replay_options=None, replay_dir=None, |
49 | 75 log_path=None): |
50 def __init__(self, replay_dir, archive_path, log_dir, replay_options=None): | |
51 """Initialize ReplayServer. | 76 """Initialize ReplayServer. |
52 | 77 |
53 Args: | 78 Args: |
| 79 archive_path: a path to a specific WPR archive (required). |
| 80 replay_options: a list of options strings to forward to replay.py. |
54 replay_dir: directory that has replay.py and related modules. | 81 replay_dir: directory that has replay.py and related modules. |
55 archive_path: either a directory that contains WPR archives or, | 82 log_path: a path to a log file. |
56 a path to a specific WPR archive. | 83 """ |
57 log_dir: where to write log.txt. | 84 self.archive_path = os.environ.get('WPR_ARCHIVE_PATH', archive_path) |
58 replay_options: a list of options strings to forward to replay.py. | 85 self.replay_options = replay_options or [] |
59 """ | 86 self.replay_dir = os.environ.get('WPR_REPLAY_DIR', replay_dir or REPLAY_DIR) |
60 self.replay_dir = replay_dir | 87 self.log_path = log_path or LOG_PATH |
61 self.archive_path = archive_path | |
62 self.log_dir = log_dir | |
63 self.replay_options = replay_options if replay_options else [] | |
64 | 88 |
65 self.log_name = os.path.join(self.log_dir, self.LOG_FILE) | 89 if 'WPR_RECORD' in os.environ and '--record' not in self.replay_options: |
| 90 self.replay_options.append('--record') |
| 91 self.is_record_mode = '--record' in self.replay_options |
| 92 self._AddDefaultReplayOptions() |
| 93 |
| 94 self.replay_py = os.path.join(self.replay_dir, 'replay.py') |
| 95 |
| 96 if self.is_record_mode: |
| 97 self._CheckPath('archive directory', os.path.dirname(self.archive_path)) |
| 98 elif not os.path.exists(self.archive_path): |
| 99 self._CheckPath('archive file', self.archive_path) |
| 100 self._CheckPath('replay script', self.replay_py) |
| 101 |
66 self.log_fh = None | 102 self.log_fh = None |
67 self.replay_process = None | 103 self.replay_process = None |
68 | 104 |
69 self.wpr_py = os.path.join(self.replay_dir, 'replay.py') | 105 def _AddDefaultReplayOptions(self): |
70 if not os.path.exists(self.wpr_py): | 106 """Set WPR command-line options. Can be overridden if needed.""" |
71 raise ReplayNotFoundError('Path does not exist: %s' % self.wpr_py) | 107 self.replay_options += [ |
72 self.wpr_options = [ | |
73 '--port', str(HTTP_PORT), | 108 '--port', str(HTTP_PORT), |
74 '--ssl_port', str(HTTPS_PORT), | 109 '--ssl_port', str(HTTPS_PORT), |
75 '--use_closest_match', | 110 '--use_closest_match', |
76 # TODO(slamm): Add traffic shaping (requires root): | 111 '--no-dns_forwarding', |
77 # '--net', 'fios', | 112 # '--net', 'fios', # TODO(slamm): Add traffic shaping (requires root). |
78 ] | 113 ] |
79 self.wpr_options.extend(self.replay_options) | 114 |
| 115 def _CheckPath(self, label, path): |
| 116 if not os.path.exists(path): |
| 117 raise ReplayNotFoundError(label, path) |
80 | 118 |
81 def _OpenLogFile(self): | 119 def _OpenLogFile(self): |
82 if not os.path.exists(self.log_dir): | 120 log_dir = os.path.dirname(self.log_path) |
83 os.makedirs(self.log_dir) | 121 if not os.path.exists(log_dir): |
84 return open(self.log_name, 'w') | 122 os.makedirs(log_dir) |
| 123 return open(self.log_path, 'w') |
85 | 124 |
86 def IsStarted(self): | 125 def IsStarted(self): |
87 """Checks to see if the server is up and running.""" | 126 """Checks to see if the server is up and running.""" |
88 for _ in range(5): | 127 for _ in range(5): |
89 if self.replay_process.poll() is not None: | 128 if self.replay_process.poll() is not None: |
90 # The process has exited. | 129 # The process has exited. |
91 break | 130 break |
92 try: | 131 try: |
93 up_url = '%s://localhost:%s/web-page-replay-generate-200' | 132 up_url = '%s://localhost:%s/web-page-replay-generate-200' |
94 http_up_url = up_url % ('http', HTTP_PORT) | 133 http_up_url = up_url % ('http', HTTP_PORT) |
95 https_up_url = up_url % ('https', HTTPS_PORT) | 134 https_up_url = up_url % ('https', HTTPS_PORT) |
96 if (200 == urllib.urlopen(http_up_url, None, {}).getcode() and | 135 if (200 == urllib.urlopen(http_up_url, None, {}).getcode() and |
97 200 == urllib.urlopen(https_up_url, None, {}).getcode()): | 136 200 == urllib.urlopen(https_up_url, None, {}).getcode()): |
98 return True | 137 return True |
99 except IOError: | 138 except IOError: |
100 time.sleep(1) | 139 time.sleep(1) |
101 return False | 140 return False |
102 | 141 |
103 def StartServer(self): | 142 def StartServer(self): |
104 """Start Web Page Replay and verify that it started. | 143 """Start Web Page Replay and verify that it started. |
105 | 144 |
106 Raises: | 145 Raises: |
107 ReplayNotStartedError if Replay start-up fails. | 146 ReplayNotStartedError if Replay start-up fails. |
108 """ | 147 """ |
109 cmd_line = [self.wpr_py] | 148 cmd_line = [self.replay_py] |
110 cmd_line.extend(self.wpr_options) | 149 cmd_line.extend(self.replay_options) |
111 cmd_line.append(self.archive_path) | 150 cmd_line.append(self.archive_path) |
112 self.log_fh = self._OpenLogFile() | 151 self.log_fh = self._OpenLogFile() |
113 logging.debug('Starting Web-Page-Replay: %s', cmd_line) | 152 logging.debug('Starting Web-Page-Replay: %s', cmd_line) |
114 self.replay_process = subprocess.Popen( | 153 self.replay_process = subprocess.Popen( |
115 cmd_line, stdout=self.log_fh, stderr=subprocess.STDOUT) | 154 cmd_line, stdout=self.log_fh, stderr=subprocess.STDOUT) |
116 if not self.IsStarted(): | 155 if not self.IsStarted(): |
117 raise ReplayNotStartedError( | 156 raise ReplayNotStartedError( |
118 'Web Page Replay failed to start. See the log file: ' + self.log_name) | 157 'Web Page Replay failed to start. See the log file: ' + self.log_name) |
119 | 158 |
120 def StopServer(self): | 159 def StopServer(self): |
121 """Stop Web Page Replay.""" | 160 """Stop Web Page Replay.""" |
122 if self.replay_process: | 161 if self.replay_process: |
123 logging.debug('Stopping Web-Page-Replay') | 162 logging.debug('Stopping Web-Page-Replay') |
124 # Use a SIGINT here so that it can do graceful cleanup. | 163 # Use a SIGINT here so that it can do graceful cleanup. |
125 # Otherwise, we will leave subprocesses hanging. | 164 # Otherwise, we will leave subprocesses hanging. |
126 self.replay_process.send_signal(signal.SIGINT) | 165 self.replay_process.send_signal(signal.SIGINT) |
127 self.replay_process.wait() | 166 self.replay_process.wait() |
128 if self.log_fh: | 167 if self.log_fh: |
129 self.log_fh.close() | 168 self.log_fh.close() |
130 | 169 |
131 def __enter__(self): | 170 def __enter__(self): |
132 """Add support for with-statement.""" | 171 """Add support for with-statement.""" |
133 self.StartServer() | 172 self.StartServer() |
| 173 return self |
134 | 174 |
135 def __exit__(self, unused_exc_type, unused_exc_val, unused_exc_tb): | 175 def __exit__(self, unused_exc_type, unused_exc_val, unused_exc_tb): |
136 """Add support for with-statement.""" | 176 """Add support for with-statement.""" |
137 self.StopServer() | 177 self.StopServer() |
OLD | NEW |