Home | History | Annotate | Download | only in cros
      1 # Copyright (c) 2012 The Chromium OS 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 """A tool to measure single-stream link bandwidth using HTTP connections."""
      6 
      7 import logging, random, time, urllib2
      8 
      9 import numpy.random
     10 
     11 TIMEOUT = 90
     12 
     13 
     14 class Error(Exception):
     15   pass
     16 
     17 
     18 def TimeTransfer(url, data):
     19     """Transfers data to/from url.  Returns (time, url contents)."""
     20     start_time = time.time()
     21     result = urllib2.urlopen(url, data=data, timeout=TIMEOUT)
     22     got = result.read()
     23     transfer_time = time.time() - start_time
     24     if transfer_time <= 0:
     25         raise Error("Transfer of %s bytes took nonsensical time %s"
     26                     % (url, transfer_time))
     27     return (transfer_time, got)
     28 
     29 
     30 def TimeTransferDown(url_pattern, size):
     31     url = url_pattern % {'size': size}
     32     (transfer_time, got) = TimeTransfer(url, data=None)
     33     if len(got) != size:
     34       raise Error('Got %d bytes, expected %d' % (len(got), size))
     35     return transfer_time
     36 
     37 
     38 def TimeTransferUp(url, size):
     39     """If size > 0, POST size bytes to URL, else GET url.  Return time taken."""
     40     data = numpy.random.bytes(size)
     41     (transfer_time, _) = TimeTransfer(url, data)
     42     return transfer_time
     43 
     44 
     45 def BenchmarkOneDirection(latency, label, url, benchmark_function):
     46     """Transfer a reasonable amount of data and record the speed.
     47 
     48     Args:
     49         latency:  Time for a 1-byte transfer
     50         label:  Label to add to perf keyvals
     51         url:  URL (or pattern) to transfer at
     52         benchmark_function:  Function to perform actual transfer
     53     Returns:
     54         Key-value dictionary, suitable for reporting to write_perf_keyval.
     55         """
     56 
     57     size = 1 << 15              # Start with a small download
     58     maximum_size = 1 << 24      # Go large, if necessary
     59     multiple = 1
     60 
     61     remaining = 2
     62     transfer_time = 0
     63 
     64     # Long enough that startup latency shouldn't dominate.
     65     target = max(20 * latency, 10)
     66     logging.info('Target time: %s' % target)
     67 
     68     while remaining > 0:
     69         size = min(int(size * multiple), maximum_size)
     70         transfer_time = benchmark_function(url, size)
     71         logging.info('Transfer of %s took %s (%s b/s)'
     72                      % (size, transfer_time, 8 * size / transfer_time))
     73         if transfer_time >= target:
     74             break
     75         remaining -= 1
     76 
     77         # Take the latency into account when guessing a size for a
     78         # larger transfer.  This is a pretty simple model, but it
     79         # appears to work.
     80         adjusted_transfer_time = max(transfer_time - latency, 0.01)
     81         multiple = target / adjusted_transfer_time
     82 
     83     if remaining == 0:
     84         logging.warning(
     85             'Max size transfer still took less than minimum desired time %s'
     86             % target)
     87 
     88     return {'seconds_%s_fetch_time' % label: transfer_time,
     89             'bytes_%s_bytes_transferred' % label: size,
     90             'bits_second_%s_speed' % label: 8 * size / transfer_time,
     91             }
     92 
     93 
     94 def HttpSpeed(download_url_format_string,
     95               upload_url):
     96     """Measures upload and download performance to the supplied URLs.
     97 
     98     Args:
     99         download_url_format_string:  URL pattern with %(size) for payload bytes
    100         upload_url:  URL that accepts large POSTs
    101     Returns:
    102         A dict of perf_keyval
    103     """
    104     # We want the download to be substantially longer than the
    105     # one-byte fetch time that we can isolate bandwidth instead of
    106     # latency.
    107     latency = TimeTransferDown(download_url_format_string, 1)
    108 
    109     logging.info('Latency is %s'  % latency)
    110 
    111     down = BenchmarkOneDirection(
    112         latency,
    113         'downlink',
    114         download_url_format_string,
    115         TimeTransferDown)
    116 
    117     up = BenchmarkOneDirection(
    118         latency,
    119         'uplink',
    120         upload_url,
    121         TimeTransferUp)
    122 
    123     up.update(down)
    124     return up
    125