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 |
| 26 | 27 |
| 27 import pyauto_media | 28 import pyauto_media |
| 28 import pyauto | 29 import pyauto |
| 29 import pyauto_paths | 30 import pyauto_paths |
| 30 import pyauto_utils | 31 import pyauto_utils |
| 31 | 32 import urllib2 |
|
DaleCurtis
2012/01/06 23:19:42
This should go under threading.
shadi
2012/01/07 04:56:19
Done.
| |
| 32 | 33 |
| 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(), |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 96 | 97 |
| 97 def _FindTabLocked(self, url): | 98 def _FindTabLocked(self, url): |
| 98 """Returns the tab index for the tab belonging to this url. | 99 """Returns the tab index for the tab belonging to this url. |
| 99 | 100 |
| 100 self._automation_lock must be owned by caller. | 101 self._automation_lock must be owned by caller. |
| 101 """ | 102 """ |
| 102 for tab in self._pyauto.GetBrowserInfo()['windows'][0]['tabs']: | 103 for tab in self._pyauto.GetBrowserInfo()['windows'][0]['tabs']: |
| 103 if tab['url'] == url: | 104 if tab['url'] == url: |
| 104 return tab['index'] | 105 return tab['index'] |
| 105 | 106 |
| 106 def _HaveMetrics(self, unique_url): | 107 def _HaveTTPMetric(self, unique_url): |
|
DaleCurtis
2012/01/06 23:19:42
These methods are very similar, you should create
shadi
2012/01/07 04:56:19
Done.
| |
| 107 """Returns true if metrics are ready. Set self.{_epp,_ttp} < 0 pre-run.""" | 108 """Returns true if ttp metrics is ready. Set self._ttp < 0 pre-run.""" |
| 108 with self._automation_lock: | 109 with self._automation_lock: |
| 109 tab = self._FindTabLocked(unique_url) | 110 tab = self._FindTabLocked(unique_url) |
| 111 self._ttp = int(self._pyauto.GetDOMValue('time_to_playback', | |
| 112 tab_index=tab)) | |
|
DaleCurtis
2012/01/06 23:19:42
Bad indent. Please run gpylint before publishing.
shadi
2012/01/07 04:56:19
Done.
| |
| 113 return self._ttp >= 0 | |
| 110 | 114 |
| 111 if self._epp < 0: | 115 def _HaveEPPMetric(self, unique_url): |
| 112 self._epp = int(self._pyauto.GetDOMValue('extra_play_percentage', | 116 """Returns true if epp metric is ready. Set self._epp < 0 pre-run.""" |
| 117 with self._automation_lock: | |
| 118 tab = self._FindTabLocked(unique_url) | |
| 119 self._epp = int(self._pyauto.GetDOMValue('extra_play_percentage', | |
| 113 tab_index=tab)) | 120 tab_index=tab)) |
| 114 if self._ttp < 0: | 121 return self._epp >= 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 | 122 |
| 119 def run(self): | 123 def run(self): |
| 120 """Opens tab, starts HTML test, and records metrics for each queue entry. | 124 """Opens tab, starts HTML test, and records metrics for each queue entry. |
| 121 | 125 |
| 122 No exception handling is done to make sure the main thread exits properly | 126 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 | 127 during Chrome crashes or other failures. Doing otherwise has the potential |
| 124 to leave the CNS server running in the background. | 128 to leave the CNS server running in the background. |
| 125 | 129 |
| 126 For a clean shutdown, put the magic exit value (None, None) in the queue. | 130 For a clean shutdown, put the magic exit value (None, None) in the queue. |
| 127 """ | 131 """ |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 148 # Start the test! | 152 # Start the test! |
| 149 with self._automation_lock: | 153 with self._automation_lock: |
| 150 self._pyauto.AppendTab(pyauto.GURL(unique_url)) | 154 self._pyauto.AppendTab(pyauto.GURL(unique_url)) |
| 151 self._pyauto.CallJavascriptFunc( | 155 self._pyauto.CallJavascriptFunc( |
| 152 'startTest', [video_url], tab_index=self._FindTabLocked(unique_url)) | 156 'startTest', [video_url], tab_index=self._FindTabLocked(unique_url)) |
| 153 | 157 |
| 154 # Wait until the necessary metrics have been collected. Okay to not lock | 158 # Wait until the necessary metrics have been collected. Okay to not lock |
| 155 # here since pyauto.WaitUntil doesn't call into Chrome. | 159 # here since pyauto.WaitUntil doesn't call into Chrome. |
| 156 self._epp = self._ttp = -1 | 160 self._epp = self._ttp = -1 |
| 157 self._pyauto.WaitUntil( | 161 self._pyauto.WaitUntil( |
| 158 self._HaveMetrics, args=[unique_url], retry_sleep=2, | 162 self._HaveTTPMetric, args=[unique_url], retry_sleep=1, timeout= 5, |
|
DaleCurtis
2012/01/06 23:19:42
Bad spacing. Again gpylint is your friend here :)
shadi
2012/01/07 04:56:19
Done.
| |
| 159 timeout=_TEST_VIDEO_DURATION_SEC * 10) | 163 debug=False) |
| 164 | |
| 165 # Do not wait for epp if ttp is not available. | |
| 166 if self._ttp >= 0: | |
|
DaleCurtis
2012/01/06 23:19:42
Should we log an error? Skip logging of epp, ttp b
shadi
2012/01/07 04:56:19
Done.
| |
| 167 self._pyauto.WaitUntil( | |
| 168 self._HaveEPPMetric, args=[unique_url], retry_sleep=2, | |
| 169 timeout=_TEST_VIDEO_DURATION_SEC * 10, debug=False) | |
| 160 | 170 |
| 161 # Record results. | 171 # Record results. |
| 162 # TODO(dalecurtis): Support reference builds. | 172 # TODO(dalecurtis): Support reference builds. |
| 163 series_name = ''.join(name) | 173 series_name = ''.join(name) |
| 164 pyauto_utils.PrintPerfResult('epp', series_name, self._epp, '%') | 174 pyauto_utils.PrintPerfResult('epp', series_name, self._epp, '%') |
| 165 pyauto_utils.PrintPerfResult('ttp', series_name, self._ttp, 'ms') | 175 pyauto_utils.PrintPerfResult('ttp', series_name, self._ttp, 'ms') |
| 166 | 176 |
| 167 # Close the tab. | 177 # Close the tab. |
| 168 with self._automation_lock: | 178 with self._automation_lock: |
| 169 self._pyauto.GetBrowserWindow(0).GetTab( | 179 self._pyauto.GetBrowserWindow(0).GetTab( |
| 170 self._FindTabLocked(unique_url)).Close(True) | 180 self._FindTabLocked(unique_url)).Close(True) |
| 171 | 181 |
| 172 # TODO(dalecurtis): Check results for regressions. | 182 # TODO(dalecurtis): Check results for regressions. |
| 173 self._tasks.task_done() | 183 self._tasks.task_done() |
| 174 | 184 |
| 175 | 185 |
| 186 class ProcessLogger(threading.Thread): | |
| 187 """A thread to log a process's stderr output.""" | |
| 188 def __init__(self, process): | |
|
DaleCurtis
2012/01/06 23:19:42
Need blank line above this. gpylint... :)
shadi
2012/01/07 04:56:19
Done.
| |
| 189 """Starts the process logger thread. | |
| 190 | |
| 191 Args: | |
| 192 process: The process to log. | |
| 193 """ | |
| 194 threading.Thread.__init__(self) | |
| 195 self._process = process | |
| 196 self.start() | |
| 197 | |
| 198 def run(self): | |
| 199 """Adds debug statements for the process's stderr output.""" | |
| 200 line = True | |
| 201 while line: | |
| 202 line = self._process.stderr.readline() | |
| 203 logging.debug(line) | |
| 204 | |
| 205 | |
| 176 class MediaConstrainedNetworkPerfTest(pyauto.PyUITest): | 206 class MediaConstrainedNetworkPerfTest(pyauto.PyUITest): |
| 177 """PyAuto test container. See file doc string for more information.""" | 207 """PyAuto test container. See file doc string for more information.""" |
| 178 | 208 |
| 179 def setUp(self): | 209 def setUp(self): |
| 180 """Starts the Constrained Network Server (CNS).""" | 210 """Starts the Constrained Network Server (CNS).""" |
| 181 cmd = [sys.executable, os.path.join(pyauto_paths.GetSourceDir(), _CNS_PATH), | 211 cmd = [sys.executable, os.path.join(pyauto_paths.GetSourceDir(), _CNS_PATH), |
| 182 '--port', str(_CNS_PORT), | 212 '--port', str(_CNS_PORT), |
| 183 '--interface', 'lo', | 213 '--interface', 'lo', |
| 184 '--www-root', os.path.join( | 214 '--www-root', os.path.join( |
| 185 self.DataDir(), 'pyauto_private', 'media')] | 215 self.DataDir(), 'pyauto_private', 'media'), |
| 216 '-v'] | |
| 186 | 217 |
| 187 process = subprocess.Popen(cmd, stderr=subprocess.PIPE) | 218 process = subprocess.Popen(cmd, stderr=subprocess.PIPE) |
| 188 | 219 |
| 189 # Wait for server to start up. | 220 # Wait for server to start up. |
| 190 line = True | 221 line = True |
| 191 while line: | 222 while line: |
| 192 line = process.stderr.readline() | 223 line = process.stderr.readline() |
| 224 logging.debug(line) | |
| 193 if 'STARTED' in line: | 225 if 'STARTED' in line: |
| 194 self._server_pid = process.pid | 226 self._server_pid = process.pid |
| 195 pyauto.PyUITest.setUp(self) | 227 pyauto.PyUITest.setUp(self) |
| 228 ProcessLogger(process) | |
| 229 self.CheckServerAccess() | |
| 196 return | 230 return |
| 197 self.fail('Failed to start CNS.') | 231 self.fail('Failed to start CNS.') |
| 198 | 232 |
| 233 def CheckServerAccess(self): | |
| 234 """Checks is the CNS server can serve a file with no network constraints.""" | |
| 235 test_url = ''.join([_CNS_BASE_URL, 'f=' ,_TEST_VIDEO]) | |
| 236 try: | |
| 237 urllib2.urlopen(test_url) | |
| 238 except: | |
| 239 # Need to call teardown since the server has already started. | |
| 240 self.tearDown() | |
|
DaleCurtis
2012/01/06 23:19:42
This should be called automatically. Did you find
shadi
2012/01/07 04:56:19
If setup() fails, tearDown() doesn't get called au
| |
| 241 self.fail('Cannot connect to CNS server.') | |
|
DaleCurtis
2012/01/06 23:19:42
Instead of having a new fail here, why not just re
shadi
2012/01/07 04:56:19
Done.
| |
| 242 | |
| 199 def tearDown(self): | 243 def tearDown(self): |
| 200 """Stops the Constrained Network Server (CNS).""" | 244 """Stops the Constrained Network Server (CNS).""" |
| 201 pyauto.PyUITest.tearDown(self) | 245 pyauto.PyUITest.tearDown(self) |
| 202 self.Kill(self._server_pid) | 246 self.Kill(self._server_pid) |
| 203 | 247 |
| 204 def testConstrainedNetworkPerf(self): | 248 def testConstrainedNetworkPerf(self): |
| 205 """Starts CNS, spins up worker threads to run through _TEST_CONSTRAINTS.""" | 249 """Starts CNS, spins up worker threads to run through _TEST_CONSTRAINTS.""" |
| 206 # Convert relative test path into an absolute path. | 250 # Convert relative test path into an absolute path. |
| 207 test_url = self.GetFileURLForDataPath(_TEST_HTML_PATH) | 251 test_url = self.GetFileURLForDataPath(_TEST_HTML_PATH) |
| 208 | 252 |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 223 tasks.put((None, None)) | 267 tasks.put((None, None)) |
| 224 | 268 |
| 225 # Wait for threads to exit, gracefully or otherwise. | 269 # Wait for threads to exit, gracefully or otherwise. |
| 226 for thread in threads: | 270 for thread in threads: |
| 227 thread.join() | 271 thread.join() |
| 228 | 272 |
| 229 | 273 |
| 230 if __name__ == '__main__': | 274 if __name__ == '__main__': |
| 231 # TODO(dalecurtis): Process command line parameters here. | 275 # TODO(dalecurtis): Process command line parameters here. |
| 232 pyauto_media.Main() | 276 pyauto_media.Main() |
| OLD | NEW |