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.client.common_lib import error 8 from autotest_lib.client.common_lib import utils 9 from autotest_lib.client.common_lib.cros.network import iw_runner 10 from autotest_lib.client.common_lib.cros.network import tcpdump_analyzer 11 from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes 12 from autotest_lib.server.cros.network import hostap_config 13 from autotest_lib.server.cros.network import netperf_runner 14 from autotest_lib.server.cros.network import wifi_cell_test_base 15 16 17 class network_WiFi_RateControl(wifi_cell_test_base.WiFiCellTestBase): 18 """ 19 Test maximal achievable bandwidth on several channels per band. 20 21 Conducts a performance test for a set of specified router configurations 22 and reports results as keyval pairs. 23 24 """ 25 26 version = 1 27 28 # In case of aggregated frames, we need to capture whole packet to be able 29 # to parse the A-MSDU subframe and extract the IP/UDP content. 30 # Per spec the max AMSDU size for 11n is 7935bytes, let use it here. 31 TEST_SNAPLEN = 7935 32 33 34 def parse_additional_arguments(self, commandline_args, additional_params): 35 """ 36 Hook into super class to take control files parameters. 37 38 @param commandline_args: dict of parsed parameters from the autotest. 39 @param additional_params: list of HostapConfig objects. 40 41 """ 42 self._ap_configs = additional_params 43 44 45 def get_highest_mcs_rate(self, frequency): 46 """ 47 Get the highest MCS index supported by the DUT on |frequency|. 48 49 @param frequency: int frequency to look for supported MCS rates. 50 @return int highest rate supported. 51 52 """ 53 # Figure out the highest MCS index supported by this hardware. 54 phys = iw_runner.IwRunner( 55 remote_host=self.context.client.host).list_phys() 56 if len(phys) != 1: 57 raise error.TestFail('Test expects a single PHY, but we got %d' % 58 len(phys)) 59 60 phy = phys[0] 61 bands = [band for band in phy.bands if frequency in band.frequencies] 62 if len(bands) != 1: 63 raise error.TestFail('Test expects a single possible band for a ' 64 'given frequency, but this device has %d ' 65 'such bands.' % len(bands)) 66 67 # 32 is a special low throughput, high resilience mode. Ignore it. 68 possible_indices = filter(lambda x: x != 32, bands[0].mcs_indices) 69 70 if not possible_indices: 71 raise error.TestFail('No possible MCS indices on frequency %d' % 72 frequency) 73 74 return max(possible_indices) 75 76 77 def check_bitrates_in_capture(self, pcap_result, max_mcs_index): 78 """ 79 Check that frames in a packet capture have expected MCS indices. 80 81 @param pcap_result: RemoteCaptureResult tuple. 82 @param max_mcs_index: int MCS index representing the highest possible 83 bitrate on this device. 84 85 """ 86 logging.info('Analyzing packet capture...') 87 display_filter = 'udp and ip.src==%s' % self.context.client.wifi_ip 88 frames = tcpdump_analyzer.get_frames( 89 pcap_result.local_pcap_path, 90 display_filter, 91 bad_fcs='include') 92 93 logging.info('Grouping frames by MCS index') 94 counts = {} 95 for frame in frames: 96 counts[frame.mcs_index] = counts.get(frame.mcs_index, 0) + 1 97 logging.info('Saw WiFi frames with MCS indices: %r', counts) 98 99 # Now figure out the index which the device sent the most packets with. 100 dominant_index = None 101 num_packets_sent = -1 102 for index, num_packets in counts.iteritems(): 103 if num_packets > num_packets_sent: 104 dominant_index = index 105 num_packets_sent = num_packets 106 107 # We should see that the device sent more frames with the maximal index 108 # than anything else. This checks that the rate controller is fairly 109 # aggressive and using all of the device's capabilities. 110 if dominant_index != max_mcs_index: 111 raise error.TestFail('Failed to use best possible MCS ' 112 'index %d in a clean RF environment: %r' % 113 (max_mcs_index, counts)) 114 115 116 def run_once(self): 117 """Test body.""" 118 if utils.host_could_be_in_afe(self.context.client.host.hostname): 119 # Just abort the test if we're in the lab and not on a 120 # machine known to be conducted. The performance 121 # requirements of this test are hard to meet, without 122 # strong multi-path effects. (Our conducted setups are 123 # designed to provide strong multi-path.) 124 if not self.context.client.conductive: 125 raise error.TestNAError( 126 'This test requires a great RF environment.') 127 else: 128 logging.error('Unable to determine if DUT has conducted ' 129 'connection to AP. Treat any TestFail with ' 130 'skepticism.') 131 132 caps = [hostap_config.HostapConfig.N_CAPABILITY_GREENFIELD, 133 hostap_config.HostapConfig.N_CAPABILITY_HT40] 134 mode_11n = hostap_config.HostapConfig.MODE_11N_PURE 135 get_config = lambda channel: hostap_config.HostapConfig( 136 channel=channel, mode=mode_11n, n_capabilities=caps) 137 netperf_config = netperf_runner.NetperfConfig( 138 netperf_runner.NetperfConfig.TEST_TYPE_UDP_STREAM) 139 for i, ap_config in enumerate([get_config(1), get_config(157)]): 140 # Set up the router and associate the client with it. 141 self.context.configure(ap_config) 142 self.context.capture_host.start_capture( 143 ap_config.frequency, 144 ht_type=ap_config.ht_packet_capture_mode, 145 snaplen=self.TEST_SNAPLEN) 146 assoc_params = xmlrpc_datatypes.AssociationParameters( 147 ssid=self.context.router.get_ssid()) 148 self.context.assert_connect_wifi(assoc_params) 149 with netperf_runner.NetperfRunner(self.context.client, 150 self.context.router, 151 netperf_config) as runner: 152 runner.run() 153 results = self.context.capture_host.stop_capture() 154 if len(results) != 1: 155 raise error.TestError('Expected to generate one packet ' 156 'capture but got %d instead.' % 157 len(results)) 158 159 # The device should sense that it is in a clean RF environment and 160 # use the highest index to achieve maximal throughput. 161 max_mcs_index = self.get_highest_mcs_rate(ap_config.frequency) 162 self.check_bitrates_in_capture(results[0], max_mcs_index) 163 # Clean up router and client state for the next run. 164 self.context.client.shill.disconnect(self.context.router.get_ssid()) 165 self.context.router.deconfig() 166