1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 """Constrained network server (CNS) test base.""" 6 7 import logging 8 import os 9 import Queue 10 import subprocess 11 import sys 12 import threading 13 import urllib2 14 15 import pyauto 16 import pyauto_paths 17 18 19 # List of commonly used network constraints settings. 20 # Each setting is a tuppe of the form: 21 # ('TEST_NAME', [BANDWIDTH_Kbps, LATENCY_ms, PACKET_LOSS_%]) 22 # 23 # Note: The test name should satisfy the regex [\w\.-]+ (check 24 # tools/perf_expectations/tests/perf_expectations_unittest.py for details). It 25 # is used to name the result graphs on the dashboards. 26 # 27 # The WiFi, DSL, and Cable settings were taken from webpagetest.org as 28 # approximations of their respective real world networks. The settings were 29 # based on 2011 FCC Broadband Data report (http://www.fcc.gov/document/ 30 # measuring-broadband-america-report-consumer-broadband-performance-us). 31 DialUp = ('DialUp', [56, 120, 5]) 32 Slow = ('Slow', [256, 105, 1]) 33 Wifi = ('Wifi', [1024, 60, 0]) 34 DSL = ('DSL', [1541, 50, 0]) 35 Cable = ('Cable', [5120, 28, 0]) 36 NoConstraints = ('NoConstraints', [0, 0, 0]) 37 38 # Path to CNS executable relative to source root. 39 _CNS_PATH = os.path.join( 40 'media', 'tools', 'constrained_network_server', 'cns.py') 41 42 # Port to start the CNS on. 43 _CNS_PORT = 9000 44 45 # A flag to determine whether to launch a local CNS instance or to connect 46 # to the external CNS server. Default to False since all current bots use an 47 # external instance. 48 # If not on Windows, set USE_LOCAL_CNS=1 env variable to switch the flag. 49 USE_LOCAL_CNS = ('win' not in sys.platform and 'USE_LOCAL_CNS' in os.environ and 50 os.environ['USE_LOCAL_CNS'] == '1') 51 52 # Base CNS URL, only requires & separated parameter names appended. 53 if USE_LOCAL_CNS: 54 CNS_BASE_URL = 'http://127.0.0.1:%d/ServeConstrained?' % _CNS_PORT 55 else: 56 CNS_BASE_URL = 'http://chromeperf34:%d/ServeConstrained?' % _CNS_PORT 57 CNS_CLEANUP_URL = 'http://chromeperf34:%d/Cleanup' % _CNS_PORT 58 59 # Used for server sanity check. 60 _TEST_VIDEO = 'roller.webm' 61 62 # Directory root to serve files from. 63 _ROOT_PATH = os.path.join(pyauto.PyUITest.DataDir(), 'pyauto_private', 'media') 64 65 66 class CNSTestBase(pyauto.PyUITest): 67 """CNS test base hadles startup and teardown of CNS server.""" 68 69 def __init__(self, *args, **kwargs): 70 """Initialize CNSTestBase by setting the arguments for CNS server. 71 72 Args: 73 Check cns.py command line argument list for details. 74 """ 75 self._port = kwargs.get('port', _CNS_PORT) 76 self._interface = kwargs.get('interface', 'lo') 77 self._www_root = kwargs.get('www_root', _ROOT_PATH) 78 self._verbose = kwargs.get('verbose', True) 79 self._expiry_time = kwargs.get('expiry_time', 0) 80 self._socket_timeout = kwargs.get('socket_timeout') 81 pyauto.PyUITest.__init__(self, *args, **kwargs) 82 83 def setUp(self): 84 """Ensures the Constrained Network Server (CNS) server is up and running.""" 85 if USE_LOCAL_CNS: 86 self._SetUpLocal() 87 else: 88 self._SetUpExternal() 89 90 def _SetUpExternal(self): 91 """Ensures the test can connect to the external CNS server.""" 92 if self.WaitUntil(self._CanAccessServer, retry_sleep=3, timeout=30, 93 debug=False): 94 pyauto.PyUITest.setUp(self) 95 else: 96 self.fail('Failed to connect to CNS.') 97 98 def _SetUpLocal(self): 99 """Starts the CNS server locally.""" 100 cmd = [sys.executable, os.path.join(pyauto_paths.GetSourceDir(), _CNS_PATH), 101 '--port', str(self._port), 102 '--interface', self._interface, 103 '--www-root', self._www_root, 104 '--expiry-time', str(self._expiry_time)] 105 106 if self._socket_timeout: 107 cmd.extend(['--socket-timeout', str(self._socket_timeout)]) 108 if self._verbose: 109 cmd.append('-v') 110 logging.debug('Starting CNS server: %s ', ' '.join(cmd)) 111 112 self._cns_process = subprocess.Popen(cmd, stderr=subprocess.PIPE) 113 ProcessLogger(self._cns_process) 114 115 if self.WaitUntil(self._CanAccessServer, retry_sleep=3, timeout=30, 116 debug=False): 117 pyauto.PyUITest.setUp(self) 118 else: 119 self.tearDown() 120 self.fail('Failed to start CNS.') 121 122 def _CanAccessServer(self): 123 """Checks if the CNS server can serve a file with no network constraints.""" 124 test_url = ''.join([CNS_BASE_URL, 'f=', _TEST_VIDEO]) 125 try: 126 return urllib2.urlopen(test_url) is not None 127 except Exception: 128 return False 129 130 def tearDown(self): 131 """Stops the Constrained Network Server (CNS).""" 132 if USE_LOCAL_CNS: 133 logging.debug('Stopping CNS server.') 134 # Do not use process.kill(), it will not clean up cns. 135 self.Kill(self._cns_process.pid) 136 # Need to wait since the process logger has a lock on the process stderr. 137 self._cns_process.wait() 138 self.assertFalse(self._cns_process.returncode is None) 139 logging.debug('CNS server stopped.') 140 else: 141 # Call CNS Cleanup to remove all ports created by this client. 142 self.NavigateToURL(CNS_CLEANUP_URL) 143 pyauto.PyUITest.tearDown(self) 144 145 146 class ProcessLogger(threading.Thread): 147 """A thread to log a process's stderr output.""" 148 149 def __init__(self, process): 150 """Starts the process logger thread. 151 152 Args: 153 process: The process to log. 154 """ 155 threading.Thread.__init__(self) 156 self._process = process 157 self.start() 158 159 def run(self): 160 """Adds debug statements for the process's stderr output.""" 161 line = True 162 while line: 163 line = self._process.stderr.readline() 164 logging.debug(line.strip()) 165 166 167 def GetFileURL(file_name, bandwidth=0, latency=0, loss=0, new_port=False): 168 """Returns CNS URL for the file with specified constraints. 169 170 Args: 171 Check cns.ServeConstrained() args for more details. 172 """ 173 video_url = [CNS_BASE_URL, 'f=' + file_name] 174 if bandwidth > 0: 175 video_url.append('bandwidth=%d' % bandwidth) 176 if latency > 0: 177 video_url.append('latency=%d' % latency) 178 if loss > 0: 179 video_url.append('loss=%d' % loss) 180 if new_port: 181 video_url.append('new_port=%s' % new_port) 182 return '&'.join(video_url) 183 184 185 def CreateCNSPerfTasks(network_constraints_settings, test_media_files): 186 """Returns a queue of tasks combinining network constrains with media files. 187 188 Args: 189 network_constraints_settings: List of (setting_name, setting_values) 190 tupples. 191 test_media_files: List of media files to run the tests on. 192 """ 193 # Convert relative test path into an absolute path. 194 tasks = Queue.Queue() 195 for file_name in test_media_files: 196 for series_name, settings in network_constraints_settings: 197 logging.debug('Add test: %s\tSettings: %s\tMedia: %s', series_name, 198 settings, file_name) 199 tasks.put((series_name, settings, file_name)) 200 201 return tasks 202