1# Lint as: python2, python3 2# Copyright (c) 2013 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 6import logging 7 8from autotest_lib.server.cros.network import netperf_runner 9 10class NetperfSession(object): 11 """Abstracts a network performance measurement to reduce variance.""" 12 13 MAX_DEVIATION_FRACTION = 0.03 14 MEASUREMENT_MAX_SAMPLES = 10 15 MEASUREMENT_MAX_FAILURES = 2 16 MEASUREMENT_MIN_SAMPLES = 3 17 WARMUP_SAMPLE_TIME_SECONDS = 2 18 WARMUP_WINDOW_SIZE = 2 19 WARMUP_MAX_SAMPLES = 10 20 21 22 @staticmethod 23 def _from_samples(samples): 24 """Construct a NetperfResult that averages previous results. 25 26 This makes typing this considerably easier. 27 28 @param samples: list of NetperfResult 29 @return NetperfResult average of samples. 30 31 """ 32 return netperf_runner.NetperfResult.from_samples(samples) 33 34 35 def __init__(self, 36 client_proxy, 37 server_proxy, 38 client_interface=None, 39 server_interface=None, 40 ignore_failures=False): 41 """Construct a NetperfSession. 42 43 @param client_proxy: WiFiClient object. 44 @param server_proxy: LinuxSystem object. 45 @param client_interface Interface object. 46 @param server_interface Interface object. 47 48 """ 49 self._client_proxy = client_proxy 50 self._server_proxy = server_proxy 51 self._client_interface = client_interface 52 self._server_interface = server_interface 53 self._ignore_failures = ignore_failures 54 55 56 def warmup_wifi_part(self, warmup_client=True): 57 """Warm up a rate controller on the client or server. 58 59 WiFi "warms up" in that rate controllers dynamically adjust to 60 environmental conditions by increasing symbol rates until loss is 61 observed. This manifests as initially slow data transfer rates that 62 get better over time. 63 64 We'll say that a rate controller is warmed up if a small sample of 65 WARMUP_WINDOW_SIZE throughput measurements has an average throughput 66 within a standard deviation of the previous WARMUP_WINDOW_SIZE samples. 67 68 @param warmup_client: bool True iff we should warmup the client rate 69 controller. Otherwise we warm up the server rate controller. 70 71 """ 72 if warmup_client: 73 # We say a station is warm if the TX throughput is maximized. 74 # Each station only controls its own transmission TX rate. 75 logging.info('Warming up the client WiFi rate controller.') 76 test_type = netperf_runner.NetperfConfig.TEST_TYPE_TCP_STREAM 77 else: 78 logging.info('Warming up the server WiFi rate controller.') 79 test_type = netperf_runner.NetperfConfig.TEST_TYPE_TCP_MAERTS 80 config = netperf_runner.NetperfConfig( 81 test_type, test_time=self.WARMUP_SAMPLE_TIME_SECONDS) 82 warmup_history = [] 83 with netperf_runner.NetperfRunner(self._client_proxy, 84 self._server_proxy, config, 85 self._client_interface, 86 self._server_interface) as runner: 87 while len(warmup_history) < self.WARMUP_MAX_SAMPLES: 88 warmup_history.append(runner.run()) 89 if len(warmup_history) > 2 * self.WARMUP_WINDOW_SIZE: 90 # Grab 2 * WARMUP_WINDOW_SIZE samples, divided into the most 91 # recent chunk and the chunk before that. 92 start = -2 * self.WARMUP_WINDOW_SIZE 93 middle = -self.WARMUP_WINDOW_SIZE 94 past_result = self._from_samples( 95 warmup_history[start:middle]) 96 recent_result = self._from_samples(warmup_history[middle:]) 97 if recent_result.throughput < (past_result.throughput + 98 past_result.throughput_dev): 99 logging.info('Rate controller is warmed.') 100 return 101 else: 102 logging.warning('Did not completely warmup the WiFi part.') 103 104 105 def warmup_stations(self): 106 """Warms up both the client and server stations.""" 107 self.warmup_wifi_part(warmup_client=True) 108 self.warmup_wifi_part(warmup_client=False) 109 110 111 def run(self, config): 112 """Measure the average and standard deviation of a netperf test. 113 114 @param config: NetperfConfig object. 115 116 """ 117 logging.info('Performing %s measurements in netperf session.', 118 config.human_readable_tag) 119 history = [] 120 none_count = 0 121 final_result = None 122 with netperf_runner.NetperfRunner(self._client_proxy, 123 self._server_proxy, config, 124 self._client_interface, 125 self._server_interface) as runner: 126 while len(history) + none_count < self.MEASUREMENT_MAX_SAMPLES: 127 result = runner.run(ignore_failures=self._ignore_failures) 128 if result is None: 129 none_count += 1 130 # Might occur when, e.g., signal strength is too low. 131 if none_count > self.MEASUREMENT_MAX_FAILURES: 132 logging.error('Too many failures (%d), aborting', 133 none_count) 134 break 135 continue 136 137 history.append(result) 138 if len(history) < self.MEASUREMENT_MIN_SAMPLES: 139 continue 140 141 final_result = self._from_samples(history) 142 if final_result.all_deviations_less_than_fraction( 143 self.MAX_DEVIATION_FRACTION): 144 break 145 146 if final_result is None: 147 final_result = self._from_samples(history) 148 logging.info('Took averaged measurement %r.', final_result) 149 return history or None 150