blob: a30734801e7d895e5b5f92e6f06932b5e8fd2bb8 [file] [log] [blame]
# Copyright (c) 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
from autotest_lib.server import site_linux_system
from autotest_lib.server.cros.network import hostap_config
from autotest_lib.server.cros.network import wifi_cell_test_base
from autotest_lib.server.cros.network import wpa_mon
from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
class network_WiFi_RoamDiagnostics(wifi_cell_test_base.WiFiCellTestBase):
"""Bring an AP up, connect to it, set the attenuation, and vary a second AP
around the same RSSI as the first AP. Perform a scan after every change in
attenuation and observe when the device roams between APs. Record all roam
events in a file for analysis.
The purpose of this diagnostic is to capture the stickiness of the device's
roam algorithm. For example, the stickier the algorithm, the more skewed
toward higher RSSI differentials (between current and target AP) the
distribution of roams in the output files will be. This is not necessarily
a good thing as it's important for a device to be able to move between APs
when it needs to. Therefore, we use network_WiFi_RoamNatural in conjunction
with this test to ensure that normal roam behavior is not broken."""
version = 1
MAX_ATTEN = 96
MIN_ATTEN = 56
ATTEN_STEP = 4
ATTEN_RANGE = 12
ROAM_BUCKETS = 7
def test_body(self, pair_num, ap_pair, logger, roam_stats):
"""
Execute the test on the given APs.
@param pair_num int: the nth time this function is called (for logging
purpose).
@param ap_pair tuple of HostapConfig objects: the APs.
@param logger WpaMon object: used for event monitoring.
"""
self.context.configure(ap_pair[0])
ssid = self.context.router.get_ssid()
bgscan_none = xmlrpc_datatypes.BgscanConfiguration(
method=xmlrpc_datatypes.BgscanConfiguration.SCAN_METHOD_NONE)
assoc_params = xmlrpc_datatypes.AssociationParameters(
ssid=ssid,
bgscan_config=bgscan_none)
self.context.assert_connect_wifi(assoc_params)
ap_pair[1].ssid = ssid
self.context.configure(ap_pair[1], configure_pcap=True)
roam_log = open(
os.path.join(self.resultsdir, str(pair_num) + "_roam.txt"), 'w')
for atten0 in range(self.MIN_ATTEN, self.MAX_ATTEN, self.ATTEN_STEP):
self.context.attenuator.set_total_attenuation(
atten0, ap_pair[0].frequency, 0)
self.context.attenuator.set_total_attenuation(
atten0, ap_pair[0].frequency, 1)
# Vary the RSSI of the second AP around that of the first AP.
min_atten = max(
atten0 - self.ATTEN_RANGE,
self.context.attenuator.get_minimal_total_attenuation())
max_atten = atten0 + self.ATTEN_RANGE
for _ in range(2):
for atten1 in range(max_atten, min_atten, -self.ATTEN_STEP) + \
range(min_atten, max_atten, self.ATTEN_STEP):
self.context.attenuator.set_total_attenuation(
atten1, ap_pair[1].frequency, 2)
self.context.attenuator.set_total_attenuation(
atten1, ap_pair[1].frequency, 3)
scan_success = False
logger.start_event_capture()
for i in range(2):
# Explicitly ask shill to perform a scan. This
# should induce a roam if the RSSI difference is
# large enough.
self.context.client.shill.request_scan()
if logger.wait_for_event(wpa_mon.WpaMon.\
CTRL_EVENT_SCAN_RESULTS):
scan_success = True
break
logging.info("Scan failed %d time(s)", i + 1)
if not scan_success:
logging.error("Unable to get scan results")
continue
# Wait for roam.
roams = logger.wait_for_event(
wpa_mon.WpaMon.CTRL_EVENT_DO_ROAM, timeout=5)
for roam in roams:
logging.info(roam)
roam_log.write(str(roam) + '\n')
freq_pair = (int(roam.cur_freq) / 1000,
int(roam.sel_freq) / 1000)
diff = max(min(int(roam.sel_level) - \
int(roam.cur_level),
(self.ROAM_BUCKETS - 1) * 2), 0)
roam_stats[freq_pair][diff / 2] += 1
roam_log.close()
self.context.client.shill.disconnect(ssid)
self.context.router.deconfig()
self.context.pcap_host.deconfig()
def output_roam_stats(self, roam_stats):
"""Output roam stats."""
for pair, stats in roam_stats.items():
total = sum(stats)
logging.info('Roams from %d GHz to %d GHz',
pair[0], pair[1])
self.output_perf_value('roam_diagnostics_%d_%d' % \
(pair[0], pair[1]),
stats, units='roams',
higher_is_better=False)
for i, roams in enumerate(stats):
logging.info('%d roams out of %d with diff >= %d', roams,
total, i * 2)
def run_once(self):
"""Body of the test."""
self.context.client.require_capabilities(
[site_linux_system.LinuxSystem.CAPABILITY_SUPPLICANT_ROAMING])
mode = hostap_config.HostapConfig.MODE_11N_PURE
ap1 = hostap_config.HostapConfig(channel=1, mode=mode)
ap2 = hostap_config.HostapConfig(channel=2, mode=mode)
ap3 = hostap_config.HostapConfig(channel=36, mode=mode)
ap_configs = [(ap1, ap2), (ap1, ap3)]
# roam_stats records the number of roams that occurred in each roam
# bucket for each frequency type (2.4 GHz to 2.4 GHz, 2.4 GHz to 5 GHz,
# and 5 GHz to 2.4 GHz; we don't test 5 GHz to 5 GHz because the logic
# should be the same as 2.4 GHz to 2.4 GHz). Each bucket holds a 2 dBm
# range of RSSI differences at which a roam occurred. For example,
# roam_stats[(2, 5)][5] represents the number of roams from 2.4 GHz to
# 5GHz that happened at an RSSI difference of 8-9 dBm.
roam_stats = {(2, 2): [0] * self.ROAM_BUCKETS,
(2, 5): [0] * self.ROAM_BUCKETS,
(5, 2): [0] * self.ROAM_BUCKETS}
with self.context.client._wpa_mon as logger:
for pair_num, ap_pair in enumerate(ap_configs):
self.test_body(pair_num, ap_pair, logger, roam_stats)
# Log roam distribution and output perf values
self.output_roam_stats(roam_stats)