Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 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 """Records metrics on playing media under constrained network conditions. | 6 """Records metrics on playing media under constrained network conditions. |
| 7 | 7 |
| 8 Spins up a Constrained Network Server (CNS) and runs through a test matrix of | 8 Spins up a Constrained Network Server (CNS) and runs through a test matrix of |
| 9 bandwidth, latency, and packet loss settings. Each run records a | 9 bandwidth, latency, and packet loss settings. Each run records a |
| 10 time-to-playback (TTP) and extra-play-percentage (EPP) metric in a format | 10 time-to-playback (TTP) and extra-play-percentage (EPP) metric in a format |
| 11 consumable by the Chromium perf bots. | 11 consumable by the Chromium perf bots. |
| 12 | 12 |
| 13 Since even a small number of different settings yields a large test matrix, the | 13 Since even a small number of different settings yields a large test matrix, the |
| 14 design is threaded... however PyAuto is not, so a global lock is used when calls | 14 design is threaded... however PyAuto is not, so a global lock is used when calls |
| 15 into PyAuto are necessary. The number of threads can be set by _TEST_THREADS. | 15 into PyAuto are necessary. The number of threads can be set by _TEST_THREADS. |
| 16 | 16 |
| 17 The CNS code is located under: <root>/src/media/tools/constrained_network_server | 17 The CNS code is located under: <root>/src/media/tools/constrained_network_server |
| 18 """ | 18 """ |
| 19 | 19 |
| 20 import itertools | 20 import itertools |
| 21 import logging | |
| 21 import os | 22 import os |
| 22 import Queue | 23 import Queue |
| 23 import subprocess | 24 import subprocess |
| 24 import sys | 25 import sys |
| 25 import threading | 26 import threading |
| 27 import urllib2 | |
| 26 | 28 |
| 27 import pyauto_media | 29 import pyauto_media |
| 28 import pyauto | 30 import pyauto |
| 29 import pyauto_paths | 31 import pyauto_paths |
| 30 import pyauto_utils | 32 import pyauto_utils |
| 31 | 33 |
| 32 | |
| 33 # Settings for each network constraint. | 34 # Settings for each network constraint. |
| 34 _BANDWIDTH_SETTINGS_KBPS = {'None': 0, 'Low': 256, 'Medium': 2000, 'High': 5000} | 35 _BANDWIDTH_SETTINGS_KBPS = {'None': 0, 'Low': 256, 'Medium': 2000, 'High': 5000} |
| 35 _LATENCY_SETTINGS_MS = {'None': 0, 'Low': 43, 'Medium': 105, 'High': 180} | 36 _LATENCY_SETTINGS_MS = {'None': 0, 'Low': 43, 'Medium': 105, 'High': 180} |
| 36 _PACKET_LOSS_SETTINGS_PERCENT = {'None': 0, 'Medium': 2, 'High': 5} | 37 _PACKET_LOSS_SETTINGS_PERCENT = {'None': 0, 'Medium': 2, 'High': 5} |
| 37 | 38 |
| 38 # Test constraints are all possible combination of the above settings. Each | 39 # Test constraints are all possible combination of the above settings. Each |
| 39 # tuple must be of the form (Bandwidth, Latency, Packet Loss). | 40 # tuple must be of the form (Bandwidth, Latency, Packet Loss). |
| 40 _TEST_CONSTRAINTS = itertools.product( | 41 _TEST_CONSTRAINTS = itertools.product( |
| 41 _BANDWIDTH_SETTINGS_KBPS.values(), | 42 _BANDWIDTH_SETTINGS_KBPS.values(), |
| 42 _LATENCY_SETTINGS_MS.values(), | 43 _LATENCY_SETTINGS_MS.values(), |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 85 pyauto_test: Reference to a pyauto.PyUITest instance. | 86 pyauto_test: Reference to a pyauto.PyUITest instance. |
| 86 tasks: Queue containing (settings, name) tuples. | 87 tasks: Queue containing (settings, name) tuples. |
| 87 automation_lock: Global automation lock for pyauto calls. | 88 automation_lock: Global automation lock for pyauto calls. |
| 88 url: File URL to HTML/JavaScript test code. | 89 url: File URL to HTML/JavaScript test code. |
| 89 """ | 90 """ |
| 90 threading.Thread.__init__(self) | 91 threading.Thread.__init__(self) |
| 91 self._tasks = tasks | 92 self._tasks = tasks |
| 92 self._automation_lock = automation_lock | 93 self._automation_lock = automation_lock |
| 93 self._pyauto = pyauto_test | 94 self._pyauto = pyauto_test |
| 94 self._url = url | 95 self._url = url |
| 96 self._metrics = {} | |
| 95 self.start() | 97 self.start() |
| 96 | 98 |
| 97 def _FindTabLocked(self, url): | 99 def _FindTabLocked(self, url): |
| 98 """Returns the tab index for the tab belonging to this url. | 100 """Returns the tab index for the tab belonging to this url. |
| 99 | 101 |
| 100 self._automation_lock must be owned by caller. | 102 self._automation_lock must be owned by caller. |
| 101 """ | 103 """ |
| 102 for tab in self._pyauto.GetBrowserInfo()['windows'][0]['tabs']: | 104 for tab in self._pyauto.GetBrowserInfo()['windows'][0]['tabs']: |
| 103 if tab['url'] == url: | 105 if tab['url'] == url: |
| 104 return tab['index'] | 106 return tab['index'] |
| 105 | 107 |
| 106 def _HaveMetrics(self, unique_url): | 108 def _HaveMetric(self, var_name, unique_url): |
| 107 """Returns true if metrics are ready. Set self.{_epp,_ttp} < 0 pre-run.""" | 109 """Checks if unique_url page has varriable value ready. Set to < 0 pre-run. |
|
DaleCurtis
2012/01/09 19:21:19
s/varriable/variable.
shadi
2012/01/09 23:07:36
Done.
| |
| 110 | |
| 111 Args: | |
| 112 var_name: The variable name to check the metric for. | |
| 113 unique_url: The url of the page to check for the variable's metric. | |
| 114 """ | |
| 108 with self._automation_lock: | 115 with self._automation_lock: |
| 109 tab = self._FindTabLocked(unique_url) | 116 tab = self._FindTabLocked(unique_url) |
| 110 | 117 self._metrics[var_name] = int(self._pyauto.GetDOMValue(var_name, |
| 111 if self._epp < 0: | 118 tab_index=tab)) |
| 112 self._epp = int(self._pyauto.GetDOMValue('extra_play_percentage', | 119 return self._metrics[var_name] >= 0 |
| 113 tab_index=tab)) | |
| 114 if self._ttp < 0: | |
| 115 self._ttp = int(self._pyauto.GetDOMValue('time_to_playback', | |
| 116 tab_index=tab)) | |
| 117 return self._epp >= 0 and self._ttp >= 0 | |
| 118 | 120 |
| 119 def run(self): | 121 def run(self): |
| 120 """Opens tab, starts HTML test, and records metrics for each queue entry. | 122 """Opens tab, starts HTML test, and records metrics for each queue entry. |
| 121 | 123 |
| 122 No exception handling is done to make sure the main thread exits properly | 124 No exception handling is done to make sure the main thread exits properly |
| 123 during Chrome crashes or other failures. Doing otherwise has the potential | 125 during Chrome crashes or other failures. Doing otherwise has the potential |
| 124 to leave the CNS server running in the background. | 126 to leave the CNS server running in the background. |
| 125 | 127 |
| 126 For a clean shutdown, put the magic exit value (None, None) in the queue. | 128 For a clean shutdown, put the magic exit value (None, None) in the queue. |
| 127 """ | 129 """ |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 146 unique_url = '%s?%d' % (self._url, TestWorker._task_id.next()) | 148 unique_url = '%s?%d' % (self._url, TestWorker._task_id.next()) |
| 147 | 149 |
| 148 # Start the test! | 150 # Start the test! |
| 149 with self._automation_lock: | 151 with self._automation_lock: |
| 150 self._pyauto.AppendTab(pyauto.GURL(unique_url)) | 152 self._pyauto.AppendTab(pyauto.GURL(unique_url)) |
| 151 self._pyauto.CallJavascriptFunc( | 153 self._pyauto.CallJavascriptFunc( |
| 152 'startTest', [video_url], tab_index=self._FindTabLocked(unique_url)) | 154 'startTest', [video_url], tab_index=self._FindTabLocked(unique_url)) |
| 153 | 155 |
| 154 # Wait until the necessary metrics have been collected. Okay to not lock | 156 # Wait until the necessary metrics have been collected. Okay to not lock |
| 155 # here since pyauto.WaitUntil doesn't call into Chrome. | 157 # here since pyauto.WaitUntil doesn't call into Chrome. |
| 156 self._epp = self._ttp = -1 | 158 self._metrics['epp'] = self._metrics['ttp'] = -1 |
| 157 self._pyauto.WaitUntil( | 159 self._pyauto.WaitUntil( |
| 158 self._HaveMetrics, args=[unique_url], retry_sleep=2, | 160 self._HaveMetric, args=['ttp', unique_url], retry_sleep=1, timeout=10, |
| 159 timeout=_TEST_VIDEO_DURATION_SEC * 10) | 161 debug=False) |
| 160 | 162 |
| 161 # Record results. | 163 # Do not wait for epp if ttp is not available. |
| 162 # TODO(dalecurtis): Support reference builds. | |
| 163 series_name = ''.join(name) | 164 series_name = ''.join(name) |
| 164 pyauto_utils.PrintPerfResult('epp', series_name, self._epp, '%') | 165 if self._metrics['ttp'] >= 0: |
| 165 pyauto_utils.PrintPerfResult('ttp', series_name, self._ttp, 'ms') | 166 self._pyauto.WaitUntil( |
| 167 self._HaveMetric, args=['epp', unique_url], retry_sleep=2, | |
| 168 timeout=_TEST_VIDEO_DURATION_SEC * 10, debug=False) | |
| 169 | |
| 170 # Record results. | |
| 171 # TODO(dalecurtis): Support reference builds. | |
| 172 pyauto_utils.PrintPerfResult('epp', series_name, self._metrics['epp'], | |
| 173 '%') | |
| 174 pyauto_utils.PrintPerfResult('ttp', series_name, self._metrics['ttp'], | |
| 175 'ms') | |
| 176 else: | |
| 177 logging.error('Test %s timed-out.', series_name) | |
| 166 | 178 |
| 167 # Close the tab. | 179 # Close the tab. |
| 168 with self._automation_lock: | 180 with self._automation_lock: |
| 169 self._pyauto.GetBrowserWindow(0).GetTab( | 181 self._pyauto.GetBrowserWindow(0).GetTab( |
| 170 self._FindTabLocked(unique_url)).Close(True) | 182 self._FindTabLocked(unique_url)).Close(True) |
| 171 | 183 |
| 172 # TODO(dalecurtis): Check results for regressions. | 184 # TODO(dalecurtis): Check results for regressions. |
| 173 self._tasks.task_done() | 185 self._tasks.task_done() |
| 174 | 186 |
| 175 | 187 |
| 188 class ProcessLogger(threading.Thread): | |
| 189 """A thread to log a process's stderr output.""" | |
| 190 | |
| 191 def __init__(self, process): | |
| 192 """Starts the process logger thread. | |
| 193 | |
| 194 Args: | |
| 195 process: The process to log. | |
| 196 """ | |
| 197 threading.Thread.__init__(self) | |
| 198 self._process = process | |
| 199 self.start() | |
| 200 | |
| 201 def run(self): | |
| 202 """Adds debug statements for the process's stderr output.""" | |
| 203 line = True | |
| 204 while line: | |
| 205 line = self._process.stderr.readline() | |
| 206 logging.debug(line) | |
| 207 | |
| 208 | |
| 176 class MediaConstrainedNetworkPerfTest(pyauto.PyUITest): | 209 class MediaConstrainedNetworkPerfTest(pyauto.PyUITest): |
| 177 """PyAuto test container. See file doc string for more information.""" | 210 """PyAuto test container. See file doc string for more information.""" |
| 178 | 211 |
| 179 def setUp(self): | 212 def setUp(self): |
| 180 """Starts the Constrained Network Server (CNS).""" | 213 """Starts the Constrained Network Server (CNS).""" |
| 181 cmd = [sys.executable, os.path.join(pyauto_paths.GetSourceDir(), _CNS_PATH), | 214 cmd = [sys.executable, os.path.join(pyauto_paths.GetSourceDir(), _CNS_PATH), |
| 182 '--port', str(_CNS_PORT), | 215 '--port', str(_CNS_PORT), |
| 183 '--interface', 'lo', | 216 '--interface', 'lo', |
| 184 '--www-root', os.path.join( | 217 '--www-root', os.path.join( |
| 185 self.DataDir(), 'pyauto_private', 'media')] | 218 self.DataDir(), 'pyauto_private', 'media'), |
| 219 '-v'] | |
| 186 | 220 |
| 187 process = subprocess.Popen(cmd, stderr=subprocess.PIPE) | 221 process = subprocess.Popen(cmd, stderr=subprocess.PIPE) |
| 188 | 222 |
| 189 # Wait for server to start up. | 223 # Wait for server to start up. |
| 190 line = True | 224 line = True |
| 191 while line: | 225 while line: |
| 192 line = process.stderr.readline() | 226 line = process.stderr.readline() |
| 227 logging.debug(line) | |
| 193 if 'STARTED' in line: | 228 if 'STARTED' in line: |
| 194 self._server_pid = process.pid | 229 self._server_pid = process.pid |
| 195 pyauto.PyUITest.setUp(self) | 230 pyauto.PyUITest.setUp(self) |
| 196 return | 231 ProcessLogger(process) |
| 232 if self._CanAccessServer(): | |
| 233 return | |
| 234 else: | |
|
DaleCurtis
2012/01/09 19:21:19
No need for else:
shadi
2012/01/09 23:07:36
Done.
| |
| 235 # Need to call teardown since the server has already started. | |
| 236 tearDown() | |
| 197 self.fail('Failed to start CNS.') | 237 self.fail('Failed to start CNS.') |
| 198 | 238 |
| 239 def _CanAccessServer(self): | |
| 240 """Checks if the CNS server can serve a file with no network constraints.""" | |
| 241 test_url = ''.join([_CNS_BASE_URL, 'f=', _TEST_VIDEO]) | |
| 242 try: | |
| 243 urllib2.urlopen(test_url) | |
|
DaleCurtis
2012/01/09 19:21:19
Have you tested to make sure this actually catches
shadi
2012/01/09 23:07:36
I have tested with a file not found or wrong url.
| |
| 244 return True | |
| 245 except: | |
| 246 return False | |
| 247 | |
| 199 def tearDown(self): | 248 def tearDown(self): |
| 200 """Stops the Constrained Network Server (CNS).""" | 249 """Stops the Constrained Network Server (CNS).""" |
| 201 pyauto.PyUITest.tearDown(self) | 250 pyauto.PyUITest.tearDown(self) |
| 202 self.Kill(self._server_pid) | 251 self.Kill(self._server_pid) |
| 203 | 252 |
| 204 def testConstrainedNetworkPerf(self): | 253 def testConstrainedNetworkPerf(self): |
| 205 """Starts CNS, spins up worker threads to run through _TEST_CONSTRAINTS.""" | 254 """Starts CNS, spins up worker threads to run through _TEST_CONSTRAINTS.""" |
| 206 # Convert relative test path into an absolute path. | 255 # Convert relative test path into an absolute path. |
| 207 test_url = self.GetFileURLForDataPath(_TEST_HTML_PATH) | 256 test_url = self.GetFileURLForDataPath(_TEST_HTML_PATH) |
| 208 | 257 |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 223 tasks.put((None, None)) | 272 tasks.put((None, None)) |
| 224 | 273 |
| 225 # Wait for threads to exit, gracefully or otherwise. | 274 # Wait for threads to exit, gracefully or otherwise. |
| 226 for thread in threads: | 275 for thread in threads: |
| 227 thread.join() | 276 thread.join() |
| 228 | 277 |
| 229 | 278 |
| 230 if __name__ == '__main__': | 279 if __name__ == '__main__': |
| 231 # TODO(dalecurtis): Process command line parameters here. | 280 # TODO(dalecurtis): Process command line parameters here. |
| 232 pyauto_media.Main() | 281 pyauto_media.Main() |
| OLD | NEW |