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