#!/usr/bin/env python3.4 # # Copyright 2022 - The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the 'License'); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an 'AS IS' BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import collections import csv import itertools import json import re import numpy import os from acts import context from acts import base_test from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils from acts_contrib.test_utils.cellular.performance.CellularThroughputBaseTest import CellularThroughputBaseTest from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils from functools import partial LONG_SLEEP = 10 MEDIUM_SLEEP = 2 IPERF_TIMEOUT = 10 SHORT_SLEEP = 1 SUBFRAME_LENGTH = 0.001 STOP_COUNTER_LIMIT = 3 class CellularFr2UplinkPowerSweepTest(CellularThroughputBaseTest): """Base class to test cellular FR2 throughput This class implements cellular FR2 throughput tests on a callbox setup. The class setups up the callbox in the desired configurations, configures and connects the phone, and runs traffic/iperf throughput. """ def __init__(self, controllers): super().__init__(controllers) base_test.BaseTestClass.__init__(self, controllers) self.testcase_metric_logger = ( BlackboxMappedMetricLogger.for_test_case()) self.testclass_metric_logger = ( BlackboxMappedMetricLogger.for_test_class()) self.publish_testcase_metrics = True def process_testcase_results(self): """Publish test case metrics and save results""" if self.current_test_name not in self.testclass_results: return testcase_data = self.testclass_results[self.current_test_name] results_file_path = os.path.join( context.get_current_context().get_full_output_path(), '{}.json'.format(self.current_test_name)) with open(results_file_path, 'w') as results_file: json.dump(wputils.serialize_dict(testcase_data), results_file, indent=4) testcase_result = testcase_data['results'][0] def process_testclass_results(self): pass def get_per_cell_power_sweeps(self, testcase_params): cell_power_sweeps = [] for cell in testcase_params['endc_combo_config']['cell_list']: if cell['cell_type'] == 'LTE': sweep = [self.testclass_params['lte_cell_power']] else: sweep = [self.testclass_params['nr_cell_power']] cell_power_sweeps.append(sweep) return cell_power_sweeps def generate_endc_combo_config(self, test_config): """Function to generate ENDC combo config from CSV test config Args: test_config: dict containing ENDC combo config from CSV Returns: endc_combo_config: dictionary with all ENDC combo settings """ endc_combo_config = collections.OrderedDict() cell_config_list = [] lte_cell_count = 1 lte_carriers = [1] lte_scc_list = [] endc_combo_config['lte_pcc'] = 1 lte_cell = { 'cell_type': 'LTE', 'cell_number': 1, 'pcc': 1, 'band': self.testclass_params['lte_anchor_band'], 'dl_bandwidth': self.testclass_params['lte_anchor_bandwidth'], 'ul_enabled': 1, 'duplex_mode': self.testclass_params['lte_anchor_duplex_mode'], 'dl_mimo_config': 'D{nss}U{nss}'.format(nss=1), 'ul_mimo_config': 'D{nss}U{nss}'.format(nss=1), 'transmission_mode': 'TM1', 'num_codewords': 1, 'num_layers': 1, 'dl_subframe_allocation': [1] * 10, } cell_config_list.append(lte_cell) nr_cell_count = 0 nr_dl_carriers = [] nr_ul_carriers = [] for nr_cell_idx in range(1, test_config['num_dl_cells'] + 1): nr_cell = { 'cell_type': 'NR5G', 'cell_number': nr_cell_idx, 'nr_cell_type': 'NSA', 'band': test_config['nr_band'], 'duplex_mode': test_config['nr_duplex_mode'], 'channel': test_config['nr_channel'], 'dl_mimo_config': 'N{nss}X{nss}'.format(nss=test_config['nr_dl_mimo_config']), 'dl_bandwidth_class': 'A', 'dl_bandwidth': test_config['nr_bandwidth'], 'ul_enabled': 1 if nr_cell_idx <= test_config['num_ul_cells'] else 0, 'ul_bandwidth_class': 'A', 'ul_mimo_config': 'N{nss}X{nss}'.format(nss=test_config['nr_ul_mimo_config']), 'subcarrier_spacing': 'MU3' } cell_config_list.append(nr_cell) nr_cell_count = nr_cell_count + 1 nr_dl_carriers.append(nr_cell_idx) if nr_cell_idx <= test_config['num_ul_cells']: nr_ul_carriers.append(nr_cell_idx) endc_combo_config['lte_cell_count'] = lte_cell_count endc_combo_config['nr_cell_count'] = nr_cell_count endc_combo_config['nr_dl_carriers'] = nr_dl_carriers endc_combo_config['nr_ul_carriers'] = nr_ul_carriers endc_combo_config['cell_list'] = cell_config_list endc_combo_config['lte_scc_list'] = lte_scc_list endc_combo_config['lte_dl_carriers'] = lte_carriers endc_combo_config['lte_ul_carriers'] = lte_carriers return endc_combo_config def _test_throughput_bler_sweep_ul_power(self, testcase_params): """Test function to run cellular throughput and BLER measurements. The function runs BLER/throughput measurement after configuring the callbox and DUT. The test supports running PHY or TCP/UDP layer traffic in a variety of band/carrier/mcs/etc configurations. Args: testcase_params: dict containing test-specific parameters Returns: result: dict containing throughput results and metadata """ # Prepare results dicts testcase_params = self.compile_test_params(testcase_params) testcase_params['nr_target_power_sweep'] = list( numpy.arange(self.testclass_params['nr_target_power_start'], self.testclass_params['nr_target_power_stop'], self.testclass_params['nr_target_power_step'])) testcase_results = collections.OrderedDict() testcase_results['testcase_params'] = testcase_params testcase_results['results'] = [] # Setup ota chamber if needed if hasattr(self, 'keysight_chamber') and 'orientation' in testcase_params: self.keysight_chamber.move_theta_phi_abs( self.keysight_chamber.preset_orientations[ testcase_params['orientation']]['theta'], self.keysight_chamber.preset_orientations[ testcase_params['orientation']]['phi']) # Setup tester and wait for DUT to connect self.setup_tester(testcase_params) # Run throughput test loop stop_counter = 0 if testcase_params['endc_combo_config']['nr_cell_count']: self.keysight_test_app.select_display_tab('NR5G', 1, 'BTHR', 'OTAGRAPH') else: self.keysight_test_app.select_display_tab('LTE', 1, 'BTHR', 'OTAGRAPH') for power_idx in range(len(testcase_params['nr_target_power_sweep'])): result = collections.OrderedDict() # Check that cells are still connected connected = 1 for cell in testcase_params['endc_combo_config']['cell_list']: if not self.keysight_test_app.wait_for_cell_status( cell['cell_type'], cell['cell_number'], ['ACT', 'CONN'], SHORT_SLEEP, SHORT_SLEEP): connected = 0 if not connected: self.log.info('DUT lost connection to cells. Ending test.') break # Set DL cell power current_target_power = testcase_params['nr_target_power_sweep'][ power_idx] self.log.info( 'Setting target power to {}dBm'.format(current_target_power)) for cell_idx, cell in enumerate( testcase_params['endc_combo_config']['cell_list']): self.keysight_test_app.set_cell_ul_power_control( cell['cell_type'], cell['cell_number'], 'TARget', current_target_power) # Start BLER and throughput measurements current_throughput = self.run_single_throughput_measurement( testcase_params) result['throughput_measurements'] = current_throughput result['nr_target_power'] = current_target_power self.print_throughput_result(current_throughput) tx_power = self.dut_utils.get_fr2_tx_power() testcase_results['results'].append(result) if (('lte_bler_result' in result['throughput_measurements'] and result['throughput_measurements']['lte_bler_result'] ['total']['DL']['nack_ratio'] * 100 > 99) or ('nr_bler_result' in result['throughput_measurements'] and result['throughput_measurements']['nr_bler_result'] ['total']['DL']['nack_ratio'] * 100 > 99)): stop_counter = stop_counter + 1 else: stop_counter = 0 if stop_counter == STOP_COUNTER_LIMIT: break # Save results self.testclass_results[self.current_test_name] = testcase_results def generate_test_cases(self, bands, channels, nr_mcs_pair_list, num_dl_cells_list, num_ul_cells_list, orientation_list, dl_mimo_config, ul_mimo_config, **kwargs): """Function that auto-generates test cases for a test class.""" test_cases = [] for orientation, band, channel, num_ul_cells, num_dl_cells, nr_mcs_pair in itertools.product( orientation_list, bands, channels, num_ul_cells_list, num_dl_cells_list, nr_mcs_pair_list): if num_ul_cells > num_dl_cells: continue if channel not in cputils.PCC_PRESET_MAPPING[band]: continue test_config = { 'nr_band': band, 'nr_bandwidth': 'BW100', 'nr_duplex_mode': 'TDD', 'nr_cell_type': 'NSA', 'nr_channel': cputils.PCC_PRESET_MAPPING[band][channel], 'num_dl_cells': num_dl_cells, 'num_ul_cells': num_ul_cells, 'nr_dl_mimo_config': dl_mimo_config, 'nr_ul_mimo_config': ul_mimo_config } endc_combo_config = self.generate_endc_combo_config(test_config) test_name = 'test_fr2_ul_power_sweep_{}_{}_{}_DL_{}CC_mcs{}_{}x{}_UL_{}CC_mcs{}_{}x{}'.format( orientation, band, channel, num_dl_cells, nr_mcs_pair[0], dl_mimo_config, dl_mimo_config, num_ul_cells, nr_mcs_pair[1], ul_mimo_config, ul_mimo_config) test_params = collections.OrderedDict( endc_combo_config=endc_combo_config, nr_dl_mcs=nr_mcs_pair[0], nr_ul_mcs=nr_mcs_pair[1], orientation=orientation, **kwargs) setattr( self, test_name, partial(self._test_throughput_bler_sweep_ul_power, test_params)) test_cases.append(test_name) return test_cases class CellularFr2CpOfdmUplinkPowerSweepTest(CellularFr2UplinkPowerSweepTest): def __init__(self, controllers): super().__init__(controllers) self.testclass_params = self.user_params[ 'fr2_uplink_power_sweep_test_params'] self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], ['low', 'mid', 'high'], [(4, 16), (4, 25), (4, 27), (4, 28)], [1], [1], ['A_Plane', 'B_Plane'], force_contiguous_nr_channel=True, dl_mimo_config=2, ul_mimo_config=2, schedule_scenario="FULL_TPUT", schedule_slot_ratio=80, traffic_direction='UL', transform_precoding=0, lte_dl_mcs=4, lte_dl_mcs_table='QAM64', lte_ul_mcs=4, lte_ul_mcs_table='QAM64', nr_dl_mcs_table='Q256', nr_ul_mcs_table='Q64') self.tests.extend( self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], ['low', 'mid', 'high'], [(4, 16), (4, 25), (4, 27), (4, 28)], [2], [2], ['A_Plane', 'B_Plane'], force_contiguous_nr_channel=True, dl_mimo_config=2, ul_mimo_config=2, schedule_scenario="FULL_TPUT", schedule_slot_ratio=80, traffic_direction='UL', transform_precoding=0, lte_dl_mcs=4, lte_dl_mcs_table='QAM64', lte_ul_mcs=4, lte_ul_mcs_table='QAM64', nr_dl_mcs_table='Q256', nr_ul_mcs_table='Q64')) self.tests.extend( self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], ['low', 'mid', 'high'], [(4, 16), (4, 25), (4, 27), (4, 28)], [4], [4], ['A_Plane', 'B_Plane'], force_contiguous_nr_channel=True, dl_mimo_config=2, ul_mimo_config=2, schedule_scenario="FULL_TPUT", schedule_slot_ratio=80, traffic_direction='UL', transform_precoding=0, lte_dl_mcs=4, lte_dl_mcs_table='QAM64', lte_ul_mcs=4, lte_ul_mcs_table='QAM64', nr_dl_mcs_table='Q256', nr_ul_mcs_table='Q64')) class CellularFr2DftsOfdmUplinkPowerSweepTest(CellularFr2UplinkPowerSweepTest): def __init__(self, controllers): super().__init__(controllers) self.testclass_params = self.user_params[ 'fr2_uplink_power_sweep_test_params'] self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], ['low', 'mid', 'high'], [(4, 16), (4, 25), (4, 27), (4, 28)], [1], [1], ['A_Plane', 'B_Plane'], force_contiguous_nr_channel=True, dl_mimo_config=2, ul_mimo_config=1, schedule_scenario="FULL_TPUT", schedule_slot_ratio=80, traffic_direction='UL', transform_precoding=1, lte_dl_mcs=4, lte_dl_mcs_table='QAM64', lte_ul_mcs=4, lte_ul_mcs_table='QAM64', nr_dl_mcs_table='Q256', nr_ul_mcs_table='Q64') self.tests.extend( self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], ['low', 'mid', 'high'], [(4, 16), (4, 25), (4, 27), (4, 28)], [2], [2], ['A_Plane', 'B_Plane'], force_contiguous_nr_channel=True, dl_mimo_config=2, ul_mimo_config=2, schedule_scenario="FULL_TPUT", schedule_slot_ratio=80, traffic_direction='UL', transform_precoding=1, lte_dl_mcs=4, lte_dl_mcs_table='QAM64', lte_ul_mcs=4, lte_ul_mcs_table='QAM64', nr_dl_mcs_table='Q256', nr_ul_mcs_table='Q64')) self.tests.extend( self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], ['low', 'mid', 'high'], [(4, 16), (4, 25), (4, 27), (4, 28)], [4], [4], ['A_Plane', 'B_Plane'], force_contiguous_nr_channel=True, dl_mimo_config=2, ul_mimo_config=2, schedule_scenario="FULL_TPUT", schedule_slot_ratio=80, traffic_direction='UL', transform_precoding=1, lte_dl_mcs=4, lte_dl_mcs_table='QAM64', lte_ul_mcs=4, lte_ul_mcs_table='QAM64', nr_dl_mcs_table='Q256', nr_ul_mcs_table='Q64'))