• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3.4
2#
3#   Copyright 2018 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the 'License');
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an 'AS IS' BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import time
18
19import scapy.all as scapy
20
21from acts import asserts
22from acts.test_utils.power import IperfHelper as IPH
23from acts.test_utils.power import PowerCellularLabBaseTest as PWCEL
24from acts.test_utils.wifi import wifi_power_test_utils as wputils
25
26
27class PowerTelTrafficTest(PWCEL.PowerCellularLabBaseTest):
28    """ Cellular traffic power test.
29
30    Inherits from PowerCellularLabBaseTest. Parses config specific
31    to this kind of test. Contains methods to start data traffic
32    between a local instance of iPerf and one running in the dut.
33
34    """
35
36    # Keywords for test name parameters
37    PARAM_DIRECTION = 'direction'
38    PARAM_DIRECTION_UL = 'ul'
39    PARAM_DIRECTION_DL = 'dl'
40    PARAM_DIRECTION_DL_UL = 'dlul'
41    PARAM_BANDWIDTH_LIMIT = 'blimit'
42
43    # Iperf waiting time
44    IPERF_MARGIN = 10
45
46    # Constant used to calculate the tcp window size from total throughput
47    TCP_WINDOW_FRACTION = 40
48
49    def __init__(self, controllers):
50        """ Class initialization.
51
52        Sets test parameters to initial values.
53        """
54
55        super().__init__(controllers)
56
57        # These variables are passed to iPerf when starting data
58        # traffic with the -b parameter to limit throughput on
59        # the application layer.
60        self.bandwidth_limit_dl = None
61        self.bandwidth_limit_ul = None
62
63        # Throughput obtained from iPerf
64        self.iperf_results = None
65
66    def setup_test(self):
67        """ Executed before every test case.
68
69        Parses test configuration from the test name and prepares
70        the simulation for measurement.
71        """
72
73        # Call parent method first to setup simulation
74        if not super().setup_test():
75            return False
76
77        # Traffic direction
78
79        values = self.consume_parameter(self.PARAM_DIRECTION, 1)
80
81        if values:
82            self.traffic_direction = values[1]
83        else:
84            self.log.error("The test name has to include parameter {} "
85                           "followed by {}/{}/{}.".format(
86                               self.PARAM_DIRECTION, self.PARAM_DIRECTION_UL,
87                               self.PARAM_DIRECTION_DL,
88                               self.PARAM_DIRECTION_DL_UL))
89            return False
90
91        # Bandwidth limit
92
93        values = self.consume_parameter(self.PARAM_BANDWIDTH_LIMIT, 2)
94
95        if values:
96            self.bandwidth_limit_dl = values[1]
97            self.bandwidth_limit_ul = values[2]
98        else:
99            self.bandwidth_limit_dl = 0
100            self.bandwidth_limit_ul = 0
101            self.log.error(
102                "No bandwidth limit was indicated in the test parameters. "
103                "Setting to default value of 0 (no limit to bandwidth). To set "
104                "a different value include parameter '{}' followed by two "
105                "strings indicating downlink and uplink bandwidth limits for "
106                "iPerf.".format(self.PARAM_BANDWIDTH_LIMIT))
107
108        # No errors when parsing parameters
109        return True
110
111    def teardown_test(self):
112        """Tear down necessary objects after test case is finished.
113
114        """
115
116        for ips in self.iperf_servers:
117            ips.stop()
118
119    def power_tel_traffic_test(self):
120        """ Measures power and throughput during data transmission.
121
122        Measurement step in this test. Starts iPerf client in the DUT and then
123        initiates power measurement. After that, DUT is connected again and
124        the result from iPerf is collected. Pass or fail is decided with a
125        threshold value.
126        """
127
128        # Start data traffic
129        iperf_helpers = self.start_tel_traffic(self.dut)
130
131        # Measure power
132        self.collect_power_data()
133
134        # Wait for iPerf to finish
135        time.sleep(self.IPERF_MARGIN + 2)
136
137        # Collect throughput measurement
138        self.iperf_results = self.get_iperf_results(self.dut, iperf_helpers)
139
140        # Check if power measurement is below the required value
141        self.pass_fail_check()
142
143        return self.test_result, self.iperf_results
144
145    def get_iperf_results(self, device, iperf_helpers):
146        """ Pulls iperf results from the device.
147
148        Args:
149            device: the device from which iperf results need to be pulled.
150
151        Returns:
152            a dictionary containing DL/UL throughput in Mbit/s.
153        """
154
155        throughput = {}
156
157        for iph in iperf_helpers:
158
159            self.log.info("Getting {} throughput results.".format(
160                iph.traffic_direction))
161
162            iperf_result = iph.process_iperf_results(
163                device, self.log, self.iperf_servers, self.test_name)
164
165            throughput[iph.traffic_direction] = iperf_result
166
167        return throughput
168
169    def pass_fail_check(self):
170        """ Checks power consumption and throughput.
171
172        Uses the base class method to check power consumption. Also, compares
173        the obtained throughput with the expected value provided by the
174        simulation class.
175
176        """
177
178        for direction, throughput in self.iperf_results.items():
179            try:
180                if direction == "UL":
181                    expected_t = self.simulation.maximum_uplink_throughput()
182                elif direction == "DL":
183                    expected_t = self.simulation.maximum_downlink_throughput()
184                else:
185                    raise RuntimeError("Unexpected traffic direction value.")
186            except NotImplementedError:
187                # Some simulation classes might not have implemented the max
188                # throughput calculation yet.
189                self.log.debug("Expected throughput is not available for the "
190                               "current simulation class.")
191            else:
192
193                self.log.info(
194                    "The expected {} throughput is {} Mbit/s.".format(
195                        direction, expected_t))
196                asserts.assert_true(
197                    0.90 < throughput / expected_t < 1.10,
198                    "{} throughput differed more than 10% from the expected "
199                    "value! ({}/{} = {})".format(direction,
200                                                 round(throughput, 3),
201                                                 round(expected_t, 3),
202                                                 round(throughput / expected_t,
203                                                       3)))
204
205        super().pass_fail_check()
206
207    def start_tel_traffic(self, client_host):
208        """ Starts iPerf in the indicated device and initiates traffic.
209
210        Starts the required iperf clients and servers according to the traffic
211        pattern config in the current test.
212
213        Args:
214            client_host: device handler in which to start the iperf client.
215
216        Returns:
217            A list of iperf helpers.
218        """
219
220        # The iPerf server is hosted in this computer
221        self.iperf_server_address = scapy.get_if_addr(
222            self.pkt_sender.interface)
223
224        # Start iPerf traffic
225        iperf_helpers = []
226
227        # Calculate TCP windows as a fraction of the expected throughput
228        # Some simulation classes don't implement this method yed
229        try:
230            dl_tcp_window = (self.simulation.maximum_downlink_throughput() /
231                             self.TCP_WINDOW_FRACTION)
232            ul_tcp_window = (self.simulation.maximum_uplink_throughput() /
233                             self.TCP_WINDOW_FRACTION)
234        except NotImplementedError:
235            dl_tcp_window = None
236            ul_tcp_window = None
237
238        if self.traffic_direction in [
239                self.PARAM_DIRECTION_DL, self.PARAM_DIRECTION_DL_UL
240        ]:
241            # Downlink traffic
242            iperf_helpers.append(
243                self.start_iperf_traffic(
244                    client_host,
245                    server_idx=len(iperf_helpers),
246                    traffic_direction='DL',
247                    window=dl_tcp_window,
248                    bandwidth=self.bandwidth_limit_dl))
249
250        if self.traffic_direction in [
251                self.PARAM_DIRECTION_UL, self.PARAM_DIRECTION_DL_UL
252        ]:
253            # Uplink traffic
254            iperf_helpers.append(
255                self.start_iperf_traffic(
256                    client_host,
257                    server_idx=len(iperf_helpers),
258                    traffic_direction='UL',
259                    window=ul_tcp_window,
260                    bandwidth=self.bandwidth_limit_ul))
261
262        return iperf_helpers
263
264    def start_iperf_traffic(self,
265                            client_host,
266                            server_idx,
267                            traffic_direction,
268                            bandwidth=0,
269                            window=None):
270        """Starts iPerf data traffic.
271
272        Starts an iperf client in an android device and a server locally.
273
274        Args:
275            client_host: device handler in which to start the iperf client
276            server_idx: id of the iperf server to connect to
277            traffic_direction: has to be either 'UL' or 'DL'
278            bandwidth: bandwidth limit for data traffic
279            window: the tcp window. if None, no window will be passed to iperf
280
281        Returns:
282            An IperfHelper object for the started client/server pair.
283        """
284
285        config = {
286            'traffic_type': 'TCP',
287            'duration':
288            self.mon_duration + self.mon_offset + self.IPERF_MARGIN,
289            'start_meas_time': 4,
290            'server_idx': server_idx,
291            'port': self.iperf_servers[server_idx].port,
292            'traffic_direction': traffic_direction,
293            'window': window
294        }
295
296        # If bandwidth is equal to zero then no bandwith requirements are set
297        if bandwidth > 0:
298            config['bandwidth'] = bandwidth
299
300        iph = IPH.IperfHelper(config)
301
302        # Start the server locally
303        self.iperf_servers[server_idx].start()
304
305        # Start the client in the android device
306        wputils.run_iperf_client_nonblocking(
307            client_host, self.iperf_server_address, iph.iperf_args)
308
309        return iph
310
311
312class PowerTelRvRTest(PowerTelTrafficTest):
313    """ Gets Range vs Rate curves while measuring power consumption.
314
315    Uses PowerTelTrafficTest as a base class.
316    """
317
318    # Test name configuration keywords
319    PARAM_SWEEP = "sweep"
320    PARAM_SWEEP_UPLINK = "uplink"
321    PARAM_SWEEP_DOWNLINK = "downlink"
322
323    # Sweep values. Need to be set before starting test by test
324    # function or child class.
325    downlink_power_sweep = None
326    uplink_power_sweep = None
327
328    def setup_test(self):
329        """ Executed before every test case.
330
331        Parses test configuration from the test name and prepares
332        the simulation for measurement.
333        """
334
335        # Call parent method first to setup simulation
336        if not super().setup_test():
337            return False
338
339        # Get which power value to sweep from config
340
341        try:
342            values = self.consume_parameter(self.PARAM_SWEEP, 1)
343
344            if values[1] == self.PARAM_SWEEP_UPLINK:
345                self.sweep = self.PARAM_SWEEP_UPLINK
346            elif values[1] == self.PARAM_SWEEP_DOWNLINK:
347                self.sweep = self.PARAM_SWEEP_DOWNLINK
348            else:
349                raise ValueError()
350        except:
351            self.log.error(
352                "The test name has to include parameter {} followed by "
353                "either {} or {}.".format(self.PARAM_SWEEP,
354                                          self.PARAM_SWEEP_DOWNLINK,
355                                          self.PARAM_SWEEP_UPLINK))
356            return False
357
358        return True
359
360    def power_tel_rvr_test(self):
361        """ Main function for the RvR test.
362
363        Produces the RvR curve according to the indicated sweep values.
364        """
365
366        if self.sweep == self.PARAM_SWEEP_DOWNLINK:
367            sweep_range = self.downlink_power_sweep
368        elif self.sweep == self.PARAM_SWEEP_UPLINK:
369            sweep_range = self.uplink_power_sweep
370
371        current = []
372        throughput = []
373
374        for pw in sweep_range:
375
376            if self.sweep == self.PARAM_SWEEP_DOWNLINK:
377                self.simulation.set_downlink_rx_power(self.simulation.bts1, pw)
378            elif self.sweep == self.PARAM_SWEEP_UPLINK:
379                self.simulation.set_uplink_tx_power(self.simulation.bts1, pw)
380
381            i, t = self.power_tel_traffic_test()
382            self.log.info("---------------------")
383            self.log.info("{} -- {} --".format(self.sweep, pw))
384            self.log.info("{} ----- {}".format(i, t[0]))
385            self.log.info("---------------------")
386
387            current.append(i)
388            throughput.append(t[0])
389
390        print(sweep_range)
391        print(current)
392        print(throughput)
393