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 --- .gitignore | 1 + include/Makefile.am | 1 + include/osmocom/core/stat_item.h | 104 +++++++++++++++++++ src/Makefile.am | 2 +- src/stat_item.c | 215 +++++++++++++++++++++++++++++++++++++++ tests/Makefile.am | 7 +- tests/stats/stats_test.c | 198 +++++++++++++++++++++++++++++++++++ tests/stats/stats_test.ok | 0 tests/testsuite.at | 6 ++ 9 files changed, 531 insertions(+), 3 deletions(-) create mode 100644 include/osmocom/core/stat_item.h create mode 100644 src/stat_item.c create mode 100644 tests/stats/stats_test.c create mode 100644 tests/stats/stats_test.ok diff --git a/.gitignore b/.gitignore index 24c3af7a..598f88af 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ tests/testsuite.dir/ tests/testsuite.log tests/utils/utils_test +tests/stats/stats_test tests/kasumi/kasumi_test tests/sms/sms_test tests/timer/timer_test diff --git a/include/Makefile.am b/include/Makefile.am index 52c6a38f..7f8dd777 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -28,6 +28,7 @@ nobase_include_HEADERS = \ osmocom/core/prim.h \ osmocom/core/process.h \ osmocom/core/rate_ctr.h \ + osmocom/core/stat_item.h \ osmocom/core/select.h \ osmocom/core/signal.h \ osmocom/core/socket.h \ diff --git a/include/osmocom/core/stat_item.h b/include/osmocom/core/stat_item.h new file mode 100644 index 00000000..e166579f --- /dev/null +++ b/include/osmocom/core/stat_item.h @@ -0,0 +1,104 @@ +#pragma once + +/*! \defgroup stat_item Statistics value item + * @{ + */ + +/*! \file stat_item.h */ + +#include + +#include + +struct stat_item_desc; + +/*! \brief data we keep for each actual value */ +struct stat_item { + const struct stat_item_desc *desc; + /*! \brief the index of the freshest value */ + int32_t last_value_index; + /*! \brief offset to the freshest value in the value fifo */ + int16_t last_offs; + /*! \brief value fifo */ + int32_t values[0]; +}; + +/*! \brief statistics value description */ +struct stat_item_desc { + const char *name; /*!< \brief name of the item */ + const char *description;/*!< \brief description of the item */ + const char *unit; /*!< \brief unit of a value */ + unsigned int num_values;/*!< \brief number of values to store */ + int32_t default_value; +}; + +/*! \brief description of a statistics value group */ +struct stat_item_group_desc { + /*! \brief The prefix to the name of all values in this group */ + const char *group_name_prefix; + /*! \brief The human-readable description of the group */ + const char *group_description; + /*! \brief The number of values in this group */ + const unsigned int num_items; + /*! \brief Pointer to array of value names */ + const struct stat_item_desc *item_desc; +}; + +/*! \brief One instance of a counter group class */ +struct stat_item_group { + /*! \brief Linked list of all value groups in the system */ + struct llist_head list; + /*! \brief Pointer to the counter group class */ + const struct stat_item_group_desc *desc; + /*! \brief The index of this value group within its class */ + unsigned int idx; + /*! \brief Actual counter structures below */ + struct stat_item *items[0]; +}; + +struct stat_item_group *stat_item_group_alloc( + void *ctx, + const struct stat_item_group_desc *desc, + unsigned int idx); + +void stat_item_group_free(struct stat_item_group *grp); + +void stat_item_set(struct stat_item *val, int32_t value); + +int stat_item_init(void *tall_ctx); + +struct stat_item_group *stat_item_get_group_by_name_idx( + const char *name, const unsigned int idx); + +const struct stat_item *stat_item_get_by_name( + const struct stat_item_group *valg, const char *name); + +/*! \brief Retrieve the next value from the stat_item object. + * If a new value has been set, it is returned. The idx is used to decide + * which value to return. + * On success, *idx is updated to refer to the next unread value. If + * values have been missed due to FIFO overflow, *idx is incremented by + * (1 + num_lost). + * This way, the stat_item object can be kept stateless from the reader's + * perspective and therefore be used by several backends simultaneously. + * + * \param val the stat_item object + * \param idx identifies the next value to be read + * \param value a pointer to store the value + * \returns the increment of the index (0: no value has been read, + * 1: one value has been taken, + * (1+n): n values have been skipped, one has been taken) + */ +int stat_item_get_next(const struct stat_item *val, int32_t *idx, int32_t *value); + +/*! \brief Get the last (freshest) value */ +static int32_t stat_item_get_last(const struct stat_item *val); + +/*! \brief Skip all values of the item and update idx accordingly */ +int stat_item_discard(const struct stat_item *val, int32_t *idx); + +static inline int32_t stat_item_get_last(const struct stat_item *val) +{ + return val->values[val->last_offs]; +} +/*! @} */ 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; +} + +/*! @} */ diff --git a/tests/Makefile.am b/tests/Makefile.am index cf0977de..223535ff 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -9,7 +9,7 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \ kasumi/kasumi_test logging/logging_test fr/fr_test \ loggingrb/loggingrb_test strrb/strrb_test \ vty/vty_test comp128/comp128_test utils/utils_test \ - smscb/gsm0341_test + smscb/gsm0341_test stats/stats_test if ENABLE_MSGFILE check_PROGRAMS += msgfile/msgfile_test @@ -18,6 +18,9 @@ endif utils_utils_test_SOURCES = utils/utils_test.c utils_utils_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la +stats_stats_test_SOURCES = stats/stats_test.c +stats_stats_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la + a5_a5_test_SOURCES = a5/a5_test.c a5_a5_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libgsmint.la @@ -120,7 +123,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \ fr/fr_test.ok loggingrb/logging_test.ok \ loggingrb/logging_test.err strrb/strrb_test.ok \ vty/vty_test.ok comp128/comp128_test.ok \ - utils/utils_test.ok + utils/utils_test.ok stats/stats_test.ok DISTCLEANFILES = atconfig diff --git a/tests/stats/stats_test.c b/tests/stats/stats_test.c new file mode 100644 index 00000000..b4143853 --- /dev/null +++ b/tests/stats/stats_test.c @@ -0,0 +1,198 @@ +/* tests for statistics */ +/* + * (C) 2015 Sysmocom s.m.f.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 + +#include + +static void stat_test(void) +{ + enum test_items { + TEST_A_ITEM, + TEST_B_ITEM, + }; + + static const struct stat_item_desc item_description[] = { + { "item.a", "The A value", "ma", 4, -1 }, + { "item.b", "The B value", "kb", 7, -1 }, + }; + + static const struct stat_item_group_desc statg_desc = { + .group_name_prefix = "test.one", + .group_description = "Test number 1", + .num_items = ARRAY_SIZE(item_description), + .item_desc = item_description, + }; + + struct stat_item_group *statg = + stat_item_group_alloc(NULL, &statg_desc, 0); + + struct stat_item_group *sgrp2; + const struct stat_item *sitem1, *sitem2; + int rc; + int32_t value; + int32_t rd_a = 0; + int32_t rd_b = 0; + int i; + + OSMO_ASSERT(statg != NULL); + + sgrp2 = stat_item_get_group_by_name_idx("test.one", 0); + OSMO_ASSERT(sgrp2 == statg); + + sgrp2 = stat_item_get_group_by_name_idx("test.one", 1); + OSMO_ASSERT(sgrp2 == NULL); + + sgrp2 = stat_item_get_group_by_name_idx("test.two", 0); + OSMO_ASSERT(sgrp2 == NULL); + + sitem1 = stat_item_get_by_name(statg, "item.c"); + OSMO_ASSERT(sitem1 == NULL); + + sitem1 = stat_item_get_by_name(statg, "item.a"); + OSMO_ASSERT(sitem1 != NULL); + OSMO_ASSERT(sitem1 == statg->items[TEST_A_ITEM]); + + sitem2 = stat_item_get_by_name(statg, "item.b"); + OSMO_ASSERT(sitem2 != NULL); + OSMO_ASSERT(sitem2 != sitem1); + OSMO_ASSERT(sitem2 == statg->items[TEST_B_ITEM]); + + value = stat_item_get_last(statg->items[TEST_A_ITEM]); + OSMO_ASSERT(value == -1); + + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 0); + + stat_item_set(statg->items[TEST_A_ITEM], 1); + + value = stat_item_get_last(statg->items[TEST_A_ITEM]); + OSMO_ASSERT(value == 1); + + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 1); + OSMO_ASSERT(value == 1); + + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 0); + + for (i = 2; i <= 32; i++) { + stat_item_set(statg->items[TEST_A_ITEM], i); + stat_item_set(statg->items[TEST_B_ITEM], 1000 + i); + + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 1); + OSMO_ASSERT(value == i); + + rc = stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc == 1); + OSMO_ASSERT(value == 1000 + i); + } + + /* Keep 2 in FIFO */ + stat_item_set(statg->items[TEST_A_ITEM], 33); + stat_item_set(statg->items[TEST_B_ITEM], 1000 + 33); + + for (i = 34; i <= 64; i++) { + stat_item_set(statg->items[TEST_A_ITEM], i); + stat_item_set(statg->items[TEST_B_ITEM], 1000 + i); + + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 1); + OSMO_ASSERT(value == i-1); + + rc = stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc == 1); + OSMO_ASSERT(value == 1000 + i-1); + } + + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 1); + OSMO_ASSERT(value == 64); + + rc = stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc == 1); + OSMO_ASSERT(value == 1000 + 64); + + /* Overrun FIFOs */ + for (i = 65; i <= 96; i++) { + stat_item_set(statg->items[TEST_A_ITEM], i); + stat_item_set(statg->items[TEST_B_ITEM], 1000 + i); + } + + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 93 - 65 + 1); + OSMO_ASSERT(value == 93); + + for (i = 94; i <= 96; i++) { + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 1); + OSMO_ASSERT(value == i); + } + + rc = stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc == 90 - 65 + 1); + OSMO_ASSERT(value == 1000 + 90); + + for (i = 91; i <= 96; i++) { + rc = stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc == 1); + OSMO_ASSERT(value == 1000 + i); + } + + /* Test Discard */ + stat_item_set(statg->items[TEST_A_ITEM], 97); + rc = stat_item_discard(statg->items[TEST_A_ITEM], &rd_a); + OSMO_ASSERT(rc == 1); + + rc = stat_item_discard(statg->items[TEST_A_ITEM], &rd_a); + OSMO_ASSERT(rc == 0); + + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 0); + + stat_item_set(statg->items[TEST_A_ITEM], 98); + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 1); + OSMO_ASSERT(value == 98); + + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 0); + + stat_item_group_free(statg); + + sgrp2 = stat_item_get_group_by_name_idx("test.one", 0); + OSMO_ASSERT(sgrp2 == NULL); +} + +int main(int argc, char **argv) +{ + static const struct log_info log_info = {}; + log_init(&log_info, NULL); + + stat_item_init(NULL); + + stat_test(); + return 0; +} diff --git a/tests/stats/stats_test.ok b/tests/stats/stats_test.ok new file mode 100644 index 00000000..e69de29b diff --git a/tests/testsuite.at b/tests/testsuite.at index fe30363c..a5427986 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -136,6 +136,12 @@ cat $abs_srcdir/utils/utils_test.ok > expout AT_CHECK([$abs_top_builddir/tests/utils/utils_test], [0], [expout], [ignore]) AT_CLEANUP +AT_SETUP([stats]) +AT_KEYWORDS([stats]) +cat $abs_srcdir/stats/stats_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/stats/stats_test], [0], [expout], [ignore]) +AT_CLEANUP + AT_SETUP([bssgp-fc]) AT_KEYWORDS([bssgp-fc]) cat $abs_srcdir/gb/bssgp_fc_tests.ok > expout -- 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 --- include/osmocom/vty/misc.h | 6 +++++- src/vty/utils.c | 22 ++++++++++++++++++++++ tests/Makefile.am | 8 ++++---- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/include/osmocom/vty/misc.h b/include/osmocom/vty/misc.h index db552e77..ad878db2 100644 --- a/include/osmocom/vty/misc.h +++ b/include/osmocom/vty/misc.h @@ -2,6 +2,7 @@ #include #include +#include #include #define VTY_DO_LOWER 1 @@ -10,7 +11,10 @@ char *vty_cmd_string_from_valstr(void *ctx, const struct value_string *vals, const char *end, int do_lower); void vty_out_rate_ctr_group(struct vty *vty, const char *prefix, - struct rate_ctr_group *ctrg); + struct rate_ctr_group *ctrg); + +void vty_out_stat_item_group(struct vty *vty, const char *prefix, + struct stat_item_group *statg); int osmo_vty_write_config_file(const char *filename); int osmo_vty_save_config_file(void); 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, diff --git a/tests/Makefile.am b/tests/Makefile.am index 223535ff..6065c0d0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -67,19 +67,19 @@ ussd_ussd_test_SOURCES = ussd/ussd_test.c ussd_ussd_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la gb_bssgp_fc_test_SOURCES = gb/bssgp_fc_test.c -gb_bssgp_fc_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gb/libosmogb.la +gb_bssgp_fc_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gb/libosmogb.la $(top_builddir)/src/vty/libosmovty.la gb_gprs_bssgp_test_SOURCES = gb/gprs_bssgp_test.c -gb_gprs_bssgp_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gb/libosmogb.la $(LIBRARY_DL) +gb_gprs_bssgp_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gb/libosmogb.la $(top_builddir)/src/vty/libosmovty.la $(LIBRARY_DL) gb_gprs_ns_test_SOURCES = gb/gprs_ns_test.c -gb_gprs_ns_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gb/libosmogb.la $(LIBRARY_DL) +gb_gprs_ns_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gb/libosmogb.la $(top_builddir)/src/vty/libosmovty.la $(LIBRARY_DL) logging_logging_test_SOURCES = logging/logging_test.c logging_logging_test_LDADD = $(top_builddir)/src/libosmocore.la fr_fr_test_SOURCES = fr/fr_test.c -fr_fr_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gb/libosmogb.la $(LIBRARY_DL) +fr_fr_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gb/libosmogb.la $(top_builddir)/src/vty/libosmovty.la $(LIBRARY_DL) loggingrb_loggingrb_test_SOURCES = loggingrb/loggingrb_test.c loggingrb_loggingrb_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/vty/libosmovty.la -- 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 --- include/osmocom/gprs/gprs_ns.h | 2 ++ src/gb/gprs_ns.c | 36 ++++++++++++++++++++++++++++++++++++ src/gb/gprs_ns_vty.c | 4 +++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/include/osmocom/gprs/gprs_ns.h b/include/osmocom/gprs/gprs_ns.h index d5a605df..42e0d884 100644 --- a/include/osmocom/gprs/gprs_ns.h +++ b/include/osmocom/gprs/gprs_ns.h @@ -118,6 +118,7 @@ struct gprs_nsvc { struct osmo_timer_list timer; enum nsvc_timer_mode timer_mode; + struct timeval timer_started; int alive_retries; unsigned int remote_end_is_sgsn:1; @@ -125,6 +126,7 @@ struct gprs_nsvc { unsigned int nsvci_is_valid:1; struct rate_ctr_group *ctrg; + struct stat_item_group *statg; /*! \brief which link-layer are we based on? */ enum gprs_ns_ll ll; 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 --- include/osmocom/core/stat_item.h | 28 +++++++++++----- src/stat_item.c | 71 ++++++++++++++++++++++++++-------------- tests/stats/stats_test.c | 43 ++++++++++++++++-------- 3 files changed, 95 insertions(+), 47 deletions(-) diff --git a/include/osmocom/core/stat_item.h b/include/osmocom/core/stat_item.h index e166579f..003c9e07 100644 --- a/include/osmocom/core/stat_item.h +++ b/include/osmocom/core/stat_item.h @@ -12,6 +12,13 @@ struct stat_item_desc; +#define STAT_ITEM_NOVALUE_ID 0 + +struct stat_item_value { + int32_t id; + int32_t value; +}; + /*! \brief data we keep for each actual value */ struct stat_item { const struct stat_item_desc *desc; @@ -20,7 +27,7 @@ struct stat_item { /*! \brief offset to the freshest value in the value fifo */ int16_t last_offs; /*! \brief value fifo */ - int32_t values[0]; + struct stat_item_value values[0]; }; /*! \brief statistics value description */ @@ -61,9 +68,9 @@ struct stat_item_group *stat_item_group_alloc( const struct stat_item_group_desc *desc, unsigned int idx); -void stat_item_group_free(struct stat_item_group *grp); +void stat_item_group_free(struct stat_item_group *statg); -void stat_item_set(struct stat_item *val, int32_t value); +void stat_item_set(struct stat_item *item, int32_t value); int stat_item_init(void *tall_ctx); @@ -71,7 +78,7 @@ struct stat_item_group *stat_item_get_group_by_name_idx( const char *name, const unsigned int idx); const struct stat_item *stat_item_get_by_name( - const struct stat_item_group *valg, const char *name); + const struct stat_item_group *statg, const char *name); /*! \brief Retrieve the next value from the stat_item object. * If a new value has been set, it is returned. The idx is used to decide @@ -89,16 +96,19 @@ const struct stat_item *stat_item_get_by_name( * 1: one value has been taken, * (1+n): n values have been skipped, one has been taken) */ -int stat_item_get_next(const struct stat_item *val, int32_t *idx, int32_t *value); +int stat_item_get_next(const struct stat_item *item, int32_t *idx, int32_t *value); /*! \brief Get the last (freshest) value */ -static int32_t stat_item_get_last(const struct stat_item *val); +static int32_t stat_item_get_last(const struct stat_item *item); /*! \brief Skip all values of the item and update idx accordingly */ -int stat_item_discard(const struct stat_item *val, int32_t *idx); +int stat_item_discard(const struct stat_item *item, int32_t *idx); + +/*! \brief Skip all values of all items and update idx accordingly */ +int stat_item_discard_all(int32_t *idx); -static inline int32_t stat_item_get_last(const struct stat_item *val) +static inline int32_t stat_item_get_last(const struct stat_item *item) { - return val->values[val->last_offs]; + return item->values[item->last_offs].value; } /*! @} */ 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) diff --git a/tests/stats/stats_test.c b/tests/stats/stats_test.c index b4143853..9da49a4f 100644 --- a/tests/stats/stats_test.c +++ b/tests/stats/stats_test.c @@ -91,7 +91,7 @@ static void stat_test(void) OSMO_ASSERT(value == 1); rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); - OSMO_ASSERT(rc == 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == 1); rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); @@ -102,11 +102,11 @@ static void stat_test(void) stat_item_set(statg->items[TEST_B_ITEM], 1000 + i); rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); - OSMO_ASSERT(rc == 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == i); rc = stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); - OSMO_ASSERT(rc == 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == 1000 + i); } @@ -119,20 +119,20 @@ static void stat_test(void) stat_item_set(statg->items[TEST_B_ITEM], 1000 + i); rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); - OSMO_ASSERT(rc == 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == i-1); rc = stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); - OSMO_ASSERT(rc == 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == 1000 + i-1); } rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); - OSMO_ASSERT(rc == 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == 64); rc = stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); - OSMO_ASSERT(rc == 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == 1000 + 64); /* Overrun FIFOs */ @@ -142,29 +142,29 @@ static void stat_test(void) } rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); - OSMO_ASSERT(rc == 93 - 65 + 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == 93); for (i = 94; i <= 96; i++) { rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); - OSMO_ASSERT(rc == 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == i); } rc = stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); - OSMO_ASSERT(rc == 90 - 65 + 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == 1000 + 90); for (i = 91; i <= 96; i++) { rc = stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); - OSMO_ASSERT(rc == 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == 1000 + i); } - /* Test Discard */ + /* Test Discard (single item) */ stat_item_set(statg->items[TEST_A_ITEM], 97); rc = stat_item_discard(statg->items[TEST_A_ITEM], &rd_a); - OSMO_ASSERT(rc == 1); + OSMO_ASSERT(rc > 0); rc = stat_item_discard(statg->items[TEST_A_ITEM], &rd_a); OSMO_ASSERT(rc == 0); @@ -174,12 +174,27 @@ static void stat_test(void) stat_item_set(statg->items[TEST_A_ITEM], 98); rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); - OSMO_ASSERT(rc == 1); + OSMO_ASSERT(rc > 0); OSMO_ASSERT(value == 98); rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); OSMO_ASSERT(rc == 0); + /* Test Discard (all items) */ + stat_item_set(statg->items[TEST_A_ITEM], 99); + stat_item_set(statg->items[TEST_A_ITEM], 100); + stat_item_set(statg->items[TEST_A_ITEM], 101); + stat_item_set(statg->items[TEST_B_ITEM], 99); + stat_item_set(statg->items[TEST_B_ITEM], 100); + + rc = stat_item_discard_all(&rd_a); + rc = stat_item_discard_all(&rd_b); + + rc = stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 0); + rc = stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc == 0); + stat_item_group_free(statg); sgrp2 = stat_item_get_group_by_name_idx("test.one", 0); -- 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 --- include/osmocom/core/rate_ctr.h | 19 +++++++++++++++++++ src/rate_ctr.c | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/include/osmocom/core/rate_ctr.h b/include/osmocom/core/rate_ctr.h index 821c7cfd..f3c03de3 100644 --- a/include/osmocom/core/rate_ctr.h +++ b/include/osmocom/core/rate_ctr.h @@ -30,6 +30,7 @@ struct rate_ctr_per_intv { /*! \brief data we keep for each actual value */ struct rate_ctr { uint64_t current; /*!< \brief current value */ + uint64_t previous; /*!< \brief previous value, used for delta */ /*! \brief per-interval data */ struct rate_ctr_per_intv intv[RATE_CTR_INTV_NUM]; }; @@ -78,9 +79,27 @@ static inline void rate_ctr_inc(struct rate_ctr *ctr) rate_ctr_add(ctr, 1); } +/*! \brief Return the counter difference since the last call to this function */ +int64_t rate_ctr_difference(struct rate_ctr *ctr); + int rate_ctr_init(void *tall_ctx); struct rate_ctr_group *rate_ctr_get_group_by_name_idx(const char *name, const unsigned int idx); const struct rate_ctr *rate_ctr_get_by_name(const struct rate_ctr_group *ctrg, const char *name); +typedef int (*rate_ctr_handler_t)( + struct rate_ctr_group *, struct rate_ctr *, + const struct rate_ctr_desc *, void *); +typedef int (*rate_ctr_group_handler_t)(struct rate_ctr_group *, void *); + + +/*! \brief Iterate over all counters + * \param[in] handle_item Call-back function, aborts if rc < 0 + * \param[in] data Private data handed through to \a handle_counter + */ +int rate_ctr_for_each_counter(struct rate_ctr_group *ctrg, + rate_ctr_handler_t handle_counter, void *data); + +int rate_ctr_for_each_group(rate_ctr_group_handler_t handle_group, void *data); + /*! @} */ 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 --- include/osmocom/core/stat_item.h | 14 ++++++++++++++ src/stat_item.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/include/osmocom/core/stat_item.h b/include/osmocom/core/stat_item.h index 003c9e07..0786a3ba 100644 --- a/include/osmocom/core/stat_item.h +++ b/include/osmocom/core/stat_item.h @@ -107,6 +107,20 @@ int stat_item_discard(const struct stat_item *item, int32_t *idx); /*! \brief Skip all values of all items and update idx accordingly */ int stat_item_discard_all(int32_t *idx); +typedef int (*stat_item_handler_t)( + struct stat_item_group *, struct stat_item *, void *); + +typedef int (*stat_item_group_handler_t)(struct stat_item_group *, void *); + +/*! \brief Iteate over all items + * \param[in] handle_item Call-back function, aborts if rc < 0 + * \param[in] data Private data handed through to \a handle_item + */ +int stat_item_for_each_item(struct stat_item_group *statg, + stat_item_handler_t handle_item, void *data); + +int stat_item_for_each_group(stat_item_group_handler_t handle_group, void *data); + static inline int32_t stat_item_get_last(const struct stat_item *item) { return item->values[item->last_offs].value; 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 e5b0fe2e3c84dd0de7021d65d416356612db4260 Mon Sep 17 00:00:00 2001 From: Jacob Erlbeck Date: Mon, 19 Oct 2015 15:00:59 +0200 Subject: core: Update osmo_counters_for_each doc Fix type and add a note about the semantics of the handler's return code. Sponsored-by: On-Waves ehf --- include/osmocom/core/statistics.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/osmocom/core/statistics.h b/include/osmocom/core/statistics.h index de250bec..a9a623d3 100644 --- a/include/osmocom/core/statistics.h +++ b/include/osmocom/core/statistics.h @@ -37,8 +37,8 @@ struct osmo_counter *osmo_counter_alloc(const char *name); */ void osmo_counter_free(struct osmo_counter *ctr); -/*! \brief Iteate over all counters - * \param[in] handle_counter Call-back function +/*! \brief Iterate over all counters + * \param[in] handle_counter Call-back function, aborts if rc < 0 * \param[in] data Private dtata handed through to \a handle_counter */ int osmo_counters_for_each(int (*handle_counter)(struct osmo_counter *, void *), void *data); -- 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(-) 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 --- include/osmocom/vty/misc.h | 2 ++ src/vty/utils.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/include/osmocom/vty/misc.h b/include/osmocom/vty/misc.h index ad878db2..99c2ee68 100644 --- a/include/osmocom/vty/misc.h +++ b/include/osmocom/vty/misc.h @@ -16,5 +16,7 @@ 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); +void vty_out_statistics_full(struct vty *vty, const char *prefix); + int osmo_vty_write_config_file(const char *filename); int osmo_vty_save_config_file(void); 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 --- include/Makefile.am | 1 + include/osmocom/vty/stats.h | 3 +++ src/vty/Makefile.am | 2 +- src/vty/stats_vty.c | 52 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 include/osmocom/vty/stats.h create mode 100644 src/vty/stats_vty.c diff --git a/include/Makefile.am b/include/Makefile.am index 7f8dd777..af189c36 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -113,6 +113,7 @@ nobase_include_HEADERS += \ osmocom/vty/buffer.h \ osmocom/vty/command.h \ osmocom/vty/logging.h \ + osmocom/vty/stats.h \ osmocom/vty/misc.h \ osmocom/vty/telnet_interface.h \ osmocom/vty/vector.h \ diff --git a/include/osmocom/vty/stats.h b/include/osmocom/vty/stats.h new file mode 100644 index 00000000..eaf854e5 --- /dev/null +++ b/include/osmocom/vty/stats.h @@ -0,0 +1,3 @@ +#pragma once + +void stats_vty_add_cmds(); 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 --- include/Makefile.am | 1 + include/osmocom/core/stats.h | 70 ++++++++ src/Makefile.am | 2 +- src/stats.c | 387 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 include/osmocom/core/stats.h create mode 100644 src/stats.c diff --git a/include/Makefile.am b/include/Makefile.am index af189c36..20735800 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -22,6 +22,7 @@ nobase_include_HEADERS = \ osmocom/core/linuxrbtree.h \ osmocom/core/logging.h \ osmocom/core/loggingrb.h \ + osmocom/core/stats.h \ osmocom/core/macaddr.h \ osmocom/core/msgb.h \ osmocom/core/panic.h \ diff --git a/include/osmocom/core/stats.h b/include/osmocom/core/stats.h new file mode 100644 index 00000000..b4fb727c --- /dev/null +++ b/include/osmocom/core/stats.h @@ -0,0 +1,70 @@ +/* (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. + * + */ +#pragma once + +#include +#include + +enum stats_reporter_type { + STATS_REPORTER_STATSD, +}; + +struct stats_reporter { + enum stats_reporter_type type; + char *name; + + /* config */ + int enabled; + int interval; + char *name_prefix; + char *dest_addr_str; + char *bind_addr_str; + int dest_port; + int mtu; + + /* state */ + int running; + struct sockaddr dest_addr; + int dest_addr_len; + struct sockaddr bind_addr; + int bind_addr_len; + int fd; + + struct llist_head list; +}; + +void stats_init(void *ctx); +int stats_report(); + +struct stats_reporter *stats_reporter_alloc(enum stats_reporter_type type, + const char *name); +void stats_reporter_free(struct stats_reporter *srep); +struct stats_reporter *stats_reporter_create_statsd(const char *name); + +struct stats_reporter *stats_reporter_find(enum stats_reporter_type type, + const char *name); + +int stats_reporter_set_remote_addr(struct stats_reporter *srep, const char *addr); +int stats_reporter_set_remote_port(struct stats_reporter *srep, int port); +int stats_reporter_set_local_addr(struct stats_reporter *srep, const char *addr); +int stats_reporter_set_interval(struct stats_reporter *srep, int interval); +int stats_reporter_set_name_prefix(struct stats_reporter *srep, const char *prefix); +int stats_reporter_enable(struct stats_reporter *srep); +int stats_reporter_disable(struct stats_reporter *srep); 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