blob: 33ad8743b8e823d845ea2cf1db4295af98350c39 [file] [log] [blame]
Hayden Nix3f2eebf2020-01-14 10:39:21 -08001#!/usr/bin/env python3
2#
3# Copyright 2020 - 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
Hayden Nixc8a02bf2020-04-14 19:12:34 +000017import json
18import os
19import time
Hayden Nix3f2eebf2020-01-14 10:39:21 -080020
Hayden Nixc8a02bf2020-04-14 19:12:34 +000021from statistics import pstdev
22
23from bokeh.models import FixedTicker
24from bokeh.plotting import ColumnDataSource
25from bokeh.plotting import figure
26from bokeh.plotting import output_file
27from bokeh.plotting import save
28
29from acts import asserts
30from acts import context
Hayden Nix3f2eebf2020-01-14 10:39:21 -080031from acts import utils
Hayden Nix3f2eebf2020-01-14 10:39:21 -080032from acts.controllers.ap_lib import hostapd_config
Hayden Nixc8a02bf2020-04-14 19:12:34 +000033from acts.controllers.ap_lib import hostapd_constants
34from acts.controllers.ap_lib.hostapd_security import Security
35from acts.controllers.iperf_server import IPerfResult
36from acts.test_utils.abstract_devices.utils_lib import wlan_utils
Hayden Nix3f2eebf2020-01-14 10:39:21 -080037from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
Hayden Nix3f2eebf2020-01-14 10:39:21 -080038from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
Hayden Nix3f2eebf2020-01-14 10:39:21 -080039
40N_CAPABILITIES_DEFAULT = [
41 hostapd_constants.N_CAPABILITY_LDPC, hostapd_constants.N_CAPABILITY_SGI20,
42 hostapd_constants.N_CAPABILITY_SGI40,
43 hostapd_constants.N_CAPABILITY_TX_STBC,
44 hostapd_constants.N_CAPABILITY_RX_STBC1
45]
46
47AC_CAPABILITIES_DEFAULT = [
48 hostapd_constants.AC_CAPABILITY_MAX_MPDU_11454,
49 hostapd_constants.AC_CAPABILITY_RXLDPC,
50 hostapd_constants.AC_CAPABILITY_SHORT_GI_80,
51 hostapd_constants.AC_CAPABILITY_TX_STBC_2BY1,
52 hostapd_constants.AC_CAPABILITY_RX_STBC_1,
53 hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP7,
54 hostapd_constants.AC_CAPABILITY_RX_ANTENNA_PATTERN,
55 hostapd_constants.AC_CAPABILITY_TX_ANTENNA_PATTERN
56]
57
Hayden Nixc8a02bf2020-04-14 19:12:34 +000058DEFAULT_MIN_THROUGHPUT = 0
59DEFAULT_MAX_STD_DEV = 1
Hayden Nix3f2eebf2020-01-14 10:39:21 -080060
Hayden Nixc8a02bf2020-04-14 19:12:34 +000061DEFAULT_TIME_TO_WAIT_FOR_IP_ADDR = 30
62GRAPH_CIRCLE_SIZE = 10
63IPERF_NO_THROUGHPUT_VALUE = 0
64MAX_2_4_CHANNEL = 14
65TIME_TO_SLEEP_BETWEEN_RETRIES = 1
66WEP_HEX_STRING_LENGTH = 10
Hayden Nix3f2eebf2020-01-14 10:39:21 -080067
Hayden Nix3f2eebf2020-01-14 10:39:21 -080068
Hayden Nixc8a02bf2020-04-14 19:12:34 +000069def get_test_name(settings):
70 """Retrieves the test_name value from test_settings"""
71 return settings.get('test_name')
Hayden Nix3f2eebf2020-01-14 10:39:21 -080072
73
74class ChannelSweepTest(WifiBaseTest):
Hayden Nixc8a02bf2020-04-14 19:12:34 +000075 """Tests channel performance.
Hayden Nix3f2eebf2020-01-14 10:39:21 -080076
77 Testbed Requirement:
78 * One ACTS compatible device (dut)
79 * One Access Point
Hayden Nixc8a02bf2020-04-14 19:12:34 +000080 * One Linux Machine used as IPerfServer if running performance tests
81 Note: Performance tests should be done in isolated testbed.
Hayden Nix3f2eebf2020-01-14 10:39:21 -080082 """
83 def __init__(self, controllers):
84 WifiBaseTest.__init__(self, controllers)
Hayden Nixc8a02bf2020-04-14 19:12:34 +000085 if 'channel_sweep_test_params' in self.user_params:
86 self.time_to_wait_for_ip_addr = self.user_params[
87 'channel_sweep_test_params'].get(
88 'time_to_wait_for_ip_addr',
89 DEFAULT_TIME_TO_WAIT_FOR_IP_ADDR)
90 else:
91 self.time_to_wait_for_ip_addr = DEFAULT_TIME_TO_WAIT_FOR_IP_ADDR
Hayden Nix3f2eebf2020-01-14 10:39:21 -080092
93 def setup_class(self):
94 super().setup_class()
95 if 'dut' in self.user_params:
96 if self.user_params['dut'] == 'fuchsia_devices':
97 self.dut = create_wlan_device(self.fuchsia_devices[0])
98 elif self.user_params['dut'] == 'android_devices':
99 self.dut = create_wlan_device(self.android_devices[0])
100 else:
101 raise ValueError('Invalid DUT specified in config. (%s)' %
102 self.user_params['dut'])
103 else:
104 # Default is an android device, just like the other tests
105 self.dut = create_wlan_device(self.android_devices[0])
106
107 self.android_devices = getattr(self, 'android_devices', [])
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000108
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800109 self.access_point = self.access_points[0]
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000110 self.iperf_server = self.iperf_servers[0]
111 self.iperf_server.start()
112 self.iperf_client = self.iperf_clients[0]
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800113 self.access_point.stop_all_aps()
114
115 def setup_test(self):
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000116 self.access_point.stop_all_aps()
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800117 for ad in self.android_devices:
118 ad.droid.wakeLockAcquireBright()
119 ad.droid.wakeUpNow()
120 self.dut.wifi_toggle_state(True)
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000121 self.dut.disconnect()
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800122
123 def teardown_test(self):
124 for ad in self.android_devices:
125 ad.droid.wakeLockRelease()
126 ad.droid.goToSleepNow()
127 self.dut.turn_location_off_and_scan_toggle_off()
128 self.dut.disconnect()
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800129 self.access_point.stop_all_aps()
130
131 def on_fail(self, test_name, begin_time):
132 self.dut.take_bug_report(test_name, begin_time)
133 self.dut.get_log(test_name, begin_time)
134
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000135 def setup_ap(self, channel, channel_bandwidth, security_profile=None):
136 """Start network on AP with basic configuration.
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800137
138 Args:
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000139 channel: int, channel to use for network
140 channel_bandwidth: int, channel bandwidth in mhz to use for network,
141 security_profile: Security object, or None if open
142
143 Returns:
144 string, ssid of network running
145
146 Raises:
147 ConnectionError if network is not started successfully.
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800148 """
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000149 if channel > MAX_2_4_CHANNEL:
150 vht_bandwidth = channel_bandwidth
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800151 else:
152 vht_bandwidth = None
153
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000154 if channel_bandwidth == hostapd_constants.CHANNEL_BANDWIDTH_20MHZ:
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800155 n_capabilities = N_CAPABILITIES_DEFAULT + [
156 hostapd_constants.N_CAPABILITY_HT20
157 ]
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000158 elif (channel_bandwidth == hostapd_constants.CHANNEL_BANDWIDTH_40MHZ or
159 channel_bandwidth == hostapd_constants.CHANNEL_BANDWIDTH_80MHZ):
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800160 if hostapd_config.ht40_plus_allowed(channel):
161 extended_channel = [hostapd_constants.N_CAPABILITY_HT40_PLUS]
162 elif hostapd_config.ht40_minus_allowed(channel):
163 extended_channel = [hostapd_constants.N_CAPABILITY_HT40_MINUS]
164 else:
165 raise ValueError('Invalid Channel: %s' % channel)
166 n_capabilities = N_CAPABILITIES_DEFAULT + extended_channel
167 else:
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000168 raise ValueError('Invalid Bandwidth: %s' % channel_bandwidth)
169 ssid = utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G)
170 try:
171 wlan_utils.setup_ap(access_point=self.access_point,
172 profile_name='whirlwind',
173 channel=channel,
174 security=security_profile,
175 n_capabilities=n_capabilities,
176 ac_capabilities=None,
177 force_wmm=True,
178 ssid=ssid,
179 vht_bandwidth=vht_bandwidth,
180 setup_bridge=True)
181 except Exception as err:
182 raise ConnectionError(
183 'Failed to setup ap on channel: %s, channel bandwidth: %smhz. '
184 'Error: %s' % (channel, channel_bandwidth, err))
185 else:
186 self.log.info(
187 'Network (ssid: %s) up on channel %s w/ channel bandwidth %smhz'
188 % (ssid, channel, channel_bandwidth))
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800189
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000190 return ssid
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800191
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000192 def get_and_verify_iperf_address(self, channel, device, interface=None):
193 """Get ip address from a devices interface and verify it belongs to
194 expected subnet based on APs DHCP config.
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800195
196 Args:
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000197 channel: int, channel network is running on, to determine subnet
198 device: device to get ip address for
199 interface (default: None): interface on device to get ip address.
200 If None, uses device.test_interface.
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800201
202 Returns:
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000203 String, ip address of device on given interface (or test_interface)
204
205 Raises:
206 ConnectionError, if device does not have a valid ip address after
207 all retries.
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800208 """
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000209 if channel <= MAX_2_4_CHANNEL:
210 subnet = self.access_point._AP_2G_SUBNET_STR
211 else:
212 subnet = self.access_point._AP_5G_SUBNET_STR
213 end_time = time.time() + self.time_to_wait_for_ip_addr
214 while time.time() < end_time:
215 if interface:
216 device_addresses = device.get_interface_ip_addresses(interface)
217 else:
218 device_addresses = device.get_interface_ip_addresses(
219 device.test_interface)
220
221 if device_addresses['ipv4_private']:
222 for ip_addr in device_addresses['ipv4_private']:
223 if utils.ip_in_subnet(ip_addr, subnet):
224 return ip_addr
225 else:
226 self.log.debug(
227 'Device has an ip address (%s), but it is not in '
228 'subnet %s' % (ip_addr, subnet))
229 else:
230 self.log.debug(
231 'Device does not have a valid ip address. Retrying.')
232 time.sleep(TIME_TO_SLEEP_BETWEEN_RETRIES)
233 raise ConnectionError('Device failed to get an ip address.')
234
235 def get_iperf_throughput(self,
236 iperf_server_address,
237 iperf_client_address,
238 reverse=False):
239 """Run iperf between client and server and get the throughput.
240
241 Args:
242 iperf_server_address: string, ip address of running iperf server
243 iperf_client_address: string, ip address of iperf client (dut)
244 reverse (default: False): If True, run traffic in reverse direction,
245 from server to client.
246
247 Returns:
248 int, iperf throughput OR IPERF_NO_THROUGHPUT_VALUE, if iperf fails
249 """
250 if reverse:
251 self.log.info(
252 'Running IPerf traffic from server (%s) to dut (%s).' %
253 (iperf_server_address, iperf_client_address))
254 iperf_results_file = self.iperf_client.start(
255 iperf_server_address, '-i 1 -t 10 -R -J', 'channel_sweep_rx')
256 else:
257 self.log.info(
258 'Running IPerf traffic from dut (%s) to server (%s).' %
259 (iperf_client_address, iperf_server_address))
260 iperf_results_file = self.iperf_client.start(
261 iperf_server_address, '-i 1 -t 10 -J', 'channel_sweep_tx')
262 if iperf_results_file:
263 iperf_results = IPerfResult(iperf_results_file)
264 return iperf_results.avg_send_rate
265 else:
266 return IPERF_NO_THROUGHPUT_VALUE
267
268 def log_to_file_and_throughput_data(self, channel, channel_bandwidth,
269 tx_throughput, rx_throughput):
270 """Write performance info to csv file and to throughput data.
271
272 Args:
273 channel: int, channel that test was run on
274 channel_bandwidth: int, channel bandwidth the test used
275 tx_throughput: float, throughput value from dut to iperf server
276 rx_throughput: float, throughput value from iperf server to dut
277 """
278 test_name = self.throughput_data['test']
279 output_path = context.get_current_context().get_base_output_path()
280 log_path = '%s/ChannelSweepTest/%s' % (output_path, test_name)
281 if not os.path.exists(log_path):
282 os.makedirs(log_path)
283 log_file = '%s/%s_%smhz.csv' % (log_path, test_name, channel_bandwidth)
284 self.log.info('Writing IPerf results for %s to %s' %
285 (test_name, log_file))
286 with open(log_file, 'a') as csv_file:
287 csv_file.write('%s,%s,%s\n' %
288 (channel, tx_throughput, rx_throughput))
289 self.throughput_data['results'][str(channel)] = {
290 'tx_throughput': tx_throughput,
291 'rx_throughput': rx_throughput
292 }
293
294 def write_graph(self):
295 """Create graph html files from throughput data, plotting channel vs
296 tx_throughput and channel vs rx_throughput.
297 """
298 output_path = context.get_current_context().get_base_output_path()
299 test_name = self.throughput_data['test']
300 channel_bandwidth = self.throughput_data['channel_bandwidth']
301 output_file_name = '%s/ChannelSweepTest/%s/%s_%smhz.html' % (
302 output_path, test_name, test_name, channel_bandwidth)
303 output_file(output_file_name)
304 channels = []
305 tx_throughputs = []
306 rx_throughputs = []
307 for channel in self.throughput_data['results']:
308 channels.append(str(channel))
309 tx_throughputs.append(
310 self.throughput_data['results'][channel]['tx_throughput'])
311 rx_throughputs.append(
312 self.throughput_data['results'][channel]['rx_throughput'])
313 channel_vs_throughput_data = ColumnDataSource(
314 data=dict(channels=channels,
315 tx_throughput=tx_throughputs,
316 rx_throughput=rx_throughputs))
317 TOOLTIPS = [('Channel', '@channels'),
318 ('TX_Throughput', '@tx_throughput'),
319 ('RX_Throughput', '@rx_throughput')]
320 channel_vs_throughput_graph = figure(title='Channels vs. Throughput',
321 x_axis_label='Channels',
322 x_range=channels,
323 y_axis_label='Throughput',
324 tooltips=TOOLTIPS)
325 channel_vs_throughput_graph.sizing_mode = 'stretch_both'
326 channel_vs_throughput_graph.title.align = 'center'
327 channel_vs_throughput_graph.line('channels',
328 'tx_throughput',
329 source=channel_vs_throughput_data,
330 line_width=2,
331 line_color='blue',
332 legend_label='TX_Throughput')
333 channel_vs_throughput_graph.circle('channels',
334 'tx_throughput',
335 source=channel_vs_throughput_data,
336 size=GRAPH_CIRCLE_SIZE,
337 color='blue')
338 channel_vs_throughput_graph.line('channels',
339 'rx_throughput',
340 source=channel_vs_throughput_data,
341 line_width=2,
342 line_color='red',
343 legend_label='RX_Throughput')
344 channel_vs_throughput_graph.circle('channels',
345 'rx_throughput',
346 source=channel_vs_throughput_data,
347 size=GRAPH_CIRCLE_SIZE,
348 color='red')
349
350 channel_vs_throughput_graph.legend.location = "top_left"
351 graph_file = save([channel_vs_throughput_graph])
352 self.log.info('Saved graph to %s' % graph_file)
353
354 def verify_standard_deviation(self, max_std_dev):
355 """Verifies the standard deviation of the throughput across the channels
356 does not exceed the max_std_dev value.
357
358 Args:
359 max_std_dev: float, max standard deviation of throughput for a test
360 to pass (in mb/s)
361
362 Raises:
363 TestFailure, if standard deviation of throughput exceeds max_std_dev
364 """
365 self.log.info('Verifying standard deviation across channels does not '
366 'exceed max standard deviation of %s mb/s' % max_std_dev)
367 tx_values = []
368 rx_values = []
369 for channel in self.throughput_data['results']:
370 if self.throughput_data['results'][channel][
371 'tx_throughput'] is not None:
372 tx_values.append(
373 self.throughput_data['results'][channel]['tx_throughput'])
374 if self.throughput_data['results'][channel][
375 'rx_throughput'] is not None:
376 rx_values.append(
377 self.throughput_data['results'][channel]['rx_throughput'])
378 tx_std_dev = pstdev(tx_values)
379 rx_std_dev = pstdev(rx_values)
380 if tx_std_dev > max_std_dev or rx_std_dev > max_std_dev:
381 asserts.fail(
382 'With %smhz channel bandwidth, throughput standard '
383 'deviation (tx: %s mb/s, rx: %s mb/s) exceeds max standard deviation'
384 ' (%s mb/s).' % (self.throughput_data['channel_bandwidth'],
385 tx_std_dev, rx_std_dev, max_std_dev))
386 else:
387 asserts.explicit_pass(
388 'Throughput standard deviation (tx: %s mb/s, rx: %s mb/s) '
389 'with %smhz channel bandwidth does not exceed maximum (%s mb/s).'
390 % (tx_std_dev, rx_std_dev,
391 self.throughput_data['channel_bandwidth'], max_std_dev))
392
393 def run_channel_performance_tests(self, settings):
394 """Test function for running channel performance tests. Used by both
395 explicit test cases and debug test cases from config. Runs a performance
396 test for each channel in test_channels with test_channel_bandwidth, then
397 writes a graph and csv file of the channel vs throughput.
398
399 Args:
400 settings: dict, containing the following test settings
401 test_channels: list of channels to test.
402 test_channel_bandwidth: int, channel bandwidth to use for test.
403 test_security (optional): string, security type to use for test.
404 min_tx_throughput (optional, default: 0): float, minimum tx
405 throughput threshold to pass individual channel tests
406 (in mb/s).
407 min_rx_throughput (optional, default: 0): float, minimum rx
408 throughput threshold to pass individual channel tests
409 (in mb/s).
410 max_std_dev (optional, default: 1): float, maximum standard
411 deviation of throughput across all test channels to pass
412 test (in mb/s).
413 base_test_name (optional): string, test name prefix to use with
414 generated subtests.
415 test_name (debug tests only): string, the test name for this
416 parent test case from the config file. In explicit tests,
417 this is not necessary.
418
419 Writes:
420 CSV file: channel, tx_throughput, rx_throughput
421 for every test channel.
422 Graph: channel vs tx_throughput & channel vs rx_throughput
423
424 Raises:
425 TestFailure, if throughput standard deviation across channels
426 exceeds max_std_dev
427
428 Example Explicit Test (see EOF for debug JSON example):
429 def test_us_2g_20mhz_wpa2(self):
430 self.run_channel_performance_tests(
431 dict(
432 test_channels=hostapd_constants.US_CHANNELS_2G,
433 test_channel_bandwidth=20,
434 test_security=hostapd_constants.WPA2_STRING,
435 min_tx_throughput=2,
436 min_rx_throughput=4,
437 max_std_dev=0.75,
438 base_test_name='test_us'))
439 """
440 test_channels = settings['test_channels']
441 test_channel_bandwidth = settings['test_channel_bandwidth']
442 test_security = settings.get('test_security')
443 test_name = settings.get('test_name', self.test_name)
444 base_test_name = settings.get('base_test_name', 'test')
445 min_tx_throughput = settings.get('min_tx_throughput',
446 DEFAULT_MIN_THROUGHPUT)
447 min_rx_throughput = settings.get('min_rx_throughput',
448 DEFAULT_MIN_THROUGHPUT)
449 max_std_dev = settings.get('max_std_dev', DEFAULT_MAX_STD_DEV)
450
451 self.throughput_data = {
452 'test': test_name,
453 'channel_bandwidth': test_channel_bandwidth,
454 'results': {}
455 }
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800456 test_list = []
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000457 for channel in test_channels:
458 sub_test_name = '%s_channel_%s_%smhz' % (base_test_name, channel,
459 test_channel_bandwidth)
460 test_list.append({
461 'test_name': sub_test_name,
462 'channel': int(channel),
463 'channel_bandwidth': int(test_channel_bandwidth),
464 'security': test_security,
465 'min_tx_throughput': min_tx_throughput,
466 'min_rx_throughput': min_rx_throughput
467 })
468 self.run_generated_testcases(self.get_channel_performance,
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800469 settings=test_list,
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000470 name_func=get_test_name)
471 self.log.info('Channel tests completed.')
472 self.write_graph()
473 self.verify_standard_deviation(max_std_dev)
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800474
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000475 def get_channel_performance(self, settings):
476 """Run a single channel performance test and logs results to csv file
477 and throughput data. Run with generated sub test cases in
478 run_channel_performance_tests.
479
480 1. Sets up network with test settings
481 2. Associates DUT
482 3. Runs traffic between DUT and iperf server (both directions)
483 4. Logs channel, tx_throughput (mb/s), and rx_throughput (mb/s) to
484 log file and throughput data.
485 5. Checks throughput values against minimum throughput thresholds.
486
487 Args:
488 settings: see run_channel_performance_tests
489
490 Raises:
491 TestFailure, if throughput (either direction) is less than
492 the directions given minimum throughput threshold.
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800493 """
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000494 channel = settings['channel']
495 channel_bandwidth = settings['channel_bandwidth']
496 security = settings['security']
497 test_name = settings['test_name']
498 min_tx_throughput = settings['min_tx_throughput']
499 min_rx_throughput = settings['min_rx_throughput']
500 if security:
501 if security == hostapd_constants.WEP_STRING:
502 password = utils.rand_hex_str(WEP_HEX_STRING_LENGTH)
503 else:
504 password = utils.rand_ascii_str(
505 hostapd_constants.MIN_WPA_PSK_LENGTH)
506 security_profile = Security(security_mode=security,
507 password=password)
508 else:
509 password = None
510 security_profile = None
511 ssid = self.setup_ap(channel, channel_bandwidth, security_profile)
512 associated = wlan_utils.associate(client=self.dut,
513 ssid=ssid,
514 password=password)
515 if not associated:
516 self.log_to_file_and_throughput_data(channel, channel_bandwidth,
517 None, None)
518 asserts.fail('Device failed to associate with network %s' % ssid)
519 self.log.info('DUT (%s) connected to network %s.' %
520 (self.dut.device.ip, ssid))
521 self.iperf_server.renew_test_interface_ip_address()
522 self.log.info(
523 'Getting ip address for iperf server. Will retry for %s seconds.' %
524 self.time_to_wait_for_ip_addr)
525 iperf_server_address = self.get_and_verify_iperf_address(
526 channel, self.iperf_server)
527 self.log.info(
528 'Getting ip address for DUT. Will retry for %s seconds.' %
529 self.time_to_wait_for_ip_addr)
530 iperf_client_address = self.get_and_verify_iperf_address(
531 channel, self.dut, self.iperf_client.test_interface)
532 tx_throughput = self.get_iperf_throughput(iperf_server_address,
533 iperf_client_address)
534 rx_throughput = self.get_iperf_throughput(iperf_server_address,
535 iperf_client_address,
536 reverse=True)
537 self.log_to_file_and_throughput_data(channel, channel_bandwidth,
538 tx_throughput, rx_throughput)
539 self.log.info('Throughput (tx, rx): (%s mb/s, %s mb/s), '
540 'Minimum threshold (tx, rx): (%s mb/s, %s mb/s)' %
541 (tx_throughput, rx_throughput, min_tx_throughput,
542 min_rx_throughput))
543 base_message = 'Actual throughput (on channel: %s, channel bandwidth: %s, security: %s)' % (
544 channel, channel_bandwidth, security)
545 if tx_throughput < min_tx_throughput or rx_throughput < min_rx_throughput:
546 asserts.fail('%s below the minimum threshold.' % base_message)
547 asserts.explicit_pass('%s above the minimum threshold.' % base_message)
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800548
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000549 # Helper functions to allow explicit tests throughput and standard deviation
550 # thresholds to be passed in via config.
551 def _get_min_tx_throughput(self, test_name):
552 return self.user_params.get('channel_sweep_test_params',
553 {}).get(test_name,
554 {}).get('min_tx_throughput',
555 DEFAULT_MIN_THROUGHPUT)
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800556
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000557 def _get_min_rx_throughput(self, test_name):
558 return self.user_params.get('channel_sweep_test_params',
559 {}).get(test_name,
560 {}).get('min_rx_throughput',
561 DEFAULT_MIN_THROUGHPUT)
562
563 def _get_max_std_dev(self, test_name):
564 return self.user_params.get('channel_sweep_test_params',
565 {}).get(test_name,
566 {}).get('min_std_dev',
567 DEFAULT_MAX_STD_DEV)
568
569 # Test cases
570 def test_us_20mhz_open_channel_performance(self):
571 self.run_channel_performance_tests(
572 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
573 hostapd_constants.US_CHANNELS_5G,
574 test_channel_bandwidth=hostapd_constants.
575 CHANNEL_BANDWIDTH_20MHZ,
576 base_test_name=self.test_name,
577 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
578 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
579 max_std_dev=self._get_max_std_dev(self.test_name)))
580
581 def test_us_40mhz_open_channel_performance(self):
582 self.run_channel_performance_tests(
583 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
584 hostapd_constants.US_CHANNELS_5G[:-1],
585 test_channel_bandwidth=hostapd_constants.
586 CHANNEL_BANDWIDTH_40MHZ,
587 base_test_name=self.test_name,
588 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
589 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
590 max_std_dev=self._get_max_std_dev(self.test_name)))
591
592 def test_us_80mhz_open_channel_performance(self):
593 self.run_channel_performance_tests(
594 dict(test_channels=hostapd_constants.US_CHANNELS_5G[:-1],
595 test_channel_bandwidth=hostapd_constants.
596 CHANNEL_BANDWIDTH_80MHZ,
597 base_test_name=self.test_name,
598 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
599 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
600 max_std_dev=self._get_max_std_dev(self.test_name)))
601
602 def test_us_20mhz_wep_channel_performance(self):
603 self.run_channel_performance_tests(
604 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
605 hostapd_constants.US_CHANNELS_5G,
606 test_channel_bandwidth=hostapd_constants.
607 CHANNEL_BANDWIDTH_20MHZ,
608 test_security=hostapd_constants.WEP_STRING,
609 base_test_name=self.test_name,
610 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
611 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
612 max_std_dev=self._get_max_std_dev(self.test_name)))
613
614 def test_us_40mhz_wep_channel_performance(self):
615 self.run_channel_performance_tests(
616 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
617 hostapd_constants.US_CHANNELS_5G[:-1],
618 test_channel_bandwidth=hostapd_constants.
619 CHANNEL_BANDWIDTH_40MHZ,
620 test_security=hostapd_constants.WEP_STRING,
621 base_test_name=self.test_name,
622 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
623 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
624 max_std_dev=self._get_max_std_dev(self.test_name)))
625
626 def test_us_80mhz_wep_channel_performance(self):
627 self.run_channel_performance_tests(
628 dict(test_channels=hostapd_constants.US_CHANNELS_5G[:-1],
629 test_channel_bandwidth=hostapd_constants.
630 CHANNEL_BANDWIDTH_80MHZ,
631 test_security=hostapd_constants.WEP_STRING,
632 base_test_name=self.test_name,
633 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
634 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
635 max_std_dev=self._get_max_std_dev(self.test_name)))
636
637 def test_us_20mhz_wpa_channel_performance(self):
638 self.run_channel_performance_tests(
639 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
640 hostapd_constants.US_CHANNELS_5G,
641 test_channel_bandwidth=hostapd_constants.
642 CHANNEL_BANDWIDTH_20MHZ,
643 test_security=hostapd_constants.WPA_STRING,
644 base_test_name=self.test_name,
645 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
646 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
647 max_std_dev=self._get_max_std_dev(self.test_name)))
648
649 def test_us_40mhz_wpa_channel_performance(self):
650 self.run_channel_performance_tests(
651 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
652 hostapd_constants.US_CHANNELS_5G[:-1],
653 test_channel_bandwidth=hostapd_constants.
654 CHANNEL_BANDWIDTH_40MHZ,
655 test_security=hostapd_constants.WPA_STRING,
656 base_test_name=self.test_name,
657 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
658 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
659 max_std_dev=self._get_max_std_dev(self.test_name)))
660
661 def test_us_80mhz_wpa_channel_performance(self):
662 self.run_channel_performance_tests(
663 dict(test_channels=hostapd_constants.US_CHANNELS_5G[:-1],
664 test_channel_bandwidth=hostapd_constants.
665 CHANNEL_BANDWIDTH_80MHZ,
666 test_security=hostapd_constants.WPA_STRING,
667 base_test_name=self.test_name,
668 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
669 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
670 max_std_dev=self._get_max_std_dev(self.test_name)))
671
672 def test_us_20mhz_wpa2_channel_performance(self):
673 self.run_channel_performance_tests(
674 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
675 hostapd_constants.US_CHANNELS_5G,
676 test_channel_bandwidth=hostapd_constants.
677 CHANNEL_BANDWIDTH_20MHZ,
678 test_security=hostapd_constants.WPA2_STRING,
679 base_test_name=self.test_name,
680 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
681 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
682 max_std_dev=self._get_max_std_dev(self.test_name)))
683
684 def test_us_40mhz_wpa2_channel_performance(self):
685 self.run_channel_performance_tests(
686 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
687 hostapd_constants.US_CHANNELS_5G[:-1],
688 test_channel_bandwidth=hostapd_constants.
689 CHANNEL_BANDWIDTH_40MHZ,
690 test_security=hostapd_constants.WPA2_STRING,
691 base_test_name=self.test_name,
692 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
693 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
694 max_std_dev=self._get_max_std_dev(self.test_name)))
695
696 def test_us_80mhz_wpa2_channel_performance(self):
697 self.run_channel_performance_tests(
698 dict(test_channels=hostapd_constants.US_CHANNELS_5G[:-1],
699 test_channel_bandwidth=hostapd_constants.
700 CHANNEL_BANDWIDTH_80MHZ,
701 test_security=hostapd_constants.WPA2_STRING,
702 base_test_name=self.test_name,
703 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
704 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
705 max_std_dev=self._get_max_std_dev(self.test_name)))
706
707 def test_us_20mhz_wpa_wpa2_channel_performance(self):
708 self.run_channel_performance_tests(
709 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
710 hostapd_constants.US_CHANNELS_5G,
711 test_channel_bandwidth=hostapd_constants.
712 CHANNEL_BANDWIDTH_20MHZ,
713 test_security=hostapd_constants.WPA_MIXED_STRING,
714 base_test_name=self.test_name,
715 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
716 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
717 max_std_dev=self._get_max_std_dev(self.test_name)))
718
719 def test_us_40mhz_wpa_wpa2_channel_performance(self):
720 self.run_channel_performance_tests(
721 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
722 hostapd_constants.US_CHANNELS_5G[:-1],
723 test_channel_bandwidth=hostapd_constants.
724 CHANNEL_BANDWIDTH_40MHZ,
725 test_security=hostapd_constants.WPA_MIXED_STRING,
726 base_test_name=self.test_name,
727 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
728 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
729 max_std_dev=self._get_max_std_dev(self.test_name)))
730
731 def test_us_80mhz_wpa_wpa2_channel_performance(self):
732 self.run_channel_performance_tests(
733 dict(test_channels=hostapd_constants.US_CHANNELS_5G[:-1],
734 test_channel_bandwidth=hostapd_constants.
735 CHANNEL_BANDWIDTH_80MHZ,
736 test_security=hostapd_constants.WPA_MIXED_STRING,
737 base_test_name=self.test_name,
738 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
739 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
740 max_std_dev=self._get_max_std_dev(self.test_name)))
741
742 def test_us_20mhz_wpa3_channel_performance(self):
743 self.run_channel_performance_tests(
744 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
745 hostapd_constants.US_CHANNELS_5G,
746 test_channel_bandwidth=hostapd_constants.
747 CHANNEL_BANDWIDTH_20MHZ,
748 test_security=hostapd_constants.WPA3_STRING,
749 base_test_name=self.test_name,
750 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
751 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
752 max_std_dev=self._get_max_std_dev(self.test_name)))
753
754 def test_us_40mhz_wpa3_channel_performance(self):
755 self.run_channel_performance_tests(
756 dict(test_channels=hostapd_constants.US_CHANNELS_2G +
757 hostapd_constants.US_CHANNELS_5G[:-1],
758 test_channel_bandwidth=hostapd_constants.
759 CHANNEL_BANDWIDTH_40MHZ,
760 test_security=hostapd_constants.WPA3_STRING,
761 base_test_name=self.test_name,
762 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
763 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
764 max_std_dev=self._get_max_std_dev(self.test_name)))
765
766 def test_us_80mhz_wpa3_channel_performance(self):
767 self.run_channel_performance_tests(
768 dict(test_channels=hostapd_constants.US_CHANNELS_5G[:-1],
769 test_channel_bandwidth=hostapd_constants.
770 CHANNEL_BANDWIDTH_80MHZ,
771 test_security=hostapd_constants.WPA3_STRING,
772 base_test_name=self.test_name,
773 min_tx_throughput=self._get_min_tx_throughput(self.test_name),
774 min_rx_throughput=self._get_min_rx_throughput(self.test_name),
775 max_std_dev=self._get_max_std_dev(self.test_name)))
776
777 def test_channel_performance_debug(self):
778 """Run channel performance test cases from the ACTS config file.
779
780 Example:
781 "channel_sweep_test_params": {
782 "debug_channel_performance_tests": [
783 {
784 "test_name": "test_123_20mhz_wpa2_performance"
785 "test_channels": [1, 2, 3],
786 "test_channel_bandwidth": 20,
787 "test_security": "wpa2",
788 "base_test_name": "test_123_perf",
789 "min_tx_throughput": 1.1,
790 "min_rx_throughput": 3,
791 "max_std_dev": 0.5
792 },
793 ...
794 ]
795 }
796
Hayden Nix3f2eebf2020-01-14 10:39:21 -0800797 """
Hayden Nixc8a02bf2020-04-14 19:12:34 +0000798 asserts.skip_if(
799 'debug_channel_performance_tests' not in self.user_params.get(
800 'channel_sweep_test_params', {}),
801 'No custom channel performance tests provided in config.')
802 base_tests = self.user_params['channel_sweep_test_params'][
803 'debug_channel_performance_tests']
804 self.run_generated_testcases(self.run_channel_performance_tests,
805 settings=base_tests,
806 name_func=get_test_name)