From 9732cb4a92a883c7e9f7dcd928b6e22976a797ca Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Thu, 1 Oct 2015 20:43:53 +0200 Subject: stats: Add stat_item for value monitoring This commit adds instrumentation function to gather measurement and statistical values similar to counter groups. Multiple values can be stored per item, which can be retrieved in FIFO order. Getting values from the item does not modify its state to allow for multiple independant backends (e.g. VTY and statd). When a new value is set, the oldest value gets silently overwritten. Lost values are skipped when getting values from the item. Sponsored-by: On-Waves ehf --- src/Makefile.am | 2 +- src/stat_item.c | 215 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 src/stat_item.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 4bf3408e..93fc3833 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 BUILT_SOURCES = crc8gen.c crc16gen.c crc32gen.c crc64gen.c diff --git a/src/stat_item.c b/src/stat_item.c new file mode 100644 index 00000000..7b169ea0 --- /dev/null +++ b/src/stat_item.c @@ -0,0 +1,215 @@ +/* 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 + * + * 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 stat_item + * @{ + */ + +/*! \file stat_item.c */ + + +#include +#include + +#include +#include +#include +#include +#include + +static LLIST_HEAD(stat_item_groups); + +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 stat_item_group *stat_item_group_alloc(void *ctx, + const struct stat_item_group_desc *desc, + unsigned int idx) +{ + unsigned int group_size; + unsigned int items_size = 0; + unsigned int item_idx; + void *items; + + struct stat_item_group *group; + + group_size = sizeof(struct stat_item_group) + + desc->num_items * sizeof(struct 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 stat_item) + + sizeof(int32_t) * 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 stat_item *item = (struct 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] = desc->item_desc[item_idx].default_value; + } + + llist_add(&group->list, &stat_item_groups); + + return group; +} + +/*! \brief Free the memory for the specified group of counters */ +void stat_item_group_free(struct stat_item_group *grp) +{ + llist_del(&grp->list); + talloc_free(grp); +} + +void stat_item_set(struct stat_item *item, int32_t value) +{ + item->last_offs += 1; + if (item->last_offs >= item->desc->num_values) + item->last_offs = 0; + + item->last_value_index += 1; + + item->values[item->last_offs] = value; +} + +int stat_item_get_next(const struct stat_item *item, int32_t *next_idx, + int32_t *value) +{ + int32_t delta = item->last_value_index + 1 - *next_idx; + int n_values = 0; + int next_offs; + + if (delta == 0) + /* All items have been read */ + return 0; + + if (delta < 0 || delta > item->desc->num_values) { + n_values = delta - item->desc->num_values; + delta = item->desc->num_values; + } + + next_offs = item->last_offs + 1 - delta; + if (next_offs < 0) + next_offs += item->desc->num_values; + + *value = item->values[next_offs]; + + n_values += 1; + delta -= 1; + *next_idx = item->last_value_index + 1 - delta; + + return n_values; +} + +/*! \brief Skip all values and update idx accordingly */ +int stat_item_discard(const struct stat_item *item, int32_t *idx) +{ + int discarded = item->last_value_index + 1 - *idx; + *idx = item->last_value_index + 1; + + return discarded; +} + + +/*! \brief Initialize the stat item module */ +int 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 stat_item_group *stat_item_get_group_by_name_idx( + const char *name, const unsigned int idx) +{ + struct stat_item_group *statg; + + llist_for_each_entry(statg, &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 stat_item *stat_item_get_by_name( + const struct stat_item_group *statg, const char *name) +{ + int i; + const struct 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; +} + +/*! @} */ -- cgit v1.2.3 From 738d9e22108a8e472458fad42509fd8d96994d6c Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Tue, 6 Oct 2015 15:21:56 +0200 Subject: stats: Add vty_out_stat_item_group This functions dumps a whole stat item group to the VTY. Sponsored-by: On-Waves ehf --- src/vty/utils.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src') diff --git a/src/vty/utils.c b/src/vty/utils.c index d0ad431d..e190337c 100644 --- a/src/vty/utils.c +++ b/src/vty/utils.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -63,6 +64,27 @@ void vty_out_rate_ctr_group(struct vty *vty, const char *prefix, }; } +/*! \brief print a stat item group to given VTY + * \param[in] vty The VTY to which it should be printed + * \param[in] prefix Any additional log prefix ahead of each line + * \param[in] statg Stat item group to be printed + */ +void vty_out_stat_item_group(struct vty *vty, const char *prefix, + struct stat_item_group *statg) +{ + unsigned int i; + + vty_out(vty, "%s%s:%s", prefix, statg->desc->group_description, + VTY_NEWLINE); + for (i = 0; i < statg->desc->num_items; i++) { + struct stat_item *item = statg->items[i]; + vty_out(vty, " %s%s: %8" PRIi32 " %s%s", + prefix, item->desc->description, + stat_item_get_last(item), + item->desc->unit, VTY_NEWLINE); + }; +} + /*! \brief Generate a VTY command string from value_string */ char *vty_cmd_string_from_valstr(void *ctx, const struct value_string *vals, const char *prefix, const char *sep, -- cgit v1.2.3 From 0a1400fc8311268d0a66bb20e0620e546e8d11c8 Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Tue, 6 Oct 2015 15:23:25 +0200 Subject: ns: Add statistics for some events The following counters are added to the ns.nsvc counter group: lost.alive The number of missing ALIVE ACK messages lost.reset The number of missing RESET ACK messages The following items are added to the ns.nsvc stat item group: alive.delay The time in ms between sending ALIVE and receiving the next ALIVE ACK Sponsored-by: On-Waves ehf --- src/gb/gprs_ns.c | 36 ++++++++++++++++++++++++++++++++++++ src/gb/gprs_ns_vty.c | 4 +++- 2 files changed, 39 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c index 827d09d7..afc62497 100644 --- a/src/gb/gprs_ns.c +++ b/src/gb/gprs_ns.c @@ -75,6 +75,7 @@ #include #include #include +#include #include #include #include @@ -104,6 +105,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 +120,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 +131,21 @@ 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 stat_item_desc nsvc_stat_description[] = { + { "alive.delay", "ALIVE reponse time ", "ms", 16, 0 }, +}; + +static const struct 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, +}; + #define CHECK_TX_RC(rc, nsvc) \ if (rc < 0) \ LOGP(DNS, LOGL_ERROR, "TX failed (%d) to peer %s\n", \ @@ -218,6 +238,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 = stat_item_group_alloc(nsvc, &nsvc_statg_desc, nsvci); llist_add(&nsvc->list, &nsi->gprs_nsvcs); @@ -531,10 +552,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 +580,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 +610,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 +1305,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) + 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) -- cgit v1.2.3 From b27b352e937dd0760da1e7fb05f9207be05702b8 Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 12 Oct 2015 18:47:09 +0200 Subject: stats: Use a global index for stat item values Currently each stat item has a separate index value which basically counts each single value added to the item and which can be used by a reporter to get all new values that have not been reported yet. The drawback is, that such an index must be stored for each stat item. This commit introduces a global index which is incremented for each new stat item value. This index is then stored together with the item value. So a single stored index per reporter is sufficient to make sure that only new values are reported. Sponsored-by: On-Waves ehf --- src/stat_item.c | 71 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/stat_item.c b/src/stat_item.c index 7b169ea0..1e283d48 100644 --- a/src/stat_item.c +++ b/src/stat_item.c @@ -38,6 +38,7 @@ #include static LLIST_HEAD(stat_item_groups); +static int32_t global_value_id = 0; static void *tall_stat_item_ctx; @@ -74,7 +75,8 @@ struct stat_item_group *stat_item_group_alloc(void *ctx, for (item_idx = 0; item_idx < desc->num_items; item_idx++) { unsigned int size; size = sizeof(struct stat_item) + - sizeof(int32_t) * desc->item_desc[item_idx].num_values; + sizeof(struct stat_item_value) * + desc->item_desc[item_idx].num_values; /* Align to pointer size */ size = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1); @@ -101,8 +103,10 @@ struct stat_item_group *stat_item_group_alloc(void *ctx, item->last_value_index = -1; item->desc = &desc->item_desc[item_idx]; - for (i = 0; i <= item->last_offs; i++) - item->values[i] = desc->item_desc[item_idx].default_value; + 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, &stat_item_groups); @@ -123,49 +127,68 @@ void stat_item_set(struct stat_item *item, int32_t value) if (item->last_offs >= item->desc->num_values) item->last_offs = 0; - item->last_value_index += 1; + global_value_id += 1; + if (global_value_id == STAT_ITEM_NOVALUE_ID) + global_value_id += 1; - item->values[item->last_offs] = value; + item->values[item->last_offs].value = value; + item->values[item->last_offs].id = global_value_id; } int stat_item_get_next(const struct stat_item *item, int32_t *next_idx, int32_t *value) { - int32_t delta = item->last_value_index + 1 - *next_idx; - int n_values = 0; + const struct stat_item_value *next_value; + const struct stat_item_value *item_value = NULL; + int idx_delta; int next_offs; - if (delta == 0) - /* All items have been read */ - return 0; + next_offs = item->last_offs; + next_value = &item->values[next_offs]; - if (delta < 0 || delta > item->desc->num_values) { - n_values = delta - item->desc->num_values; - delta = item->desc->num_values; + 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]; } - next_offs = item->last_offs + 1 - delta; - if (next_offs < 0) - next_offs += item->desc->num_values; + if (!item_value) + /* All items have been read */ + return 0; - *value = item->values[next_offs]; + *value = item_value->value; - n_values += 1; - delta -= 1; - *next_idx = item->last_value_index + 1 - delta; + idx_delta = item_value->id + 1 - *next_idx; - return n_values; + *next_idx = item_value->id + 1; + + return idx_delta; } -/*! \brief Skip all values and update idx accordingly */ +/*! \brief Skip all values of this item and update idx accordingly */ int stat_item_discard(const struct stat_item *item, int32_t *idx) { - int discarded = item->last_value_index + 1 - *idx; - *idx = item->last_value_index + 1; + 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 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 stat_item_init(void *tall_ctx) -- cgit v1.2.3 From 423c1e5a4fc7ad2cd5e95e852b778c7e2c892bc1 Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 19 Oct 2015 13:45:42 +0200 Subject: core: Extend rate_ctr by helper functions For global value reporting, some additional helper functions are needed. The statsd protocol expects differential counter values, which are currently not provided by rate_ctr (except for s/m/h/d intervals). This commit adds several helper functions to rate_ctr: - rate_ctr_difference returns the counter delta since the last call to this function for a given counter - rate_ctr_for_each_counter iterates through each counter of a group - rate_ctr_for_each_group iterates through all globally registered counter groups Note that the rate_ctr_difference function can only be used by a single backend, since it modifies the 'previous' field in the rate_ctr obj. Sponsored-by: On-Waves ehf --- src/rate_ctr.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'src') 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; +} + + /*! @} */ -- cgit v1.2.3 From c6a7108828bf98ebcaf31d24bd8d789afdd4da94 Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 19 Oct 2015 14:04:38 +0200 Subject: stats: Add stat_item_for_each functions This commit adds the following functions: stat_item_for_each_group Call a handler for each group stat_item_for_each_item Call a handler for each item of a group Sponsored-by: On-Waves ehf --- src/stat_item.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src') diff --git a/src/stat_item.c b/src/stat_item.c index 1e283d48..525a94b9 100644 --- a/src/stat_item.c +++ b/src/stat_item.c @@ -235,4 +235,34 @@ const struct stat_item *stat_item_get_by_name( return NULL; } +int stat_item_for_each_item(struct stat_item_group *statg, + stat_item_handler_t handle_item, void *data) +{ + int rc = 0; + int i; + + for (i = 0; i < statg->desc->num_items; i++) { + struct stat_item *item = statg->items[i]; + rc = handle_item(statg, item, data); + if (rc < 0) + return rc; + } + + return rc; +} + +int stat_item_for_each_group(stat_item_group_handler_t handle_group, void *data) +{ + struct stat_item_group *statg; + int rc = 0; + + llist_for_each_entry(statg, &stat_item_groups, list) { + rc = handle_group(statg, data); + if (rc < 0) + return rc; + } + + return rc; +} + /*! @} */ -- cgit v1.2.3 From aec583f68786f91c3f0d76a8f8706c85aaca07a8 Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 19 Oct 2015 15:06:01 +0200 Subject: stat/vty: Use the iterator algorithms to show ctrg and statg Currently the groups for stat_items and counter are iterated manually. This commit makes use of the new iterator functions to access the single elements via handlers. Sponsored-by: On-Waves ehf --- src/vty/utils.c | 63 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/vty/utils.c b/src/vty/utils.c index e190337c..93e9ef48 100644 --- a/src/vty/utils.c +++ b/src/vty/utils.c @@ -40,6 +40,30 @@ * @{ */ +struct vty_out_context { + struct vty *vty; + const char *prefix; +}; + +static int rate_ctr_handler( + struct rate_ctr_group *ctrg, struct rate_ctr *ctr, + const struct rate_ctr_desc *desc, void *vctx_) +{ + struct vty_out_context *vctx = vctx_; + struct vty *vty = vctx->vty; + + vty_out(vty, " %s%s: %8" PRIu64 " " + "(%" PRIu64 "/s %" PRIu64 "/m %" PRIu64 "/h %" PRIu64 "/d)%s", + vctx->prefix, desc->description, ctr->current, + ctr->intv[RATE_CTR_INTV_SEC].rate, + ctr->intv[RATE_CTR_INTV_MIN].rate, + ctr->intv[RATE_CTR_INTV_HOUR].rate, + ctr->intv[RATE_CTR_INTV_DAY].rate, + VTY_NEWLINE); + + return 0; +} + /*! \brief print a rate counter group to given VTY * \param[in] vty The VTY to which it should be printed * \param[in] prefix Any additional log prefix ahead of each line @@ -48,20 +72,25 @@ void vty_out_rate_ctr_group(struct vty *vty, const char *prefix, struct rate_ctr_group *ctrg) { - unsigned int i; + struct vty_out_context vctx = {vty, prefix}; vty_out(vty, "%s%s:%s", prefix, ctrg->desc->group_description, VTY_NEWLINE); - for (i = 0; i < ctrg->desc->num_ctr; i++) { - struct rate_ctr *ctr = &ctrg->ctr[i]; - vty_out(vty, " %s%s: %8" PRIu64 " " - "(%" PRIu64 "/s %" PRIu64 "/m %" PRIu64 "/h %" PRIu64 "/d)%s", - prefix, ctrg->desc->ctr_desc[i].description, ctr->current, - ctr->intv[RATE_CTR_INTV_SEC].rate, - ctr->intv[RATE_CTR_INTV_MIN].rate, - ctr->intv[RATE_CTR_INTV_HOUR].rate, - ctr->intv[RATE_CTR_INTV_DAY].rate, - VTY_NEWLINE); - }; + + rate_ctr_for_each_counter(ctrg, rate_ctr_handler, &vctx); +} + +static int stat_item_handler( + struct stat_item_group *statg, struct stat_item *item, void *vctx_) +{ + struct vty_out_context *vctx = vctx_; + struct vty *vty = vctx->vty; + + vty_out(vty, " %s%s: %8" PRIi32 " %s%s", + vctx->prefix, item->desc->description, + stat_item_get_last(item), + item->desc->unit, VTY_NEWLINE); + + return 0; } /*! \brief print a stat item group to given VTY @@ -72,17 +101,11 @@ void vty_out_rate_ctr_group(struct vty *vty, const char *prefix, void vty_out_stat_item_group(struct vty *vty, const char *prefix, struct stat_item_group *statg) { - unsigned int i; + struct vty_out_context vctx = {vty, prefix}; vty_out(vty, "%s%s:%s", prefix, statg->desc->group_description, VTY_NEWLINE); - for (i = 0; i < statg->desc->num_items; i++) { - struct stat_item *item = statg->items[i]; - vty_out(vty, " %s%s: %8" PRIi32 " %s%s", - prefix, item->desc->description, - stat_item_get_last(item), - item->desc->unit, VTY_NEWLINE); - }; + stat_item_for_each_item(statg, stat_item_handler, &vctx); } /*! \brief Generate a VTY command string from value_string */ -- cgit v1.2.3 From 7211fe157e1107d4a9c04a0ecf494a7b9633c400 Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 19 Oct 2015 15:11:50 +0200 Subject: stat/vty: Add vty_out_statistics_full to show all statistics This functions shows the state of all osmo_counters, stat_item groups, and counter groups. Sponsored-by: On-Waves ehf --- src/vty/utils.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) (limited to 'src') diff --git a/src/vty/utils.c b/src/vty/utils.c index 93e9ef48..474a25ef 100644 --- a/src/vty/utils.c +++ b/src/vty/utils.c @@ -31,6 +31,7 @@ #include #include #include +#include #include @@ -108,6 +109,63 @@ void vty_out_stat_item_group(struct vty *vty, const char *prefix, stat_item_for_each_item(statg, stat_item_handler, &vctx); } +static int stat_item_group_handler(struct stat_item_group *statg, void *vctx_) +{ + struct vty_out_context *vctx = vctx_; + struct vty *vty = vctx->vty; + + if (statg->idx) + vty_out(vty, "%s%s (%d):%s", vctx->prefix, + statg->desc->group_description, statg->idx, + VTY_NEWLINE); + else + vty_out(vty, "%s%s:%s", vctx->prefix, + statg->desc->group_description, VTY_NEWLINE); + + stat_item_for_each_item(statg, stat_item_handler, vctx); + + return 0; +} + +static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *vctx_) +{ + struct vty_out_context *vctx = vctx_; + struct vty *vty = vctx->vty; + + if (ctrg->idx) + vty_out(vty, "%s%s (%d):%s", vctx->prefix, + ctrg->desc->group_description, ctrg->idx, VTY_NEWLINE); + else + vty_out(vty, "%s%s:%s", vctx->prefix, + ctrg->desc->group_description, VTY_NEWLINE); + + rate_ctr_for_each_counter(ctrg, rate_ctr_handler, vctx); + + return 0; +} + +static int handle_counter(struct osmo_counter *counter, void *vctx_) +{ + struct vty_out_context *vctx = vctx_; + struct vty *vty = vctx->vty; + + vty_out(vty, " %s%s: %8lu%s", + vctx->prefix, counter->description, + osmo_counter_get(counter), VTY_NEWLINE); + + return 0; +} + +void vty_out_statistics_full(struct vty *vty, const char *prefix) +{ + struct vty_out_context vctx = {vty, prefix}; + + vty_out(vty, "%sUngrouped counters:%s", prefix, VTY_NEWLINE); + osmo_counters_for_each(handle_counter, &vctx); + rate_ctr_for_each_group(rate_ctr_group_handler, &vctx); + stat_item_for_each_group(stat_item_group_handler, &vctx); +} + /*! \brief Generate a VTY command string from value_string */ char *vty_cmd_string_from_valstr(void *ctx, const struct value_string *vals, const char *prefix, const char *sep, -- cgit v1.2.3 From 45513e6040195f5494d40a2750de4dac7037593f Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 19 Oct 2015 15:14:13 +0200 Subject: stats/vty: Add stats_vty.c This file will contain the VTY code related to statistics. This commit adds a minimal file with just as single VTY command: - show stats This command shows all statistical values To enable this and future commands, the main program needs to call stats_vty_add_cmds(). Sponsored-by: On-Waves ehf --- src/vty/Makefile.am | 2 +- src/vty/stats_vty.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/vty/stats_vty.c (limited to 'src') diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am index 4225d27d..7c549d91 100644 --- a/src/vty/Makefile.am +++ b/src/vty/Makefile.am @@ -9,7 +9,7 @@ if ENABLE_VTY lib_LTLIBRARIES = libosmovty.la libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \ - telnet_interface.c logging_vty.c + telnet_interface.c logging_vty.c stats_vty.c libosmovty_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la endif diff --git a/src/vty/stats_vty.c b/src/vty/stats_vty.c new file mode 100644 index 00000000..c19b2259 --- /dev/null +++ b/src/vty/stats_vty.c @@ -0,0 +1,52 @@ +/* OpenBSC stats helper for the VTY */ +/* (C) 2009-2010 by Harald Welte + * (C) 2009-2014 by Holger Hans Peter Freyther + * (C) 2015 by Sysmocom s.f.m.c. GmbH + * 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 +#include + +#include "../../config.h" + +#include +#include +#include +#include +#include + +#define CFG_STATS_STR "Configure stats sub-system\n" +#define CFG_REPORTER_STR "Configure a stats reporter\n" + +#define SHOW_STATS_STR "Show statistical values\n" + +DEFUN(show_stats, + show_stats_cmd, + "show stats", + SHOW_STR SHOW_STATS_STR) +{ + vty_out_statistics_full(vty, ""); + + return CMD_SUCCESS; +} + +void stats_vty_add_cmds(const struct log_info *cat) +{ + install_element_ve(&show_stats_cmd); +} -- cgit v1.2.3 From 95bf828003b065f00a78144296072a9730cbf7bc Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Tue, 20 Oct 2015 19:05:52 +0200 Subject: stats: Add the reporting framework This commit provides the stats reporting framework that can manage several types of measurement reporters. Initially support for rate_ctr and the statsd protocol is included. Sponsored-by: On-Waves ehf --- src/Makefile.am | 2 +- src/stats.c | 387 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 src/stats.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 93fc3833..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 stat_item.c + macaddr.c stat_item.c stats.c BUILT_SOURCES = crc8gen.c crc16gen.c crc32gen.c crc64gen.c diff --git a/src/stats.c b/src/stats.c new file mode 100644 index 00000000..180452e2 --- /dev/null +++ b/src/stats.c @@ -0,0 +1,387 @@ +/* + * (C) 2015 by Sysmocom s.f.m.c. GmbH + * + * Author: Jacob Erlbeck + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* TODO: register properly */ +#define DSTATS DLGLOBAL + + +static LLIST_HEAD(stats_reporter_list); +static void *stats_ctx = NULL; + +static int stats_reporter_statsd_open(struct stats_reporter *srep); +static int stats_reporter_statsd_close(struct stats_reporter *srep); +static int stats_reporter_send(struct stats_reporter *srep, const char *data, + int data_len); + +static int update_srep_config(struct stats_reporter *srep) +{ + int rc = 0; + + if (srep->type != STATS_REPORTER_STATSD) { + srep->enabled = 0; + return -ENOTSUP; + } + + if (srep->running) { + rc = stats_reporter_statsd_close(srep); + srep->running = 0; + } + + if (!srep->enabled) + return rc; + + rc = stats_reporter_statsd_open(srep); + if (rc < 0) + srep->enabled = 0; + else + srep->running = 1; + + return rc; +} + +struct stats_reporter *stats_reporter_alloc(enum stats_reporter_type type, + const char *name) +{ + struct stats_reporter *srep; + srep = talloc_zero(stats_ctx, struct stats_reporter); + OSMO_ASSERT(srep); + srep->type = type; + if (name) + srep->name = talloc_strdup(srep, name); + srep->fd = -1; + + llist_add(&srep->list, &stats_reporter_list); + + return srep; +} + +void stats_reporter_free(struct stats_reporter *srep) +{ + stats_reporter_disable(srep); + llist_del(&srep->list); + talloc_free(srep); +} + +void stats_init(void *ctx) +{ + stats_ctx = ctx; +} + +struct stats_reporter *stats_reporter_find(enum stats_reporter_type type, + const char *name) +{ + struct stats_reporter *srep; + llist_for_each_entry(srep, &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 stats_reporter_set_remote_addr(struct stats_reporter *srep, const char *addr) +{ + int rc; + struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->dest_addr; + struct in_addr inaddr; + + 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 stats_reporter_set_remote_port(struct stats_reporter *srep, int port) +{ + struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->dest_addr; + + srep->dest_port = port; + sock_addr->sin_port = htons(port); + + return update_srep_config(srep); +} + +int stats_reporter_set_local_addr(struct stats_reporter *srep, const char *addr) +{ + int rc; + struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->bind_addr; + struct in_addr inaddr; + + if (addr) { + rc = inet_pton(AF_INET, addr, &inaddr); + if (rc <= 0) + return -EINVAL; + } else { + 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 stats_reporter_set_interval(struct stats_reporter *srep, int interval) +{ + srep->interval = interval; + + return update_srep_config(srep); +} + +int stats_reporter_set_name_prefix(struct 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 stats_reporter_enable(struct stats_reporter *srep) +{ + srep->enabled = 1; + + return update_srep_config(srep); +} + +int stats_reporter_disable(struct stats_reporter *srep) +{ + srep->enabled = 0; + + return update_srep_config(srep); +} + +static int stats_reporter_send(struct 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; +} + +/*** statsd reporter ***/ + +struct stats_reporter *stats_reporter_create_statsd(const char *name) +{ + struct stats_reporter *srep; + srep = stats_reporter_alloc(STATS_REPORTER_STATSD, name); + + return srep; +} + +static int stats_reporter_statsd_open(struct stats_reporter *srep) +{ + int sock; + int rc; + + if (srep->fd != -1) + 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; + + return 0; + +failed: + rc = -errno; + close(sock); + + return rc; +} + +static int stats_reporter_statsd_close(struct stats_reporter *srep) +{ + int rc; + if (srep->fd == -1) + return -EBADF; + + rc = close(srep->fd); + srep->fd = -1; + return rc == -1 ? -errno : 0; +} + +static int stats_reporter_statsd_send(struct stats_reporter *srep, + const char *name1, int index1, const char *name2, int value, + const char *unit) +{ + char buf[256]; + int nchars, rc; + char *fmt = NULL; + + if (name1) { + if (index1 > 0) + fmt = "%1$s.%2$s.%3$d.%4$s:%5$d|%6$s"; + else + fmt = "%1$s.%2$s.%4$s:%5$d|%6$s"; + } else { + fmt = "%1$s.%4$s:%5$d|%6$s"; + } + if (!srep->name_prefix) + fmt += 5; /* skip prefix part */ + + nchars = snprintf(buf, sizeof(buf), fmt, + srep->name_prefix, name1, index1, name2, + value, unit); + + if (nchars >= sizeof(buf)) + /* Truncated */ + return -EMSGSIZE; + + rc = stats_reporter_send(srep, buf, nchars); + + return rc; +} + +static int stats_reporter_statsd_send_counter(struct stats_reporter *srep, + const struct rate_ctr_group *ctrg, + const struct rate_ctr_desc *desc, + int64_t value, int64_t delta) +{ + if (ctrg) + return stats_reporter_statsd_send(srep, + ctrg->desc->group_name_prefix, + ctrg->idx, + desc->name, delta, "c"); + else + return stats_reporter_statsd_send(srep, + NULL, -1, + desc->name, delta, "c"); +} + +static int stats_reporter_statsd_send_item(struct stats_reporter *srep, + const struct stat_item_group *statg, + const struct stat_item_desc *desc, int value) +{ + return stats_reporter_statsd_send(srep, + statg->desc->group_name_prefix, statg->idx, + desc->name, value, desc->unit); +} + +/*** generic rate counter support ***/ + +static int stats_reporter_send_counter(struct stats_reporter *srep, + const struct rate_ctr_group *ctrg, + const struct rate_ctr_desc *desc, + int64_t value, int64_t delta) +{ + int rc; + + switch (srep->type) { + case STATS_REPORTER_STATSD: + rc = stats_reporter_statsd_send_counter(srep, ctrg, desc, + value, delta); + break; + } + + return rc; +} + +static int rate_ctr_handler( + struct rate_ctr_group *ctrg, struct rate_ctr *ctr, + const struct rate_ctr_desc *desc, void *sctx_) +{ + struct stats_reporter *srep; + int rc; + int64_t delta = rate_ctr_difference(ctr); + + if (delta == 0) + return 0; + + llist_for_each_entry(srep, &stats_reporter_list, list) { + if (!srep->running) + continue; + + rc = stats_reporter_send_counter(srep, ctrg, desc, + ctr->current, delta); + + /* TODO: handle rc (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; +} + + +/*** main reporting function ***/ + +int stats_report() +{ + rate_ctr_for_each_group(rate_ctr_group_handler, NULL); + + return 0; +} -- cgit v1.2.3 From adc900e0e38373193c8451c0310fe742d62c2c8e Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Tue, 20 Oct 2015 19:05:52 +0200 Subject: stats/vty: Add stats configuration This commit provides stats configuration similar to the log configuration. The following vty commands are added to the config node: stats reporter statsd Create/Modify a statsd reporter no stats reporter statsd Remove a statsd reporter To actually configure a reporter, the config-stats node is entered when the "stats reporter" command has succeeded. The following new vty commands are available there: local-ip ADDR Set the IP address to which we bind locally no local-ip Do not bind to a certain IP address remote-ip ADDR Set the remote IP address to which we connect remote-port <1-65535> Set the remote port to which we connect prefix PREFIX Set the item/counter name prefix no prefix Do not use a prefix enable Enable the reporter disable Disable the reporter Sponsored-by: On-Waves ehf --- src/vty/stats_vty.c | 261 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 260 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/vty/stats_vty.c b/src/vty/stats_vty.c index c19b2259..954f3581 100644 --- a/src/vty/stats_vty.c +++ b/src/vty/stats_vty.c @@ -29,13 +29,209 @@ #include #include #include +#include #include +#include + #define CFG_STATS_STR "Configure stats sub-system\n" #define CFG_REPORTER_STR "Configure a stats reporter\n" #define SHOW_STATS_STR "Show statistical values\n" +struct cmd_node cfg_stats_node = { + CFG_STATS_NODE, + "%s(config-stats)# ", + 1 +}; + +static struct stats_reporter *osmo_stats_vty2srep(struct vty *vty) +{ + if (vty->node == CFG_STATS_NODE) + return vty->index; + + return NULL; +} + +static int set_srep_parameter_str(struct vty *vty, + int (*fun)(struct stats_reporter *, const char *), + const char *val, const char *param_name) +{ + int rc; + struct stats_reporter *srep = osmo_stats_vty2srep(vty); + OSMO_ASSERT(srep); + + rc = fun(srep, val); + if (rc < 0) { + vty_out(vty, "%% Unable to set %s: %s%s", + param_name, strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +static int set_srep_parameter_int(struct vty *vty, + int (*fun)(struct stats_reporter *, int), + const char *val, const char *param_name) +{ + int rc; + int int_val; + struct stats_reporter *srep = osmo_stats_vty2srep(vty); + OSMO_ASSERT(srep); + + int_val = atoi(val); + + rc = fun(srep, int_val); + if (rc < 0) { + vty_out(vty, "%% Unable to set %s: %s%s", + param_name, strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_stats_reporter_local_ip, cfg_stats_reporter_local_ip_cmd, + "local-ip ADDR", + "Set the IP address to which we bind locally\n" + "IP Address\n") +{ + return set_srep_parameter_str(vty, stats_reporter_set_local_addr, + argv[0], "local address"); +} + +DEFUN(cfg_no_stats_reporter_local_ip, cfg_no_stats_reporter_local_ip_cmd, + "no local-ip", + NO_STR + "Set the IP address to which we bind locally\n") +{ + return set_srep_parameter_str(vty, stats_reporter_set_local_addr, + NULL, "local address"); +} + +DEFUN(cfg_stats_reporter_remote_ip, cfg_stats_reporter_remote_ip_cmd, + "remote-ip ADDR", + "Set the remote IP address to which we connect\n" + "IP Address\n") +{ + return set_srep_parameter_str(vty, stats_reporter_set_remote_addr, + argv[0], "remote address"); +} + +DEFUN(cfg_stats_reporter_remote_port, cfg_stats_reporter_remote_port_cmd, + "remote-port <1-65535>", + "Set the remote port to which we connect\n" + "Remote port number\n") +{ + return set_srep_parameter_int(vty, stats_reporter_set_remote_port, + argv[0], "remote port"); +} + +DEFUN(cfg_stats_reporter_interval, cfg_stats_reporter_interval_cmd, + "interval <1-65535>", + "Set the reporting interval\n" + "Interval in seconds\n") +{ + return set_srep_parameter_int(vty, stats_reporter_set_interval, + argv[0], "reporting interval"); +} + +DEFUN(cfg_stats_reporter_prefix, cfg_stats_reporter_prefix_cmd, + "prefix PREFIX", + "Set the item name prefix\n" + "The prefix string\n") +{ + return set_srep_parameter_str(vty, stats_reporter_set_name_prefix, + argv[0], "prefix string"); +} + +DEFUN(cfg_no_stats_reporter_prefix, cfg_no_stats_reporter_prefix_cmd, + "no prefix", + NO_STR + "Set the item name prefix\n") +{ + return set_srep_parameter_str(vty, stats_reporter_set_name_prefix, + "", "prefix string"); +} + +DEFUN(cfg_stats_reporter_enable, cfg_stats_reporter_enable_cmd, + "enable", + "Enable the reporter\n") +{ + int rc; + struct stats_reporter *srep = osmo_stats_vty2srep(vty); + OSMO_ASSERT(srep); + + rc = stats_reporter_enable(srep); + if (rc < 0) { + vty_out(vty, "%% Unable to enable the reporter: %s%s", + strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_stats_reporter_disable, cfg_stats_reporter_disable_cmd, + "disable", + "Disable the reporter\n") +{ + int rc; + struct stats_reporter *srep = osmo_stats_vty2srep(vty); + OSMO_ASSERT(srep); + + rc = stats_reporter_disable(srep); + if (rc < 0) { + vty_out(vty, "%% Unable to disable the reporter: %s%s", + strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_stats_reporter_statsd, cfg_stats_reporter_statsd_cmd, + "stats reporter statsd", + CFG_STATS_STR CFG_REPORTER_STR "Report to a STATSD server\n") +{ + struct stats_reporter *srep; + + srep = stats_reporter_find(STATS_REPORTER_STATSD, NULL); + if (!srep) { + srep = stats_reporter_create_statsd(NULL); + if (!srep) { + vty_out(vty, "%% Unable to create statsd reporter%s", + VTY_NEWLINE); + return CMD_WARNING; + } + /* TODO: if needed, add stats_add_reporter(srep); */ + } + + vty->index = srep; + vty->node = CFG_STATS_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_stats_reporter_statsd, cfg_no_stats_reporter_statsd_cmd, + "no stats reporter statsd", + NO_STR CFG_STATS_STR CFG_REPORTER_STR "Report to a STATSD server\n") +{ + struct stats_reporter *srep; + + srep = stats_reporter_find(STATS_REPORTER_STATSD, NULL); + if (!srep) { + vty_out(vty, "%% No statsd logging active%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + stats_reporter_free(srep); + + return CMD_SUCCESS; +} + DEFUN(show_stats, show_stats_cmd, "show stats", @@ -46,7 +242,70 @@ DEFUN(show_stats, return CMD_SUCCESS; } -void stats_vty_add_cmds(const struct log_info *cat) +static int config_write_stats_reporter(struct vty *vty, struct stats_reporter *srep) +{ + if (srep == NULL) + return 0; + + switch (srep->type) { + case STATS_REPORTER_STATSD: + vty_out(vty, "stats reporter statsd%s", VTY_NEWLINE); + break; + } + + vty_out(vty, " disable%s", VTY_NEWLINE); + + if (srep->dest_addr_str) + vty_out(vty, " remote-ip %s%s", + srep->dest_addr_str, VTY_NEWLINE); + if (srep->dest_port) + vty_out(vty, " remote-port %d%s", + srep->dest_port, VTY_NEWLINE); + if (srep->bind_addr_str) + vty_out(vty, " local-ip %s%s", + srep->bind_addr_str, VTY_NEWLINE); + if (srep->interval) + vty_out(vty, " interval %d%s", + srep->interval, VTY_NEWLINE); + if (srep->name_prefix && *srep->name_prefix) + vty_out(vty, " prefix %s%s", + srep->name_prefix, VTY_NEWLINE); + else + vty_out(vty, " no prefix%s", VTY_NEWLINE); + + if (srep->enabled) + vty_out(vty, " enable%s", VTY_NEWLINE); + + return 1; +} + +static int config_write_stats(struct vty *vty) +{ + struct stats_reporter *srep; + + srep = stats_reporter_find(STATS_REPORTER_STATSD, NULL); + config_write_stats_reporter(vty, srep); + + return 1; +} + +void stats_vty_add_cmds() { install_element_ve(&show_stats_cmd); + + install_element(CONFIG_NODE, &cfg_stats_reporter_statsd_cmd); + install_element(CONFIG_NODE, &cfg_no_stats_reporter_statsd_cmd); + + install_node(&cfg_stats_node, config_write_stats); + vty_install_default(CFG_STATS_NODE); + + install_element(CFG_STATS_NODE, &cfg_stats_reporter_local_ip_cmd); + install_element(CFG_STATS_NODE, &cfg_no_stats_reporter_local_ip_cmd); + install_element(CFG_STATS_NODE, &cfg_stats_reporter_remote_ip_cmd); + install_element(CFG_STATS_NODE, &cfg_stats_reporter_remote_port_cmd); + install_element(CFG_STATS_NODE, &cfg_stats_reporter_interval_cmd); + install_element(CFG_STATS_NODE, &cfg_stats_reporter_prefix_cmd); + install_element(CFG_STATS_NODE, &cfg_no_stats_reporter_prefix_cmd); + install_element(CFG_STATS_NODE, &cfg_stats_reporter_enable_cmd); + install_element(CFG_STATS_NODE, &cfg_stats_reporter_disable_cmd); } -- cgit v1.2.3 From b1dbfb4c4179a62cd4b761ebdc7a3c2de5bdc0d9 Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 26 Oct 2015 11:58:38 +0100 Subject: stats: Implement timer based reporting This calls stats_flush in regular intervals which polls the statistical values and calls the active reporters when values have changed. Sponsored-by: On-Waves ehf --- src/stats.c | 46 +++++++++++++++++++++++++++++++++++++++++++--- src/vty/stats_vty.c | 34 +++++++++++++++++++++------------- 2 files changed, 64 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/stats.c b/src/stats.c index 180452e2..c9be0b74 100644 --- a/src/stats.c +++ b/src/stats.c @@ -36,13 +36,24 @@ #include #include #include +#include /* TODO: register properly */ #define DSTATS DLGLOBAL +#define STATS_DEFAULT_INTERVAL 5 /* secs */ static LLIST_HEAD(stats_reporter_list); static void *stats_ctx = NULL; +static int is_initialised = 0; +static int32_t current_stat_item_index = 0; + +static struct stats_config s_stats_config = { + .interval = STATS_DEFAULT_INTERVAL, +}; +struct stats_config *stats_config = &s_stats_config; + +static struct osmo_timer_list stats_timer; static int stats_reporter_statsd_open(struct stats_reporter *srep); static int stats_reporter_statsd_close(struct stats_reporter *srep); @@ -75,6 +86,27 @@ static int update_srep_config(struct stats_reporter *srep) return rc; } +static void stats_timer_cb(void *data) +{ + int interval = stats_config->interval; + + if (!llist_empty(&stats_reporter_list)) + stats_report(); + + osmo_timer_schedule(&stats_timer, interval, 0); +} + +static int start_timer() +{ + if (!is_initialised) + return -ESRCH; + + stats_timer.cb = stats_timer_cb; + osmo_timer_schedule(&stats_timer, 0, 1); + + return 0; +} + struct stats_reporter *stats_reporter_alloc(enum stats_reporter_type type, const char *name) { @@ -101,6 +133,9 @@ void stats_reporter_free(struct stats_reporter *srep) void stats_init(void *ctx) { stats_ctx = ctx; + + is_initialised = 1; + start_timer(); } struct stats_reporter *stats_reporter_find(enum stats_reporter_type type, @@ -176,11 +211,16 @@ int stats_reporter_set_local_addr(struct stats_reporter *srep, const char *addr) return update_srep_config(srep); } -int stats_reporter_set_interval(struct stats_reporter *srep, int interval) +int stats_set_interval(int interval) { - srep->interval = interval; + if (interval <= 0) + return -EINVAL; - return update_srep_config(srep); + stats_config->interval = interval; + if (is_initialised) + start_timer(); + + return 0; } int stats_reporter_set_name_prefix(struct stats_reporter *srep, const char *prefix) diff --git a/src/vty/stats_vty.c b/src/vty/stats_vty.c index 954f3581..a4fd7b05 100644 --- a/src/vty/stats_vty.c +++ b/src/vty/stats_vty.c @@ -128,15 +128,6 @@ DEFUN(cfg_stats_reporter_remote_port, cfg_stats_reporter_remote_port_cmd, argv[0], "remote port"); } -DEFUN(cfg_stats_reporter_interval, cfg_stats_reporter_interval_cmd, - "interval <1-65535>", - "Set the reporting interval\n" - "Interval in seconds\n") -{ - return set_srep_parameter_int(vty, stats_reporter_set_interval, - argv[0], "reporting interval"); -} - DEFUN(cfg_stats_reporter_prefix, cfg_stats_reporter_prefix_cmd, "prefix PREFIX", "Set the item name prefix\n" @@ -214,6 +205,24 @@ DEFUN(cfg_stats_reporter_statsd, cfg_stats_reporter_statsd_cmd, return CMD_SUCCESS; } +DEFUN(cfg_stats_interval, cfg_stats_interval_cmd, + "stats interval <1-65535>", + CFG_STATS_STR "Set the reporting interval\n" + "Interval in seconds\n") +{ + int rc; + int interval = atoi(argv[0]); + rc = stats_set_interval(interval); + if (rc < 0) { + vty_out(vty, "%% Unable to set interval: %s%s", + strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + + DEFUN(cfg_no_stats_reporter_statsd, cfg_no_stats_reporter_statsd_cmd, "no stats reporter statsd", NO_STR CFG_STATS_STR CFG_REPORTER_STR "Report to a STATSD server\n") @@ -264,9 +273,6 @@ static int config_write_stats_reporter(struct vty *vty, struct stats_reporter *s if (srep->bind_addr_str) vty_out(vty, " local-ip %s%s", srep->bind_addr_str, VTY_NEWLINE); - if (srep->interval) - vty_out(vty, " interval %d%s", - srep->interval, VTY_NEWLINE); if (srep->name_prefix && *srep->name_prefix) vty_out(vty, " prefix %s%s", srep->name_prefix, VTY_NEWLINE); @@ -286,6 +292,8 @@ static int config_write_stats(struct vty *vty) srep = stats_reporter_find(STATS_REPORTER_STATSD, NULL); config_write_stats_reporter(vty, srep); + vty_out(vty, "stats interval %d%s", stats_config->interval, VTY_NEWLINE); + return 1; } @@ -295,6 +303,7 @@ void stats_vty_add_cmds() install_element(CONFIG_NODE, &cfg_stats_reporter_statsd_cmd); install_element(CONFIG_NODE, &cfg_no_stats_reporter_statsd_cmd); + install_element(CONFIG_NODE, &cfg_stats_interval_cmd); install_node(&cfg_stats_node, config_write_stats); vty_install_default(CFG_STATS_NODE); @@ -303,7 +312,6 @@ void stats_vty_add_cmds() install_element(CFG_STATS_NODE, &cfg_no_stats_reporter_local_ip_cmd); install_element(CFG_STATS_NODE, &cfg_stats_reporter_remote_ip_cmd); install_element(CFG_STATS_NODE, &cfg_stats_reporter_remote_port_cmd); - install_element(CFG_STATS_NODE, &cfg_stats_reporter_interval_cmd); install_element(CFG_STATS_NODE, &cfg_stats_reporter_prefix_cmd); install_element(CFG_STATS_NODE, &cfg_no_stats_reporter_prefix_cmd); install_element(CFG_STATS_NODE, &cfg_stats_reporter_enable_cmd); -- cgit v1.2.3 From c27671c10935ee384d03a87170c3f31ab435da07 Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 26 Oct 2015 12:32:07 +0100 Subject: stats: Report stat item values Currently only rate counter are being supported. This commit adds support for stat items. All groups are polled for changed values. Sponsored-by: On-Waves ehf --- src/stats.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) (limited to 'src') diff --git a/src/stats.c b/src/stats.c index c9be0b74..8adb6d4a 100644 --- a/src/stats.c +++ b/src/stats.c @@ -133,6 +133,7 @@ void stats_reporter_free(struct stats_reporter *srep) void stats_init(void *ctx) { stats_ctx = ctx; + stat_item_discard_all(¤t_stat_item_index); is_initialised = 1; start_timer(); @@ -416,12 +417,60 @@ static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *sctx_) return 0; } +/*** stat item support ***/ + +static int stats_reporter_send_item(struct stats_reporter *srep, + const struct stat_item_group *statg, + const struct stat_item_desc *desc, + int32_t value) +{ + int rc; + + switch (srep->type) { + case STATS_REPORTER_STATSD: + rc = stats_reporter_statsd_send_item(srep, statg, desc, + value); + break; + } + + return rc; +} + +static int stat_item_handler( + struct stat_item_group *statg, struct stat_item *item, void *sctx_) +{ + struct stats_reporter *srep; + int rc; + int32_t idx = current_stat_item_index; + int32_t value; + + while (stat_item_get_next(item, &idx, &value) > 0) { + llist_for_each_entry(srep, &stats_reporter_list, list) { + if (!srep->running) + continue; + + rc = stats_reporter_send_item(srep, statg, + item->desc, value); + } + } + + return 0; +} + +static int stat_item_group_handler(struct stat_item_group *statg, void *sctx_) +{ + stat_item_for_each_item(statg, stat_item_handler, sctx_); + stat_item_discard_all(¤t_stat_item_index); + + return 0; +} /*** main reporting function ***/ int stats_report() { rate_ctr_for_each_group(rate_ctr_group_handler, NULL); + stat_item_for_each_group(stat_item_group_handler, NULL); return 0; } -- cgit v1.2.3 From 80db4ec3875b0de7f06de769881d6c5d4b713f2d Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 26 Oct 2015 14:39:08 +0100 Subject: core: Add difference function to osmo_counter The osmo_counter_difference returns the counter value difference since the last call of this function with the given counter object. Sponsored-by: On-Waves ehf --- src/statistics.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src') 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; +} -- cgit v1.2.3 From c8f47b600f8e2ad21266d4c27e960e477e5fe35c Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 26 Oct 2015 14:42:05 +0100 Subject: stats: Add support for osmo_counters This commit changes the reporting code to also show all modified osmo_counter values. Since there is no grouping of these values, the name string just consists of the optional prefix and the counter name. Sponsored-by: On-Waves ehf --- src/stats.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'src') diff --git a/src/stats.c b/src/stats.c index 8adb6d4a..ef4be828 100644 --- a/src/stats.c +++ b/src/stats.c @@ -37,6 +37,7 @@ #include #include #include +#include /* TODO: register properly */ #define DSTATS DLGLOBAL @@ -465,10 +466,41 @@ static int stat_item_group_handler(struct stat_item_group *statg, void *sctx_) return 0; } +/*** osmo counter support ***/ + +static int handle_counter(struct osmo_counter *counter, void *sctx_) +{ + struct stats_reporter *srep; + int rc; + 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, &stats_reporter_list, list) { + if (!srep->running) + continue; + + rc = stats_reporter_send_counter(srep, NULL, &desc, + counter->value, delta); + + /* TODO: handle rc (log?, inc counter(!)?) */ + } + + return 0; +} + + /*** main reporting function ***/ int stats_report() { + osmo_counters_for_each(handle_counter, NULL); rate_ctr_for_each_group(rate_ctr_group_handler, NULL); stat_item_for_each_group(stat_item_group_handler, NULL); -- cgit v1.2.3 From d01acfcc75a6c5798a95a8ccca9be18eba65a0bf Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 26 Oct 2015 16:22:45 +0100 Subject: stats: Support statsd Multi-Metric Packets If the MTU is given, combine several messages into a single UDP packet until the limit is reached. Flush all reporters after the values have been scanned. New vty commands (node config-stats): mtu <100-65535> Enable multi-metric packets and set the maximum packet size (in byte) no mtu Disable multi-metric packets Note that single messages that are longer than the given MTU (minus 28 octets protocol overhead) will be dropped. Sponsored-by: On-Waves ehf --- src/stats.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++------ src/vty/stats_vty.c | 19 +++++++++ 2 files changed, 116 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/stats.c b/src/stats.c index ef4be828..8faed89a 100644 --- a/src/stats.c +++ b/src/stats.c @@ -38,11 +38,13 @@ #include #include #include +#include /* TODO: register properly */ #define DSTATS DLGLOBAL #define STATS_DEFAULT_INTERVAL 5 /* secs */ +#define STATS_DEFAULT_STATSD_BUFLEN 256 static LLIST_HEAD(stats_reporter_list); static void *stats_ctx = NULL; @@ -60,6 +62,7 @@ static int stats_reporter_statsd_open(struct stats_reporter *srep); static int stats_reporter_statsd_close(struct stats_reporter *srep); static int stats_reporter_send(struct stats_reporter *srep, const char *data, int data_len); +static int stats_reporter_send_buffer(struct stats_reporter *srep); static int update_srep_config(struct stats_reporter *srep) { @@ -213,6 +216,16 @@ int stats_reporter_set_local_addr(struct stats_reporter *srep, const char *addr) return update_srep_config(srep); } +int stats_reporter_set_mtu(struct stats_reporter *srep, int mtu) +{ + if (mtu < 0) + return -EINVAL; + + srep->mtu = mtu; + + return update_srep_config(srep); +} + int stats_set_interval(int interval) { if (interval <= 0) @@ -261,6 +274,21 @@ static int stats_reporter_send(struct stats_reporter *srep, const char *data, return rc; } +static int stats_reporter_send_buffer(struct stats_reporter *srep) +{ + int rc; + + if (!srep->buffer || msgb_length(srep->buffer) == 0) + return 0; + + rc = stats_reporter_send(srep, + (const char *)msgb_data(srep->buffer), msgb_length(srep->buffer)); + + msgb_trim(srep->buffer, 0); + + return rc; +} + /*** statsd reporter ***/ struct stats_reporter *stats_reporter_create_statsd(const char *name) @@ -275,6 +303,7 @@ static int stats_reporter_statsd_open(struct stats_reporter *srep) { int sock; int rc; + int buffer_size = STATS_DEFAULT_STATSD_BUFLEN; if (srep->fd != -1) stats_reporter_statsd_close(srep); @@ -291,6 +320,13 @@ static int stats_reporter_statsd_open(struct stats_reporter *srep) 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: @@ -306,8 +342,12 @@ static int stats_reporter_statsd_close(struct stats_reporter *srep) if (srep->fd == -1) return -EBADF; + stats_reporter_send_buffer(srep); + rc = close(srep->fd); srep->fd = -1; + msgb_free(srep->buffer); + srep->buffer = NULL; return rc == -1 ? -errno : 0; } @@ -315,30 +355,62 @@ static int stats_reporter_statsd_send(struct stats_reporter *srep, const char *name1, int index1, const char *name2, int value, const char *unit) { - char buf[256]; - int nchars, rc; + 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.%3$d.%4$s:%5$d|%6$s"; + fmt = "%1$s.%2$s.%6$d.%3$s:%4$d|%5$s"; else - fmt = "%1$s.%2$s.%4$s:%5$d|%6$s"; + fmt = "%1$s.%2$s.%3$s:%4$d|%5$s"; } else { - fmt = "%1$s.%4$s:%5$d|%6$s"; + fmt = "%1$s.%2$0.0s%3$s:%4$d|%5$s"; } if (!srep->name_prefix) fmt += 5; /* skip prefix part */ - nchars = snprintf(buf, sizeof(buf), fmt, - srep->name_prefix, name1, index1, name2, - value, unit); + if (srep->agg_enabl