1 /*
2 * Copyright (c) 2024 Trackunit Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #ifdef _POSIX_VERSION
8 #undef _POSIX_VERSION
9 #endif
10 #define _POSIX_VERSION 200809L
11
12 #include <zephyr/modem/stats.h>
13 #include <zephyr/shell/shell.h>
14
15 #include <zephyr/logging/log.h>
16 LOG_MODULE_REGISTER(modem_stats);
17
18 static struct k_spinlock stats_buffer_lock;
19 static sys_slist_t stats_buffer_list;
20
stats_buffer_from_node(sys_snode_t * node)21 static struct modem_stats_buffer *stats_buffer_from_node(sys_snode_t *node)
22 {
23 return (struct modem_stats_buffer *)node;
24 }
25
stats_buffer_list_append(struct modem_stats_buffer * buffer)26 static void stats_buffer_list_append(struct modem_stats_buffer *buffer)
27 {
28 K_SPINLOCK(&stats_buffer_lock) {
29 sys_slist_append(&stats_buffer_list, &buffer->node);
30 }
31 }
32
stats_buffer_list_first(void)33 static struct modem_stats_buffer *stats_buffer_list_first(void)
34 {
35 struct modem_stats_buffer *first = NULL;
36
37 K_SPINLOCK(&stats_buffer_lock) {
38 first = stats_buffer_from_node(sys_slist_peek_head(&stats_buffer_list));
39 }
40
41 return first;
42 }
43
stats_buffer_list_next(struct modem_stats_buffer * buffer)44 static struct modem_stats_buffer *stats_buffer_list_next(struct modem_stats_buffer *buffer)
45 {
46 struct modem_stats_buffer *next = NULL;
47
48 K_SPINLOCK(&stats_buffer_lock) {
49 next = stats_buffer_from_node(sys_slist_peek_next(&buffer->node));
50 }
51
52 return next;
53 }
54
percent_used(uint32_t max_used,uint32_t cap)55 static uint8_t percent_used(uint32_t max_used, uint32_t cap)
56 {
57 uint64_t percent;
58
59 if (max_used == 0) {
60 return 0;
61 }
62
63 if (max_used == cap) {
64 return 100;
65 }
66
67 percent = 100;
68 percent *= max_used;
69 percent /= cap;
70
71 return (uint8_t)percent;
72 }
73
stats_buffer_get_and_clear_max_used(struct modem_stats_buffer * buffer,uint32_t * max_used)74 static void stats_buffer_get_and_clear_max_used(struct modem_stats_buffer *buffer,
75 uint32_t *max_used)
76 {
77 K_SPINLOCK(&stats_buffer_lock) {
78 *max_used = buffer->max_used;
79 buffer->max_used = 0;
80 }
81 }
82
stats_buffer_length_is_valid(const struct modem_stats_buffer * buffer,uint32_t length)83 static bool stats_buffer_length_is_valid(const struct modem_stats_buffer *buffer, uint32_t length)
84 {
85 return length <= buffer->size;
86 }
87
stats_buffer_log_invalid_length(const struct modem_stats_buffer * buffer,uint32_t length)88 static void stats_buffer_log_invalid_length(const struct modem_stats_buffer *buffer,
89 uint32_t length)
90 {
91 LOG_ERR("%s: length (%u) exceeds size (%u)", buffer->name, length, buffer->size);
92 }
93
stats_buffer_update_max_used(struct modem_stats_buffer * buffer,uint32_t length)94 static void stats_buffer_update_max_used(struct modem_stats_buffer *buffer, uint32_t length)
95 {
96 K_SPINLOCK(&stats_buffer_lock) {
97 if (buffer->max_used < length) {
98 buffer->max_used = length;
99 }
100 }
101 }
102
stats_buffer_print_to_shell(const struct shell * sh,const struct modem_stats_buffer * buffer,uint32_t max_used)103 static void stats_buffer_print_to_shell(const struct shell *sh,
104 const struct modem_stats_buffer *buffer,
105 uint32_t max_used)
106 {
107 shell_print(sh, "%s: used at most: %u of %u (%u%%)", buffer->name, max_used,
108 buffer->size, percent_used(max_used, buffer->size));
109 }
110
stats_buffer_shell_cmd_handler(const struct shell * sh,size_t argc,char ** argv)111 static int stats_buffer_shell_cmd_handler(const struct shell *sh, size_t argc, char **argv)
112 {
113 struct modem_stats_buffer *buffer;
114 uint32_t max_used;
115
116 ARG_UNUSED(argc);
117 ARG_UNUSED(argv);
118
119 buffer = stats_buffer_list_first();
120
121 if (buffer == NULL) {
122 shell_print(sh, "no buffers exist");
123 return 0;
124 }
125
126 while (buffer != NULL) {
127 stats_buffer_get_and_clear_max_used(buffer, &max_used);
128 stats_buffer_print_to_shell(sh, buffer, max_used);
129 buffer = stats_buffer_list_next(buffer);
130 }
131
132 return 0;
133 }
134
135 SHELL_STATIC_SUBCMD_SET_CREATE(
136 sub_stats_cmds,
137 SHELL_CMD(buffer, NULL, "Get buffer statistics", stats_buffer_shell_cmd_handler),
138 SHELL_SUBCMD_SET_END
139 );
140
141 SHELL_CMD_REGISTER(modem_stats, &sub_stats_cmds, "Modem statistics commands", NULL);
142
stats_buffer_set_name(struct modem_stats_buffer * buffer,const char * name)143 static void stats_buffer_set_name(struct modem_stats_buffer *buffer, const char *name)
144 {
145 buffer->name[sizeof(buffer->name) - 1] = '\0';
146 strncpy(buffer->name, name, sizeof(buffer->name) - 1);
147 }
148
modem_stats_buffer_init(struct modem_stats_buffer * buffer,const char * name,uint32_t size)149 void modem_stats_buffer_init(struct modem_stats_buffer *buffer,
150 const char *name, uint32_t size)
151 {
152 stats_buffer_set_name(buffer, name);
153 buffer->max_used = 0;
154 buffer->size = size;
155 stats_buffer_list_append(buffer);
156 }
157
modem_stats_buffer_advertise_length(struct modem_stats_buffer * buffer,uint32_t length)158 void modem_stats_buffer_advertise_length(struct modem_stats_buffer *buffer, uint32_t length)
159 {
160 if (!stats_buffer_length_is_valid(buffer, length)) {
161 stats_buffer_log_invalid_length(buffer, length);
162 return;
163 }
164
165 stats_buffer_update_max_used(buffer, length);
166 }
167