blob: 8faed89ab7ccc9436c38801284e696447037c0c7 [file] [log] [blame]
Jacob Erlbeck95bf82802015-10-20 19:05:52 +02001/*
2 * (C) 2015 by Sysmocom s.f.m.c. GmbH
3 *
4 * Author: Jacob Erlbeck <jerlbeck@sysmocom.de>
5 *
6 * All Rights Reserved
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 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 */
23
24#include <osmocom/core/stats.h>
25
26#include <unistd.h>
27#include <string.h>
28#include <stdint.h>
29#include <errno.h>
30#include <stdio.h>
31#include <sys/socket.h>
32#include <netinet/ip.h>
33#include <arpa/inet.h>
34
35#include <osmocom/core/utils.h>
36#include <osmocom/core/logging.h>
37#include <osmocom/core/rate_ctr.h>
38#include <osmocom/core/stat_item.h>
Jacob Erlbeckb1dbfb42015-10-26 11:58:38 +010039#include <osmocom/core/timer.h>
Jacob Erlbeckc8f47b62015-10-26 14:42:05 +010040#include <osmocom/core/statistics.h>
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +010041#include <osmocom/core/msgb.h>
Jacob Erlbeck95bf82802015-10-20 19:05:52 +020042
43/* TODO: register properly */
44#define DSTATS DLGLOBAL
45
Jacob Erlbeckb1dbfb42015-10-26 11:58:38 +010046#define STATS_DEFAULT_INTERVAL 5 /* secs */
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +010047#define STATS_DEFAULT_STATSD_BUFLEN 256
Jacob Erlbeck95bf82802015-10-20 19:05:52 +020048
49static LLIST_HEAD(stats_reporter_list);
50static void *stats_ctx = NULL;
Jacob Erlbeckb1dbfb42015-10-26 11:58:38 +010051static int is_initialised = 0;
52static int32_t current_stat_item_index = 0;
53
54static struct stats_config s_stats_config = {
55 .interval = STATS_DEFAULT_INTERVAL,
56};
57struct stats_config *stats_config = &s_stats_config;
58
59static struct osmo_timer_list stats_timer;
Jacob Erlbeck95bf82802015-10-20 19:05:52 +020060
61static int stats_reporter_statsd_open(struct stats_reporter *srep);
62static int stats_reporter_statsd_close(struct stats_reporter *srep);
63static int stats_reporter_send(struct stats_reporter *srep, const char *data,
64 int data_len);
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +010065static int stats_reporter_send_buffer(struct stats_reporter *srep);
Jacob Erlbeck95bf82802015-10-20 19:05:52 +020066
67static int update_srep_config(struct stats_reporter *srep)
68{
69 int rc = 0;
70
71 if (srep->type != STATS_REPORTER_STATSD) {
72 srep->enabled = 0;
73 return -ENOTSUP;
74 }
75
76 if (srep->running) {
77 rc = stats_reporter_statsd_close(srep);
78 srep->running = 0;
79 }
80
81 if (!srep->enabled)
82 return rc;
83
84 rc = stats_reporter_statsd_open(srep);
85 if (rc < 0)
86 srep->enabled = 0;
87 else
88 srep->running = 1;
89
90 return rc;
91}
92
Jacob Erlbeckb1dbfb42015-10-26 11:58:38 +010093static void stats_timer_cb(void *data)
94{
95 int interval = stats_config->interval;
96
97 if (!llist_empty(&stats_reporter_list))
98 stats_report();
99
100 osmo_timer_schedule(&stats_timer, interval, 0);
101}
102
103static int start_timer()
104{
105 if (!is_initialised)
106 return -ESRCH;
107
108 stats_timer.cb = stats_timer_cb;
109 osmo_timer_schedule(&stats_timer, 0, 1);
110
111 return 0;
112}
113
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200114struct stats_reporter *stats_reporter_alloc(enum stats_reporter_type type,
115 const char *name)
116{
117 struct stats_reporter *srep;
118 srep = talloc_zero(stats_ctx, struct stats_reporter);
119 OSMO_ASSERT(srep);
120 srep->type = type;
121 if (name)
122 srep->name = talloc_strdup(srep, name);
123 srep->fd = -1;
124
125 llist_add(&srep->list, &stats_reporter_list);
126
127 return srep;
128}
129
130void stats_reporter_free(struct stats_reporter *srep)
131{
132 stats_reporter_disable(srep);
133 llist_del(&srep->list);
134 talloc_free(srep);
135}
136
137void stats_init(void *ctx)
138{
139 stats_ctx = ctx;
Jacob Erlbeckc27671c2015-10-26 12:32:07 +0100140 stat_item_discard_all(&current_stat_item_index);
Jacob Erlbeckb1dbfb42015-10-26 11:58:38 +0100141
142 is_initialised = 1;
143 start_timer();
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200144}
145
146struct stats_reporter *stats_reporter_find(enum stats_reporter_type type,
147 const char *name)
148{
149 struct stats_reporter *srep;
150 llist_for_each_entry(srep, &stats_reporter_list, list) {
151 if (srep->type != type)
152 continue;
153 if (srep->name != name) {
154 if (name == NULL || srep->name == NULL ||
155 strcmp(name, srep->name) != 0)
156 continue;
157 }
158 return srep;
159 }
160 return NULL;
161}
162
163int stats_reporter_set_remote_addr(struct stats_reporter *srep, const char *addr)
164{
165 int rc;
166 struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->dest_addr;
167 struct in_addr inaddr;
168
169 OSMO_ASSERT(addr != NULL);
170
171 rc = inet_pton(AF_INET, addr, &inaddr);
172 if (rc <= 0)
173 return -EINVAL;
174
175 sock_addr->sin_addr = inaddr;
176 sock_addr->sin_family = AF_INET;
177 srep->dest_addr_len = sizeof(*sock_addr);
178
179 talloc_free(srep->dest_addr_str);
180 srep->dest_addr_str = talloc_strdup(srep, addr);
181
182 return update_srep_config(srep);
183}
184
185int stats_reporter_set_remote_port(struct stats_reporter *srep, int port)
186{
187 struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->dest_addr;
188
189 srep->dest_port = port;
190 sock_addr->sin_port = htons(port);
191
192 return update_srep_config(srep);
193}
194
195int stats_reporter_set_local_addr(struct stats_reporter *srep, const char *addr)
196{
197 int rc;
198 struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->bind_addr;
199 struct in_addr inaddr;
200
201 if (addr) {
202 rc = inet_pton(AF_INET, addr, &inaddr);
203 if (rc <= 0)
204 return -EINVAL;
205 } else {
206 addr = INADDR_ANY;
207 }
208
209 sock_addr->sin_addr = inaddr;
210 sock_addr->sin_family = AF_INET;
211 srep->bind_addr_len = addr ? sizeof(*sock_addr) : 0;
212
213 talloc_free(srep->bind_addr_str);
214 srep->bind_addr_str = addr ? talloc_strdup(srep, addr) : NULL;
215
216 return update_srep_config(srep);
217}
218
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100219int stats_reporter_set_mtu(struct stats_reporter *srep, int mtu)
220{
221 if (mtu < 0)
222 return -EINVAL;
223
224 srep->mtu = mtu;
225
226 return update_srep_config(srep);
227}
228
Jacob Erlbeckb1dbfb42015-10-26 11:58:38 +0100229int stats_set_interval(int interval)
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200230{
Jacob Erlbeckb1dbfb42015-10-26 11:58:38 +0100231 if (interval <= 0)
232 return -EINVAL;
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200233
Jacob Erlbeckb1dbfb42015-10-26 11:58:38 +0100234 stats_config->interval = interval;
235 if (is_initialised)
236 start_timer();
237
238 return 0;
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200239}
240
241int stats_reporter_set_name_prefix(struct stats_reporter *srep, const char *prefix)
242{
243 talloc_free(srep->name_prefix);
244 srep->name_prefix = prefix ? talloc_strdup(srep, prefix) : NULL;
245
246 return update_srep_config(srep);
247}
248
249int stats_reporter_enable(struct stats_reporter *srep)
250{
251 srep->enabled = 1;
252
253 return update_srep_config(srep);
254}
255
256int stats_reporter_disable(struct stats_reporter *srep)
257{
258 srep->enabled = 0;
259
260 return update_srep_config(srep);
261}
262
263static int stats_reporter_send(struct stats_reporter *srep, const char *data,
264 int data_len)
265{
266 int rc;
267
268 rc = sendto(srep->fd, data, data_len, MSG_NOSIGNAL | MSG_DONTWAIT,
269 &srep->dest_addr, srep->dest_addr_len);
270
271 if (rc == -1)
272 rc = -errno;
273
274 return rc;
275}
276
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100277static int stats_reporter_send_buffer(struct stats_reporter *srep)
278{
279 int rc;
280
281 if (!srep->buffer || msgb_length(srep->buffer) == 0)
282 return 0;
283
284 rc = stats_reporter_send(srep,
285 (const char *)msgb_data(srep->buffer), msgb_length(srep->buffer));
286
287 msgb_trim(srep->buffer, 0);
288
289 return rc;
290}
291
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200292/*** statsd reporter ***/
293
294struct stats_reporter *stats_reporter_create_statsd(const char *name)
295{
296 struct stats_reporter *srep;
297 srep = stats_reporter_alloc(STATS_REPORTER_STATSD, name);
298
299 return srep;
300}
301
302static int stats_reporter_statsd_open(struct stats_reporter *srep)
303{
304 int sock;
305 int rc;
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100306 int buffer_size = STATS_DEFAULT_STATSD_BUFLEN;
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200307
308 if (srep->fd != -1)
309 stats_reporter_statsd_close(srep);
310
311 sock = socket(AF_INET, SOCK_DGRAM, 0);
312 if (sock == -1)
313 return -errno;
314
315 if (srep->bind_addr_len > 0) {
316 rc = bind(sock, &srep->bind_addr, srep->bind_addr_len);
317 if (rc == -1)
318 goto failed;
319 }
320
321 srep->fd = sock;
322
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100323 if (srep->mtu > 0) {
324 buffer_size = srep->mtu - 20 /* IP */ - 8 /* UDP */;
325 srep->agg_enabled = 1;
326 }
327
328 srep->buffer = msgb_alloc(buffer_size, "stats buffer");
329
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200330 return 0;
331
332failed:
333 rc = -errno;
334 close(sock);
335
336 return rc;
337}
338
339static int stats_reporter_statsd_close(struct stats_reporter *srep)
340{
341 int rc;
342 if (srep->fd == -1)
343 return -EBADF;
344
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100345 stats_reporter_send_buffer(srep);
346
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200347 rc = close(srep->fd);
348 srep->fd = -1;
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100349 msgb_free(srep->buffer);
350 srep->buffer = NULL;
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200351 return rc == -1 ? -errno : 0;
352}
353
354static int stats_reporter_statsd_send(struct stats_reporter *srep,
355 const char *name1, int index1, const char *name2, int value,
356 const char *unit)
357{
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100358 char *buf;
359 int buf_size;
360 int nchars, rc = 0;
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200361 char *fmt = NULL;
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100362 int old_len = msgb_length(srep->buffer);
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200363
364 if (name1) {
365 if (index1 > 0)
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100366 fmt = "%1$s.%2$s.%6$d.%3$s:%4$d|%5$s";
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200367 else
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100368 fmt = "%1$s.%2$s.%3$s:%4$d|%5$s";
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200369 } else {
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100370 fmt = "%1$s.%2$0.0s%3$s:%4$d|%5$s";
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200371 }
372 if (!srep->name_prefix)
373 fmt += 5; /* skip prefix part */
374
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100375 if (srep->agg_enabled) {
376 if (msgb_length(srep->buffer) > 0 &&
377 msgb_tailroom(srep->buffer) > 0)
378 {
379 msgb_put_u8(srep->buffer, '\n');
380 }
381 }
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200382
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100383 buf = (char *)msgb_put(srep->buffer, 0);
384 buf_size = msgb_tailroom(srep->buffer);
385
386 nchars = snprintf(buf, buf_size, fmt,
387 srep->name_prefix, name1, name2,
388 value, unit, index1);
389
390 if (nchars >= buf_size) {
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200391 /* Truncated */
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100392 /* Restore original buffer (without trailing LF) */
393 msgb_trim(srep->buffer, old_len);
394 /* Send it */
395 rc = stats_reporter_send_buffer(srep);
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200396
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100397 /* Try again */
398 buf = (char *)msgb_put(srep->buffer, 0);
399 buf_size = msgb_tailroom(srep->buffer);
400
401 nchars = snprintf(buf, buf_size, fmt,
402 srep->name_prefix, name1, name2,
403 value, unit, index1);
404
405 if (nchars >= buf_size)
406 return -EMSGSIZE;
407 }
408
409 if (nchars > 0)
410 msgb_trim(srep->buffer, msgb_length(srep->buffer) + nchars);
411
412 if (!srep->agg_enabled)
413 rc = stats_reporter_send_buffer(srep);
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200414
415 return rc;
416}
417
418static int stats_reporter_statsd_send_counter(struct stats_reporter *srep,
419 const struct rate_ctr_group *ctrg,
420 const struct rate_ctr_desc *desc,
421 int64_t value, int64_t delta)
422{
423 if (ctrg)
424 return stats_reporter_statsd_send(srep,
425 ctrg->desc->group_name_prefix,
426 ctrg->idx,
427 desc->name, delta, "c");
428 else
429 return stats_reporter_statsd_send(srep,
430 NULL, -1,
431 desc->name, delta, "c");
432}
433
434static int stats_reporter_statsd_send_item(struct stats_reporter *srep,
435 const struct stat_item_group *statg,
436 const struct stat_item_desc *desc, int value)
437{
438 return stats_reporter_statsd_send(srep,
439 statg->desc->group_name_prefix, statg->idx,
440 desc->name, value, desc->unit);
441}
442
443/*** generic rate counter support ***/
444
445static int stats_reporter_send_counter(struct stats_reporter *srep,
446 const struct rate_ctr_group *ctrg,
447 const struct rate_ctr_desc *desc,
448 int64_t value, int64_t delta)
449{
450 int rc;
451
452 switch (srep->type) {
453 case STATS_REPORTER_STATSD:
454 rc = stats_reporter_statsd_send_counter(srep, ctrg, desc,
455 value, delta);
456 break;
457 }
458
459 return rc;
460}
461
462static int rate_ctr_handler(
463 struct rate_ctr_group *ctrg, struct rate_ctr *ctr,
464 const struct rate_ctr_desc *desc, void *sctx_)
465{
466 struct stats_reporter *srep;
467 int rc;
468 int64_t delta = rate_ctr_difference(ctr);
469
470 if (delta == 0)
471 return 0;
472
473 llist_for_each_entry(srep, &stats_reporter_list, list) {
474 if (!srep->running)
475 continue;
476
477 rc = stats_reporter_send_counter(srep, ctrg, desc,
478 ctr->current, delta);
479
480 /* TODO: handle rc (log?, inc counter(!)?) or remove it */
481 }
482
483 return 0;
484}
485
486static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *sctx_)
487{
488 rate_ctr_for_each_counter(ctrg, rate_ctr_handler, sctx_);
489
490 return 0;
491}
492
Jacob Erlbeckc27671c2015-10-26 12:32:07 +0100493/*** stat item support ***/
494
495static int stats_reporter_send_item(struct stats_reporter *srep,
496 const struct stat_item_group *statg,
497 const struct stat_item_desc *desc,
498 int32_t value)
499{
500 int rc;
501
502 switch (srep->type) {
503 case STATS_REPORTER_STATSD:
504 rc = stats_reporter_statsd_send_item(srep, statg, desc,
505 value);
506 break;
507 }
508
509 return rc;
510}
511
512static int stat_item_handler(
513 struct stat_item_group *statg, struct stat_item *item, void *sctx_)
514{
515 struct stats_reporter *srep;
516 int rc;
517 int32_t idx = current_stat_item_index;
518 int32_t value;
519
520 while (stat_item_get_next(item, &idx, &value) > 0) {
521 llist_for_each_entry(srep, &stats_reporter_list, list) {
522 if (!srep->running)
523 continue;
524
525 rc = stats_reporter_send_item(srep, statg,
526 item->desc, value);
527 }
528 }
529
530 return 0;
531}
532
533static int stat_item_group_handler(struct stat_item_group *statg, void *sctx_)
534{
535 stat_item_for_each_item(statg, stat_item_handler, sctx_);
536 stat_item_discard_all(&current_stat_item_index);
537
538 return 0;
539}
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200540
Jacob Erlbeckc8f47b62015-10-26 14:42:05 +0100541/*** osmo counter support ***/
542
543static int handle_counter(struct osmo_counter *counter, void *sctx_)
544{
545 struct stats_reporter *srep;
546 int rc;
547 struct rate_ctr_desc desc = {0};
548 /* Fake a rate counter description */
549 desc.name = counter->name;
550 desc.description = counter->description;
551
552 int delta = osmo_counter_difference(counter);
553
554 if (delta == 0)
555 return 0;
556
557 llist_for_each_entry(srep, &stats_reporter_list, list) {
558 if (!srep->running)
559 continue;
560
561 rc = stats_reporter_send_counter(srep, NULL, &desc,
562 counter->value, delta);
563
564 /* TODO: handle rc (log?, inc counter(!)?) */
565 }
566
567 return 0;
568}
569
570
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200571/*** main reporting function ***/
572
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100573static void flush_all_reporters()
574{
575 struct stats_reporter *srep;
576
577 llist_for_each_entry(srep, &stats_reporter_list, list) {
578 if (!srep->running)
579 continue;
580
581 stats_reporter_send_buffer(srep);
582 }
583}
584
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200585int stats_report()
586{
Jacob Erlbeckc8f47b62015-10-26 14:42:05 +0100587 osmo_counters_for_each(handle_counter, NULL);
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200588 rate_ctr_for_each_group(rate_ctr_group_handler, NULL);
Jacob Erlbeckc27671c2015-10-26 12:32:07 +0100589 stat_item_for_each_group(stat_item_group_handler, NULL);
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200590
Jacob Erlbeckd01acfc2015-10-26 16:22:45 +0100591 flush_all_reporters();
592
Jacob Erlbeck95bf82802015-10-20 19:05:52 +0200593 return 0;
594}