blob: b15222b6264036ac0be4c1167eef0140b94c67e1 [file] [log] [blame]
# Lint as: python2, python3
# Copyright 2022 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
import time
from autotest_lib.client.common_lib import error
from autotest_lib.server import site_linux_rpi
from autotest_lib.client.common_lib.cros.network import interface
from autotest_lib.server.cros.network import ip_config_context_manager
from autotest_lib.server.cros.network import hackrf_runner_context_manager
from autotest_lib.server.cros.network import perf_test_manager as perf_manager
from autotest_lib.server.cros.network import wifi_cell_perf_test_base
class network_WiFi_PerfNoisyEnv(wifi_cell_perf_test_base.WiFiCellPerfTestBase):
""" Test maximal achievable bandwidth while varying the gain of noise and
identify the gain at which a DUT disconnects.
For a given router configuration, broadcast noise while the gain is
increased step by step until the DUT disconnects or no throughput is
detected. The throughput at different gain values and the maximum gain
at which there were meaningful throughput values are logged as keyval pairs.
After running it on more boards to gather more data, this test will include
pass/fail criteria.
"""
version = 1
PERF_TEST_TYPES = [
perf_manager.PerfTestTypes.TEST_TYPE_TCP_TX,
perf_manager.PerfTestTypes.TEST_TYPE_TCP_RX,
perf_manager.PerfTestTypes.TEST_TYPE_UDP_TX,
perf_manager.PerfTestTypes.TEST_TYPE_UDP_RX,
perf_manager.PerfTestTypes.TEST_TYPE_TCP_BIDIRECTIONAL,
perf_manager.PerfTestTypes.TEST_TYPE_UDP_BIDIRECTIONAL
]
MAX_TX_GAIN_ON_HACKRF = 47
CHECK_CONNECTION_AND_PINGING_TIME = 30
MAX_TEST_TIME = 250
def parse_additional_arguments(self, commandline_args, additional_params):
"""Hook into super class to take control files parameters.
@param commandline_args dict of parsed parameters from the autotest.
@param additional_params the noise file name, list of HostapConfig
objects and a boolean for whether to use iperf.
"""
noise_parameters, self._ap_config, self._use_iperf = additional_params
self._noise_file = noise_parameters[0]
self._frequency = noise_parameters[1]
self._start_gain = noise_parameters[2]
self._gain_increment = noise_parameters[3]
super(network_WiFi_PerfNoisyEnv, self).parse_additional_arguments(
commandline_args)
def warmup(self, *args, **kwargs):
# This test requires include_rpi to be True and rpi_role to be set to
# the RpiRole representing a HackRF Runner.
kwargs['include_rpi'] = True
kwargs['rpi_role'] = site_linux_rpi.RPiRole.HACKRF_RUNNER
super(network_WiFi_PerfNoisyEnv, self).warmup(*args, **kwargs)
def do_run(self, manager, gain=None, noise_file=None):
"""Run a single set of perf tests, for a given AP and DUT config, and
gain value.
@param manager: a PerfTestManager instance.
@param gain: int the strength of the signal being trasmitted in dB,
should be in the 0-47dB range or None.
@param noise_file: string name of the noise file to be broadcast during
the test including the absolute path to it on the HackRF Runner
or None.
"""
ap_config_tag = self._ap_config.perf_loggable_description
signal_level = self.context.client.wifi_signal_level
signal_description = '_'.join([ap_config_tag, 'signal'])
self.write_perf_keyval({signal_description: signal_level})
for test_type in self.PERF_TEST_TYPES:
config = manager.get_config(test_type, self._is_openwrt)
pcap_lan_iface = interface.Interface(self._pcap_lan_iface_name,
self.context.pcap_host.host)
session = manager.get_session(test_type,
self.context.client,
self.context.pcap_host,
peer_device_interface=pcap_lan_iface,
ignore_failures=True)
ch_width = self._ap_config.channel_width
if ch_width is None:
raise error.TestFail(
'Failed to get the channel width used by the AP and client.'
)
if gain != None:
gain_tag = 'gain%02d' % gain
self.context.rpi.stop_broadcasting_file()
# TODO(b/241004590): Currently, to prevent noise from being
# broadcast without termination, we broadcast noise for the
# longest time we can reliably play noise at 8MHz sample rate.
# However, if this estimate is inaccurate, the noise might not
# play for the entirety of the test. So, in the future, we
# should look into broadcasting noise indefinitely and ensuring
# that the broadcasting properly terminates.
self.context.rpi.broadcast_file(rf_data_file=noise_file,
duration=self.MAX_TEST_TIME,
frequency=self._frequency,
gain=gain,
run_in_background=True)
results = session.run(config,
broadcast_rf_data=True,
broadcast_rf_time=self.MAX_TEST_TIME)
else:
gain_tag = 'no_gain'
results = session.run(config)
if not results:
logging.warning('Unable to take measurement for %s; '
'aborting', config.test_type)
break
graph_name = '.'.join(
[self._ap_config.perf_loggable_description,
config.test_type])
values = [sample.throughput for sample in results]
self.output_perf_value(gain_tag, values, units='Mbps',
higher_is_better=True, graph=graph_name)
result = manager.get_result(results, config)
keyval_prefix = '_'.join(
[self._ap_config.perf_loggable_description,
config.test_type, gain_tag])
self.write_perf_keyval(result.get_keyval(prefix=keyval_prefix))
return results
def run_once(self):
"""Test body."""
start_time = time.time()
logging.info(self.context.client.board)
max_gain = None
# Install a specific noise file.
noise_file_on_dut = os.path.join(self.bindir, 'test_data',
self._noise_file)
self.context.rpi.install_rf_data_file(noise_file_on_dut)
noise_files_list = self.context.rpi.host.run('ls %s' %
self.context.rpi.RF_DATA_FILES_FOLDER).stdout.split()
if self._noise_file not in noise_files_list:
raise error.TestFail('Couldn\'t install %s on the HackRF Runner' %
self._noise_file)
noise_file_on_hackrf_runner = os.path.join(
self.context.rpi.RF_DATA_FILES_FOLDER,
self._noise_file)
# Setup the router and associate the client with it.
self.configure_and_connect_to_ap(self._ap_config)
with ip_config_context_manager.IpConfigContextManager() as ip_context:
self._setup_ip_config(ip_context)
manager = perf_manager.PerfTestManager(self._use_iperf)
# Determine baseline throughput (without noise).
logging.info('Trying without gain')
baseline_results = self.do_run(manager)
# Measure throughput for various gain values of RF interference.
for gain in range(self._start_gain, self.MAX_TX_GAIN_ON_HACKRF+1,
self._gain_increment):
with hackrf_runner_context_manager.HackRFRunnerContextManager(
self.context.rpi) as hackrf_runner_context:
logging.info('Trying gain = %d dB', gain)
self.context.rpi.broadcast_file(
rf_data_file=noise_file_on_hackrf_runner,
duration=self.CHECK_CONNECTION_AND_PINGING_TIME,
frequency=self._frequency, gain=gain,
run_in_background=True)
# Give this gain level a quick check. If we can't stay
# associated and handle a few pings, we probably won't get
# meaningful results out of iperf/netperf.
try:
self.context.wait_for_connection(self.context.router.get_ssid())
except error.TestFail as e:
logging.warning('Could not establish connection at %d'
'dB (%s)', gain, str(e))
break
results = self.do_run(manager, gain,
noise_file_on_hackrf_runner)
if not results:
logging.warning('No results for gain %d dB; '
'terminating', gain)
break
max_gain = gain
if max_gain == self.MAX_TX_GAIN_ON_HACKRF:
logging.info('The maximum gain at which we get meaningful '
'throughput measurements is beyond the maximum '
'gain a HackRF can broadcast (%d dB).',
self.MAX_TX_GAIN_ON_HACKRF)
elif not max_gain:
raise error.TestFail('Did not succeed at any gain level')
else:
logging.info('Reached gain of: %d dB', max_gain)
self.write_perf_keyval({'ch%03d_max_gain' % self._ap_config.channel:
max_gain})
# Clean up router and client state for the next run.
self.context.client.shill.disconnect(self.context.router.get_ssid())
self.context.router.deconfig()
# Delete a specific noise file.
self.context.rpi.delete_rf_data_file(noise_file_on_hackrf_runner)
noise_files_list = self.context.rpi.host.run('ls %s' %
self.context.rpi.RF_DATA_FILES_FOLDER).stdout.split()
if self._noise_file in noise_files_list:
logging.error('Couldn\'t delete %s from the HackRF Runner',
self._noise_file)
end_time = time.time()
logging.info('Running time %0.1f seconds.', end_time - start_time)