|  | /* | 
|  | * Copyright (c) 2024 Trackunit Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #ifdef _POSIX_VERSION | 
|  | #undef _POSIX_VERSION | 
|  | #endif | 
|  | #define _POSIX_VERSION 200809L | 
|  |  | 
|  | #include <zephyr/modem/stats.h> | 
|  | #include <zephyr/shell/shell.h> | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(modem_stats); | 
|  |  | 
|  | static struct k_spinlock stats_buffer_lock; | 
|  | static sys_slist_t stats_buffer_list; | 
|  |  | 
|  | static struct modem_stats_buffer *stats_buffer_from_node(sys_snode_t *node) | 
|  | { | 
|  | return (struct modem_stats_buffer *)node; | 
|  | } | 
|  |  | 
|  | static void stats_buffer_list_append(struct modem_stats_buffer *buffer) | 
|  | { | 
|  | K_SPINLOCK(&stats_buffer_lock) { | 
|  | sys_slist_append(&stats_buffer_list, &buffer->node); | 
|  | } | 
|  | } | 
|  |  | 
|  | static struct modem_stats_buffer *stats_buffer_list_first(void) | 
|  | { | 
|  | struct modem_stats_buffer *first = NULL; | 
|  |  | 
|  | K_SPINLOCK(&stats_buffer_lock) { | 
|  | first = stats_buffer_from_node(sys_slist_peek_head(&stats_buffer_list)); | 
|  | } | 
|  |  | 
|  | return first; | 
|  | } | 
|  |  | 
|  | static struct modem_stats_buffer *stats_buffer_list_next(struct modem_stats_buffer *buffer) | 
|  | { | 
|  | struct modem_stats_buffer *next = NULL; | 
|  |  | 
|  | K_SPINLOCK(&stats_buffer_lock) { | 
|  | next = stats_buffer_from_node(sys_slist_peek_next(&buffer->node)); | 
|  | } | 
|  |  | 
|  | return next; | 
|  | } | 
|  |  | 
|  | static uint8_t percent_used(uint32_t max_used, uint32_t cap) | 
|  | { | 
|  | uint64_t percent; | 
|  |  | 
|  | if (max_used == 0) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (max_used == cap) { | 
|  | return 100; | 
|  | } | 
|  |  | 
|  | percent = 100; | 
|  | percent *= max_used; | 
|  | percent /= cap; | 
|  |  | 
|  | return (uint8_t)percent; | 
|  | } | 
|  |  | 
|  | static void stats_buffer_get_and_clear_max_used(struct modem_stats_buffer *buffer, | 
|  | uint32_t *max_used) | 
|  | { | 
|  | K_SPINLOCK(&stats_buffer_lock) { | 
|  | *max_used = buffer->max_used; | 
|  | buffer->max_used = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | static bool stats_buffer_length_is_valid(const struct modem_stats_buffer *buffer, uint32_t length) | 
|  | { | 
|  | return length <= buffer->size; | 
|  | } | 
|  |  | 
|  | static void stats_buffer_log_invalid_length(const struct modem_stats_buffer *buffer, | 
|  | uint32_t length) | 
|  | { | 
|  | LOG_ERR("%s: length (%u) exceeds size (%u)", buffer->name, length, buffer->size); | 
|  | } | 
|  |  | 
|  | static void stats_buffer_update_max_used(struct modem_stats_buffer *buffer, uint32_t length) | 
|  | { | 
|  | K_SPINLOCK(&stats_buffer_lock) { | 
|  | if (buffer->max_used < length) { | 
|  | buffer->max_used = length; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void stats_buffer_print_to_shell(const struct shell *sh, | 
|  | const struct modem_stats_buffer *buffer, | 
|  | uint32_t max_used) | 
|  | { | 
|  | shell_print(sh, "%s: used at most: %u of %u (%u%%)", buffer->name, max_used, | 
|  | buffer->size, percent_used(max_used, buffer->size)); | 
|  | } | 
|  |  | 
|  | static int stats_buffer_shell_cmd_handler(const struct shell *sh, size_t argc, char **argv) | 
|  | { | 
|  | struct modem_stats_buffer *buffer; | 
|  | uint32_t max_used; | 
|  |  | 
|  | ARG_UNUSED(argc); | 
|  | ARG_UNUSED(argv); | 
|  |  | 
|  | buffer = stats_buffer_list_first(); | 
|  |  | 
|  | if (buffer == NULL) { | 
|  | shell_print(sh, "no buffers exist"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | while (buffer != NULL) { | 
|  | stats_buffer_get_and_clear_max_used(buffer, &max_used); | 
|  | stats_buffer_print_to_shell(sh, buffer, max_used); | 
|  | buffer = stats_buffer_list_next(buffer); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | SHELL_STATIC_SUBCMD_SET_CREATE( | 
|  | sub_stats_cmds, | 
|  | SHELL_CMD(buffer, NULL, "Get buffer statistics", stats_buffer_shell_cmd_handler), | 
|  | SHELL_SUBCMD_SET_END | 
|  | ); | 
|  |  | 
|  | SHELL_CMD_REGISTER(modem_stats, &sub_stats_cmds, "Modem statistics commands", NULL); | 
|  |  | 
|  | static void stats_buffer_set_name(struct modem_stats_buffer *buffer, const char *name) | 
|  | { | 
|  | buffer->name[sizeof(buffer->name) - 1] = '\0'; | 
|  | strncpy(buffer->name, name, sizeof(buffer->name) - 1); | 
|  | } | 
|  |  | 
|  | void modem_stats_buffer_init(struct modem_stats_buffer *buffer, | 
|  | const char *name, uint32_t size) | 
|  | { | 
|  | stats_buffer_set_name(buffer, name); | 
|  | buffer->max_used = 0; | 
|  | buffer->size = size; | 
|  | stats_buffer_list_append(buffer); | 
|  | } | 
|  |  | 
|  | void modem_stats_buffer_advertise_length(struct modem_stats_buffer *buffer, uint32_t length) | 
|  | { | 
|  | if (!stats_buffer_length_is_valid(buffer, length)) { | 
|  | stats_buffer_log_invalid_length(buffer, length); | 
|  | return; | 
|  | } | 
|  |  | 
|  | stats_buffer_update_max_used(buffer, length); | 
|  | } |