Add statistics module which tracks the usage of modem backend transmit/receive buffers if implemented by a backend. A modem backend may register itself to the modem backend stat module, and advertise its transmit and receive buffer sizes and momentary usage (length). The modem backend module tracks the maximum usage, which can be output to the shell using the "modem_stats buffer" shell command. Signed-off-by: Bjarki Arge Andreasen <bjarki@arge-andreasen.me>
167 lines
3.8 KiB
C
167 lines
3.8 KiB
C
/*
|
|
* 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;
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|