summaryrefslogtreecommitdiffstats
path: root/src/stats.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/stats.c')
-rw-r--r--src/stats.c696
1 files changed, 696 insertions, 0 deletions
diff --git a/src/stats.c b/src/stats.c
new file mode 100644
index 00000000..f979bdc8
--- /dev/null
+++ b/src/stats.c
@@ -0,0 +1,696 @@
+/*
+ * (C) 2015 by Sysmocom s.f.m.c. GmbH
+ *
+ * Author: Jacob Erlbeck <jerlbeck@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/core/stats.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/statistics.h>
+#include <osmocom/core/msgb.h>
+
+#define STATS_DEFAULT_INTERVAL 5 /* secs */
+#define STATS_DEFAULT_STATSD_BUFLEN 256
+
+static LLIST_HEAD(osmo_stats_reporter_list);
+static void *osmo_stats_ctx = NULL;
+static int is_initialised = 0;
+static int32_t current_stat_item_index = 0;
+
+static struct osmo_stats_config s_stats_config = {
+ .interval = STATS_DEFAULT_INTERVAL,
+};
+struct osmo_stats_config *osmo_stats_config = &s_stats_config;
+
+static struct osmo_timer_list osmo_stats_timer;
+
+static int osmo_stats_reporter_statsd_open(struct osmo_stats_reporter *srep);
+static int osmo_stats_reporter_statsd_close(struct osmo_stats_reporter *srep);
+static int osmo_stats_reporter_statsd_send_counter(struct osmo_stats_reporter *srep,
+ const struct rate_ctr_group *ctrg,
+ const struct rate_ctr_desc *desc,
+ int64_t value, int64_t delta);
+static int osmo_stats_reporter_statsd_send_item(struct osmo_stats_reporter *srep,
+ const struct osmo_stat_item_group *statg,
+ const struct osmo_stat_item_desc *desc, int value);
+
+static int osmo_stats_reporter_log_send_counter(struct osmo_stats_reporter *srep,
+ const struct rate_ctr_group *ctrg,
+ const struct rate_ctr_desc *desc,
+ int64_t value, int64_t delta);
+static int osmo_stats_reporter_log_send_item(struct osmo_stats_reporter *srep,
+ const struct osmo_stat_item_group *statg,
+ const struct osmo_stat_item_desc *desc, int value);
+
+static int osmo_stats_reporter_send(struct osmo_stats_reporter *srep, const char *data,
+ int data_len);
+static int osmo_stats_reporter_send_buffer(struct osmo_stats_reporter *srep);
+
+static int update_srep_config(struct osmo_stats_reporter *srep)
+{
+ int rc = 0;
+
+ if (srep->running) {
+ if (srep->close)
+ rc = srep->close(srep);
+ srep->running = 0;
+ }
+
+ if (!srep->enabled)
+ return rc;
+
+ if (srep->open)
+ rc = srep->open(srep);
+ else
+ rc = 0;
+
+ if (rc < 0)
+ srep->enabled = 0;
+ else
+ srep->running = 1;
+
+ return rc;
+}
+
+static void osmo_stats_timer_cb(void *data)
+{
+ int interval = osmo_stats_config->interval;
+
+ if (!llist_empty(&osmo_stats_reporter_list))
+ osmo_stats_report();
+
+ osmo_timer_schedule(&osmo_stats_timer, interval, 0);
+}
+
+static int start_timer()
+{
+ if (!is_initialised)
+ return -ESRCH;
+
+ osmo_stats_timer.cb = osmo_stats_timer_cb;
+ osmo_timer_schedule(&osmo_stats_timer, 0, 1);
+
+ return 0;
+}
+
+struct osmo_stats_reporter *osmo_stats_reporter_alloc(enum osmo_stats_reporter_type type,
+ const char *name)
+{
+ struct osmo_stats_reporter *srep;
+ srep = talloc_zero(osmo_stats_ctx, struct osmo_stats_reporter);
+ OSMO_ASSERT(srep);
+ srep->type = type;
+ if (name)
+ srep->name = talloc_strdup(srep, name);
+ srep->fd = -1;
+
+ llist_add(&srep->list, &osmo_stats_reporter_list);
+
+ return srep;
+}
+
+void osmo_stats_reporter_free(struct osmo_stats_reporter *srep)
+{
+ osmo_stats_reporter_disable(srep);
+ llist_del(&srep->list);
+ talloc_free(srep);
+}
+
+void osmo_stats_init(void *ctx)
+{
+ osmo_stats_ctx = ctx;
+ osmo_stat_item_discard_all(&current_stat_item_index);
+
+ is_initialised = 1;
+ start_timer();
+}
+
+struct osmo_stats_reporter *osmo_stats_reporter_find(enum osmo_stats_reporter_type type,
+ const char *name)
+{
+ struct osmo_stats_reporter *srep;
+ llist_for_each_entry(srep, &osmo_stats_reporter_list, list) {
+ if (srep->type != type)
+ continue;
+ if (srep->name != name) {
+ if (name == NULL || srep->name == NULL ||
+ strcmp(name, srep->name) != 0)
+ continue;
+ }
+ return srep;
+ }
+ return NULL;
+}
+
+int osmo_stats_reporter_set_remote_addr(struct osmo_stats_reporter *srep, const char *addr)
+{
+ int rc;
+ struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->dest_addr;
+ struct in_addr inaddr;
+
+ if (!srep->have_net_config)
+ return -ENOTSUP;
+
+ OSMO_ASSERT(addr != NULL);
+
+ rc = inet_pton(AF_INET, addr, &inaddr);
+ if (rc <= 0)
+ return -EINVAL;
+
+ sock_addr->sin_addr = inaddr;
+ sock_addr->sin_family = AF_INET;
+ srep->dest_addr_len = sizeof(*sock_addr);
+
+ talloc_free(srep->dest_addr_str);
+ srep->dest_addr_str = talloc_strdup(srep, addr);
+
+ return update_srep_config(srep);
+}
+
+int osmo_stats_reporter_set_remote_port(struct osmo_stats_reporter *srep, int port)
+{
+ struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->dest_addr;
+
+ if (!srep->have_net_config)
+ return -ENOTSUP;
+
+ srep->dest_port = port;
+ sock_addr->sin_port = htons(port);
+
+ return update_srep_config(srep);
+}
+
+int osmo_stats_reporter_set_local_addr(struct osmo_stats_reporter *srep, const char *addr)
+{
+ int rc;
+ struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->bind_addr;
+ struct in_addr inaddr;
+
+ if (!srep->have_net_config)
+ return -ENOTSUP;
+
+ if (addr) {
+ rc = inet_pton(AF_INET, addr, &inaddr);
+ if (rc <= 0)
+ return -EINVAL;
+ } else {
+ inaddr.s_addr = INADDR_ANY;
+ }
+
+ sock_addr->sin_addr = inaddr;
+ sock_addr->sin_family = AF_INET;
+ srep->bind_addr_len = addr ? sizeof(*sock_addr) : 0;
+
+ talloc_free(srep->bind_addr_str);
+ srep->bind_addr_str = addr ? talloc_strdup(srep, addr) : NULL;
+
+ return update_srep_config(srep);
+}
+
+int osmo_stats_reporter_set_mtu(struct osmo_stats_reporter *srep, int mtu)
+{
+ if (!srep->have_net_config)
+ return -ENOTSUP;
+
+ if (mtu < 0)
+ return -EINVAL;
+
+ srep->mtu = mtu;
+
+ return update_srep_config(srep);
+}
+
+int osmo_stats_reporter_set_max_class(struct osmo_stats_reporter *srep,
+ enum osmo_stats_class class_id)
+{
+ if (class_id == OSMO_STATS_CLASS_UNKNOWN)
+ return -EINVAL;
+
+ srep->max_class = class_id;
+
+ return 0;
+}
+
+int osmo_stats_set_interval(int interval)
+{
+ if (interval <= 0)
+ return -EINVAL;
+
+ osmo_stats_config->interval = interval;
+ if (is_initialised)
+ start_timer();
+
+ return 0;
+}
+
+int osmo_stats_reporter_set_name_prefix(struct osmo_stats_reporter *srep, const char *prefix)
+{
+ talloc_free(srep->name_prefix);
+ srep->name_prefix = prefix ? talloc_strdup(srep, prefix) : NULL;
+
+ return update_srep_config(srep);
+}
+
+int osmo_stats_reporter_enable(struct osmo_stats_reporter *srep)
+{
+ srep->enabled = 1;
+
+ return update_srep_config(srep);
+}
+
+int osmo_stats_reporter_disable(struct osmo_stats_reporter *srep)
+{
+ srep->enabled = 0;
+
+ return update_srep_config(srep);
+}
+
+static int osmo_stats_reporter_send(struct osmo_stats_reporter *srep, const char *data,
+ int data_len)
+{
+ int rc;
+
+ rc = sendto(srep->fd, data, data_len, MSG_NOSIGNAL | MSG_DONTWAIT,
+ &srep->dest_addr, srep->dest_addr_len);
+
+ if (rc == -1)
+ rc = -errno;
+
+ return rc;
+}
+
+static int osmo_stats_reporter_send_buffer(struct osmo_stats_reporter *srep)
+{
+ int rc;
+
+ if (!srep->buffer || msgb_length(srep->buffer) == 0)
+ return 0;
+
+ rc = osmo_stats_reporter_send(srep,
+ (const char *)msgb_data(srep->buffer), msgb_length(srep->buffer));
+
+ msgb_trim(srep->buffer, 0);
+
+ return rc;
+}
+
+static int osmo_stats_reporter_check_config(struct osmo_stats_reporter *srep,
+ unsigned int index, int class_id)
+{
+ if (class_id == OSMO_STATS_CLASS_UNKNOWN)
+ class_id = index != 0 ?
+ OSMO_STATS_CLASS_SUBSCRIBER : OSMO_STATS_CLASS_GLOBAL;
+
+ return class_id <= srep->max_class;
+}
+
+/*** log reporter ***/
+
+struct osmo_stats_reporter *osmo_stats_reporter_create_log(const char *name)
+{
+ struct osmo_stats_reporter *srep;
+ srep = osmo_stats_reporter_alloc(OSMO_STATS_REPORTER_LOG, name);
+
+ srep->have_net_config = 0;
+
+ srep->send_counter = osmo_stats_reporter_log_send_counter;
+ srep->send_item = osmo_stats_reporter_log_send_item;
+
+ return srep;
+}
+
+static int osmo_stats_reporter_log_send(struct osmo_stats_reporter *srep,
+ const char *type,
+ const char *name1, unsigned int index1, const char *name2, int value,
+ const char *unit)
+{
+ LOGP(DLSTATS, LOGL_INFO,
+ "stats t=%s p=%s g=%s i=%u n=%s v=%d u=%s\n",
+ type, srep->name_prefix ? srep->name_prefix : "",
+ name1 ? name1 : "", index1,
+ name2, value, unit ? unit : "");
+
+ return 0;
+}
+
+
+static int osmo_stats_reporter_log_send_counter(struct osmo_stats_reporter *srep,
+ const struct rate_ctr_group *ctrg,
+ const struct rate_ctr_desc *desc,
+ int64_t value, int64_t delta)
+{
+ if (ctrg)
+ return osmo_stats_reporter_log_send(srep, "c",
+ ctrg->desc->group_name_prefix,
+ ctrg->idx,
+ desc->name, value, NULL);
+ else
+ return osmo_stats_reporter_log_send(srep, "c",
+ NULL, 0,
+ desc->name, value, NULL);
+}
+
+static int osmo_stats_reporter_log_send_item(struct osmo_stats_reporter *srep,
+ const struct osmo_stat_item_group *statg,
+ const struct osmo_stat_item_desc *desc, int value)
+{
+ return osmo_stats_reporter_log_send(srep, "i",
+ statg->desc->group_name_prefix, statg->idx,
+ desc->name, value, desc->unit);
+}
+
+/*** statsd reporter ***/
+
+struct osmo_stats_reporter *osmo_stats_reporter_create_statsd(const char *name)
+{
+ struct osmo_stats_reporter *srep;
+ srep = osmo_stats_reporter_alloc(OSMO_STATS_REPORTER_STATSD, name);
+
+ srep->have_net_config = 1;
+
+ srep->open = osmo_stats_reporter_statsd_open;
+ srep->close = osmo_stats_reporter_statsd_close;
+ srep->send_counter = osmo_stats_reporter_statsd_send_counter;
+ srep->send_item = osmo_stats_reporter_statsd_send_item;
+
+ return srep;
+}
+
+static int osmo_stats_reporter_statsd_open(struct osmo_stats_reporter *srep)
+{
+ int sock;
+ int rc;
+ int buffer_size = STATS_DEFAULT_STATSD_BUFLEN;
+
+ if (srep->fd != -1)
+ osmo_stats_reporter_statsd_close(srep);
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock == -1)
+ return -errno;
+
+ if (srep->bind_addr_len > 0) {
+ rc = bind(sock, &srep->bind_addr, srep->bind_addr_len);
+ if (rc == -1)
+ goto failed;
+ }
+
+ srep->fd = sock;
+
+ if (srep->mtu > 0) {
+ buffer_size = srep->mtu - 20 /* IP */ - 8 /* UDP */;
+ srep->agg_enabled = 1;
+ }
+
+ srep->buffer = msgb_alloc(buffer_size, "stats buffer");
+
+ return 0;
+
+failed:
+ rc = -errno;
+ close(sock);
+
+ return rc;
+}
+
+static int osmo_stats_reporter_statsd_close(struct osmo_stats_reporter *srep)
+{
+ int rc;
+ if (srep->fd == -1)
+ return -EBADF;
+
+ osmo_stats_reporter_send_buffer(srep);
+
+ rc = close(srep->fd);
+ srep->fd = -1;
+ msgb_free(srep->buffer);
+ srep->buffer = NULL;
+ return rc == -1 ? -errno : 0;
+}
+
+static int osmo_stats_reporter_statsd_send(struct osmo_stats_reporter *srep,
+ const char *name1, unsigned int index1, const char *name2, int value,
+ const char *unit)
+{
+ char *buf;
+ int buf_size;
+ int nchars, rc = 0;
+ char *fmt = NULL;
+ int old_len = msgb_length(srep->buffer);
+
+ if (name1) {
+ if (index1 != 0)
+ fmt = "%1$s.%2$s.%6$u.%3$s:%4$d|%5$s";
+ else
+ fmt = "%1$s.%2$s.%3$s:%4$d|%5$s";
+ } else {
+ fmt = "%1$s.%2$0.0s%3$s:%4$d|%5$s";
+ }
+ if (!srep->name_prefix)
+ fmt += 5; /* skip prefix part */
+
+ if (srep->agg_enabled) {
+ if (msgb_length(srep->buffer) > 0 &&
+ msgb_tailroom(srep->buffer) > 0)
+ {
+ msgb_put_u8(srep->buffer, '\n');
+ }
+ }
+
+ buf = (char *)msgb_put(srep->buffer, 0);
+ buf_size = msgb_tailroom(srep->buffer);
+
+ nchars = snprintf(buf, buf_size, fmt,
+ srep->name_prefix, name1, name2,
+ value, unit, index1);
+
+ if (nchars >= buf_size) {
+ /* Truncated */
+ /* Restore original buffer (without trailing LF) */
+ msgb_trim(srep->buffer, old_len);
+ /* Send it */
+ rc = osmo_stats_reporter_send_buffer(srep);
+
+ /* Try again */
+ buf = (char *)msgb_put(srep->buffer, 0);
+ buf_size = msgb_tailroom(srep->buffer);
+
+ nchars = snprintf(buf, buf_size, fmt,
+ srep->name_prefix, name1, name2,
+ value, unit, index1);
+
+ if (nchars >= buf_size)
+ return -EMSGSIZE;
+ }
+
+ if (nchars > 0)
+ msgb_trim(srep->buffer, msgb_length(srep->buffer) + nchars);
+
+ if (!srep->agg_enabled)
+ rc = osmo_stats_reporter_send_buffer(srep);
+
+ return rc;
+}
+
+static int osmo_stats_reporter_statsd_send_counter(struct osmo_stats_reporter *srep,
+ const struct rate_ctr_group *ctrg,
+ const struct rate_ctr_desc *desc,
+ int64_t value, int64_t delta)
+{
+ if (ctrg)
+ return osmo_stats_reporter_statsd_send(srep,
+ ctrg->desc->group_name_prefix,
+ ctrg->idx,
+ desc->name, delta, "c");
+ else
+ return osmo_stats_reporter_statsd_send(srep,
+ NULL, 0,
+ desc->name, delta, "c");
+}
+
+static int osmo_stats_reporter_statsd_send_item(struct osmo_stats_reporter *srep,
+ const struct osmo_stat_item_group *statg,
+ const struct osmo_stat_item_desc *desc, int value)
+{
+ return osmo_stats_reporter_statsd_send(srep,
+ statg->desc->group_name_prefix,
+ statg->idx,
+ desc->name, value, desc->unit);
+}
+
+/*** generic rate counter support ***/
+
+static int osmo_stats_reporter_send_counter(struct osmo_stats_reporter *srep,
+ const struct rate_ctr_group *ctrg,
+ const struct rate_ctr_desc *desc,
+ int64_t value, int64_t delta)
+{
+ if (!srep->send_counter)
+ return 0;
+
+ return srep->send_counter(srep, ctrg, desc, value, delta);
+}
+
+static int rate_ctr_handler(
+ struct rate_ctr_group *ctrg, struct rate_ctr *ctr,
+ const struct rate_ctr_desc *desc, void *sctx_)
+{
+ struct osmo_stats_reporter *srep;
+ int64_t delta = rate_ctr_difference(ctr);
+
+ if (delta == 0)
+ return 0;
+
+ llist_for_each_entry(srep, &osmo_stats_reporter_list, list) {
+ if (!srep->running)
+ continue;
+
+ if (!osmo_stats_reporter_check_config(srep,
+ ctrg->idx, ctrg->desc->class_id))
+ return 0;
+
+ osmo_stats_reporter_send_counter(srep, ctrg, desc,
+ ctr->current, delta);
+
+ /* TODO: handle result (log?, inc counter(!)?) or remove it */
+ }
+
+ return 0;
+}
+
+static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *sctx_)
+{
+ rate_ctr_for_each_counter(ctrg, rate_ctr_handler, sctx_);
+
+ return 0;
+}
+
+/*** stat item support ***/
+
+static int osmo_stats_reporter_send_item(struct osmo_stats_reporter *srep,
+ const struct osmo_stat_item_group *statg,
+ const struct osmo_stat_item_desc *desc,
+ int32_t value)
+{
+ if (!srep->send_item)
+ return 0;
+
+ return srep->send_item(srep, statg, desc, value);
+}
+
+static int osmo_stat_item_handler(
+ struct osmo_stat_item_group *statg, struct osmo_stat_item *item, void *sctx_)
+{
+ struct osmo_stats_reporter *srep;
+ int32_t idx = current_stat_item_index;
+ int32_t value;
+
+ while (osmo_stat_item_get_next(item, &idx, &value) > 0) {
+ llist_for_each_entry(srep, &osmo_stats_reporter_list, list) {
+ if (!srep->running)
+ continue;
+
+ if (!osmo_stats_reporter_check_config(srep,
+ statg->idx, statg->desc->class_id))
+ return 0;
+
+ osmo_stats_reporter_send_item(srep, statg,
+ item->desc, value);
+ }
+ }
+
+ return 0;
+}
+
+static int osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void *sctx_)
+{
+ osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, sctx_);
+ osmo_stat_item_discard_all(&current_stat_item_index);
+
+ return 0;
+}
+
+/*** osmo counter support ***/
+
+static int handle_counter(struct osmo_counter *counter, void *sctx_)
+{
+ struct osmo_stats_reporter *srep;
+ struct rate_ctr_desc desc = {0};
+ /* Fake a rate counter description */
+ desc.name = counter->name;
+ desc.description = counter->description;
+
+ int delta = osmo_counter_difference(counter);
+
+ if (delta == 0)
+ return 0;
+
+ llist_for_each_entry(srep, &osmo_stats_reporter_list, list) {
+ if (!srep->running)
+ continue;
+
+ osmo_stats_reporter_send_counter(srep, NULL, &desc,
+ counter->value, delta);
+
+ /* TODO: handle result (log?, inc counter(!)?) */
+ }
+
+ return 0;
+}
+
+
+/*** main reporting function ***/
+
+static void flush_all_reporters()
+{
+ struct osmo_stats_reporter *srep;
+
+ llist_for_each_entry(srep, &osmo_stats_reporter_list, list) {
+ if (!srep->running)
+ continue;
+
+ osmo_stats_reporter_send_buffer(srep);
+ }
+}
+
+int osmo_stats_report()
+{
+ osmo_counters_for_each(handle_counter, NULL);
+ rate_ctr_for_each_group(rate_ctr_group_handler, NULL);
+ osmo_stat_item_for_each_group(osmo_stat_item_group_handler, NULL);
+
+ flush_all_reporters();
+
+ return 0;
+}