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