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