blob: ebb380e8791f6c6eaefb8ad0103380b875ad206f [file] [log] [blame]
Philipp Maierb1ef8f52021-12-06 16:31:02 +01001/*
2 * (C) 2021 by sysmocom - s.f.m.c. GmbH
3 * Author: Philipp Maier <pmaier@sysmocom.de>
4 * All Rights Reserved
5 *
6 * SPDX-License-Identifier: GPL-2.0+
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 */
19
20/*! \addtogroup stats
21 * @{
22 * \file stats_tcp.c */
23
24#include "config.h"
25#if !defined(EMBEDDED)
26
27#include <sys/types.h>
28#include <sys/stat.h>
29#include <sys/socket.h>
30#include <netinet/in.h>
31#include <netinet/ip.h>
32#include <linux/tcp.h>
33#include <errno.h>
34#include <pthread.h>
35
36#include <osmocom/core/select.h>
37#include <osmocom/core/linuxlist.h>
38#include <osmocom/core/talloc.h>
39#include <osmocom/core/utils.h>
40#include <osmocom/core/timer.h>
41#include <osmocom/core/stat_item.h>
42#include <osmocom/core/stats.h>
43#include <osmocom/core/socket.h>
44#include <osmocom/core/stats_tcp.h>
45
46static struct osmo_tcp_stats_config s_tcp_stats_config = {
47 .interval = TCP_STATS_DEFAULT_INTERVAL,
48};
49
50struct osmo_tcp_stats_config *osmo_tcp_stats_config = &s_tcp_stats_config;
51
52static struct osmo_timer_list stats_tcp_poll_timer;
53
54static LLIST_HEAD(stats_tcp);
55static struct stats_tcp_entry *stats_tcp_entry_cur;
56pthread_mutex_t stats_tcp_lock;
57
58struct stats_tcp_entry {
59 struct llist_head entry;
60 const struct osmo_fd *fd;
61 struct osmo_stat_item_group *stats_tcp;
62 const char *name;
63};
64
65enum {
66 STATS_TCP_UNACKED,
67 STATS_TCP_LOST,
68 STATS_TCP_RETRANS,
69 STATS_TCP_RTT,
70 STATS_TCP_RCV_RTT,
71 STATS_TCP_NOTSENT_BYTES,
72 STATS_TCP_RWND_LIMITED,
73 STATS_TCP_SNDBUF_LIMITED,
74 STATS_TCP_REORD_SEEN,
75};
76
77static struct osmo_stat_item_desc stats_tcp_item_desc[] = {
78 [STATS_TCP_UNACKED] = { "tcp:unacked", "unacknowledged packets", "", 60, 0 },
79 [STATS_TCP_LOST] = { "tcp:lost", "lost packets", "", 60, 0 },
80 [STATS_TCP_RETRANS] = { "tcp:retrans", "retransmitted packets", "", 60, 0 },
81 [STATS_TCP_RTT] = { "tcp:rtt", "roundtrip-time", "", 60, 0 },
82 [STATS_TCP_RCV_RTT] = { "tcp:rcv_rtt", "roundtrip-time (receive)", "", 60, 0 },
83 [STATS_TCP_NOTSENT_BYTES] = { "tcp:notsent_bytes", "bytes not yet sent", "", 60, 0 },
84 [STATS_TCP_RWND_LIMITED] = { "tcp:rwnd_limited", "time (usec) limited by receive window", "", 60, 0 },
85 [STATS_TCP_SNDBUF_LIMITED] = { "tcp:sndbuf_limited", "Time (usec) limited by send buffer", "", 60, 0 },
Philipp Maier42dcbf02022-01-07 12:00:57 +010086 [STATS_TCP_REORD_SEEN] = { "tcp:reord_seen", "reordering events seen", "", 60, 0 },
Philipp Maierb1ef8f52021-12-06 16:31:02 +010087};
88
89static struct osmo_stat_item_group_desc stats_tcp_desc = {
90 .group_name_prefix = "tcp",
91 .group_description = "stats tcp",
92 .class_id = OSMO_STATS_CLASS_GLOBAL,
93 .num_items = ARRAY_SIZE(stats_tcp_item_desc),
94 .item_desc = stats_tcp_item_desc,
95};
96
97static void fill_stats(struct stats_tcp_entry *stats_tcp_entry)
98{
99 int rc;
100 struct tcp_info tcp_info;
101 socklen_t tcp_info_len = sizeof(tcp_info);
102 char stat_name[256];
103
104 /* Do not fill in anything before the socket is connected to a remote end */
105 if (osmo_sock_get_ip_and_port(stats_tcp_entry->fd->fd, NULL, 0, NULL, 0, false) != 0)
106 return;
107
108 /* Gather TCP statistics and update the stats items */
109 rc = getsockopt(stats_tcp_entry->fd->fd, IPPROTO_TCP, TCP_INFO, &tcp_info, &tcp_info_len);
110 if (rc < 0)
111 return;
112
113 /* Create stats items if they do not exist yet */
114 if (!stats_tcp_entry->stats_tcp) {
115 stats_tcp_entry->stats_tcp =
116 osmo_stat_item_group_alloc(stats_tcp_entry, &stats_tcp_desc, stats_tcp_entry->fd->fd);
117 OSMO_ASSERT(stats_tcp_entry->stats_tcp);
118 }
119
120 /* Update statistics */
121 if (stats_tcp_entry->name)
Michael Iedemaa6d1ef02022-02-10 09:30:26 -0800122 snprintf(stat_name, sizeof(stat_name), "%s", stats_tcp_entry->name);
Philipp Maierb1ef8f52021-12-06 16:31:02 +0100123 else
124 snprintf(stat_name, sizeof(stat_name), "%s", osmo_sock_get_name2(stats_tcp_entry->fd->fd));
125 osmo_stat_item_group_set_name(stats_tcp_entry->stats_tcp, stat_name);
126
127 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_UNACKED),
128 tcp_info.tcpi_unacked);
129 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_LOST),
130 tcp_info.tcpi_lost);
131 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RETRANS),
132 tcp_info.tcpi_retrans);
133 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RTT), tcp_info.tcpi_rtt);
134 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RCV_RTT),
135 tcp_info.tcpi_rcv_rtt);
Harald Weltec809f4e2021-12-24 11:31:12 +0100136#if HAVE_TCP_INFO_TCPI_NOTSENT_BYTES == 1
Philipp Maierb1ef8f52021-12-06 16:31:02 +0100137 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_NOTSENT_BYTES),
138 tcp_info.tcpi_notsent_bytes);
Harald Weltec809f4e2021-12-24 11:31:12 +0100139#else
140 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_NOTSENT_BYTES), -1);
141#endif
Philipp Maierb1ef8f52021-12-06 16:31:02 +0100142
143#if HAVE_TCP_INFO_TCPI_RWND_LIMITED == 1
144 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RWND_LIMITED),
145 tcp_info.tcpi_rwnd_limited);
146#else
147 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RWND_LIMITED), -1);
148#endif
149
150#if STATS_TCP_SNDBUF_LIMITED == 1
151 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN),
152 tcp_info.tcpi_sndbuf_limited);
153#else
154 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN), -1);
155#endif
156
157#if HAVE_TCP_INFO_TCPI_REORD_SEEN == 1
158 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN),
159 tcp_info.tcpi_reord_seen);
160#else
161 osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN), -1);
162#endif
163
164}
165
166static bool is_tcp(const struct osmo_fd *fd)
167{
168 int rc;
169 struct stat fd_stat;
170 int so_protocol = 0;
171 socklen_t so_protocol_len = sizeof(so_protocol);
172
173 /* Is this a socket? */
174 rc = fstat(fd->fd, &fd_stat);
175 if (rc < 0)
176 return false;
177 if (!S_ISSOCK(fd_stat.st_mode))
178 return false;
179
180 /* Is it a TCP socket? */
181 rc = getsockopt(fd->fd, SOL_SOCKET, SO_PROTOCOL, &so_protocol, &so_protocol_len);
182 if (rc < 0)
183 return false;
184 if (so_protocol == IPPROTO_TCP)
185 return true;
186
187 return false;
188}
189
190/*! Register an osmo_fd for TCP stats monitoring.
191 * \param[in] fd osmocom file descriptor to be registered.
192 * \param[in] human readbla name that is used as prefix for the related stats item.
193 * \returns 0 on success; negative in case of error. */
194int osmo_stats_tcp_osmo_fd_register(const struct osmo_fd *fd, const char *name)
195{
196 struct stats_tcp_entry *stats_tcp_entry;
197
198 /* Only TCP sockets can be registered for monitoring, anything else will fall through. */
199 if (!is_tcp(fd))
200 return -EINVAL;
201
202 /* When the osmo_fd is registered and unregistered properly there shouldn't be any leftovers from already closed
203 * osmo_fds in the stats_tcp list. But lets proactively make sure that any leftovers are cleaned up. */
204 osmo_stats_tcp_osmo_fd_unregister(fd);
205
206 /* Make a new list object, attach the osmo_fd... */
207 stats_tcp_entry = talloc_zero(OTC_GLOBAL, struct stats_tcp_entry);
208 OSMO_ASSERT(stats_tcp_entry);
209 stats_tcp_entry->fd = fd;
210 stats_tcp_entry->name = talloc_strdup(stats_tcp_entry, name);
211
212 pthread_mutex_lock(&stats_tcp_lock);
213 llist_add_tail(&stats_tcp_entry->entry, &stats_tcp);
214 pthread_mutex_unlock(&stats_tcp_lock);
215
216 return 0;
217}
218
219static void next_stats_tcp_entry(void)
220{
221 struct stats_tcp_entry *last;
222
223 if (llist_empty(&stats_tcp)) {
224 stats_tcp_entry_cur = NULL;
225 return;
226 }
227
228 last = (struct stats_tcp_entry *)llist_last_entry(&stats_tcp, struct stats_tcp_entry, entry);
229
230 if (!stats_tcp_entry_cur || stats_tcp_entry_cur == last)
231 stats_tcp_entry_cur =
232 (struct stats_tcp_entry *)llist_first_entry(&stats_tcp, struct stats_tcp_entry, entry);
233 else
234 stats_tcp_entry_cur =
235 (struct stats_tcp_entry *)llist_entry(stats_tcp_entry_cur->entry.next, struct stats_tcp_entry,
236 entry);
237}
238
239/*! Register an osmo_fd for TCP stats monitoring.
240 * \param[in] fd osmocom file descriptor to be unregistered.
241 * \returns 0 on success; negative in case of error. */
242int osmo_stats_tcp_osmo_fd_unregister(const struct osmo_fd *fd)
243{
244 struct stats_tcp_entry *stats_tcp_entry;
245 int rc = -EINVAL;
246
247 pthread_mutex_lock(&stats_tcp_lock);
248 llist_for_each_entry(stats_tcp_entry, &stats_tcp, entry) {
249 if (fd->fd == stats_tcp_entry->fd->fd) {
250 /* In case we want to remove exactly that item which is also selected as the current itemy, we
251 * must designate either a different item or invalidate the current item. */
252 if (stats_tcp_entry == stats_tcp_entry_cur) {
253 if (llist_count(&stats_tcp) > 2)
254 next_stats_tcp_entry();
255 else
256 stats_tcp_entry_cur = NULL;
257 }
258
259 /* Date item from list */
260 llist_del(&stats_tcp_entry->entry);
261 osmo_stat_item_group_free(stats_tcp_entry->stats_tcp);
262 talloc_free(stats_tcp_entry);
263 rc = 0;
264 break;
265 }
266 }
267 pthread_mutex_unlock(&stats_tcp_lock);
268
269 return rc;
270}
271
272static void stats_tcp_poll_timer_cb(void *data)
273{
274 int i;
275 int batch_size;
276 int llist_size;
277
278 pthread_mutex_lock(&stats_tcp_lock);
279
280 /* Make sure we do not run over the same sockets multiple times if the
281 * configured llist_size is larger then the actual list */
282 batch_size = osmo_tcp_stats_config->batch_size;
283 llist_size = llist_count(&stats_tcp);
284 if (llist_size < batch_size)
285 batch_size = llist_size;
286
287 /* Process a batch of sockets */
288 for (i = 0; i < batch_size; i++) {
289 next_stats_tcp_entry();
290 if (stats_tcp_entry_cur)
291 fill_stats(stats_tcp_entry_cur);
292 }
293
294 pthread_mutex_unlock(&stats_tcp_lock);
295
296 if (osmo_tcp_stats_config->interval > 0)
297 osmo_timer_schedule(&stats_tcp_poll_timer, osmo_tcp_stats_config->interval, 0);
298}
299
300/*! Set the polling interval (common for all sockets)
301 * \param[in] interval Poll interval in seconds
302 * \returns 0 on success; negative on error */
303int osmo_stats_tcp_set_interval(int interval)
304{
305 osmo_tcp_stats_config->interval = interval;
306 if (osmo_tcp_stats_config->interval > 0)
307 osmo_timer_schedule(&stats_tcp_poll_timer, osmo_tcp_stats_config->interval, 0);
308 return 0;
309}
310
311static __attribute__((constructor))
312void on_dso_load_stats_tcp(void)
313{
314 stats_tcp_entry_cur = NULL;
315 pthread_mutex_init(&stats_tcp_lock, NULL);
316
317 osmo_tcp_stats_config->interval = TCP_STATS_DEFAULT_INTERVAL;
318 osmo_tcp_stats_config->batch_size = TCP_STATS_DEFAULT_BATCH_SIZE;
319
320 osmo_timer_setup(&stats_tcp_poll_timer, stats_tcp_poll_timer_cb, NULL);
321}
322
323#endif /* !EMBEDDED */
324
325/* @} */