diff --git a/src/infuse_iot/generated/rpc_definitions.py b/src/infuse_iot/generated/rpc_definitions.py index 263b29e..21ddbb5 100644 --- a/src/infuse_iot/generated/rpc_definitions.py +++ b/src/infuse_iot/generated/rpc_definitions.py @@ -232,6 +232,18 @@ class rpc_struct_public_key_info_256bit(VLACompatLittleEndianStruct): _pack_ = 1 +class rpc_struct_thread_stats(VLACompatLittleEndianStruct): + """IPv6 address""" + + _fields_ = [ + ("stack_size", ctypes.c_uint32), + ("stack_used", ctypes.c_uint32), + ("utilization", ctypes.c_uint8), + ] + vla_field = ("name", 0 * ctypes.c_char) + _pack_ = 1 + + class rpc_enum_bt_le_addr_type(enum.IntEnum): """Bluetooth LE address type""" @@ -872,6 +884,25 @@ class response(VLACompatLittleEndianStruct): _pack_ = 1 +class thread_stats(RPCDefinitionBase): + """Query runtime thread statistics""" + + NAME = "thread_stats" + HELP = "Query runtime thread statistics" + DESCRIPTION = "Query runtime thread statistics" + COMMAND_ID = 26 + + class request(VLACompatLittleEndianStruct): + _fields_ = [] + _pack_ = 1 + + class response(VLACompatLittleEndianStruct): + _fields_ = [ + ("num_threads", ctypes.c_uint16), + ] + _pack_ = 1 + + class coap_download(RPCDefinitionBase): """Download a file from a COAP server (Infuse-IoT DTLS protected)""" @@ -1334,6 +1365,7 @@ class response(VLACompatLittleEndianStruct): lte_state_v2.COMMAND_ID: lte_state_v2, data_logger_state_v2.COMMAND_ID: data_logger_state_v2, data_logger_read_chunks.COMMAND_ID: data_logger_read_chunks, + thread_stats.COMMAND_ID: thread_stats, coap_download.COMMAND_ID: coap_download, zperf_upload.COMMAND_ID: zperf_upload, coap_download_v2.COMMAND_ID: coap_download_v2, @@ -1373,6 +1405,7 @@ class response(VLACompatLittleEndianStruct): "rpc_struct_heap_info", "rpc_struct_data_logger_chunk", "rpc_struct_public_key_info_256bit", + "rpc_struct_thread_stats", "rpc_enum_bt_le_addr_type", "rpc_enum_file_action", "rpc_enum_infuse_bt_characteristic", @@ -1406,6 +1439,7 @@ class response(VLACompatLittleEndianStruct): "lte_state_v2", "data_logger_state_v2", "data_logger_read_chunks", + "thread_stats", "coap_download", "zperf_upload", "coap_download_v2", diff --git a/src/infuse_iot/rpc_wrappers/security_state.py b/src/infuse_iot/rpc_wrappers/security_state.py index 7628c5b..f3f8cd9 100644 --- a/src/infuse_iot/rpc_wrappers/security_state.py +++ b/src/infuse_iot/rpc_wrappers/security_state.py @@ -52,14 +52,14 @@ def add_parser(cls, parser): parser.add_argument("--pem", type=ValidFile, help="Cloud .pem file for identity validation") def __init__(self, args): - self.challenge = bytes_to_uint8(random.randbytes(16)) + self.challenge = random.randbytes(16) self.pem = args.pem def auth_level(self): return Auth.NETWORK def request_struct(self): - return self.request(self.challenge) + return self.request(bytes_to_uint8(self.challenge)) def _decrypt_response(self, response): rsp = response.response @@ -87,6 +87,9 @@ def handle_response(self, return_code, response): print(f"Failed to query current time ({errno.strerror(-return_code)})") return + print("Challenge:") + print(f"\t Challenge Bytes: {bytes(self.challenge).hex()}") + # Decrypt identity information print("Security State:") print(f"\tDevice Public Key: {bytes(response.device_public_key).hex()}") @@ -94,6 +97,7 @@ def handle_response(self, return_code, response): print(f"\t Network: 0x{response.network_id:06x}") if self.pem is None: print("\t Identity: Cannot validate") + print(f"\t Raw: {bytes(response.challenge_response).hex()}") else: challenge_rsp = self._decrypt_response(response) diff --git a/src/infuse_iot/rpc_wrappers/thread_stats.py b/src/infuse_iot/rpc_wrappers/thread_stats.py new file mode 100644 index 0000000..feb6cf4 --- /dev/null +++ b/src/infuse_iot/rpc_wrappers/thread_stats.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import ctypes + +import tabulate + +import infuse_iot.definitions.rpc as defs +from infuse_iot.commands import InfuseRpcCommand +from infuse_iot.zephyr.errno import errno + + +class thread_stats(InfuseRpcCommand, defs.thread_stats): + RPC_DATA_RECEIVE = True + + @classmethod + def add_parser(cls, parser): + sort = parser.add_mutually_exclusive_group() + sort.add_argument("--sort-stack", action="store_true", help="Sort threads by stack usage (%)") + + def __init__(self, args): + self.args = args + self._info = [] + + def request_struct(self): + return self.request() + + def request_json(self): + return {} + + def data_recv_cb(self, offset: int, data: bytes) -> None: + base_size = ctypes.sizeof(defs.rpc_struct_thread_stats) + while len(data) > 0: + base = defs.rpc_struct_thread_stats.from_buffer_copy(data) + data = data[base_size:] + name_len = data.find(b"\x00") + 1 + name = data[:name_len].decode() + data = data[name_len:] + percent = int(100 * base.stack_used / base.stack_size) + self._info.append((name, base.stack_used, base.stack_size, percent, base.utilization)) + + def handle_response(self, return_code, response): + if return_code != 0: + print(f"Failed to query thread stats ({errno.strerror(-return_code)})") + return + + if self.args.sort_stack: + # Sort by stack usage + display = sorted(self._info, key=lambda x: x[3], reverse=True) + else: + display = self._info + + headings = ["Thread", "Stack Used", "Stack Size", "Stack %", "CPU %"] + print(tabulate.tabulate(display, headers=headings))