diff options
author | Holger Hans Peter Freyther <holger@moiji-mobile.com> | 2015-11-02 15:57:34 +0100 |
---|---|---|
committer | Holger Hans Peter Freyther <holger@moiji-mobile.com> | 2015-11-02 15:57:34 +0100 |
commit | d7b0577d7d30139491b5cfeffb467440f9e88818 (patch) | |
tree | 0666fa8f415a47e8f5645dd87ad8844251251c6a | |
parent | c84851bccc2e5e60536afa474a5f13134a3b79c9 (diff) | |
parent | 8f0374f7521376bdb721e821047e8a6a4a727283 (diff) |
Merge branch 'jerlbeck/wip/stats'
* This adds a new counter type (to measure time or delay)
* A statsd reporting backend. This can be fed into graphite
or similar tools.
* A periodic log backend for performance values
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | TODO-RELEASE | 2 | ||||
-rw-r--r-- | include/Makefile.am | 3 | ||||
-rw-r--r-- | include/osmocom/core/logging.h | 3 | ||||
-rw-r--r-- | include/osmocom/core/rate_ctr.h | 21 | ||||
-rw-r--r-- | include/osmocom/core/stat_item.h | 130 | ||||
-rw-r--r-- | include/osmocom/core/statistics.h | 8 | ||||
-rw-r--r-- | include/osmocom/core/stats.h | 110 | ||||
-rw-r--r-- | include/osmocom/gprs/gprs_ns.h | 2 | ||||
-rw-r--r-- | include/osmocom/vty/command.h | 10 | ||||
-rw-r--r-- | include/osmocom/vty/misc.h | 8 | ||||
-rw-r--r-- | include/osmocom/vty/stats.h | 3 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/gb/gprs_bssgp.c | 2 | ||||
-rw-r--r-- | src/gb/gprs_ns.c | 38 | ||||
-rw-r--r-- | src/gb/gprs_ns_vty.c | 4 | ||||
-rw-r--r-- | src/logging.c | 5 | ||||
-rw-r--r-- | src/rate_ctr.c | 41 | ||||
-rw-r--r-- | src/stat_item.c | 268 | ||||
-rw-r--r-- | src/statistics.c | 8 | ||||
-rw-r--r-- | src/stats.c | 696 | ||||
-rw-r--r-- | src/vty/Makefile.am | 2 | ||||
-rw-r--r-- | src/vty/stats_vty.c | 430 | ||||
-rw-r--r-- | src/vty/utils.c | 125 | ||||
-rw-r--r-- | tests/Makefile.am | 15 | ||||
-rw-r--r-- | tests/stats/stats_test.c | 213 | ||||
-rw-r--r-- | tests/stats/stats_test.ok | 0 | ||||
-rw-r--r-- | tests/testsuite.at | 6 | ||||
-rw-r--r-- | tests/vty/vty_test.ok | 6 |
29 files changed, 2135 insertions, 27 deletions
@@ -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/TODO-RELEASE b/TODO-RELEASE index 43b1e8ef..4d22f958 100644 --- a/TODO-RELEASE +++ b/TODO-RELEASE @@ -1 +1,3 @@ #library what description / commit summary line +libosmovty abi-change stats/vty: Add stats configuration (enum node_type has changed) +libosmovty abi-change vty: Add reserved nodes to enum node_type diff --git a/include/Makefile.am b/include/Makefile.am index 52c6a38f..20735800 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -22,12 +22,14 @@ 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 \ 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 \ @@ -112,6 +114,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/core/logging.h b/include/osmocom/core/logging.h index ba41762f..1c159d0b 100644 --- a/include/osmocom/core/logging.h +++ b/include/osmocom/core/logging.h @@ -69,7 +69,8 @@ void logp(int subsys, const char *file, int line, int cont, const char *format, #define DLSMS -7 #define DLCTRL -8 #define DLGTP -9 -#define OSMO_NUM_DLIB 9 +#define DLSTATS -10 +#define OSMO_NUM_DLIB 10 struct log_category { uint8_t loglevel; diff --git a/include/osmocom/core/rate_ctr.h b/include/osmocom/core/rate_ctr.h index 821c7cfd..03b1bfbe 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]; }; @@ -46,6 +47,8 @@ struct rate_ctr_group_desc { const char *group_name_prefix; /*! \brief The human-readable description of the group */ const char *group_description; + /*! \brief The class to which this group belongs */ + int class_id; /*! \brief The number of counters in this group */ const unsigned int num_ctr; /*! \brief Pointer to array of counter names */ @@ -78,9 +81,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/include/osmocom/core/stat_item.h b/include/osmocom/core/stat_item.h new file mode 100644 index 00000000..c2ad8cfd --- /dev/null +++ b/include/osmocom/core/stat_item.h @@ -0,0 +1,130 @@ +#pragma once + +/*! \defgroup osmo_stat_item Statistics value item + * @{ + */ + +/*! \file stat_item.h */ + +#include <stdint.h> + +#include <osmocom/core/linuxlist.h> + +struct osmo_stat_item_desc; + +#define STAT_ITEM_NOVALUE_ID 0 + +struct osmo_stat_item_value { + int32_t id; + int32_t value; +}; + +/*! \brief data we keep for each actual value */ +struct osmo_stat_item { + const struct osmo_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 */ + struct osmo_stat_item_value values[0]; +}; + +/*! \brief statistics value description */ +struct osmo_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 osmo_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 class to which this group belongs */ + int class_id; + /*! \brief The number of values in this group */ + const unsigned int num_items; + /*! \brief Pointer to array of value names */ + const struct osmo_stat_item_desc *item_desc; +}; + +/*! \brief One instance of a counter group class */ +struct osmo_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 osmo_stat_item_group_desc *desc; + /*! \brief The index of this value group within its class */ + unsigned int idx; + /*! \brief Actual counter structures below */ + struct osmo_stat_item *items[0]; +}; + +struct osmo_stat_item_group *osmo_stat_item_group_alloc( + void *ctx, + const struct osmo_stat_item_group_desc *desc, + unsigned int idx); + +void osmo_stat_item_group_free(struct osmo_stat_item_group *statg); + +void osmo_stat_item_set(struct osmo_stat_item *item, int32_t value); + +int osmo_stat_item_init(void *tall_ctx); + +struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idx( + const char *name, const unsigned int idx); + +const struct osmo_stat_item *osmo_stat_item_get_by_name( + const struct osmo_stat_item_group *statg, const char *name); + +/*! \brief Retrieve the next value from the osmo_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 osmo_stat_item object can be kept stateless from the reader's + * perspective and therefore be used by several backends simultaneously. + * + * \param val the osmo_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 osmo_stat_item_get_next(const struct osmo_stat_item *item, int32_t *idx, int32_t *value); + +/*! \brief Get the last (freshest) value */ +static int32_t osmo_stat_item_get_last(const struct osmo_stat_item *item); + +/*! \brief Skip all values of the item and update idx accordingly */ +int osmo_stat_item_discard(const struct osmo_stat_item *item, int32_t *idx); + +/*! \brief Skip all values of all items and update idx accordingly */ +int osmo_stat_item_discard_all(int32_t *idx); + +typedef int (*osmo_stat_item_handler_t)( + struct osmo_stat_item_group *, struct osmo_stat_item *, void *); + +typedef int (*osmo_stat_item_group_handler_t)(struct osmo_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 osmo_stat_item_for_each_item(struct osmo_stat_item_group *statg, + osmo_stat_item_handler_t handle_item, void *data); + +int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, void *data); + +static inline int32_t osmo_stat_item_get_last(const struct osmo_stat_item *item) +{ + return item->values[item->last_offs].value; +} +/*! @} */ diff --git a/include/osmocom/core/statistics.h b/include/osmocom/core/statistics.h index de250bec..1e472ffd 100644 --- a/include/osmocom/core/statistics.h +++ b/include/osmocom/core/statistics.h @@ -9,6 +9,7 @@ struct osmo_counter { const char *name; /*!< \brief human-readable name */ const char *description; /*!< \brief humn-readable description */ unsigned long value; /*!< \brief current value */ + unsigned long previous; /*!< \brief previous value */ }; /*! \brief Increment counter */ @@ -37,8 +38,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); @@ -48,3 +49,6 @@ int osmo_counters_for_each(int (*handle_counter)(struct osmo_counter *, void *), * \returns pointer to counter (\ref osmo_counter) or NULL otherwise */ struct osmo_counter *osmo_counter_get_by_name(const char *name); + +/*! \brief Return the counter difference since the last call to this function */ +int osmo_counter_difference(struct osmo_counter *ctr); diff --git a/include/osmocom/core/stats.h b/include/osmocom/core/stats.h new file mode 100644 index 00000000..731fdb9b --- /dev/null +++ b/include/osmocom/core/stats.h @@ -0,0 +1,110 @@ +/* (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 <sys/socket.h> +#include <osmocom/core/linuxlist.h> + +struct msgb; +struct osmo_stat_item_group; +struct osmo_stat_item_desc; +struct rate_ctr_group; +struct rate_ctr_desc; + +enum osmo_stats_class { + OSMO_STATS_CLASS_UNKNOWN, + OSMO_STATS_CLASS_GLOBAL, + OSMO_STATS_CLASS_PEER, + OSMO_STATS_CLASS_SUBSCRIBER, +}; + +enum osmo_stats_reporter_type { + OSMO_STATS_REPORTER_STATSD, + OSMO_STATS_REPORTER_LOG, +}; + +struct osmo_stats_reporter { + enum osmo_stats_reporter_type type; + char *name; + + unsigned int have_net_config : 1; + + /* config */ + int enabled; + char *name_prefix; + char *dest_addr_str; + char *bind_addr_str; + int dest_port; + int mtu; + enum osmo_stats_class max_class; + + /* state */ + int running; + struct sockaddr dest_addr; + int dest_addr_len; + struct sockaddr bind_addr; + int bind_addr_len; + int fd; + struct msgb *buffer; + int agg_enabled; + + struct llist_head list; + int (*open)(struct osmo_stats_reporter *srep); + int (*close)(struct osmo_stats_reporter *srep); + int (*send_counter)(struct osmo_stats_reporter *srep, + const struct rate_ctr_group *ctrg, + const struct rate_ctr_desc *desc, + int64_t value, int64_t delta); + int (*send_item)(struct osmo_stats_reporter *srep, + const struct osmo_stat_item_group *statg, + const struct osmo_stat_item_desc *desc, + int32_t value); +}; + +struct osmo_stats_config { + int interval; +}; + +extern struct osmo_stats_config *osmo_stats_config; + +void osmo_stats_init(void *ctx); +int osmo_stats_report(); + +int osmo_stats_set_interval(int interval); + +struct osmo_stats_reporter *osmo_stats_reporter_alloc(enum osmo_stats_reporter_type type, + const char *name); +void osmo_stats_reporter_free(struct osmo_stats_reporter *srep); + +struct osmo_stats_reporter *osmo_stats_reporter_create_statsd(const char *name); +struct osmo_stats_reporter *osmo_stats_reporter_create_log(const char *name); + +struct osmo_stats_reporter *osmo_stats_reporter_find(enum osmo_stats_reporter_type type, + const char *name); + +int osmo_stats_reporter_set_remote_addr(struct osmo_stats_reporter *srep, const char *addr); +int osmo_stats_reporter_set_remote_port(struct osmo_stats_reporter *srep, int port); +int osmo_stats_reporter_set_local_addr(struct osmo_stats_reporter *srep, const char *addr); +int osmo_stats_reporter_set_mtu(struct osmo_stats_reporter *srep, int mtu); +int osmo_stats_reporter_set_max_class(struct osmo_stats_reporter *srep, + enum osmo_stats_class class_id); +int osmo_stats_reporter_set_name_prefix(struct osmo_stats_reporter *srep, const char *prefix); +int osmo_stats_reporter_enable(struct osmo_stats_reporter *srep); +int osmo_stats_reporter_disable(struct osmo_stats_reporter *srep); diff --git a/include/osmocom/gprs/gprs_ns.h b/include/osmocom/gprs/gprs_ns.h index d5a605df..7c3b23c1 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 osmo_stat_item_group *statg; /*! \brief which link-layer are we based on? */ enum gprs_ns_ll ll; diff --git a/include/osmocom/vty/command.h b/include/osmocom/vty/command.h index 4eb519f6..2ef4109e 100644 --- a/include/osmocom/vty/command.h +++ b/include/osmocom/vty/command.h @@ -75,6 +75,7 @@ enum node_type { SERVICE_NODE, /*!< \brief Service node. */ DEBUG_NODE, /*!< \brief Debug node. */ CFG_LOG_NODE, /*!< \brief Configure the logging */ + CFG_STATS_NODE, /*!< \brief Configure the statistics */ VTY_NODE, /*!< \brief Vty node. */ @@ -83,6 +84,15 @@ enum node_type { L_NS_NODE, /*!< \brief NS node in libosmo-gb. */ L_BSSGP_NODE, /*!< \brief BSSGP node in libosmo-gb. */ + /* + * When adding new nodes to the libosmocore project, these nodes can be + * used to avoid ABI changes for unrelated projects. + */ + RESERVED1_NODE, /*!< \brief Reserved for later extensions */ + RESERVED2_NODE, /*!< \brief Reserved for later extensions */ + RESERVED3_NODE, /*!< \brief Reserved for later extensions */ + RESERVED4_NODE, /*!< \brief Reserved for later extensions */ + _LAST_OSMOVTY_NODE }; diff --git a/include/osmocom/vty/misc.h b/include/osmocom/vty/misc.h index db552e77..f3b46dbd 100644 --- a/include/osmocom/vty/misc.h +++ b/include/osmocom/vty/misc.h @@ -2,6 +2,7 @@ #include <osmocom/vty/vty.h> #include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stat_item.h> #include <osmocom/core/utils.h> #define VTY_DO_LOWER 1 @@ -10,7 +11,12 @@ 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 osmo_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/include/osmocom/vty/stats.h b/include/osmocom/vty/stats.h new file mode 100644 index 00000000..3851b4df --- /dev/null +++ b/include/osmocom/vty/stats.h @@ -0,0 +1,3 @@ +#pragma once + +void osmo_stats_vty_add_cmds(); diff --git a/src/Makefile.am b/src/Makefile.am index 4bf3408e..7aa6a78a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,7 +15,7 @@ libosmocore_la_SOURCES = timer.c select.c signal.c msgb.c bits.c \ gsmtap_util.c crc16.c panic.c backtrace.c \ conv.c application.c rbtree.c strrb.c \ loggingrb.c crc8gen.c crc16gen.c crc32gen.c crc64gen.c \ - macaddr.c + macaddr.c stat_item.c stats.c BUILT_SOURCES = crc8gen.c crc16gen.c crc32gen.c crc64gen.c diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c index fe4fccae..e3e69c9c 100644 --- a/src/gb/gprs_bssgp.c +++ b/src/gb/gprs_bssgp.c @@ -31,6 +31,7 @@ #include <osmocom/gsm/tlv.h> #include <osmocom/core/talloc.h> #include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stats.h> #include <osmocom/gprs/gprs_bssgp.h> #include <osmocom/gprs/gprs_ns.h> @@ -54,6 +55,7 @@ static const struct rate_ctr_group_desc bssgp_ctrg_desc = { .group_description = "BSSGP Peer Statistics", .num_ctr = ARRAY_SIZE(bssgp_ctr_description), .ctr_desc = bssgp_ctr_description, + .class_id = OSMO_STATS_CLASS_PEER, }; LLIST_HEAD(bssgp_bvc_ctxts); diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c index 827d09d7..2b189cd3 100644 --- a/src/gb/gprs_ns.c +++ b/src/gb/gprs_ns.c @@ -75,6 +75,8 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/select.h> #include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stat_item.h> +#include <osmocom/core/stats.h> #include <osmocom/core/socket.h> #include <osmocom/core/signal.h> #include <osmocom/gprs/gprs_ns.h> @@ -104,6 +106,8 @@ enum ns_ctr { NS_CTR_NSEI_CHG, NS_CTR_INV_VCI, NS_CTR_INV_NSEI, + NS_CTR_LOST_ALIVE, + NS_CTR_LOST_RESET, }; static const struct rate_ctr_desc nsvc_ctr_description[] = { @@ -117,6 +121,8 @@ static const struct rate_ctr_desc nsvc_ctr_description[] = { { "nsei-chg", "NS-VC changed NSEI count " }, { "inv-nsvci", "NS-VCI was invalid count " }, { "inv-nsei", "NSEI was invalid count " }, + { "lost.alive", "ALIVE ACK missing count " }, + { "lost.reset", "RESET ACK missing count " }, }; static const struct rate_ctr_group_desc nsvc_ctrg_desc = { @@ -126,6 +132,22 @@ static const struct rate_ctr_group_desc nsvc_ctrg_desc = { .ctr_desc = nsvc_ctr_description, }; +enum ns_stat { + NS_STAT_ALIVE_DELAY, +}; + +static const struct osmo_stat_item_desc nsvc_stat_description[] = { + { "alive.delay", "ALIVE reponse time ", "ms", 16, 0 }, +}; + +static const struct osmo_stat_item_group_desc nsvc_statg_desc = { + .group_name_prefix = "ns.nsvc", + .group_description = "NSVC Peer Statistics", + .num_items = ARRAY_SIZE(nsvc_stat_description), + .item_desc = nsvc_stat_description, + .class_id = OSMO_STATS_CLASS_PEER, +}; + #define CHECK_TX_RC(rc, nsvc) \ if (rc < 0) \ LOGP(DNS, LOGL_ERROR, "TX failed (%d) to peer %s\n", \ @@ -218,6 +240,7 @@ struct gprs_nsvc *gprs_nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci) nsvc->timer.cb = gprs_ns_timer_cb; nsvc->timer.data = nsvc; nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, nsvci); + nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, nsvci); llist_add(&nsvc->list, &nsi->gprs_nsvcs); @@ -531,10 +554,20 @@ static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode) if (osmo_timer_pending(&nsvc->timer)) osmo_timer_del(&nsvc->timer); + gettimeofday(&nsvc->timer_started, NULL); nsvc->timer_mode = mode; osmo_timer_schedule(&nsvc->timer, seconds, 0); } +static int nsvc_timer_elapsed_ms(struct gprs_nsvc *nsvc) +{ + struct timeval now, elapsed; + gettimeofday(&now, NULL); + timersub(&now, &nsvc->timer_started, &elapsed); + + return 1000 * elapsed.tv_sec + elapsed.tv_usec / 1000; +} + static void gprs_ns_timer_cb(void *data) { struct gprs_nsvc *nsvc = data; @@ -549,6 +582,7 @@ static void gprs_ns_timer_cb(void *data) switch (nsvc->timer_mode) { case NSVC_TIMER_TNS_ALIVE: /* Tns-alive case: we expired without response ! */ + rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_ALIVE]); nsvc->alive_retries++; if (nsvc->alive_retries > nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) { @@ -578,6 +612,7 @@ static void gprs_ns_timer_cb(void *data) nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE); break; case NSVC_TIMER_TNS_RESET: + rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_RESET]); /* Chapter 7.3: Re-send the RESET */ gprs_ns_tx_reset(nsvc, NS_CAUSE_OM_INTERVENTION); /* Re-start Tns-reset timer */ @@ -1272,6 +1307,9 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg, rc = gprs_ns_tx_alive_ack(*nsvc); break; case NS_PDUT_ALIVE_ACK: + if ((*nsvc)->timer_mode == NSVC_TIMER_TNS_ALIVE) + osmo_stat_item_set((*nsvc)->statg->items[NS_STAT_ALIVE_DELAY], + nsvc_timer_elapsed_ms(*nsvc)); /* stop Tns-alive and start Tns-test */ nsvc_start_timer(*nsvc, NSVC_TIMER_TNS_TEST); if ((*nsvc)->remote_end_is_sgsn) { diff --git a/src/gb/gprs_ns_vty.c b/src/gb/gprs_ns_vty.c index 155e1e97..5a951dca 100644 --- a/src/gb/gprs_ns_vty.c +++ b/src/gb/gprs_ns_vty.c @@ -167,8 +167,10 @@ static void dump_nse(struct vty *vty, struct gprs_nsvc *nsvc, int stats) inet_ntoa(nsvc->ip.bts_addr.sin_addr), ntohs(nsvc->ip.bts_addr.sin_port)); vty_out(vty, "%s", VTY_NEWLINE); - if (stats) + if (stats) { vty_out_rate_ctr_group(vty, " ", nsvc->ctrg); + vty_out_stat_item_group(vty, " ", nsvc->statg); + } } static void dump_ns(struct vty *vty, struct gprs_ns_inst *nsi, int stats) diff --git a/src/logging.c b/src/logging.c index 20b0596b..876964ae 100644 --- a/src/logging.c +++ b/src/logging.c @@ -117,6 +117,11 @@ static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = { .description = "GPRS GTP library", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [INT2IDX(DLSTATS)] = { + .name = "DLSTATS", + .description = "Statistics messages and logging", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; /*! \brief descriptive string for each log level */ diff --git a/src/rate_ctr.c b/src/rate_ctr.c index 8a232e86..50b3fe74 100644 --- a/src/rate_ctr.c +++ b/src/rate_ctr.c @@ -83,6 +83,15 @@ void rate_ctr_add(struct rate_ctr *ctr, int inc) ctr->current += inc; } +/*! \brief Return the counter difference since the last call to this function */ +int64_t rate_ctr_difference(struct rate_ctr *ctr) +{ + int64_t result = ctr->current - ctr->previous; + ctr->previous = ctr->current; + + return result; +} + static void interval_expired(struct rate_ctr *ctr, enum rate_ctr_intv intv) { /* calculate rate over last interval */ @@ -177,4 +186,36 @@ const struct rate_ctr *rate_ctr_get_by_name(const struct rate_ctr_group *ctrg, c return NULL; } +int rate_ctr_for_each_counter(struct rate_ctr_group *ctrg, + rate_ctr_handler_t handle_counter, void *data) +{ + int rc = 0; + int i; + + for (i = 0; i < ctrg->desc->num_ctr; i++) { + struct rate_ctr *ctr = &ctrg->ctr[i]; + rc = handle_counter(ctrg, + ctr, &ctrg->desc->ctr_desc[i], data); + if (rc < 0) + return rc; + } + + return rc; +} + +int rate_ctr_for_each_group(rate_ctr_group_handler_t handle_group, void *data) +{ + struct rate_ctr_group *statg; + int rc = 0; + + llist_for_each_entry(statg, &rate_ctr_groups, list) { + rc = handle_group(statg, data); + if (rc < 0) + return rc; + } + + return rc; +} + + /*! @} */ diff --git a/src/stat_item.c b/src/stat_item.c new file mode 100644 index 00000000..0545ea0d --- /dev/null +++ b/src/stat_item.c @@ -0,0 +1,268 @@ +/* utility routines for keeping conters about events and the event rates */ + +/* (C) 2015 by Sysmocom s.f.m.c. GmbH + * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +/*! \addtogroup osmo_stat_item + * @{ + */ + +/*! \file stat_item.c */ + + +#include <stdint.h> +#include <string.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/stat_item.h> + +static LLIST_HEAD(osmo_stat_item_groups); +static int32_t global_value_id = 0; + +static void *tall_stat_item_ctx; + +/*! \brief Allocate a new group of counters according to description + * \param[in] ctx \ref talloc context + * \param[in] desc Statistics item group description + * \param[in] idx Index of new stat item group + */ +struct osmo_stat_item_group *osmo_stat_item_group_alloc(void *ctx, + const struct osmo_stat_item_group_desc *desc, + unsigned int idx) +{ + unsigned int group_size; + unsigned int items_size = 0; + unsigned int item_idx; + void *items; + + struct osmo_stat_item_group *group; + + group_size = sizeof(struct osmo_stat_item_group) + + desc->num_items * sizeof(struct osmo_stat_item *); + + if (!ctx) + ctx = tall_stat_item_ctx; + + group = talloc_zero_size(ctx, group_size); + if (!group) + return NULL; + + group->desc = desc; + group->idx = idx; + + /* Get combined size of all items */ + for (item_idx = 0; item_idx < desc->num_items; item_idx++) { + unsigned int size; + size = sizeof(struct osmo_stat_item) + + sizeof(struct osmo_stat_item_value) * + desc->item_desc[item_idx].num_values; + /* Align to pointer size */ + size = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1); + + /* Store offsets into the item array */ + group->items[item_idx] = (void *)items_size; + + items_size += size; + } + + items = talloc_zero_size(group, items_size); + if (!items) { + talloc_free(group); + return NULL; + } + + /* Update item pointers */ + for (item_idx = 0; item_idx < desc->num_items; item_idx++) { + struct osmo_stat_item *item = (struct osmo_stat_item *) + ((uint8_t *)items + (int)group->items[item_idx]); + unsigned int i; + + group->items[item_idx] = item; + item->last_offs = desc->item_desc[item_idx].num_values - 1; + item->last_value_index = -1; + item->desc = &desc->item_desc[item_idx]; + + for (i = 0; i <= item->last_offs; i++) { + item->values[i].value = desc->item_desc[item_idx].default_value; + item->values[i].id = STAT_ITEM_NOVALUE_ID; + } + } + + llist_add(&group->list, &osmo_stat_item_groups); + + return group; +} + +/*! \brief Free the memory for the specified group of counters */ +void osmo_stat_item_group_free(struct osmo_stat_item_group *grp) +{ + llist_del(&grp->list); + talloc_free(grp); +} + +void osmo_stat_item_set(struct osmo_stat_item *item, int32_t value) +{ + item->last_offs += 1; + if (item->last_offs >= item->desc->num_values) + item->last_offs = 0; + + global_value_id += 1; + if (global_value_id == STAT_ITEM_NOVALUE_ID) + global_value_id += 1; + + item->values[item->last_offs].value = value; + item->values[item->last_offs].id = global_value_id; +} + +int osmo_stat_item_get_next(const struct osmo_stat_item *item, int32_t *next_idx, + int32_t *value) +{ + const struct osmo_stat_item_value *next_value; + const struct osmo_stat_item_value *item_value = NULL; + int idx_delta; + int next_offs; + + next_offs = item->last_offs; + next_value = &item->values[next_offs]; + + while (next_value->id - *next_idx >= 0 && + next_value->id != STAT_ITEM_NOVALUE_ID) + { + item_value = next_value; + + next_offs -= 1; + if (next_offs < 0) + next_offs = item->desc->num_values - 1; + if (next_offs == item->last_offs) + break; + next_value = &item->values[next_offs]; + } + + if (!item_value) + /* All items have been read */ + return 0; + + *value = item_value->value; + + idx_delta = item_value->id + 1 - *next_idx; + + *next_idx = item_value->id + 1; + + return idx_delta; +} + +/*! \brief Skip all values of this item and update idx accordingly */ +int osmo_stat_item_discard(const struct osmo_stat_item *item, int32_t *idx) +{ + int discarded = item->values[item->last_offs].id + 1 - *idx; + *idx = item->values[item->last_offs].id + 1; + + return discarded; +} + +/*! \brief Skip all values of all items and update idx accordingly */ +int osmo_stat_item_discard_all(int32_t *idx) +{ + int discarded = global_value_id + 1 - *idx; + *idx = global_value_id + 1; + + return discarded; +} + +/*! \brief Initialize the stat item module */ +int osmo_stat_item_init(void *tall_ctx) +{ + tall_stat_item_ctx = tall_ctx; + + return 0; +} + +/*! \brief Search for item group based on group name and index */ +struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idx( + const char *name, const unsigned int idx) +{ + struct osmo_stat_item_group *statg; + + llist_for_each_entry(statg, &osmo_stat_item_groups, list) { + if (!statg->desc) + continue; + + if (!strcmp(statg->desc->group_name_prefix, name) && + statg->idx == idx) + return statg; + } + return NULL; +} + +/*! \brief Search for item group based on group name */ +const struct osmo_stat_item *osmo_stat_item_get_by_name( + const struct osmo_stat_item_group *statg, const char *name) +{ + int i; + const struct osmo_stat_item_desc *item_desc; + + if (!statg->desc) + return NULL; + + for (i = 0; i < statg->desc->num_items; i++) { + item_desc = &statg->desc->item_desc[i]; + + if (!strcmp(item_desc->name, name)) { + return statg->items[i]; + } + } + return NULL; +} + +int osmo_stat_item_for_each_item(struct osmo_stat_item_group *statg, + osmo_stat_item_handler_t handle_item, void *data) +{ + int rc = 0; + int i; + + for (i = 0; i < statg->desc->num_items; i++) { + struct osmo_stat_item *item = statg->items[i]; + rc = handle_item(statg, item, data); + if (rc < 0) + return rc; + } + + return rc; +} + +int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, void *data) +{ + struct osmo_stat_item_group *statg; + int rc = 0; + + llist_for_each_entry(statg, &osmo_stat_item_groups, list) { + rc = handle_group(statg, data); + if (rc < 0) + return rc; + } + + return rc; +} + +/*! @} */ diff --git a/src/statistics.c b/src/statistics.c index e28541ba..ad069cea 100644 --- a/src/statistics.c +++ b/src/statistics.c @@ -74,3 +74,11 @@ struct osmo_counter *osmo_counter_get_by_name(const char *name) } return NULL; } + +int osmo_counter_difference(struct osmo_counter *ctr) +{ + int delta = ctr->value - ctr->previous; + ctr->previous = ctr->value; + + return delta; +} diff --git a/src/stats.c b/src/stats.c new file mode 100644 index 00000000..f979bdc8 --- /dev/null +++ b/src/stats.c @@ -0,0 +1,696 @@ +/* + * (C) 2015 by Sysmocom s.f.m.c. GmbH + * + * Author: Jacob Erlbeck <jerlbeck@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <osmocom/core/stats.h> + +#include <unistd.h> +#include <string.h> +#include <stdint.h> +#include <errno.h> +#include <stdio.h> +#include <sys/socket.h> +#include <netinet/ip.h> +#include <arpa/inet.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stat_item.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/statistics.h> +#include <osmocom/core/msgb.h> + +#define STATS_DEFAULT_INTERVAL 5 /* secs */ +#define STATS_DEFAULT_STATSD_BUFLEN 256 + +static LLIST_HEAD(osmo_stats_reporter_list); +static void *osmo_stats_ctx = NULL; +static int is_initialised = 0; +static int32_t current_stat_item_index = 0; + +static struct osmo_stats_config s_stats_config = { + .interval = STATS_DEFAULT_INTERVAL, +}; +struct osmo_stats_config *osmo_stats_config = &s_stats_config; + +static struct osmo_timer_list osmo_stats_timer; + +static int osmo_stats_reporter_statsd_open(struct osmo_stats_reporter *srep); +static int osmo_stats_reporter_statsd_close(struct osmo_stats_reporter *srep); +static int osmo_stats_reporter_statsd_send_counter(struct osmo_stats_reporter *srep, + const struct rate_ctr_group *ctrg, + const struct rate_ctr_desc *desc, + int64_t value, int64_t delta); +static int osmo_stats_reporter_statsd_send_item(struct osmo_stats_reporter *srep, + const struct osmo_stat_item_group *statg, + const struct osmo_stat_item_desc *desc, int value); + +static int osmo_stats_reporter_log_send_counter(struct osmo_stats_reporter *srep, + const struct rate_ctr_group *ctrg, + const struct rate_ctr_desc *desc, + int64_t value, int64_t delta); +static int osmo_stats_reporter_log_send_item(struct osmo_stats_reporter *srep, + const struct osmo_stat_item_group *statg, + const struct osmo_stat_item_desc *desc, int value); + +static int osmo_stats_reporter_send(struct osmo_stats_reporter *srep, const char *data, + int data_len); +static int osmo_stats_reporter_send_buffer(struct osmo_stats_reporter *srep); + +static int update_srep_config(struct osmo_stats_reporter *srep) +{ + int rc = 0; + + if (srep->running) { + if (srep->close) + rc = srep->close(srep); + srep->running = 0; + } + + if (!srep->enabled) + return rc; + + if (srep->open) + rc = srep->open(srep); + else + rc = 0; + + if (rc < 0) + srep->enabled = 0; + else + srep->running = 1; + + return rc; +} + +static void osmo_stats_timer_cb(void *data) +{ + int interval = osmo_stats_config->interval; + + if (!llist_empty(&osmo_stats_reporter_list)) + osmo_stats_report(); + + osmo_timer_schedule(&osmo_stats_timer, interval, 0); +} + +static int start_timer() +{ + if (!is_initialised) + return -ESRCH; + + osmo_stats_timer.cb = osmo_stats_timer_cb; + osmo_timer_schedule(&osmo_stats_timer, 0, 1); + + return 0; +} + +struct osmo_stats_reporter *osmo_stats_reporter_alloc(enum osmo_stats_reporter_type type, + const char *name) +{ + struct osmo_stats_reporter *srep; + srep = talloc_zero(osmo_stats_ctx, struct osmo_stats_reporter); + OSMO_ASSERT(srep); + srep->type = type; + if (name) + srep->name = talloc_strdup(srep, name); + srep->fd = -1; + + llist_add(&srep->list, &osmo_stats_reporter_list); + + return srep; +} + +void osmo_stats_reporter_free(struct osmo_stats_reporter *srep) +{ + osmo_stats_reporter_disable(srep); + llist_del(&srep->list); + talloc_free(srep); +} + +void osmo_stats_init(void *ctx) +{ + osmo_stats_ctx = ctx; + osmo_stat_item_discard_all(¤t_stat_item_index); + + is_initialised = 1; + start_timer(); +} + +struct osmo_stats_reporter *osmo_stats_reporter_find(enum osmo_stats_reporter_type type, + const char *name) +{ + struct osmo_stats_reporter *srep; + llist_for_each_entry(srep, &osmo_stats_reporter_list, list) { + if (srep->type != type) + continue; + if (srep->name != name) { + if (name == NULL || srep->name == NULL || + strcmp(name, srep->name) != 0) + continue; + } + return srep; + } + return NULL; +} + +int osmo_stats_reporter_set_remote_addr(struct osmo_stats_reporter *srep, const char *addr) +{ + int rc; + struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->dest_addr; + struct in_addr inaddr; + + if (!srep->have_net_config) + return -ENOTSUP; + + OSMO_ASSERT(addr != NULL); + + rc = inet_pton(AF_INET, addr, &inaddr); + if (rc <= 0) + return -EINVAL; + + sock_addr->sin_addr = inaddr; + sock_addr->sin_family = AF_INET; + srep->dest_addr_len = sizeof(*sock_addr); + + talloc_free(srep->dest_addr_str); + srep->dest_addr_str = talloc_strdup(srep, addr); + + return update_srep_config(srep); +} + +int osmo_stats_reporter_set_remote_port(struct osmo_stats_reporter *srep, int port) +{ + struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->dest_addr; + + if (!srep->have_net_config) + return -ENOTSUP; + + srep->dest_port = port; + sock_addr->sin_port = htons(port); + + return update_srep_config(srep); +} + +int osmo_stats_reporter_set_local_addr(struct osmo_stats_reporter *srep, const char *addr) +{ + int rc; + struct sockaddr_in *sock_addr = (struct sockaddr_in *)&srep->bind_addr; + struct in_addr inaddr; + + if (!srep->have_net_config) + return -ENOTSUP; + + if (addr) { + rc = inet_pton(AF_INET, addr, &inaddr); + if (rc <= 0) + return -EINVAL; + } else { + inaddr.s_addr = INADDR_ANY; + } + + sock_addr->sin_addr = inaddr; + sock_addr->sin_family = AF_INET; + srep->bind_addr_len = addr ? sizeof(*sock_addr) : 0; + + talloc_free(srep->bind_addr_str); + srep->bind_addr_str = addr ? talloc_strdup(srep, addr) : NULL; + + return update_srep_config(srep); +} + +int osmo_stats_reporter_set_mtu(struct osmo_stats_reporter *srep, int mtu) +{ + if (!srep->have_net_config) + return -ENOTSUP; + + if (mtu < 0) + return -EINVAL; + + srep->mtu = mtu; + + return update_srep_config(srep); +} + +int osmo_stats_reporter_set_max_class(struct osmo_stats_reporter *srep, + enum osmo_stats_class class_id) +{ + if (class_id == OSMO_STATS_CLASS_UNKNOWN) + return -EINVAL; + + srep->max_class = class_id; + + return 0; +} + +int osmo_stats_set_interval(int interval) +{ + if (interval <= 0) + return -EINVAL; + + osmo_stats_config->interval = interval; + if (is_initialised) + start_timer(); + + return 0; +} + +int osmo_stats_reporter_set_name_prefix(struct osmo_stats_reporter *srep, const char *prefix) +{ + talloc_free(srep->name_prefix); + srep->name_prefix = prefix ? talloc_strdup(srep, prefix) : NULL; + + return update_srep_config(srep); +} + +int osmo_stats_reporter_enable(struct osmo_stats_reporter *srep) +{ + srep->enabled = 1; + + return update_srep_config(srep); +} + +int osmo_stats_reporter_disable(struct osmo_stats_reporter *srep) +{ + srep->enabled = 0; + + return update_srep_config(srep); +} + +static int osmo_stats_reporter_send(struct osmo_stats_reporter *srep, const char *data, + int data_len) +{ + int rc; + + rc = sendto(srep->fd, data, data_len, MSG_NOSIGNAL | MSG_DONTWAIT, + &srep->dest_addr, srep->dest_addr_len); + + if (rc == -1) + rc = -errno; + + return rc; +} + +static int osmo_stats_reporter_send_buffer(struct osmo_stats_reporter *srep) +{ + int rc; + + if (!srep->buffer || msgb_length(srep->buffer) == 0) + return 0; + + rc = osmo_stats_reporter_send(srep, + (const char *)msgb_data(srep->buffer), msgb_length(srep->buffer)); + + msgb_trim(srep->buffer, 0); + + return rc; +} + +static int osmo_stats_reporter_check_config(struct osmo_stats_reporter *srep, + unsigned int index, int class_id) +{ + if (class_id == OSMO_STATS_CLASS_UNKNOWN) + class_id = index != 0 ? + OSMO_STATS_CLASS_SUBSCRIBER : OSMO_STATS_CLASS_GLOBAL; + + return class_id <= srep->max_class; +} + +/*** log reporter ***/ + +struct osmo_stats_reporter *osmo_stats_reporter_create_log(const char *name) +{ + struct osmo_stats_reporter *srep; + srep = osmo_stats_reporter_alloc(OSMO_STATS_REPORTER_LOG, name); + + srep->have_net_config = 0; + + srep->send_counter = osmo_stats_reporter_log_send_counter; + srep->send_item = osmo_stats_reporter_log_send_item; + + return srep; +} + +static int osmo_stats_reporter_log_send(struct osmo_stats_reporter *srep, + const char *type, + const char *name1, unsigned int index1, const char *name2, int value, + const char *unit) +{ + LOGP(DLSTATS, LOGL_INFO, + "stats t=%s p=%s g=%s i=%u n=%s v=%d u=%s\n", + type, srep->name_prefix ? srep->name_prefix : "", + name1 ? name1 : "", index1, + name2, value, unit ? unit : ""); + + return 0; +} + + +static int osmo_stats_reporter_log_send_counter(struct osmo_stats_reporter *srep, + const struct rate_ctr_group *ctrg, + const struct rate_ctr_desc *desc, + int64_t value, int64_t delta) +{ + if (ctrg) + return osmo_stats_reporter_log_send(srep, "c", + ctrg->desc->group_name_prefix, + ctrg->idx, + desc->name, value, NULL); + else + return osmo_stats_reporter_log_send(srep, "c", + NULL, 0, + desc->name, value, NULL); +} + +static int osmo_stats_reporter_log_send_item(struct osmo_stats_reporter *srep, + const struct osmo_stat_item_group *statg, + const struct osmo_stat_item_desc *desc, int value) +{ + return osmo_stats_reporter_log_send(srep, "i", + statg->desc->group_name_prefix, statg->idx, + desc->name, value, desc->unit); +} + +/*** statsd reporter ***/ + +struct osmo_stats_reporter *osmo_stats_reporter_create_statsd(const char *name) +{ + struct osmo_stats_reporter *srep; + srep = osmo_stats_reporter_alloc(OSMO_STATS_REPORTER_STATSD, name); + + srep->have_net_config = 1; + + srep->open = osmo_stats_reporter_statsd_open; + srep->close = osmo_stats_reporter_statsd_close; + srep->send_counter = osmo_stats_reporter_statsd_send_counter; + srep->send_item = osmo_stats_reporter_statsd_send_item; + + return srep; +} + +static int osmo_stats_reporter_statsd_open(struct osmo_stats_reporter *srep) +{ + int sock; + int rc; + int buffer_size = STATS_DEFAULT_STATSD_BUFLEN; + + if (srep->fd != -1) + osmo_stats_reporter_statsd_close(srep); + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + return -errno; + + if (srep->bind_addr_len > 0) { + rc = bind(sock, &srep->bind_addr, srep->bind_addr_len); + if (rc == -1) + goto failed; + } + + srep->fd = sock; + + if (srep->mtu > 0) { + buffer_size = srep->mtu - 20 /* IP */ - 8 /* UDP */; + srep->agg_enabled = 1; + } + + srep->buffer = msgb_alloc(buffer_size, "stats buffer"); + + return 0; + +failed: + rc = -errno; + close(sock); + + return rc; +} + +static int osmo_stats_reporter_statsd_close(struct osmo_stats_reporter *srep) +{ + int rc; + if (srep->fd == -1) + return -EBADF; + + osmo_stats_reporter_send_buffer(srep); + + rc = close(srep->fd); + srep->fd = -1; + msgb_free(srep->buffer); + srep->buffer = NULL; + return rc == -1 ? -errno : 0; +} + +static int osmo_stats_reporter_statsd_send(struct osmo_stats_reporter *srep, + const char *name1, unsigned int index1, const char *name2, int value, + const char *unit) +{ + char *buf; + int buf_size; + int nchars, rc = 0; + char *fmt = NULL; + int old_len = msgb_length(srep->buffer); + + if (name1) { + if (index1 != 0) + fmt = "%1$s.%2$s.%6$u.%3$s:%4$d|%5$s"; + else + fmt = "%1$s.%2$s.%3$s:%4$d|%5$s"; + } else { + fmt = "%1$s.%2$0.0s%3$s:%4$d|%5$s"; + } + if (!srep->name_prefix) + fmt += 5; /* skip prefix part */ + + if (srep->agg_enabled) { + if (msgb_length(srep->buffer) > 0 && + msgb_tailroom(srep->buffer) > 0) + { + msgb_put_u8(srep->buffer, '\n'); + } + } + + buf = (char *)msgb_put(srep->buffer, 0); + buf_size = msgb_tailroom(srep->buffer); + + nchars = snprintf(buf, buf_size, fmt, + srep->name_prefix, name1, name2, + value, unit, index1); + + if (nchars >= buf_size) { + /* Truncated */ + /* Restore original buffer (without trailing LF) */ + msgb_trim(srep->buffer, old_len); + /* Send it */ + rc = osmo_stats_reporter_send_buffer(srep); + + /* Try again */ + buf = (char *)msgb_put(srep->buffer, 0); + buf_size = msgb_tailroom(srep->buffer); + + nchars = snprintf(buf, buf_size, fmt, + srep->name_prefix, name1, name2, + value, unit, index1); + + if (nchars >= buf_size) + return -EMSGSIZE; + } + + if (nchars > 0) + msgb_trim(srep->buffer, msgb_length(srep->buffer) + nchars); + + if (!srep->agg_enabled) + rc = osmo_stats_reporter_send_buffer(srep); + + return rc; +} + +static int osmo_stats_reporter_statsd_send_counter(struct osmo_stats_reporter *srep, + const struct rate_ctr_group *ctrg, + const struct rate_ctr_desc *desc, + int64_t value, int64_t delta) +{ + if (ctrg) + return osmo_stats_reporter_statsd_send(srep, + ctrg->desc->group_name_prefix, + ctrg->idx, + desc->name, delta, "c"); + else + return osmo_stats_reporter_statsd_send(srep, + NULL, 0, + desc->name, delta, "c"); +} + +static int osmo_stats_reporter_statsd_send_item(struct osmo_stats_reporter *srep, + const struct osmo_stat_item_group *statg, + const struct osmo_stat_item_desc *desc, int value) +{ + return osmo_stats_reporter_statsd_send(srep, + statg->desc->group_name_prefix, + statg->idx, + desc->name, value, desc->unit); +} + +/*** generic rate counter support ***/ + +static int osmo_stats_reporter_send_counter(struct osmo_stats_reporter *srep, + const struct rate_ctr_group *ctrg, + const struct rate_ctr_desc *desc, + int64_t value, int64_t delta) +{ + if (!srep->send_counter) + return 0; + + return srep->send_counter(srep, ctrg, desc, value, delta); +} + +static int rate_ctr_handler( + struct rate_ctr_group *ctrg, struct rate_ctr *ctr, + const struct rate_ctr_desc *desc, void *sctx_) +{ + struct osmo_stats_reporter *srep; + int64_t delta = rate_ctr_difference(ctr); + + if (delta == 0) + return 0; + + llist_for_each_entry(srep, &osmo_stats_reporter_list, list) { + if (!srep->running) + continue; + + if (!osmo_stats_reporter_check_config(srep, + ctrg->idx, ctrg->desc->class_id)) + return 0; + + osmo_stats_reporter_send_counter(srep, ctrg, desc, + ctr->current, delta); + + /* TODO: handle result (log?, inc counter(!)?) or remove it */ + } + + return 0; +} + +static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *sctx_) +{ + rate_ctr_for_each_counter(ctrg, rate_ctr_handler, sctx_); + + return 0; +} + +/*** stat item support ***/ + +static int osmo_stats_reporter_send_item(struct osmo_stats_reporter *srep, + const struct osmo_stat_item_group *statg, + const struct osmo_stat_item_desc *desc, + int32_t value) +{ + if (!srep->send_item) + return 0; + + return srep->send_item(srep, statg, desc, value); +} + +static int osmo_stat_item_handler( + struct osmo_stat_item_group *statg, struct osmo_stat_item *item, void *sctx_) +{ + struct osmo_stats_reporter *srep; + int32_t idx = current_stat_item_index; + int32_t value; + + while (osmo_stat_item_get_next(item, &idx, &value) > 0) { + llist_for_each_entry(srep, &osmo_stats_reporter_list, list) { + if (!srep->running) + continue; + + if (!osmo_stats_reporter_check_config(srep, + statg->idx, statg->desc->class_id)) + return 0; + + osmo_stats_reporter_send_item(srep, statg, + item->desc, value); + } + } + + return 0; +} + +static int osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void *sctx_) +{ + osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, sctx_); + osmo_stat_item_discard_all(¤t_stat_item_index); + + return 0; +} + +/*** osmo counter support ***/ + +static int handle_counter(struct osmo_counter *counter, void *sctx_) +{ + struct osmo_stats_reporter *srep; + struct rate_ctr_desc desc = {0}; + /* Fake a rate counter description */ + desc.name = counter->name; + desc.description = counter->description; + + int delta = osmo_counter_difference(counter); + + if (delta == 0) + return 0; + + llist_for_each_entry(srep, &osmo_stats_reporter_list, list) { + if (!srep->running) + continue; + + osmo_stats_reporter_send_counter(srep, NULL, &desc, + counter->value, delta); + + /* TODO: handle result (log?, inc counter(!)?) */ + } + + return 0; +} + + +/*** main reporting function ***/ + +static void flush_all_reporters() +{ + struct osmo_stats_reporter *srep; + + llist_for_each_entry(srep, &osmo_stats_reporter_list, list) { + if (!srep->running) + continue; + + osmo_stats_reporter_send_buffer(srep); + } +} + +int osmo_stats_report() +{ + osmo_counters_for_each(handle_counter, NULL); + rate_ctr_for_each_group(rate_ctr_group_handler, NULL); + osmo_stat_item_for_each_group(osmo_stat_item_group_handler, NULL); + + flush_all_reporters(); + + return 0; +} 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..98253fff --- /dev/null +++ b/src/vty/stats_vty.c @@ -0,0 +1,430 @@ +/* OpenBSC stats helper for the VTY */ +/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org> + * (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 <stdlib.h> +#include <string.h> + +#include "../../config.h" + +#include <osmocom/vty/command.h> +#include <osmocom/vty/buffer.h> +#include <osmocom/vty/vty.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/misc.h> + +#include <osmocom/core/stats.h> + +#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 const struct value_string stats_class_strs[] = { + { OSMO_STATS_CLASS_GLOBAL, "global" }, + { OSMO_STATS_CLASS_PEER, "peer" }, + { OSMO_STATS_CLASS_SUBSCRIBER, "subscriber" }, + { 0, NULL } +}; + +static struct osmo_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 osmo_stats_reporter *, const char *), + const char *val, const char *param_name) +{ + int rc; + struct osmo_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 osmo_stats_reporter *, int), + const char *val, const char *param_name) +{ + int rc; + int int_val; + struct osmo_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, osmo_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, osmo_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, osmo_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, osmo_stats_reporter_set_remote_port, + argv[0], "remote port"); +} + +DEFUN(cfg_stats_reporter_mtu, cfg_stats_reporter_mtu_cmd, + "mtu <100-65535>", + "Set the maximum packet size\n" + "Size in byte\n") +{ + return set_srep_parameter_int(vty, osmo_stats_reporter_set_mtu, + argv[0], "mtu"); +} + +DEFUN(cfg_no_stats_reporter_mtu, cfg_no_stats_reporter_mtu_cmd, + "no mtu", + NO_STR "Set the maximum packet size\n") +{ + return set_srep_parameter_int(vty, osmo_stats_reporter_set_mtu, + "0", "mtu"); +} + +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, osmo_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, osmo_stats_reporter_set_name_prefix, + "", "prefix string"); +} + +DEFUN(cfg_stats_reporter_level, cfg_stats_reporter_level_cmd, + "level (global|peer|subscriber)", + "Set the maximum group level\n" + "Report global groups only\n" + "Report global and network peer related groups\n" + "Report global, peer, and subscriber groups\n") +{ + int level = get_string_value(stats_class_strs, argv[0]); + int rc; + struct osmo_stats_reporter *srep = osmo_stats_vty2srep(vty); + + OSMO_ASSERT(srep); + rc = osmo_stats_reporter_set_max_class(srep, level); + if (rc < 0) { + vty_out(vty, "%% Unable to set level: %s%s", + strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + + return 0; +} + +DEFUN(cfg_stats_reporter_enable, cfg_stats_reporter_enable_cmd, + "enable", + "Enable the reporter\n") +{ + int rc; + struct osmo_stats_reporter *srep = osmo_stats_vty2srep(vty); + OSMO_ASSERT(srep); + + rc = osmo_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 osmo_stats_reporter *srep = osmo_stats_vty2srep(vty); + OSMO_ASSERT(srep); + + rc = osmo_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 osmo_stats_reporter *srep; + + srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL); + if (!srep) { + srep = osmo_stats_reporter_create_statsd(NULL); + if (!srep) { + vty_out(vty, "%% Unable to create statsd reporter%s", + VTY_NEWLINE); + return CMD_WARNING; + } + srep->max_class = OSMO_STATS_CLASS_GLOBAL; + /* TODO: if needed, add osmo_stats_add_reporter(srep); */ + } + + vty->index = srep; + vty->node = CFG_STATS_NODE; + + 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 = osmo_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") +{ + struct osmo_stats_reporter *srep; + + srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL); + if (!srep) { + vty_out(vty, "%% No statsd logging active%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_stats_reporter_free(srep); + + return CMD_SUCCESS; +} + +DEFUN(cfg_stats_reporter_log, cfg_stats_reporter_log_cmd, + "stats reporter log", + CFG_STATS_STR CFG_REPORTER_STR "Report to the logger\n") +{ + struct osmo_stats_reporter *srep; + + srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL); + if (!srep) { + srep = osmo_stats_reporter_create_log(NULL); + if (!srep) { + vty_out(vty, "%% Unable to create log reporter%s", + VTY_NEWLINE); + return CMD_WARNING; + } + srep->max_class = OSMO_STATS_CLASS_GLOBAL; + /* TODO: if needed, add osmo_stats_add_reporter(srep); */ + } + + vty->index = srep; + vty->node = CFG_STATS_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_stats_reporter_log, cfg_no_stats_reporter_log_cmd, + "no stats reporter log", + NO_STR CFG_STATS_STR CFG_REPORTER_STR "Report to the logger\n") +{ + struct osmo_stats_reporter *srep; + + srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL); + if (!srep) { + vty_out(vty, "%% No log reporting active%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_stats_reporter_free(srep); + + return CMD_SUCCESS; +} + +DEFUN(show_stats, + show_stats_cmd, + "show stats", + SHOW_STR SHOW_STATS_STR) +{ + vty_out_statistics_full(vty, ""); + + return CMD_SUCCESS; +} + +static int config_write_stats_reporter(struct vty *vty, struct osmo_stats_reporter *srep) +{ + if (srep == NULL) + return 0; + + switch (srep->type) { + case OSMO_STATS_REPORTER_STATSD: + vty_out(vty, "stats reporter statsd%s", VTY_NEWLINE); + break; + case OSMO_STATS_REPORTER_LOG: + vty_out(vty, "stats reporter log%s", VTY_NEWLINE); + break; + } + + vty_out(vty, " disable%s", VTY_NEWLINE); + + if (srep->have_net_config) { + 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->mtu) + vty_out(vty, " mtu %d%s", + srep->mtu, VTY_NEWLINE); + } + + if (srep->max_class) + vty_out(vty, " level %s%s", + get_value_string(stats_class_strs, srep->max_class), + 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 osmo_stats_reporter *srep; + + /* TODO: loop through all reporters */ + srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL); + config_write_stats_reporter(vty, srep); + srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL); + config_write_stats_reporter(vty, srep); + + vty_out(vty, "stats interval %d%s", osmo_stats_config->interval, VTY_NEWLINE); + + return 1; +} + +void osmo_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_element(CONFIG_NODE, &cfg_stats_reporter_log_cmd); + install_element(CONFIG_NODE, &cfg_no_stats_reporter_log_cmd); + install_element(CONFIG_NODE, &cfg_stats_interval_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_mtu_cmd); + install_element(CFG_STATS_NODE, &cfg_no_stats_reporter_mtu_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_level_cmd); + install_element(CFG_STATS_NODE, &cfg_stats_reporter_enable_cmd); + install_element(CFG_STATS_NODE, &cfg_stats_reporter_disable_cmd); +} diff --git a/src/vty/utils.c b/src/vty/utils.c index d0ad431d..8df44ae1 100644 --- a/src/vty/utils.c +++ b/src/vty/utils.c @@ -29,7 +29,9 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/timer.h> #include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stat_item.h> #include <osmocom/core/utils.h> +#include <osmocom/core/statistics.h> #include <osmocom/vty/vty.h> @@ -39,6 +41,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 @@ -47,20 +73,97 @@ 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, + + rate_ctr_for_each_counter(ctrg, rate_ctr_handler, &vctx); +} + +static int osmo_stat_item_handler( + struct osmo_stat_item_group *statg, struct osmo_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, + osmo_stat_item_get_last(item), + item->desc->unit, VTY_NEWLINE); + + return 0; +} + +/*! \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 osmo_stat_item_group *statg) +{ + struct vty_out_context vctx = {vty, prefix}; + + vty_out(vty, "%s%s:%s", prefix, statg->desc->group_description, + VTY_NEWLINE); + osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, &vctx); +} + +static int osmo_stat_item_group_handler(struct osmo_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); + + osmo_stat_item_for_each_item(statg, osmo_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); + osmo_stat_item_for_each_group(osmo_stat_item_group_handler, &vctx); } /*! \brief Generate a VTY command string from value_string */ diff --git a/tests/Makefile.am b/tests/Makefile.am index cf0977de..6065c0d0 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 @@ -64,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 @@ -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..f8c7dc00 --- /dev/null +++ b/tests/stats/stats_test.c @@ -0,0 +1,213 @@ +/* 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 <osmocom/core/logging.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/stat_item.h> + +#include <stdio.h> + +static void stat_test(void) +{ + enum test_items { + TEST_A_ITEM, + TEST_B_ITEM, + }; + + static const struct osmo_stat_item_desc item_description[] = { + { "item.a", "The A value", "ma", 4, -1 }, + { "item.b", "The B value", "kb", 7, -1 }, + }; + + static const struct osmo_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 osmo_stat_item_group *statg = + osmo_stat_item_group_alloc(NULL, &statg_desc, 0); + + struct osmo_stat_item_group *sgrp2; + const struct osmo_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 = osmo_stat_item_get_group_by_name_idx("test.one", 0); + OSMO_ASSERT(sgrp2 == statg); + + sgrp2 = osmo_stat_item_get_group_by_name_idx("test.one", 1); + OSMO_ASSERT(sgrp2 == NULL); + + sgrp2 = osmo_stat_item_get_group_by_name_idx("test.two", 0); + OSMO_ASSERT(sgrp2 == NULL); + + sitem1 = osmo_stat_item_get_by_name(statg, "item.c"); + OSMO_ASSERT(sitem1 == NULL); + + sitem1 = osmo_stat_item_get_by_name(statg, "item.a"); + OSMO_ASSERT(sitem1 != NULL); + OSMO_ASSERT(sitem1 == statg->items[TEST_A_ITEM]); + + sitem2 = osmo_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 = osmo_stat_item_get_last(statg->items[TEST_A_ITEM]); + OSMO_ASSERT(value == -1); + + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 0); + + osmo_stat_item_set(statg->items[TEST_A_ITEM], 1); + + value = osmo_stat_item_get_last(statg->items[TEST_A_ITEM]); + OSMO_ASSERT(value == 1); + + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == 1); + + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 0); + + for (i = 2; i <= 32; i++) { + osmo_stat_item_set(statg->items[TEST_A_ITEM], i); + osmo_stat_item_set(statg->items[TEST_B_ITEM], 1000 + i); + + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == i); + + rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == 1000 + i); + } + + /* Keep 2 in FIFO */ + osmo_stat_item_set(statg->items[TEST_A_ITEM], 33); + osmo_stat_item_set(statg->items[TEST_B_ITEM], 1000 + 33); + + for (i = 34; i <= 64; i++) { + osmo_stat_item_set(statg->items[TEST_A_ITEM], i); + osmo_stat_item_set(statg->items[TEST_B_ITEM], 1000 + i); + + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == i-1); + + rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == 1000 + i-1); + } + + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == 64); + + rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == 1000 + 64); + + /* Overrun FIFOs */ + for (i = 65; i <= 96; i++) { + osmo_stat_item_set(statg->items[TEST_A_ITEM], i); + osmo_stat_item_set(statg->items[TEST_B_ITEM], 1000 + i); + } + + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == 93); + + for (i = 94; i <= 96; i++) { + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == i); + } + + rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == 1000 + 90); + + for (i = 91; i <= 96; i++) { + rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == 1000 + i); + } + + /* Test Discard (single item) */ + osmo_stat_item_set(statg->items[TEST_A_ITEM], 97); + rc = osmo_stat_item_discard(statg->items[TEST_A_ITEM], &rd_a); + OSMO_ASSERT(rc > 0); + + rc = osmo_stat_item_discard(statg->items[TEST_A_ITEM], &rd_a); + OSMO_ASSERT(rc == 0); + + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 0); + + osmo_stat_item_set(statg->items[TEST_A_ITEM], 98); + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(value == 98); + + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 0); + + /* Test Discard (all items) */ + osmo_stat_item_set(statg->items[TEST_A_ITEM], 99); + osmo_stat_item_set(statg->items[TEST_A_ITEM], 100); + osmo_stat_item_set(statg->items[TEST_A_ITEM], 101); + osmo_stat_item_set(statg->items[TEST_B_ITEM], 99); + osmo_stat_item_set(statg->items[TEST_B_ITEM], 100); + + rc = osmo_stat_item_discard_all(&rd_a); + rc = osmo_stat_item_discard_all(&rd_b); + + rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value); + OSMO_ASSERT(rc == 0); + rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value); + OSMO_ASSERT(rc == 0); + + osmo_stat_item_group_free(statg); + + sgrp2 = osmo_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); + + osmo_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 --- /dev/null +++ b/tests/stats/stats_test.ok 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 diff --git a/tests/vty/vty_test.ok b/tests/vty/vty_test.ok index 0ea2dabf..c6365907 100644 --- a/tests/vty/vty_test.ok +++ b/tests/vty/vty_test.ok @@ -24,11 +24,11 @@ Returned: 0, Current node: 3 '%s# ' Going to execute 'configure terminal' Returned: 0, Current node: 4 '%s(config)# ' Going to execute 'line vty' -Returned: 0, Current node: 8 '%s(config-line)# ' +Returned: 0, Current node: 9 '%s(config-line)# ' Going to execute 'exit' Returned: 0, Current node: 4 '%s(config)# ' Going to execute 'line vty' -Returned: 0, Current node: 8 '%s(config-line)# ' +Returned: 0, Current node: 9 '%s(config-line)# ' Going to execute 'end' Returned: 0, Current node: 3 '%s# ' Going to execute 'configure terminal' @@ -36,7 +36,7 @@ Returned: 0, Current node: 4 '%s(config)# ' Going to execute 'log stderr' Returned: 0, Current node: 7 '%s(config-log)# ' Going to execute 'line vty' -Returned: 0, Current node: 8 '%s(config-line)# ' +Returned: 0, Current node: 9 '%s(config-line)# ' Going to execute 'log stderr' Returned: 0, Current node: 7 '%s(config-log)# ' Going to execute 'end' |