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