Home | History | Annotate | Download | only in media
      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