Home | History | Annotate | Download | only in network
      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 
      5 import logging
      6 
      7 from autotest_lib.server.cros.network import netperf_runner
      8 
      9 class 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