summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorHolger Hans Peter Freyther <holger@moiji-mobile.com>2015-11-02 15:57:34 +0100
committerHolger Hans Peter Freyther <holger@moiji-mobile.com>2015-11-02 15:57:34 +0100
commitd7b0577d7d30139491b5cfeffb467440f9e88818 (patch)
tree0666fa8f415a47e8f5645dd87ad8844251251c6a /src
parentc84851bccc2e5e60536afa474a5f13134a3b79c9 (diff)
parent8f0374f7521376bdb721e821047e8a6a4a727283 (diff)
Merge branch 'jerlbeck/wip/stats'
* This adds a new counter type (to measure time or delay) * A statsd reporting backend. This can be fed into graphite or similar tools. * A periodic log backend for performance values
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/gb/gprs_bssgp.c2
-rw-r--r--src/gb/gprs_ns.c38
-rw-r--r--src/gb/gprs_ns_vty.c4
-rw-r--r--src/logging.c5
-rw-r--r--src/rate_ctr.c41
-rw-r--r--src/stat_item.c268
-rw-r--r--src/statistics.c8
-rw-r--r--src/stats.c696
-rw-r--r--src/vty/Makefile.am2
-rw-r--r--src/vty/stats_vty.c430
-rw-r--r--src/vty/utils.c125
12 files changed, 1607 insertions, 14 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 4bf3408e..7aa6a78a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,7 +15,7 @@ libosmocore_la_SOURCES = timer.c select.c signal.c msgb.c bits.c \
gsmtap_util.c crc16.c panic.c backtrace.c \
conv.c application.c rbtree.c strrb.c \
loggingrb.c crc8gen.c crc16gen.c crc32gen.c crc64gen.c \
- macaddr.c
+ macaddr.c stat_item.c stats.c
BUILT_SOURCES = crc8gen.c crc16gen.c crc32gen.c crc64gen.c
diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c
index fe4fccae..e3e69c9c 100644
--- a/src/gb/gprs_bssgp.c
+++ b/src/gb/gprs_bssgp.c
@@ -31,6 +31,7 @@
#include <osmocom/gsm/tlv.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats.h>
#include <osmocom/gprs/gprs_bssgp.h>
#include <osmocom/gprs/gprs_ns.h>
@@ -54,6 +55,7 @@ static const struct rate_ctr_group_desc bssgp_ctrg_desc = {
.group_description = "BSSGP Peer Statistics",
.num_ctr = ARRAY_SIZE(bssgp_ctr_description),
.ctr_desc = bssgp_ctr_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
};
LLIST_HEAD(bssgp_bvc_ctxts);
diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c
index 827d09d7..2b189cd3 100644
--- a/src/gb/gprs_ns.c
+++ b/src/gb/gprs_ns.c
@@ -75,6 +75,8 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/stats.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/signal.h>
#include <osmocom/gprs/gprs_ns.h>
@@ -104,6 +106,8 @@ enum ns_ctr {
NS_CTR_NSEI_CHG,
NS_CTR_INV_VCI,
NS_CTR_INV_NSEI,
+ NS_CTR_LOST_ALIVE,
+ NS_CTR_LOST_RESET,
};
static const struct rate_ctr_desc nsvc_ctr_description[] = {
@@ -117,6 +121,8 @@ static const struct rate_ctr_desc nsvc_ctr_description[] = {
{ "nsei-chg", "NS-VC changed NSEI count " },
{ "inv-nsvci", "NS-VCI was invalid count " },
{ "inv-nsei", "NSEI was invalid count " },
+ { "lost.alive", "ALIVE ACK missing count " },
+ { "lost.reset", "RESET ACK missing count " },
};
static const struct rate_ctr_group_desc nsvc_ctrg_desc = {
@@ -126,6 +132,22 @@ static const struct rate_ctr_group_desc nsvc_ctrg_desc = {
.ctr_desc = nsvc_ctr_description,
};
+enum ns_stat {
+ NS_STAT_ALIVE_DELAY,
+};
+
+static const struct osmo_stat_item_desc nsvc_stat_description[] = {
+ { "alive.delay", "ALIVE reponse time ", "ms", 16, 0 },
+};
+
+static const struct osmo_stat_item_group_desc nsvc_statg_desc = {
+ .group_name_prefix = "ns.nsvc",
+ .group_description = "NSVC Peer Statistics",
+ .num_items = ARRAY_SIZE(nsvc_stat_description),
+ .item_desc = nsvc_stat_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
#define CHECK_TX_RC(rc, nsvc) \
if (rc < 0) \
LOGP(DNS, LOGL_ERROR, "TX failed (%d) to peer %s\n", \
@@ -218,6 +240,7 @@ struct gprs_nsvc *gprs_nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci)
nsvc->timer.cb = gprs_ns_timer_cb;
nsvc->timer.data = nsvc;
nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, nsvci);
+ nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, nsvci);
llist_add(&nsvc->list, &nsi->gprs_nsvcs);
@@ -531,10 +554,20 @@ static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode)
if (osmo_timer_pending(&nsvc->timer))
osmo_timer_del(&nsvc->timer);
+ gettimeofday(&nsvc->timer_started, NULL);
nsvc->timer_mode = mode;
osmo_timer_schedule(&nsvc->timer, seconds, 0);
}
+static int nsvc_timer_elapsed_ms(struct gprs_nsvc *nsvc)
+{
+ struct timeval now, elapsed;
+ gettimeofday(&now, NULL);
+ timersub(&now, &nsvc->timer_started, &elapsed);
+
+ return 1000 * elapsed.tv_sec + elapsed.tv_usec / 1000;
+}
+
static void gprs_ns_timer_cb(void *data)
{
struct gprs_nsvc *nsvc = data;
@@ -549,6 +582,7 @@ static void gprs_ns_timer_cb(void *data)
switch (nsvc->timer_mode) {
case NSVC_TIMER_TNS_ALIVE:
/* Tns-alive case: we expired without response ! */
+ rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_ALIVE]);
nsvc->alive_retries++;
if (nsvc->alive_retries >
nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
@@ -578,6 +612,7 @@ static void gprs_ns_timer_cb(void *data)
nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
break;
case NSVC_TIMER_TNS_RESET:
+ rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_RESET]);
/* Chapter 7.3: Re-send the RESET */
gprs_ns_tx_reset(nsvc, NS_CAUSE_OM_INTERVENTION);
/* Re-start Tns-reset timer */
@@ -1272,6 +1307,9 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg,
rc = gprs_ns_tx_alive_ack(*nsvc);
break;
case NS_PDUT_ALIVE_ACK:
+ if ((*nsvc)->timer_mode == NSVC_TIMER_TNS_ALIVE)
+ osmo_stat_item_set((*nsvc)->statg->items[NS_STAT_ALIVE_DELAY],
+ nsvc_timer_elapsed_ms(*nsvc));
/* stop Tns-alive and start Tns-test */
nsvc_start_timer(*nsvc, NSVC_TIMER_TNS_TEST);
if ((*nsvc)->remote_end_is_sgsn) {
diff --git a/src/gb/gprs_ns_vty.c b/src/gb/gprs_ns_vty.c
index 155e1e97..5a951dca 100644
--- a/src/gb/gprs_ns_vty.c
+++ b/src/gb/gprs_ns_vty.c
@@ -167,8 +167,10 @@ static void dump_nse(struct vty *vty, struct gprs_nsvc *nsvc, int stats)
inet_ntoa(nsvc->ip.bts_addr.sin_addr),
ntohs(nsvc->ip.bts_addr.sin_port));
vty_out(vty, "%s", VTY_NEWLINE);
- if (stats)
+ if (stats) {
vty_out_rate_ctr_group(vty, " ", nsvc->ctrg);
+ vty_out_stat_item_group(vty, " ", nsvc->statg);
+ }
}
static void dump_ns(struct vty *vty, struct gprs_ns_inst *nsi, int stats)
diff --git a/src/logging.c b/src/logging.c
index 20b0596b..876964ae 100644
--- a/src/logging.c
+++ b/src/logging.c
@@ -117,6 +117,11 @@ static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = {
.description = "GPRS GTP library",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
+ [INT2IDX(DLSTATS)] = {
+ .name = "DLSTATS",
+ .description = "Statistics messages and logging",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
};
/*! \brief descriptive string for each log level */
diff --git a/src/rate_ctr.c b/src/rate_ctr.c
index 8a232e86..50b3fe74 100644
--- a/src/rate_ctr.c
+++ b/src/rate_ctr.c
@@ -83,6 +83,15 @@ void rate_ctr_add(struct rate_ctr *ctr, int inc)
ctr->current += inc;
}
+/*! \brief Return the counter difference since the last call to this function */
+int64_t rate_ctr_difference(struct rate_ctr *ctr)
+{
+ int64_t result = ctr->current - ctr->previous;
+ ctr->previous = ctr->current;
+
+ return result;
+}
+
static void interval_expired(struct rate_ctr *ctr, enum rate_ctr_intv intv)
{
/* calculate rate over last interval */
@@ -177,4 +186,36 @@ const struct rate_ctr *rate_ctr_get_by_name(const struct rate_ctr_group *ctrg, c
return NULL;
}
+int rate_ctr_for_each_counter(struct rate_ctr_group *ctrg,
+ rate_ctr_handler_t handle_counter, void *data)
+{
+ int rc = 0;
+ int i;
+
+ for (i = 0; i < ctrg->desc->num_ctr; i++) {
+ struct rate_ctr *ctr = &ctrg->ctr[i];
+ rc = handle_counter(ctrg,
+ ctr, &ctrg->desc->ctr_desc[i], data);
+ if (rc < 0)
+ return rc;
+ }
+
+ return rc;
+}
+
+int rate_ctr_for_each_group(rate_ctr_group_handler_t handle_group, void *data)
+{
+ struct rate_ctr_group *statg;
+ int rc = 0;
+
+ llist_for_each_entry(statg, &rate_ctr_groups, list) {
+ rc = handle_group(statg, data);
+ if (rc < 0)
+ return rc;
+ }
+
+ return rc;
+}
+
+
/*! @} */
diff --git a/src/stat_item.c b/src/stat_item.c
new file mode 100644
index 00000000..0545ea0d
--- /dev/null
+++ b/src/stat_item.c
@@ -0,0 +1,268 @@
+/* utility routines for keeping conters about events and the event rates */
+
+/* (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * 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.
+ *
+ */
+
+/*! \addtogroup osmo_stat_item
+ * @{
+ */
+
+/*! \file stat_item.c */
+
+
+#include <stdint.h>
+#include <string.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/stat_item.h>
+
+static LLIST_HEAD(osmo_stat_item_groups);
+static int32_t global_value_id = 0;
+
+static void *tall_stat_item_ctx;
+
+/*! \brief Allocate a new group of counters according to description
+ * \param[in] ctx \ref talloc context
+ * \param[in] desc Statistics item group description
+ * \param[in] idx Index of new stat item group
+ */
+struct osmo_stat_item_group *osmo_stat_item_group_alloc(void *ctx,
+ const struct osmo_stat_item_group_desc *desc,
+ unsigned int idx)
+{
+ unsigned int group_size;
+ unsigned int items_size = 0;
+ unsigned int item_idx;
+ void *items;
+
+ struct osmo_stat_item_group *group;
+
+ group_size = sizeof(struct osmo_stat_item_group) +
+ desc->num_items * sizeof(struct osmo_stat_item *);
+
+ if (!ctx)
+ ctx = tall_stat_item_ctx;
+
+ group = talloc_zero_size(ctx, group_size);
+ if (!group)
+ return NULL;
+
+ group->desc = desc;
+ group->idx = idx;
+
+ /* Get combined size of all items */
+ for (item_idx = 0; item_idx < desc->num_items; item_idx++) {
+ unsigned int size;
+ size = sizeof(struct osmo_stat_item) +
+ sizeof(struct osmo_stat_item_value) *
+ desc->item_desc[item_idx].num_values;
+ /* Align to pointer size */
+ size = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
+
+ /* Store offsets into the item array */
+ group->items[item_idx] = (void *)items_size;
+
+ items_size += size;
+ }
+
+ items = talloc_zero_size(group, items_size);
+ if (!items) {
+ talloc_free(group);
+ return NULL;
+ }
+
+ /* Update item pointers */
+ for (item_idx = 0; item_idx < desc->num_items; item_idx++) {
+ struct osmo_stat_item *item = (struct osmo_stat_item *)
+ ((uint8_t *)items + (int)group->items[item_idx]);
+ unsigned int i;
+
+ group->items[item_idx] = item;
+ item->last_offs = desc->item_desc[item_idx].num_values - 1;
+ item->last_value_index = -1;
+ item->desc = &desc->item_desc[item_idx];
+
+ for (i = 0; i <= item->last_offs; i++) {
+ item->values[i].value = desc->item_desc[item_idx].default_value;
+ item->values[i].id = STAT_ITEM_NOVALUE_ID;
+ }
+ }
+
+ llist_add(&group->list, &osmo_stat_item_groups);
+
+ return group;
+}
+
+/*! \brief Free the memory for the specified group of counters */
+void osmo_stat_item_group_free(struct osmo_stat_item_group *grp)
+{
+ llist_del(&grp->list);
+ talloc_free(grp);
+}
+
+void osmo_stat_item_set(struct osmo_stat_item *item, int32_t value)
+{
+ item->last_offs += 1;
+ if (item->last_offs >= item->desc->num_values)
+ item->last_offs = 0;
+
+ global_value_id += 1;
+ if (global_value_id == STAT_ITEM_NOVALUE_ID)
+ global_value_id += 1;
+
+ item->values[item->last_offs].value = value;
+ item->values[item->last_offs].id = global_value_id;
+}
+
+int osmo_stat_item_get_next(const struct osmo_stat_item *item, int32_t *next_idx,
+ int32_t *value)
+{
+ const struct osmo_stat_item_value *next_value;
+ const struct osmo_stat_item_value *item_value = NULL;
+ int idx_delta;
+ int next_offs;
+
+ next_offs = item->last_offs;
+ next_value = &item->values[next_offs];
+
+ while (next_value->id - *next_idx >= 0 &&
+ next_value->id != STAT_ITEM_NOVALUE_ID)
+ {
+ item_value = next_value;
+
+ next_offs -= 1;
+ if (next_offs < 0)
+ next_offs = item->desc->num_values - 1;
+ if (next_offs == item->last_offs)
+ break;
+ next_value = &item->values[next_offs];
+ }
+
+ if (!item_value)
+ /* All items have been read */
+ return 0;
+
+ *value = item_value->value;
+
+ idx_delta = item_value->id + 1 - *next_idx;
+
+ *next_idx = item_value->id + 1;
+
+ return idx_delta;
+}
+
+/*! \brief Skip all values of this item and update idx accordingly */
+int osmo_stat_item_discard(const struct osmo_stat_item *item, int32_t *idx)
+{
+ int discarded = item->values[item->last_offs].id + 1 - *idx;
+ *idx = item->values[item->last_offs].id + 1;
+
+ return discarded;
+}
+
+/*! \brief Skip all values of all items and update idx accordingly */
+int osmo_stat_item_discard_all(int32_t *idx)
+{
+ int discarded = global_value_id + 1 - *idx;
+ *idx = global_value_id + 1;
+
+ return discarded;
+}
+
+/*! \brief Initialize the stat item module */
+int osmo_stat_item_init(void *tall_ctx)
+{
+ tall_stat_item_ctx = tall_ctx;
+
+ return 0;
+}
+
+/*! \brief Search for item group based on group name and index */
+struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idx(
+ const char *name, const unsigned int idx)
+{
+ struct osmo_stat_item_group *statg;
+
+ llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
+ if (!statg->desc)
+ continue;
+
+ if (!strcmp(statg->desc->group_name_prefix, name) &&
+ statg->idx == idx)
+ return statg;
+ }
+ return NULL;
+}
+
+/*! \brief Search for item group based on group name */
+const struct osmo_stat_item *osmo_stat_item_get_by_name(
+ const struct osmo_stat_item_group *statg, const char *name)
+{
+ int i;
+ const struct osmo_stat_item_desc *item_desc;
+
+ if (!statg->desc)
+ return NULL;
+
+ for (i = 0; i < statg->desc->num_items; i++) {
+ item_desc = &statg->desc->item_desc[i];
+
+ if (!strcmp(item_desc->name, name)) {
+ return statg->items[i];
+ }
+ }
+ return NULL;
+}
+
+int osmo_stat_item_for_each_item(struct osmo_stat_item_group *statg,
+ osmo_stat_item_handler_t handle_item, void *data)
+{
+ int rc = 0;
+ int i;
+
+ for (i = 0; i < statg->desc->num_items; i++) {
+ struct osmo_stat_item *item = statg->items[i];
+ rc = handle_item(statg, item, data);
+ if (rc < 0)
+ return rc;
+ }
+
+ return rc;
+}
+
+int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, void *data)
+{
+ struct osmo_stat_item_group *statg;
+ int rc = 0;
+
+ llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
+ rc = handle_group(statg, data);
+ if (rc < 0)
+ return rc;
+ }
+
+ return rc;
+}
+
+/*! @} */
diff --git a/src/statistics.c b/src/statistics.c
index e28541ba..ad069cea 100644
--- a/src/statistics.c
+++ b/src/statistics.c
@@ -74,3 +74,11 @@ struct osmo_counter *osmo_counter_get_by_name(const char *name)
}
return NULL;
}
+
+int osmo_counter_difference(struct osmo_counter *ctr)
+{
+ int delta = ctr->value - ctr->previous;
+ ctr->previous = ctr->value;
+
+ return delta;
+}
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");