blob: a283d6b8f598954de1454e8fdae4b90b17652a27 [file] [log] [blame]
Derek Beckett3fff4b92020-10-20 08:27:06 -07001# Lint as: python2, python3
David Rochberg4f6aa322012-02-13 16:20:10 -05002# 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
Derek Beckett3fff4b92020-10-20 08:27:06 -07008import logging, random, time
9from six.moves import urllib
David Rochberg4f6aa322012-02-13 16:20:10 -050010
barfab@chromium.orgb6d29932012-04-11 09:46:43 +020011import numpy.random
David Rochberg4f6aa322012-02-13 16:20:10 -050012
Thieu Le641bd572013-01-10 14:18:30 -080013TIMEOUT = 90
David Rochberg4f6aa322012-02-13 16:20:10 -050014
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()
Derek Beckett3fff4b92020-10-20 08:27:06 -070023 result = urllib.request.urlopen(url, data=data, timeout=TIMEOUT)
David Rochberg4f6aa322012-02-13 16:20:10 -050024 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