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