• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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