From 57e29af5d2c0ec40e95911bf9c6543af6f36ff24 Mon Sep 17 00:00:00 2001 From: Jakub Date: Mon, 23 May 2022 13:24:47 +0200 Subject: [PATCH] Init version of python tool for measuring throughput User provides HCI indexes of connected controllers, the tool configures them and starts throughput test. More in README file. --- tools/README.md | 0 tools/hci_throughput/README.md | 100 +++ tools/hci_throughput/check_addr.py | 104 +++ tools/hci_throughput/config.yaml.sample | 55 ++ tools/hci_throughput/hci.py | 605 ++++++++++++++++ tools/hci_throughput/hci_commands.py | 668 ++++++++++++++++++ tools/hci_throughput/hci_device.py | 323 +++++++++ tools/hci_throughput/hci_socket.py | 164 +++++ tools/hci_throughput/init.yaml.sample | 34 + tools/hci_throughput/log/.gitignore | 2 + tools/hci_throughput/main.py | 243 +++++++ tools/hci_throughput/requirements.txt | 3 + .../targets/nordic_pca10040_blehci/pkg.yml | 24 + .../targets/nordic_pca10040_blehci/syscfg.yml | 28 + .../targets/nordic_pca10040_blehci/target.yml | 22 + .../targets/nordic_pca10040_boot/pkg.yml | 6 + .../targets/nordic_pca10040_boot/syscfg.yml | 0 .../targets/nordic_pca10040_boot/target.yml | 3 + .../targets/nordic_pca10056_blehci/pkg.yml | 27 + .../targets/nordic_pca10056_blehci/syscfg.yml | 32 + .../targets/nordic_pca10056_blehci/target.yml | 22 + .../targets/nordic_pca10056_boot/pkg.yml | 6 + .../targets/nordic_pca10056_boot/syscfg.yml | 0 .../targets/nordic_pca10056_boot/target.yml | 3 + .../targets/nordic_pca10059_blehci/pkg.yml | 27 + .../targets/nordic_pca10059_blehci/syscfg.yml | 32 + .../targets/nordic_pca10059_blehci/target.yml | 22 + .../targets/nrf52832_blehci/pkg.yml | 24 + .../targets/nrf52832_blehci/syscfg.yml | 26 + .../targets/nrf52832_blehci/target.yml | 22 + .../targets/nrf52840_blehci/pkg.yml | 27 + .../targets/nrf52840_blehci/syscfg.yml | 32 + .../targets/nrf52840_blehci/target.yml | 22 + tools/hci_throughput/tests/.gitignore | 2 + tools/hci_throughput/throughput.py | 197 ++++++ tools/hci_throughput/transport_factory.py | 38 + tools/hci_throughput/util.py | 70 ++ 37 files changed, 3015 insertions(+) create mode 100644 tools/README.md create mode 100644 tools/hci_throughput/README.md create mode 100644 tools/hci_throughput/check_addr.py create mode 100644 tools/hci_throughput/config.yaml.sample create mode 100644 tools/hci_throughput/hci.py create mode 100644 tools/hci_throughput/hci_commands.py create mode 100644 tools/hci_throughput/hci_device.py create mode 100644 tools/hci_throughput/hci_socket.py create mode 100644 tools/hci_throughput/init.yaml.sample create mode 100644 tools/hci_throughput/log/.gitignore create mode 100644 tools/hci_throughput/main.py create mode 100644 tools/hci_throughput/requirements.txt create mode 100644 tools/hci_throughput/targets/nordic_pca10040_blehci/pkg.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10040_blehci/syscfg.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10040_blehci/target.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10040_boot/pkg.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10040_boot/syscfg.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10040_boot/target.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10056_blehci/pkg.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10056_blehci/syscfg.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10056_blehci/target.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10056_boot/pkg.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10056_boot/syscfg.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10056_boot/target.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10059_blehci/pkg.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10059_blehci/syscfg.yml create mode 100644 tools/hci_throughput/targets/nordic_pca10059_blehci/target.yml create mode 100644 tools/hci_throughput/targets/nrf52832_blehci/pkg.yml create mode 100644 tools/hci_throughput/targets/nrf52832_blehci/syscfg.yml create mode 100644 tools/hci_throughput/targets/nrf52832_blehci/target.yml create mode 100644 tools/hci_throughput/targets/nrf52840_blehci/pkg.yml create mode 100644 tools/hci_throughput/targets/nrf52840_blehci/syscfg.yml create mode 100644 tools/hci_throughput/targets/nrf52840_blehci/target.yml create mode 100755 tools/hci_throughput/tests/.gitignore create mode 100644 tools/hci_throughput/throughput.py create mode 100644 tools/hci_throughput/transport_factory.py create mode 100644 tools/hci_throughput/util.py diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/tools/hci_throughput/README.md b/tools/hci_throughput/README.md new file mode 100644 index 000000000..7a661d604 --- /dev/null +++ b/tools/hci_throughput/README.md @@ -0,0 +1,100 @@ +# HCI Throughput + +Tool for measuring BLE throughput. + +## Packages versions +Python 3.8.10 \ +Matplotlib 3.5.1 + +## Usage +### Prepare devices +This tool may be used with existing controller or with any board with ```blehci``` app. + + - If you want to use builtin PC controller, provide HCI index of the controller. Turn the Bluetooth ON on your device, run ```hciconfig``` in the terminal and get the HCI index. In the case below HCI index is equal to 0: + +``` +user@user:~$ hciconfig +hci0: Type: Primary Bus: USB + BD Address: 64:BC:58:E2:9C:52 ACL MTU: 1021:4 SCO MTU: 96:6 + UP RUNNING + RX bytes:20003 acl:0 sco:0 events:3176 errors:0 + TX bytes:771246 acl:0 sco:0 commands:3174 errors:0 +``` + + - If you want to use the nimble controller, create the image and load the provided target (can be found under ```/targets``` for NRF52840 and NRF52832). + - NRF52840 may use USB or UART transport. The target is configured for USB by default. + - NRF52832 uses UART as transport. This requires some additional configuration. Get the tty path and run in the terminal: + ``` + sudo btattach -B /dev/ttyACM0 -S 1000000 + ``` + Then proceed with ```hciconfig``` as shown above. + +### Run tests +This tool opens a raw socket which requires running all scripts as ```sudo```. Copy the ```config.yaml.sample``` file, change the name to ```config.yaml``` and fill the parameters. Run ```main.py``` as shown below: +``` +sudo python main.py -i -m rx tx -cf config.yaml +``` +Switch `````` and `````` to corresponding hci indexes present in your computer. ```-m``` and ```-cf``` may be omitted if the defaults are correct. \ +The output provides the plots of measured throughput in ```kb``` or ```kB``` as predefined in ```config.yaml```. In addition to the throughput plots, when the ```flag_plot_packets``` is turned on, the number of packets transmitted/received in time is visualized. + +#### Set ```config.yaml``` file +To run **once** the throughput measurement with given parameters, set the ```flag_testing``` to false. +``` +flag_testing: false +``` + +To run the throughput measurements **more than once** with the same parameters and to generate the plot of average throughputs, set ```config.yaml``` as shown below: +``` +show_tp_plots: false +flag_testing: true +test: + change_param_group: null + change_param_variable: null + start_value: 0 + stop_value: 5 + step: 1 +``` +This configuration provides 5 measurements. The ```show_tp_plots``` flag is optionally set as ```false``` for speed, changing it to ```true``` will trigger rx and tx throughput plots at the end of every iteration. + +To run the throughput measurement with some parameters changing within tests, fill config as below: +``` +flag_testing: true +test: + change_param_group: + - conn + - conn + change_param_variable: + - connection_interval_min + - connection_interval_max + start_value: 0x000A + stop_value: 0x0320 + step: 20 +``` +This will run each test incrementing ```connection_interval_min``` and ```connection_interval_max``` by 20. the final plot will show the influence of the parameters change on the average throughput. + +## Tools +The ```main.py``` script usees all tools mentioned below and it is advised to use it above all. Nevertheless, the sub-tools may be used separately as shown below. + +### HCI device sub-tool +```hci_device.py``` is a tool that manages one hci device. User can provide parameters and run it as receiver or transmitter as shown below: +``` +sudo python hci_device.py -m rx -oa 00:00:00:00:00:00 -oat 0 -di 0 -pa 00:00:00:00:00:00 -pat 0 -pdi 0 -cf config.yaml +``` +Run ```python hci_device.py --help``` for parameters description. \ +If properly configured ```init.yaml``` is present (it is created automatically while running ```main.py```), the script can be run like this: + +``` +sudo python hci_device.py -m tx -if init.yaml +``` + +### Check addr sub-tool +When given hci indexes, ```check_addr.py``` returns devices' address types and addresses. +``` +sudo python check_addr.py -i ... +``` + +### Throughput sub-tool +The timestamps of the received packets are stored in csv files (```tp_receiver.csv``` and ```tp_transmitter.csv``` by default). If the program stopped in the middle of the measurements, you can still plot the values and get the average througput. Provide the filename, sample time and run the tool as shown below: +``` +python throughput.py -f tp_receiver -s 0.1 +``` diff --git a/tools/hci_throughput/check_addr.py b/tools/hci_throughput/check_addr.py new file mode 100644 index 000000000..a11a416d8 --- /dev/null +++ b/tools/hci_throughput/check_addr.py @@ -0,0 +1,104 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 argparse +import asyncio +import hci_commands +import sys +import logging +import hci +import traceback +import util +import transport_factory + +def parse_arguments(): + parser = argparse.ArgumentParser( + description='Check HCI device address type and address', + epilog='How to run script: \ + sudo python check_addr.py -i 0 1 2') + parser.add_argument('-i', '--indexes', type=str, nargs='*', + help='specify hci adapters indexes', default=0) + try: + args = parser.parse_args() + except Exception as e: + print(traceback.format_exc()) + return args + +async def main(dev: hci_commands.HCI_Commands): + result = tuple() + task = asyncio.create_task(dev.rx_buffer_q_wait()) + await dev.cmd_reset() + await dev.cmd_read_bd_addr() + + if hci.bdaddr != '00:00:00:00:00:00': + logging.info("Type public: %s, address: %s", hci.PUBLIC_ADDRESS_TYPE, hci.bdaddr) + result = (0, hci.bdaddr) + print("Public address: ", result) + else: + await dev.cmd_vs_read_static_addr() + if hci.static_addr != '00:00:00:00:00:00': + logging.info("Type static random: %s, address: %s", hci.STATIC_RANDOM_ADDRESS_TYPE, hci.static_addr) + result = (1, hci.static_addr) + print("Static random address: ", result) + else: + addr = hci.gen_static_rand_addr() + logging.info("Type static random: %s, generated address: %s", hci.STATIC_RANDOM_ADDRESS_TYPE, addr) + result = (1, addr) + print("Generated static random address: ", result) + task.cancel() + return result + +def check_addr(device_indexes: list, addresses: list) -> list: + util.configure_logging(f"log/check_addr.log", clear_log_file=True) + + logging.info(f"Devices indexes: {device_indexes}") + for index in device_indexes: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.set_debug(True) + + transport = transport_factory.TransportFactory(device_index=index, + asyncio_loop=loop) + + bt_dev = hci_commands.HCI_Commands(send=transport.send, + rx_buffer_q=transport.rx_buffer_q, + asyncio_loop=loop) + + transport.start() + + addresses.append(loop.run_until_complete(main(bt_dev))) + + transport.stop() + loop.close() + + logging.info(f"Finished: {addresses}") + return addresses + + +if __name__ == '__main__': + try: + args = parse_arguments() + print(args) + addresses = [] + addresses = check_addr(args.indexes, addresses) + print(addresses) + except Exception as e: + print(traceback.format_exc()) + finally: + sys.exit() diff --git a/tools/hci_throughput/config.yaml.sample b/tools/hci_throughput/config.yaml.sample new file mode 100644 index 000000000..7a8330075 --- /dev/null +++ b/tools/hci_throughput/config.yaml.sample @@ -0,0 +1,55 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +num_of_bytes_to_send: 247 +num_of_packets_to_send: 2000 +show_tp_plots: true +flag_testing: false +test: + change_param_group: + - conn + - conn + change_param_variable: + - connection_interval_min + - connection_interval_max + start_value: 16 + stop_value: 160 + step: 8 +tp: + data_type: kb + sample_time: 0.1 + flag_plot_packets: true +phy: 2M +adv: + advertising_interval_min: 2048 + advertising_interval_max: 2048 + advertising_type: 0 + peer_address: 00:00:00:00:00:00 + advertising_channel_map: 7 + advertising_filter_policy: 0 +conn: + le_scan_interval: 2400 + le_scan_window: 2400 + initiator_filter_policy: 0 + connection_interval_min: 0x0080 + connection_interval_max: 0x0080 + max_latency: 0 + supervision_timeout: 3200 + min_ce_length: 0 + max_ce_length: 0 \ No newline at end of file diff --git a/tools/hci_throughput/hci.py b/tools/hci_throughput/hci.py new file mode 100644 index 000000000..072e395e8 --- /dev/null +++ b/tools/hci_throughput/hci.py @@ -0,0 +1,605 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +from dataclasses import dataclass +import struct +from binascii import unhexlify +import random + +############ +# DEFINES +############ +AF_BLUETOOTH = 31 +HCI_CHANNEL_USER = 1 +HCI_COMMAND_PACKET = 0x01 +HCI_ACL_DATA_PACKET = 0x02 +HCI_EVENT_PACKET = 0x04 + +HCI_EV_CODE_DISCONN_CMP = 0x05 +HCI_EV_CODE_CMD_CMP = 0x0e +HCI_EV_CODE_CMD_STATUS = 0x0f +HCI_EV_CODE_LE_META_EVENT = 0x3e +HCI_SUBEV_CODE_LE_ENHANCED_CONN_CMP = 0x0a +HCI_SUBEV_CODE_LE_DATA_LEN_CHANGE = 0x07 +HCI_SUBEV_CODE_LE_PHY_UPDATE_CMP = 0x0c +HCI_SUBEV_CODE_LE_CHAN_SEL_ALG = 0x14 +HCI_EV_NUM_COMP_PKTS = 0x13 + +CONN_FAILED_TO_BE_ESTABLISHED = 0x3e +CONN_TIMEOUT = 0x08 + +OGF_HOST_CTL = 0x03 +OCF_SET_EVENT_MASK = 0x0001 +OCF_RESET = 0X0003 + +OGF_INFO_PARAM = 0x04 +OCF_READ_LOCAL_COMMANDS = 0x0002 +OCF_READ_BD_ADDR = 0x0009 + +OGF_LE_CTL = 0x08 +OCF_LE_SET_EVENT_MASK = 0x0001 +OCF_LE_READ_BUFFER_SIZE_V1 = 0x0002 +OCF_LE_READ_BUFFER_SIZE_V2 = 0x0060 +OCF_LE_SET_RANDOM_ADDRESS = 0x0005 +OCF_LE_SET_ADVERTISING_PARAMETERS = 0x0006 +OCF_LE_SET_ADVERTISE_ENABLE = 0x000a +OCF_LE_SET_SCAN_PARAMETERS = 0x000b +OCF_LE_SET_SCAN_ENABLE = 0x000c +OCF_LE_CREATE_CONN = 0x000d +OCF_LE_SET_DATA_LEN = 0x0022 +OCF_LE_READ_SUGGESTED_DFLT_DATA_LEN = 0x0023 +OCF_LE_READ_MAX_DATA_LEN = 0x002f +OCF_LE_READ_PHY = 0x0030 +OCF_LE_SET_DFLT_PHY = 0x0031 +OCF_LE_SET_PHY = 0x0032 + +OGF_VENDOR_SPECIFIC = 0x003f +BLE_HCI_OCF_VS_RD_STATIC_ADDR = 0x0001 + +PUBLIC_ADDRESS_TYPE = 0 +STATIC_RANDOM_ADDRESS_TYPE = 1 + +WAIT_FOR_EVENT_TIMEOUT = 5 +WAIT_FOR_EVENT_CONN_TIMEOUT = 25 + +############ +# GLOBAL VAR +############ +num_of_bytes_to_send = None # based on supported_max_tx_octets +num_of_packets_to_send = None + +events_list = [] +bdaddr = '00:00:00:00:00:00' +static_addr = '00:00:00:00:00:00' +le_read_buffer_size = None +conn_handle = 0 +requested_tx_octets = 1 +requested_tx_time = 1 +suggested_dflt_data_len = None +max_data_len = None +phy = None +ev_num_comp_pkts = None +num_of_completed_packets_cnt = 0 +num_of_completed_packets_time = 0 + +############ +# FUNCTIONS +############ +def get_opcode(ogf: int, ocf: int): + return ((ocf & 0x03ff)|(ogf << 10)) + +def get_ogf_ocf(opcode: int): + ogf = opcode >> 10 + ocf = opcode & 0x03ff + return ogf, ocf + +def cmd_addr_to_ba(addr_str: str): + return unhexlify("".join(addr_str.split(':')))[::-1] + +def ba_addr_to_str(addr_ba: bytearray): + addr_str = addr_ba.hex().upper() + return ':'.join(addr_str[i:i+2] for i in range(len(addr_str), -2, -2))[1:] + +def gen_static_rand_addr(): + while True: + x = [random.randint(0,1) for _ in range(0,48)] + + if 0 in x[:-2] and 1 in x[:-2]: + x[0] = 1 + x[1] = 1 + break + addr_int = int("".join([str(x[i]) for i in range(0,len(x))]), 2) + addr_hex = "{0:0{1}x}".format(addr_int, 12) + addr = ":".join(addr_hex[i:i+2] for i in range(0, len(addr_hex), 2)) + return addr.upper() + +############ +# GLOBAL VAR CLASSES +############ +@dataclass +class Suggested_Dflt_Data_Length(): + status: int + suggested_max_tx_octets: int + suggested_max_tx_time: int + + def __init__(self): + self.set() + + def set(self, status=0, suggested_max_tx_octets=0, suggested_max_tx_time=0): + self.status = status + self.suggested_max_tx_octets = suggested_max_tx_octets + self.suggested_max_tx_time = suggested_max_tx_time + +@dataclass +class Max_Data_Length(): + status: int + supported_max_tx_octets: int + supported_max_tx_time: int + supported_max_rx_octets: int + supported_max_rx_time: int + + def __init__(self): + self.set() + + def set(self, status=0, supported_max_tx_octets=0, supported_max_tx_time=0, + supported_max_rx_octets=0, supported_max_rx_time=0): + self.status = status + self.supported_max_tx_octets = supported_max_tx_octets + self.supported_max_tx_time = supported_max_tx_time + self.supported_max_rx_octets = supported_max_rx_octets + self.supported_max_rx_time = supported_max_rx_time + +@dataclass +class LE_Read_Buffer_Size: + status: int + le_acl_data_packet_length: int + total_num_le_acl_data_packets: int + iso_data_packet_len: int + total_num_iso_data_packets: int + + def __init__(self): + self.set() + + def set(self, status=0, le_acl_data_packet_length=0, + total_num_le_acl_data_packets=0, iso_data_packet_len=0, + total_num_iso_data_packets=0): + self.status = status + self.le_acl_data_packet_length = le_acl_data_packet_length + self.total_num_le_acl_data_packets = total_num_le_acl_data_packets + self.iso_data_packet_len = iso_data_packet_len + self.total_num_iso_data_packets = total_num_iso_data_packets + +@dataclass +class LE_Read_PHY: + status: int + connection_handle: int + tx_phy: int + rx_phy: int + + def __init__(self): + self.set() + + def set(self, status=0, connection_handle=0, tx_phy=0, rx_phy=0): + self.status = status + self.connection_handle = connection_handle + self.tx_phy = tx_phy + self.rx_phy = rx_phy + + +############ +# EVENTS +############ + +@dataclass +class HCI_Ev_Disconn_Complete: + status: int + connection_handle: int + reason: int + + def __init__(self): + self.set() + + def set(self, status=0, connection_handle=0, reason=0): + self.status = status + self.connection_handle = connection_handle + self.reason = reason + +@dataclass +class HCI_Ev_Cmd_Complete: + num_hci_command_packets: int + opcode: int + return_parameters: int + + def __init__(self): + self.set() + + def set(self, num_hci_cmd_packets=0, opcode=0, return_parameters=b''): + self.num_hci_command_packets = num_hci_cmd_packets + self.opcode = opcode + self.return_parameters = return_parameters + +@dataclass +class HCI_Ev_Cmd_Status: + status: int + num_hci_command_packets: int + opcode: int + + def __init__(self): + self.set() + + def set(self, status = 0, num_hci_cmd_packets=0, opcode=0): + self.status = status + self.num_hci_command_packets = num_hci_cmd_packets + self.opcode = opcode + +@dataclass +class HCI_Ev_LE_Meta: + subevent_code: int + + def __init__(self): + self.set() + + def set(self, subevent_code=0): + self.subevent_code = subevent_code + + +@dataclass +class HCI_Ev_LE_Enhanced_Connection_Complete(HCI_Ev_LE_Meta): + status: int + connection_handle: int + role: int + peer_address_type: int + peer_address: str + local_resolvable_private_address: int + peer_resolvable_private_address: int + connection_interval: int + peripheral_latency: int + supervision_timeout: int + central_clock_accuracy: int + + def __init__(self): + self.set() + + def set(self, subevent_code=0, status=0, connection_handle=0, role=0, + peer_address_type=0, peer_address='00:00:00:00:00:00', + local_resolvable_private_address='00:00:00:00:00:00', + peer_resolvable_private_address='00:00:00:00:00:00', + connection_interval=0, peripheral_latency=0, supervision_timeout=0, + central_clock_accuracy=0): + super().set(subevent_code) + self.status = status + self.connection_handle = connection_handle + self.role = role + self.peer_address_type = peer_address_type + self.peer_address = peer_address + self.local_resolvable_private_address = local_resolvable_private_address + self.peer_resolvable_private_address = peer_resolvable_private_address + self.connection_interval = connection_interval + self.peripheral_latency = peripheral_latency + self.supervision_timeout = supervision_timeout + self.central_clock_accuracy = central_clock_accuracy + +@dataclass +class HCI_Ev_LE_Data_Length_Change(HCI_Ev_LE_Meta): + conn_handle: int + max_tx_octets: int + max_tx_time: int + max_rx_octets: int + max_rx_time: int + triggered: int + + def __init__(self): + self.set() + + def set(self, subevent_code=0, conn_handle=0, max_tx_octets=0, + max_tx_time=0, max_rx_octets=0, max_rx_time=0, triggered=0): + super().set(subevent_code) + self.conn_handle = conn_handle + self.max_tx_octets = max_tx_octets + self.max_tx_time = max_tx_time + self.max_rx_octets = max_rx_octets + self.max_rx_time = max_rx_time + self.triggered = triggered + +@dataclass +class HCI_Ev_LE_PHY_Update_Complete(HCI_Ev_LE_Meta): + status: int + connection_handle: int + tx_phy: int + rx_phy: int + + def __init__(self): + self.set() + + def set(self, subevent_code=0, status=0, connection_handle=0, + tx_phy=0, rx_phy=0): + super().set(subevent_code) + self.status = status + self.connection_handle = connection_handle + self.tx_phy = tx_phy + self.rx_phy = rx_phy + +@dataclass +class HCI_Number_Of_Completed_Packets: + num_handles: int + connection_handle: int + num_completed_packets: int + + def __init__(self): + self.set() + + def set(self, num_handles=0, connection_handle=0, num_completed_packets=0): + self.num_handles = num_handles + self.connection_handle = connection_handle + self.num_completed_packets = num_completed_packets + +class HCI_Ev_LE_Chan_Sel_Alg(HCI_Ev_LE_Meta): + connection_handle: int + algorithm: int + + def __init__(self): + self.set() + + def set(self, subevent_code=0, connection_handle=0, algorithm=0): + super().set(subevent_code) + self.connection_handle = connection_handle + self.algorithm = algorithm + +############ +# PARAMETERS +############ +@dataclass +class HCI_Advertising: + advertising_interval_min: int + advertising_interval_max: int + advertising_type: int + own_address_type: int + peer_address_type: int + peer_address: str + advertising_channel_map: int + advertising_filter_policy: int + ba_full_message: bytearray + + def __init__(self): + self.set() + + def set(self, advertising_interval_min=0, advertising_interval_max=0, \ + advertising_type=0, own_address_type=0, peer_address_type=0, \ + peer_address='00:00:00:00:00:00', advertising_channel_map=0, \ + advertising_filter_policy=0): + self.advertising_interval_min = advertising_interval_min + self.advertising_interval_max = advertising_interval_max + self.advertising_type = advertising_type + self.own_address_type = own_address_type + self.peer_address_type = peer_address_type + self.peer_address = peer_address + self.advertising_channel_map = advertising_channel_map + self.advertising_filter_policy = advertising_filter_policy + self.ba_full_message = bytearray(struct.pack(' int: + current_ev_name = type(self.hci_recv_ev_packet.current_event).__name__ + if current_ev_name == type(hci.HCI_Ev_Cmd_Complete()).__name__: + return struct.unpack_from(" hci.max_data_len.supported_max_tx_octets - 4): + logging.critical(f"Number of bytes to send: {hci.num_of_bytes_to_send}\ + not supported. Closing.") + raise SystemExit("Number of bytes to send not supported. Closing.") + + return status() + elif ocf == hci.OCF_LE_READ_PHY: + hci.phy = hci.LE_Read_PHY() + hci.phy.set(*struct.unpack('> 12 + bc_flag = (handle_pb_bc_flags & 0xC000) >> 14 + + hci_recv_acl_data_packet = hci.HCI_Recv_ACL_Data_Packet() + + if pb_flag == 0b10: + l2cap_data = hci.HCI_Recv_L2CAP_Data() + data = buffer[5:] + l2cap_data.set(*struct.unpack(" self.tp.sample_time \ + # or packet_number == 0 \ + # or packet_number == self.tp.total_packets_number-1: + # self.tp.record_throughput(packet_number, timestamp) + # self.last_timestamp = timestamp + + if packet_number >= self.tp.total_packets_number - 1: + self.async_ev_recv_data_finish.set() + + def handle_acl_data(self, buffer: bytes, timestamp: int): + hci_recv_acl_data_packet = self.parse_acl_data(buffer) + logging.debug("%s", hci_recv_acl_data_packet) + recv_data_type = type(hci_recv_acl_data_packet.data).__name__ + if recv_data_type == 'HCI_Recv_L2CAP_Data': + self.match_recv_l2cap_data(buffer, timestamp) + + async def recv_handler(self): + while not self.rx_buffer_q.empty(): + q_buffer_item, q_timestamp = self.rx_buffer_q.get() + packet_type = struct.unpack(' 0 and packets_to_send > 0: + data, last_value = tp.gen_data(hci.num_of_bytes_to_send, last_value) + l2cap_data.set(channel_id=0x0044, data=data) + acl_data.set(connection_handle=hci.conn_handle, pb_flag=0b00, bc_flag=0b00, + data=l2cap_data.ba_full_message) + await bt_dev.acl_data_send(acl_data) + async with bt_dev.async_lock_packets_cnt: + packets_to_send -= 1 + packet_credits -= 1 + else: + logging.info(f"Waiting for num_of_cmp_packets event") + await bt_dev.async_ev_num_cmp_pckts.wait() + bt_dev.async_ev_num_cmp_pckts.clear() + + if hci.num_of_completed_packets_cnt > 0: + async with bt_dev.async_lock_packets_cnt: + sent_packets += hci.num_of_completed_packets_cnt + tx_sent_timestamps.append((hci.num_of_completed_packets_time, + sent_packets)) + logging.info(f"Sent : {sent_packets}") + + packet_credits += hci.num_of_completed_packets_cnt + hci.num_of_completed_packets_cnt = 0 + + + for timestamp in tx_sent_timestamps: + bt_dev.tp.append_to_csv_file(*timestamp) + + await finish(bt_dev, cfg) + + +def parse_cfg_files(args) -> dict: + if args.init_file is None: + ini = { + "own_address": args.own_addr, + "own_address_type": args.own_addr_type, + "dev_index": str(args.dev_idx), + "peer_address": args.peer_addr, + "peer_address_type": args.peer_addr_type, + "peer_dev_index": args.peer_dev_idx + } + else: + with open(args.init_file, "r") as file: + init_file = yaml.safe_load(file) + ini = init_file[args.mode] + global test_dir + test_dir = init_file["test_dir"] + + with open(args.config_file) as f: + cfg = yaml.safe_load(f) + + global show_tp_plots + + hci.num_of_bytes_to_send = cfg["num_of_bytes_to_send"] + hci.num_of_packets_to_send = cfg["num_of_packets_to_send"] + show_tp_plots = cfg["show_tp_plots"] + + return ini, cfg + + +def signal_handler(signum, frame): + logging.critical(f"Received signal: {signal.Signals(signum).name}") + raise ParentCalledException(f"Received signal: {signal.Signals(signum).name}") + + +def main(): + args = parse_arguments() + ini, cfg = parse_cfg_files(args) + log_path = f"log/log_{args.mode}.log" + transport = None + + try: + util.configure_logging(log_path, clear_log_file=True) + + loop = asyncio.get_event_loop() + loop.set_debug(True) + + transport = transport_factory.TransportFactory(device_index=ini['dev_index'], + device_mode=args.mode, + asyncio_loop=loop) + + signal.signal(signal.SIGTERM, signal_handler) + + bt_dev = hci_commands.HCI_Commands(send=transport.send, + rx_buffer_q=transport.rx_buffer_q, + asyncio_loop=loop, + device_mode=args.mode) + + transport.start() + + if args.mode == 'rx': + loop.run_until_complete(async_main_rx(bt_dev, ini, cfg)) + elif args.mode == 'tx': + loop.run_until_complete(async_main_tx(bt_dev, ini, cfg)) + + + except Exception as e: + logging.error(traceback.format_exc()) + except (KeyboardInterrupt or ParentCalledException): + logging.critical("Hard exit triggered.") + logging.error(traceback.format_exc()) + finally: + if transport != None: + transport.stop() + sys.exit() + + +if __name__ == '__main__': + main() diff --git a/tools/hci_throughput/hci_socket.py b/tools/hci_throughput/hci_socket.py new file mode 100644 index 000000000..f95ea8657 --- /dev/null +++ b/tools/hci_throughput/hci_socket.py @@ -0,0 +1,164 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 hci +import socket +import ctypes +import struct +import asyncio +import logging +import subprocess +import sys +import time +import multiprocessing + + +SOCKET_RECV_BUFFER_SIZE = 425984 +SOCKET_RECV_TIMEOUT = 3 + + +def btmgmt_dev_reset(index): + logging.info(f"Selecting index {index}") + proc = subprocess.Popen(['btmgmt', '-i', str(index), 'power', 'off'], + shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc.communicate() + + +class BindingError(Exception): + pass + + +class HCI_User_Channel_Socket_Error(BaseException): + pass + + +class HCI_User_Channel_Socket(): + def __init__(self, device_index=0, device_mode=None, + asyncio_loop=None): + logging.debug("Device index: %s, Device address: %s", device_index, device_mode) + self.loop = asyncio_loop + self.libc = ctypes.cdll.LoadLibrary('libc.so.6') + self.rx_buffer_q = multiprocessing.Manager().Queue() + self.counter = 0 + + self.device_index = device_index + self.device_mode = device_mode + self.hci_socket = self.socket_create() + self.socket_bind(self.device_index) + self.socket_clear() + self.listener_proc = None + self.listener_ev = multiprocessing.Manager().Event() + + def socket_create(self): + logging.debug("%s", self.socket_create.__name__) + new_socket = socket.socket(socket.AF_BLUETOOTH, + socket.SOCK_RAW | socket.SOCK_NONBLOCK, + socket.BTPROTO_HCI) + if new_socket == None: + raise HCI_User_Channel_Socket_Error("Socket error. \ + Opening socket failed") + new_socket.setblocking(False) + socket_size = new_socket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) + logging.info(f"Default socket recv buffer size: {socket_size}") + new_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 500000) + socket_size = new_socket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) + logging.info(f"Set socket recv buffer size: {socket_size}") + return new_socket + + def socket_bind(self, index): + logging.debug("%s index: %s", self.socket_bind.__name__, index) + # addr: struct sockaddr_hci from /usr/include/bluetooth/hci.h + addr = struct.pack('HHH', hci.AF_BLUETOOTH, index, hci.HCI_CHANNEL_USER) + retry_binding=2 + for i in range(retry_binding): + try: + bind = self.libc.bind(self.hci_socket.fileno(), + ctypes.cast(addr, + ctypes.POINTER(ctypes.c_ubyte)), + len(addr)) + if bind != 0: + raise BindingError + except BindingError: + logging.warning("Binding error. Trying to reset bluetooth.") + btmgmt_dev_reset(self.device_index) + if i < retry_binding - 1: + continue + else: + self.hci_socket.close() + logging.error("Binding error. Check HCI index present.") + sys.exit() + logging.info("Binding done!") + break + + def socket_clear(self): + logging.debug("%s", self.socket_clear.__name__) + try: + logging.info("Clearing the buffer...") + time.sleep(1) + cnt = 0 + while True: + buff = self.hci_socket.recv(SOCKET_RECV_BUFFER_SIZE) + cnt += len(buff) + logging.debug(f"Read from buffer {cnt} bytes") + except BlockingIOError: + logging.info("Buffer empty and ready!") + return + + async def send(self, ba_message): + await self.loop.sock_sendall(self.hci_socket, ba_message) + + def socket_listener(self): + recv_at_once = 0 + while True: + try: + if self.listener_ev.is_set(): + logging.info("listener_ev set") + break + buffer = self.hci_socket.recv(SOCKET_RECV_BUFFER_SIZE) + logging.info(f"Socket recv: {self.counter} th packet with len: {len(buffer)}") + self.rx_buffer_q.put((buffer, time.perf_counter())) + recv_at_once +=1 + self.counter +=1 + + except BlockingIOError: + if recv_at_once > 1: + logging.info(f"Socket recv in one loop: {recv_at_once}") + recv_at_once = 0 + pass + except BrokenPipeError: + logging.info("BrokenPipeError: Closing...") + print("BrokenPipeError. Press Ctrl-C to exit...") + + def close(self): + logging.debug("%s ", self.close.__name__) + return self.hci_socket.close() + + def start(self): + self.listener_proc = multiprocessing.Process(target=self.socket_listener, + daemon=True) + self.listener_proc.start() + logging.info(f"start listener_proc pid: {self.listener_proc.pid}") + + def stop(self): + logging.info(f"stop listener_proc pid: {self.listener_proc.pid}") + self.listener_ev.set() + self.listener_proc.join() + self.close() diff --git a/tools/hci_throughput/init.yaml.sample b/tools/hci_throughput/init.yaml.sample new file mode 100644 index 000000000..ec1752276 --- /dev/null +++ b/tools/hci_throughput/init.yaml.sample @@ -0,0 +1,34 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +rx: + dev_index: '1' + own_address_type: 1 + own_address: C0:0D:A5:1A:98:EF + peer_dev_index: '2' + peer_address_type: 1 + peer_address: FE:69:8F:77:2F:49 +tx: + dev_index: '2' + own_address_type: 1 + own_address: FE:69:8F:77:2F:49 + peer_dev_index: '1' + peer_address_type: 1 + peer_address: C0:0D:A5:1A:98:EF +test_dir: /path/to/blehci_throughput/tests/Mon_May_23_12:29:10_2022 diff --git a/tools/hci_throughput/log/.gitignore b/tools/hci_throughput/log/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/tools/hci_throughput/log/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/tools/hci_throughput/main.py b/tools/hci_throughput/main.py new file mode 100644 index 000000000..bbaf4d7cd --- /dev/null +++ b/tools/hci_throughput/main.py @@ -0,0 +1,243 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 multiprocessing +import check_addr +import argparse +import yaml +import sys +import subprocess +import traceback +import matplotlib.pyplot as plt +import csv +import util +import os +import math + +PROCESS_TIMEOUT = 500 # seconds, adjust if necessary + +def parse_arguments(): + parser = argparse.ArgumentParser( + description='Measure throughput', + epilog='How to run python scripts: \ + sudo python main.py -i 0 1 -m rx tx -cf config.yaml\ + then hci0 -> rx and hci1 -> tx') + parser.add_argument('-i', '--indexes', type=str, nargs='*', + help='specify adapters indexes', default=[0, 1]) + parser.add_argument('-m', '--modes', type=str, nargs="*", + help='devices modes - receiver, transmitter', + choices=['rx', 'tx'], default=['rx', 'tx']) + parser.add_argument('-cf', '--config_file', type=str, nargs="*", + help='configuration file for devices', + default=["config.yaml"]) + try: + args = parser.parse_args() + except Exception as e: + print(traceback.format_exc()) + + print(f"Indexes: {args.indexes}") + print(f"Modes: {args.modes}") + + return args + + +def get_dev_addr_and_type(hci_indexes: list): + if (len(hci_indexes) != 2): + raise Exception("HCI index error.") + manager = multiprocessing.Manager() + addr_list = manager.list() + check_addrs_proc = multiprocessing.Process(target=check_addr.check_addr, + name="Check addresses", + args=(hci_indexes, addr_list)) + check_addrs_proc.start() + print("check_addrs_proc pid: ", check_addrs_proc.pid) + check_addrs_proc.join() + dev_addr_type_list = [] + for i in range(0, len(addr_list)): + dev_addr_type_list.append((hci_indexes[i],) + addr_list[i]) + return dev_addr_type_list + + +def change_config_var(filename: str, group: str, variable: str, + new_value: int): + with open(filename, "r") as file: + cfg = yaml.safe_load(file) + + if group: + cfg[group][variable] = new_value + else: + cfg[variable] = new_value + + with open(filename, "w") as file: + yaml.safe_dump(cfg, file, indent=1, sort_keys=False, + default_style=None, default_flow_style=False) + + +def get_init_dict(filename: str, args_list: list, modes: list, dir: str): + ini = { + modes[0]:{ + "dev_index": args_list[0][0], + "own_address_type": args_list[0][1], + "own_address": args_list[0][2], + "peer_dev_index": args_list[1][0], + "peer_address_type": args_list[1][1], + "peer_address": args_list[1][2] + }, + modes[1]:{ + "dev_index": args_list[1][0], + "own_address_type": args_list[1][1], + "own_address": args_list[1][2], + "peer_dev_index": args_list[0][0], + "peer_address_type": args_list[0][1], + "peer_address": args_list[0][2] + }, + "test_dir": dir + } + + with open(filename, 'w') as file: + yaml.safe_dump(ini, file, indent=1, sort_keys=False) + + return ini + + +def run_once(modes: list, cfg_file: str, init_file: str): + list_proc = [] + for mode in modes: + proc = subprocess.Popen(["python", "hci_device.py", "-m", + mode, "-if", init_file, "-cf", cfg_file]) + print("start subprocess pid: ", proc.pid) + list_proc.append(proc) + try: + for proc in list_proc: + proc.wait(PROCESS_TIMEOUT) + except subprocess.TimeoutExpired: + for proc in list_proc: + print("TimeoutExpired subprocess pid: ", proc.pid) + proc.terminate() + for proc in list_proc: + proc.wait() + return -1 + + for proc in list_proc: + print("stop subprocess pid: ", proc.pid) + proc.terminate() + proc.wait() + return 0 + + +def testing_variable_influence(cfg: dict, modes: list, cfg_file: str, + init_file: str, init_dict: dict, save_to_file: bool): + tp_test_counter = 1 + changed_params_list = [] + averages = [] + cfg_group = cfg["test"]["change_param_group"] + cfg_variable = cfg["test"]["change_param_variable"] + cfg_start_val = cfg["test"]["start_value"] + cfg_stop_val = cfg["test"]["stop_value"] + cfg_step = cfg["test"]["step"] + data_type = cfg["tp"]["data_type"] + total_iterations = math.ceil((cfg_stop_val - cfg_start_val) / cfg_step) + average_tp_csv_path = init_dict["test_dir"] + "/average_rx_tp.csv" + + with open(average_tp_csv_path, "w") as file: + file.write(f"Average throughput [{data_type}ps]\n") + + for i in range(cfg_start_val, cfg_stop_val, cfg_step): + changed_params_list.append(i) + + if cfg_group and cfg_variable: + print(f"Current param value: {i}") + num_of_params_to_change = len(cfg_variable) + + for j in range(0, num_of_params_to_change): + change_config_var(filename=cfg_file, group=cfg_group[j], + variable=cfg_variable[j], new_value=i) + + print(f"Running test: {tp_test_counter}/{total_iterations}...") + rc = run_once(modes, cfg_file, init_file) + if rc != 0: + print(f"Test {i} failed. Closing...") + return + + tp_test_counter += 1 + + with open(average_tp_csv_path, "r") as file: + csv_reader = csv.reader(file) + next(csv_reader) + for row in csv_reader: + averages.append(float(*row)) + + fig, ax = plt.subplots() + ax.plot(changed_params_list[:len(averages)], averages, '-k') + ax.set_ylabel(f"Average throughput [{data_type}/s]") + ax.set_xlabel("Changed parameter/next iteration") + ax.set_title("Average througput") + + if save_to_file: + name = init_dict["test_dir"] + "/average_tps" + plt.savefig(fname=name, format='png') + + plt.show(block=True) + + +def main(): + args = parse_arguments() + + init_file = "init.yaml" + cfg_file = args.config_file[0] + + with open(cfg_file, "r") as file: + cfg = yaml.safe_load(file) + + addr_list = get_dev_addr_and_type(args.indexes) + if len(addr_list) != len(args.indexes): + raise Exception("No device address received. Check HCI indexes.") + print(f"Received: {addr_list}") + + test_dir_path = util.create_test_directory() + init_dict = get_init_dict(filename=init_file, args_list=addr_list, + modes=args.modes, dir=test_dir_path) + + util.copy_config_files_to_test_directory([init_file, cfg_file], + init_dict["test_dir"]) + + try: + if cfg["flag_testing"]: + testing_variable_influence(cfg, args.modes, *args.config_file, + init_file, init_dict, True) + else: + print(f"Running test...") + rc = run_once(args.modes, cfg_file, init_file) + if rc != 0: + print("Test failed.") + + print("Finished. Closing...") + + except KeyboardInterrupt: + pass + except Exception as e: + print(traceback.format_exc()) + finally: + # Set default ownership for dirs and files + util.set_default_chmod_recurs(os.getcwd() + "/tests") + sys.exit() + + +if __name__ == "__main__": + main() diff --git a/tools/hci_throughput/requirements.txt b/tools/hci_throughput/requirements.txt new file mode 100644 index 000000000..8b2a114e4 --- /dev/null +++ b/tools/hci_throughput/requirements.txt @@ -0,0 +1,3 @@ +matplotlib==3.1.2 +PyYAML==6.0 +libusb1 \ No newline at end of file diff --git a/tools/hci_throughput/targets/nordic_pca10040_blehci/pkg.yml b/tools/hci_throughput/targets/nordic_pca10040_blehci/pkg.yml new file mode 100644 index 000000000..9cd3540e7 --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10040_blehci/pkg.yml @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +pkg.name: tools/hci_throughput/targets/nordic_pca10040_blehci +pkg.type: target +pkg.description: +pkg.author: +pkg.homepage: diff --git a/tools/hci_throughput/targets/nordic_pca10040_blehci/syscfg.yml b/tools/hci_throughput/targets/nordic_pca10040_blehci/syscfg.yml new file mode 100644 index 000000000..4de2fdd2c --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10040_blehci/syscfg.yml @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +syscfg.vals: + BLE_LL_CFG_FEAT_DATA_LEN_EXT: 1 + BLE_LL_CFG_FEAT_LE_2M_PHY: 1 + BLE_LL_HCI_VS_EVENT_ON_ASSERT: 1 + MSYS_1_BLOCK_COUNT: 80 + MSYS_1_BLOCK_SIZE: 308 + BLE_TRANSPORT_ACL_COUNT: 80 + BLE_TRANSPORT_ACL_SIZE: 255 + diff --git a/tools/hci_throughput/targets/nordic_pca10040_blehci/target.yml b/tools/hci_throughput/targets/nordic_pca10040_blehci/target.yml new file mode 100644 index 000000000..f9d6d0fb0 --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10040_blehci/target.yml @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +target.app: "@apache-mynewt-nimble/apps/blehci" +target.bsp: "@apache-mynewt-core/hw/bsp/nordic_pca10040" +target.build_profile: optimized diff --git a/tools/hci_throughput/targets/nordic_pca10040_boot/pkg.yml b/tools/hci_throughput/targets/nordic_pca10040_boot/pkg.yml new file mode 100644 index 000000000..dfcb1b63b --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10040_boot/pkg.yml @@ -0,0 +1,6 @@ +pkg.name: targets/nordic_pca10040_boot +pkg.type: target +pkg.description: +pkg.author: +pkg.homepage: + diff --git a/tools/hci_throughput/targets/nordic_pca10040_boot/syscfg.yml b/tools/hci_throughput/targets/nordic_pca10040_boot/syscfg.yml new file mode 100644 index 000000000..e69de29bb diff --git a/tools/hci_throughput/targets/nordic_pca10040_boot/target.yml b/tools/hci_throughput/targets/nordic_pca10040_boot/target.yml new file mode 100644 index 000000000..bbf253147 --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10040_boot/target.yml @@ -0,0 +1,3 @@ +target.app: "@mcuboot/boot/mynewt" +target.bsp: "@apache-mynewt-core/hw/bsp/nordic_pca10040" +target.build_profile: optimized diff --git a/tools/hci_throughput/targets/nordic_pca10056_blehci/pkg.yml b/tools/hci_throughput/targets/nordic_pca10056_blehci/pkg.yml new file mode 100644 index 000000000..a60adc72f --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10056_blehci/pkg.yml @@ -0,0 +1,27 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +pkg.name: tools/hci_throughput/targets/nordic_pca10056_blehci +pkg.type: target +pkg.description: +pkg.author: +pkg.homepage: + +pkg.deps: + - "@apache-mynewt-core/hw/usb/tinyusb/std_descriptors" diff --git a/tools/hci_throughput/targets/nordic_pca10056_blehci/syscfg.yml b/tools/hci_throughput/targets/nordic_pca10056_blehci/syscfg.yml new file mode 100644 index 000000000..61f14a788 --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10056_blehci/syscfg.yml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +syscfg.vals: + BLE_LL_CFG_FEAT_DATA_LEN_EXT: 1 + BLE_LL_CFG_FEAT_LE_2M_PHY: 1 + BLE_LL_HCI_VS_EVENT_ON_ASSERT: 1 + BLE_TRANSPORT_HS: usb + USBD_VID: 0xDCAB + USBD_PID: 0x1234 + USBD_BTH: 1 + USBD_PRODUCT_STRING: '"throughput"' + MSYS_1_BLOCK_COUNT: 80 + MSYS_1_BLOCK_SIZE: 308 + BLE_TRANSPORT_ACL_COUNT: 80 + BLE_TRANSPORT_ACL_SIZE: 255 \ No newline at end of file diff --git a/tools/hci_throughput/targets/nordic_pca10056_blehci/target.yml b/tools/hci_throughput/targets/nordic_pca10056_blehci/target.yml new file mode 100644 index 000000000..e6317f2d4 --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10056_blehci/target.yml @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +target.app: "@apache-mynewt-nimble/apps/blehci" +target.bsp: "@apache-mynewt-core/hw/bsp/nordic_pca10056" +target.build_profile: optimized diff --git a/tools/hci_throughput/targets/nordic_pca10056_boot/pkg.yml b/tools/hci_throughput/targets/nordic_pca10056_boot/pkg.yml new file mode 100644 index 000000000..1b9a6cde1 --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10056_boot/pkg.yml @@ -0,0 +1,6 @@ +pkg.name: targets/nordic_pca10056_boot +pkg.type: target +pkg.description: +pkg.author: +pkg.homepage: + diff --git a/tools/hci_throughput/targets/nordic_pca10056_boot/syscfg.yml b/tools/hci_throughput/targets/nordic_pca10056_boot/syscfg.yml new file mode 100644 index 000000000..e69de29bb diff --git a/tools/hci_throughput/targets/nordic_pca10056_boot/target.yml b/tools/hci_throughput/targets/nordic_pca10056_boot/target.yml new file mode 100644 index 000000000..9cde39ac0 --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10056_boot/target.yml @@ -0,0 +1,3 @@ +target.app: "@mcuboot/boot/mynewt" +target.bsp: "@apache-mynewt-core/hw/bsp/nordic_pca10056" +target.build_profile: optimized diff --git a/tools/hci_throughput/targets/nordic_pca10059_blehci/pkg.yml b/tools/hci_throughput/targets/nordic_pca10059_blehci/pkg.yml new file mode 100644 index 000000000..6b9edc3bb --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10059_blehci/pkg.yml @@ -0,0 +1,27 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +pkg.name: tools/hci_throughput/targets/nordic_pca10059_blehci +pkg.type: target +pkg.description: +pkg.author: +pkg.homepage: + +pkg.deps: + - "@apache-mynewt-core/hw/usb/tinyusb/std_descriptors" diff --git a/tools/hci_throughput/targets/nordic_pca10059_blehci/syscfg.yml b/tools/hci_throughput/targets/nordic_pca10059_blehci/syscfg.yml new file mode 100644 index 000000000..f27e8c0b8 --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10059_blehci/syscfg.yml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +syscfg.vals: + BLE_LL_CFG_FEAT_DATA_LEN_EXT: 1 + BLE_LL_CFG_FEAT_LE_2M_PHY: 1 + BLE_LL_HCI_VS_EVENT_ON_ASSERT: 1 + BLE_TRANSPORT_HS: usb + USBD_VID: 0xDCAB + USBD_PID: 0x1234 + USBD_BTH: 1 + USBD_PRODUCT_STRING: '"throughput_dongle"' + MSYS_1_BLOCK_COUNT: 80 + MSYS_1_BLOCK_SIZE: 308 + BLE_TRANSPORT_ACL_COUNT: 80 + BLE_TRANSPORT_ACL_SIZE: 255 \ No newline at end of file diff --git a/tools/hci_throughput/targets/nordic_pca10059_blehci/target.yml b/tools/hci_throughput/targets/nordic_pca10059_blehci/target.yml new file mode 100644 index 000000000..b582fddd4 --- /dev/null +++ b/tools/hci_throughput/targets/nordic_pca10059_blehci/target.yml @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +target.app: "@apache-mynewt-nimble/apps/blehci" +target.bsp: "@apache-mynewt-core/hw/bsp/nordic_pca10059" +target.build_profile: optimized diff --git a/tools/hci_throughput/targets/nrf52832_blehci/pkg.yml b/tools/hci_throughput/targets/nrf52832_blehci/pkg.yml new file mode 100644 index 000000000..72d557426 --- /dev/null +++ b/tools/hci_throughput/targets/nrf52832_blehci/pkg.yml @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +pkg.name: tools/hci_throughput/targets/nrf52832_blehci +pkg.type: target +pkg.description: +pkg.author: +pkg.homepage: diff --git a/tools/hci_throughput/targets/nrf52832_blehci/syscfg.yml b/tools/hci_throughput/targets/nrf52832_blehci/syscfg.yml new file mode 100644 index 000000000..e63f3ed72 --- /dev/null +++ b/tools/hci_throughput/targets/nrf52832_blehci/syscfg.yml @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +syscfg.vals: + BLE_LL_CFG_FEAT_LE_2M_PHY: 1 + MSYS_1_BLOCK_COUNT: 80 + MSYS_1_BLOCK_SIZE: 308 + BLE_TRANSPORT_ACL_COUNT: 80 + BLE_TRANSPORT_ACL_SIZE: 255 + diff --git a/tools/hci_throughput/targets/nrf52832_blehci/target.yml b/tools/hci_throughput/targets/nrf52832_blehci/target.yml new file mode 100644 index 000000000..f9d6d0fb0 --- /dev/null +++ b/tools/hci_throughput/targets/nrf52832_blehci/target.yml @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +target.app: "@apache-mynewt-nimble/apps/blehci" +target.bsp: "@apache-mynewt-core/hw/bsp/nordic_pca10040" +target.build_profile: optimized diff --git a/tools/hci_throughput/targets/nrf52840_blehci/pkg.yml b/tools/hci_throughput/targets/nrf52840_blehci/pkg.yml new file mode 100644 index 000000000..bc9743eac --- /dev/null +++ b/tools/hci_throughput/targets/nrf52840_blehci/pkg.yml @@ -0,0 +1,27 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +pkg.name: tools/hci_throughput/targets/nrf52840_blehci +pkg.type: target +pkg.description: +pkg.author: +pkg.homepage: + +pkg.deps: + - "@apache-mynewt-core/hw/usb/tinyusb/std_descriptors" diff --git a/tools/hci_throughput/targets/nrf52840_blehci/syscfg.yml b/tools/hci_throughput/targets/nrf52840_blehci/syscfg.yml new file mode 100644 index 000000000..61f14a788 --- /dev/null +++ b/tools/hci_throughput/targets/nrf52840_blehci/syscfg.yml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +syscfg.vals: + BLE_LL_CFG_FEAT_DATA_LEN_EXT: 1 + BLE_LL_CFG_FEAT_LE_2M_PHY: 1 + BLE_LL_HCI_VS_EVENT_ON_ASSERT: 1 + BLE_TRANSPORT_HS: usb + USBD_VID: 0xDCAB + USBD_PID: 0x1234 + USBD_BTH: 1 + USBD_PRODUCT_STRING: '"throughput"' + MSYS_1_BLOCK_COUNT: 80 + MSYS_1_BLOCK_SIZE: 308 + BLE_TRANSPORT_ACL_COUNT: 80 + BLE_TRANSPORT_ACL_SIZE: 255 \ No newline at end of file diff --git a/tools/hci_throughput/targets/nrf52840_blehci/target.yml b/tools/hci_throughput/targets/nrf52840_blehci/target.yml new file mode 100644 index 000000000..e6317f2d4 --- /dev/null +++ b/tools/hci_throughput/targets/nrf52840_blehci/target.yml @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +target.app: "@apache-mynewt-nimble/apps/blehci" +target.bsp: "@apache-mynewt-core/hw/bsp/nordic_pca10056" +target.build_profile: optimized diff --git a/tools/hci_throughput/tests/.gitignore b/tools/hci_throughput/tests/.gitignore new file mode 100755 index 000000000..c96a04f00 --- /dev/null +++ b/tools/hci_throughput/tests/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/tools/hci_throughput/throughput.py b/tools/hci_throughput/throughput.py new file mode 100644 index 000000000..dcc242d20 --- /dev/null +++ b/tools/hci_throughput/throughput.py @@ -0,0 +1,197 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 time +import matplotlib.pyplot as plt +import csv +import struct +import argparse +import traceback + +def parse_arguments(): + parser = argparse.ArgumentParser( + description='Plot throughput from the csv file.', + epilog='How to run script: \ + python throughput.py -f tests/Wed_Apr_13_08:36:29_2022/tp_receiver.csv -s 0.1') + + parser.add_argument('-f', '--file', type=str, nargs='*', + help='csv file path', default=["tp_receiver"]) + parser.add_argument('-s', '--samp_t', type=float, nargs='*', + help='specify throughput sample time', default=1.0) + try: + args = parser.parse_args() + except Exception as e: + print(traceback.format_exc()) + return args + +data_types = ['kb', 'kB'] + + +def gen_data(num_of_bytes_in_packet: int, last_number_from_previous_data_packet: int): + counter = last_number_from_previous_data_packet + 1 + rem = num_of_bytes_in_packet % 4 + valid_data_len = int((num_of_bytes_in_packet - rem) / 4) + total_data_len = valid_data_len + rem + data = [0] * total_data_len + for i in range(rem,total_data_len): + data[i] = counter + counter += 1 + last_value = data[len(data)-1] + if rem: + fmt = "<" + str(rem) + "B" + str(valid_data_len) + "I" + else: + fmt = "<" + str(valid_data_len) + "I" + data_ba = struct.pack(fmt, *data) + return data_ba, last_value + + +class Throughput(): + def __init__(self, name="tp_chart", mode="rx", total_packets_number=0, bytes_number_in_packet=0, + throughput_data_type='kb', flag_plot_packets=True, sample_time=1, test_directory=None): + self.name = name + self.mode = mode + self.total_packets_number = total_packets_number + self.bytes_number_in_packet = bytes_number_in_packet + self.predef_packet_key = int((bytes_number_in_packet - (bytes_number_in_packet % 4))/4) + self.total_bits_number = bytes_number_in_packet * 8 + assert throughput_data_type in data_types + self.throughput_data_type = throughput_data_type + self.flag_plot_packets = flag_plot_packets + self.sample_time = sample_time + self.test_directory = test_directory + + if self.test_directory is not None: + self.csv_file_name = self.test_directory + "/" + time.strftime("%Y_%m_%d_%H_%M_%S_") + self.name + ".csv" + else: + self.csv_file_name = time.strftime("%Y_%m_%d_%H_%M_%S_") + self.name + ".csv" + self.clean_csv_file() + + def calc_throughput(self, current_num, last_num, current_time, last_time): + if self.throughput_data_type == 'kb': + return float((((current_num - last_num) * \ + self.total_bits_number) / (current_time-last_time))/1000) + elif self.throughput_data_type == 'kB': + return float((((current_num - last_num) * \ + self.bytes_number_in_packet) / (current_time-last_time))/1000) + + def clean_csv_file(self): + file = open(self.csv_file_name, 'w') + file.write("Time,Packet\n") + + def append_to_csv_file(self, timestamp: float = 0.0, packet_number: int = 0): + with open(self.csv_file_name, "a") as file: + csv_writer = csv.writer(file) + csv_writer.writerow([timestamp, packet_number]) + + def get_average(self, packet_numbers, timestamps): + if self.throughput_data_type == 'kb': + average_tp = ((packet_numbers * self.total_bits_number) \ + / (timestamps[-1] - timestamps[0]))/1000 + elif self.throughput_data_type == 'kB': + average_tp = ((packet_numbers * self.bytes_number_in_packet) \ + / (timestamps[-1] - timestamps[0]))/1000 + return average_tp + + def save_average(self, tp_csv_filename = None): + if self.mode == "rx": + timestamps = [] + packet_numbers = [] + + if tp_csv_filename is None: + tp_csv_filename = self.csv_file_name + else: + tp_csv_filename += ".csv" + + with open(tp_csv_filename, "r") as file: + csv_reader = csv.reader(file) + next(csv_reader) + for row in csv_reader: + timestamps.append(float(row[0])) + packet_numbers.append(float(row[1])) + + average_tp = self.get_average(packet_numbers[-1], timestamps) + print(f"Average rx throughput: {round(average_tp, 3)} {self.throughput_data_type}ps") + + with open(self.test_directory + "/average_rx_tp.csv", "a") as file: + csv_writer = csv.writer(file) + csv_writer.writerow([average_tp]) + + def plot_tp_from_file(self, filename: str = None, sample_time: float = 1, + save_to_file: bool = True): + timestamps = [] + packet_numbers = [] + + if filename is None: + filename = self.csv_file_name + print("Results:", filename) + + with open(filename, "r") as file: + csv_reader = csv.reader(file) + next(csv_reader) + for row in csv_reader: + timestamps.append(float(row[0])) + packet_numbers.append(float(row[1])) + + last_time = 0 + last_number = packet_numbers[0] + throughput = [] + offset = timestamps[0] + + for i in range(0, len(timestamps)): + timestamps[i] -= offset + if timestamps[i] - last_time > sample_time: + throughput.append((timestamps[i], + self.calc_throughput(packet_numbers[i], + last_number, + timestamps[i], + last_time))) + last_time = timestamps[i] + last_number = packet_numbers[i] + + average_tp = self.get_average(packet_numbers[-1], timestamps) + + fig, ax = plt.subplots() + if self.flag_plot_packets: + ax2 = ax.twinx() + + ax.plot(*zip(*throughput), 'k-') + if self.flag_plot_packets: + ax2.plot(timestamps, packet_numbers, 'b-') + + ax.set_title(self.name) + ax.set_ylabel(f"Throughput [{self.throughput_data_type}/s]") + ax.set_xlabel("Time [s]") + ax.text(0.9, 1.02, f"Average: {round(average_tp, 3)}" + f"{self.throughput_data_type}ps", transform=ax.transAxes, + color='k') + if self.flag_plot_packets: + ax2 = ax2.set_ylabel(f"Packets [Max:{len(packet_numbers)}]", + color='b') + + if save_to_file: + path = filename.replace(".csv", ".png") + plt.savefig(path) + + plt.show(block=True) + + +if __name__ == "__main__": + args = parse_arguments() + tp = Throughput(bytes_number_in_packet=247) + tp.plot_tp_from_file(*args.file, args.samp_t[0], save_to_file=False) \ No newline at end of file diff --git a/tools/hci_throughput/transport_factory.py b/tools/hci_throughput/transport_factory.py new file mode 100644 index 000000000..615bccf52 --- /dev/null +++ b/tools/hci_throughput/transport_factory.py @@ -0,0 +1,38 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 hci_socket + +class TransportFactory: + def __init__(self, device_index=None, device_mode=None, + asyncio_loop=None) -> None: + if (type(device_index) == int or device_index.isnumeric()): + self.transport = hci_socket.HCI_User_Channel_Socket(int(device_index), + device_mode, asyncio_loop) + else: + raise Exception("No such transport found.") + + self.rx_buffer_q = self.transport.rx_buffer_q + self.send = self.transport.send + + def start(self): + self.transport.start() + + def stop(self): + self.transport.stop() diff --git a/tools/hci_throughput/util.py b/tools/hci_throughput/util.py new file mode 100644 index 000000000..6aeed607c --- /dev/null +++ b/tools/hci_throughput/util.py @@ -0,0 +1,70 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 logging +import shutil +import time +import os + +def create_test_directory(): + test_dir_name = "tests/" + time.strftime("%Y_%m_%d_%H_%M_%S") + path = os.path.join(os.getcwd(), test_dir_name) + os.mkdir(path, mode=0o777) + print("Test directory: ", path) + return path + + +def configure_logging(log_filename, clear_log_file=True): + format_template = ("%(asctime)s %(threadName)s %(name)s %(levelname)s " + "%(filename)-25s %(lineno)-5s " + "%(funcName)-25s : %(message)s") + logging.basicConfig(format=format_template, + filename=log_filename, + filemode='a', + level=logging.DEBUG) + if clear_log_file: + with open(log_filename, "w") as f: + f.write("asctime\t\t\t\t\tthreadName name levelname filename\ + \tlineno\tfuncName\t\t\t\tmessage\n") + + logging.getLogger("asyncio").setLevel(logging.WARNING) + logging.getLogger("matplotlib").setLevel(logging.WARNING) + + +def copy_config_files_to_test_directory(files: list, test_directory: str): + for file in files: + shutil.copy(file, test_directory + "/" + file) + + +def copy_log_files_to_test_directory(dir: str): + log_files = ["log/log_rx.log", "log/log_tx.log", "log/check_addr.log"] + for file in log_files: + shutil.copy(file, dir + "/" + time.strftime("%Y_%m_%d_%H_%M_%S_") + + file.replace("log/", "")) + + +# Running tests as sudo implies root permissions on created directories/files. +# This function sets the default permission mode to dirs/files in given path +# recursively. +def set_default_chmod_recurs(path): + for root, dirs, files in os.walk(path): + for d in dirs: + os.chmod(os.path.join(root, d), 0o0777) + for f in files: + os.chmod(os.path.join(root, f), 0o0777)