blob: 296cc16ea42ccb449519ba91813dc50fdc34460c [file] [log] [blame]
Harald Welteae510dc2017-10-03 17:46:14 +08001/* (C) 2009-2017 by Harald Welte <laforge@gnumonks.org>
Harald Welte7b45d602010-05-13 11:35:30 +02002 *
3 * All Rights Reserved
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 */
20
Harald Welte9327c6d2011-08-17 16:06:06 +020021/*! \addtogroup rate_ctr
22 * @{
Neels Hofmeyr17518fe2017-06-20 04:35:06 +020023 * Counters about events and their event rates.
24 *
Harald Welte53de0d32017-10-16 14:09:16 +020025 * As \ref osmo_counter and \ref osmo_stat_item are concerned only with
26 * a single given value that may be increased/decreased, or the difference
27 * to one given previous value, this module adds some support for keeping
28 * long term information about a given event rate.
29 *
30 * A \ref rate_ctr keeps information on the amount of events per second,
31 * per minute, per hour and per day.
32 *
33 * \ref rate_ctr come in groups: An application describes a group of counters
34 * with their names and identities once in a (typically const) \ref
35 * rate_ctr_group_desc.
36 *
37 * As objects (such as e.g. a subscriber or a PDP context) are
38 * allocated dynamically at runtime, the application calls \ref
39 * rate_ctr_group_alloc with a refernce to the \ref
40 * rate_ctr_group_desc, which causes the library to allocate one set of
41 * \ref rate_ctr: One for each in the group.
42 *
43 * The application then uses functions like \ref rate_ctr_add or \ref
44 * rate_ctr_inc to increment the value as certain events (e.g. location
45 * update) happens.
46 *
47 * The library internally keeps a timer once per second which iterates
48 * over all registered counters and which updates the per-second,
49 * per-minute, per-hour and per-day averages based on the current
50 * value.
51 *
52 * The counters can be reported using \ref stats or by VTY
53 * introspection, as well as by any application-specific code accessing
54 * the \ref rate_ctr.intv array directly.
55 *
Neels Hofmeyr17518fe2017-06-20 04:35:06 +020056 * \file rate_ctr.c */
Harald Welte9327c6d2011-08-17 16:06:06 +020057
Harald Welteae510dc2017-10-03 17:46:14 +080058#include <stdbool.h>
Harald Welte7b45d602010-05-13 11:35:30 +020059#include <stdint.h>
Harald Welte7b45d602010-05-13 11:35:30 +020060#include <string.h>
61
Pablo Neira Ayuso83419342011-03-22 16:36:13 +010062#include <osmocom/core/utils.h>
63#include <osmocom/core/linuxlist.h>
64#include <osmocom/core/talloc.h>
65#include <osmocom/core/timer.h>
66#include <osmocom/core/rate_ctr.h>
Harald Welteae510dc2017-10-03 17:46:14 +080067#include <osmocom/core/logging.h>
Harald Welte7b45d602010-05-13 11:35:30 +020068
69static LLIST_HEAD(rate_ctr_groups);
70
71static void *tall_rate_ctr_ctx;
72
Harald Welteae510dc2017-10-03 17:46:14 +080073
74static bool rate_ctrl_group_desc_validate(const struct rate_ctr_group_desc *desc, bool quiet)
75{
76 unsigned int i;
Harald Welteb48e82c2017-10-24 18:35:24 +020077 const struct rate_ctr_desc *ctr_desc;
Harald Welteae510dc2017-10-03 17:46:14 +080078
79 if (!desc) {
80 LOGP(DLGLOBAL, LOGL_ERROR, "NULL is not a valid counter group descriptor\n");
81 return false;
82 }
Harald Welteb48e82c2017-10-24 18:35:24 +020083 ctr_desc = desc->ctr_desc;
Harald Welteae510dc2017-10-03 17:46:14 +080084
85 DEBUGP(DLGLOBAL, "validating counter group %p(%s) with %u counters\n", desc,
86 desc->group_name_prefix, desc->num_ctr);
87
88 if (!osmo_identifier_valid(desc->group_name_prefix)) {
89 if (!quiet)
90 LOGP(DLGLOBAL, LOGL_ERROR, "'%s' is not a valid counter group identifier\n",
91 desc->group_name_prefix);
92 return false;
93 }
94
95 for (i = 0; i < desc->num_ctr; i++) {
96 if (!osmo_identifier_valid(ctr_desc[i].name)) {
97 if (!quiet)
98 LOGP(DLGLOBAL, LOGL_ERROR, "'%s' is not a valid counter identifier\n",
99 ctr_desc[i].name);
100 return false;
101 }
102 }
103
104 return true;
105}
106
107/* return 'in' if it doesn't contaon any '.'; otherwise allocate a copy and
108 * replace all '.' with ':' */
109static char *mangle_identifier_ifneeded(const void *ctx, const char *in)
110{
111 char *out;
112 unsigned int i;
113
114 if (!in)
115 return NULL;
116
117 if (!strchr(in, '.'))
118 return (char *)in;
119
120 out = talloc_strdup(ctx, in);
121 OSMO_ASSERT(out);
122
123 for (i = 0; i < strlen(out); i++) {
124 if (out[i] == '.')
125 out[i] = ':';
126 }
127
128 return out;
129}
130
131/* "mangle" a rate counter group descriptor, i.e. replace any '.' with ':' */
132static struct rate_ctr_group_desc *
133rate_ctr_group_desc_mangle(void *ctx, const struct rate_ctr_group_desc *desc)
134{
135 struct rate_ctr_group_desc *desc_new = talloc_zero(ctx, struct rate_ctr_group_desc);
136 int i;
137
138 OSMO_ASSERT(desc_new);
139
140 /* mangle the name_prefix but copy/keep the rest */
141 desc_new->group_name_prefix = mangle_identifier_ifneeded(desc_new, desc->group_name_prefix);
142 desc_new->group_description = desc->group_description;
143 desc_new->class_id = desc->class_id;
144 desc_new->num_ctr = desc->num_ctr;
145 desc_new->ctr_desc = talloc_array(desc_new, struct rate_ctr_desc, desc_new->num_ctr);
146 OSMO_ASSERT(desc_new->ctr_desc);
147
148 for (i = 0; i < desc->num_ctr; i++) {
149 struct rate_ctr_desc *ctrd_new = (struct rate_ctr_desc *) desc_new->ctr_desc;
150 const struct rate_ctr_desc *ctrd = desc->ctr_desc;
151
152 if (!ctrd[i].name) {
153 LOGP(DLGLOBAL, LOGL_ERROR, "counter group '%s'[%d] == NULL, aborting\n",
154 desc->group_name_prefix, i);
155 goto err_free;
156 }
157
158 ctrd_new[i].name = mangle_identifier_ifneeded(desc_new->ctr_desc, ctrd[i].name);
159 ctrd_new[i].description = ctrd[i].description;
160 }
161
162 if (!rate_ctrl_group_desc_validate(desc_new, false)) {
163 /* simple mangling of identifiers ('.' -> ':') was not sufficient to render a valid
164 * descriptor, we have to bail out */
165 LOGP(DLGLOBAL, LOGL_ERROR, "counter group '%s' still invalid after mangling\n",
166 desc->group_name_prefix);
167 goto err_free;
168 }
169
170 LOGP(DLGLOBAL, LOGL_INFO, "Needed to mangle ounter group '%s' names still using '.' as "
171 "separator, please consider updating the application\n", desc->group_name_prefix);
172
173 return desc_new;
174err_free:
175 talloc_free(desc_new);
176 return NULL;
177}
178
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200179/*! Allocate a new group of counters according to description
Harald Welte9327c6d2011-08-17 16:06:06 +0200180 * \param[in] ctx \ref talloc context
181 * \param[in] desc Rate counter group description
182 * \param[in] idx Index of new counter group
183 */
Harald Welte7b45d602010-05-13 11:35:30 +0200184struct rate_ctr_group *rate_ctr_group_alloc(void *ctx,
185 const struct rate_ctr_group_desc *desc,
186 unsigned int idx)
187{
188 unsigned int size;
189 struct rate_ctr_group *group;
190
Harald Welteae510dc2017-10-03 17:46:14 +0800191 /* attempt to mangle all '.' in identifiers to ':' for backwards compat */
192 if (!rate_ctrl_group_desc_validate(desc, true)) {
193 /* don't use 'ctx' here as it would screw up memory leak debugging e.g.
194 * in osmo-msc */
195 desc = rate_ctr_group_desc_mangle(NULL, desc);
196 if (!desc)
197 return NULL;
198 }
199
Harald Welte7b45d602010-05-13 11:35:30 +0200200 size = sizeof(struct rate_ctr_group) +
201 desc->num_ctr * sizeof(struct rate_ctr);
202
203 if (!ctx)
204 ctx = tall_rate_ctr_ctx;
205
206 group = talloc_zero_size(ctx, size);
207 if (!group)
208 return NULL;
209
210 group->desc = desc;
Harald Welte087fcff2010-05-13 12:16:17 +0200211 group->idx = idx;
Harald Welte7b45d602010-05-13 11:35:30 +0200212
213 llist_add(&group->list, &rate_ctr_groups);
214
215 return group;
216}
217
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200218/*! Free the memory for the specified group of counters */
Harald Welte7b45d602010-05-13 11:35:30 +0200219void rate_ctr_group_free(struct rate_ctr_group *grp)
220{
221 llist_del(&grp->list);
222 talloc_free(grp);
223}
224
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200225/*! Add a number to the counter */
Harald Welte7b45d602010-05-13 11:35:30 +0200226void rate_ctr_add(struct rate_ctr *ctr, int inc)
227{
228 ctr->current += inc;
229}
230
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200231/*! Return the counter difference since the last call to this function */
Jacob Erlbeck423c1e52015-10-19 13:45:42 +0200232int64_t rate_ctr_difference(struct rate_ctr *ctr)
233{
234 int64_t result = ctr->current - ctr->previous;
235 ctr->previous = ctr->current;
236
237 return result;
238}
239
Jacob Erlbeckee702cd2015-11-10 11:38:25 +0100240/* TODO: support update intervals > 1s */
241/* TODO: implement this as a special stats reporter */
242
Harald Welte7b45d602010-05-13 11:35:30 +0200243static void interval_expired(struct rate_ctr *ctr, enum rate_ctr_intv intv)
244{
245 /* calculate rate over last interval */
246 ctr->intv[intv].rate = ctr->current - ctr->intv[intv].last;
247 /* save current counter for next interval */
248 ctr->intv[intv].last = ctr->current;
Harald Welted2dce6d2010-05-13 13:28:12 +0200249
250 /* update the rate of the next bigger interval. This will
251 * be overwritten when that next larger interval expires */
252 if (intv + 1 < ARRAY_SIZE(ctr->intv))
253 ctr->intv[intv+1].rate += ctr->intv[intv].rate;
Harald Welte7b45d602010-05-13 11:35:30 +0200254}
255
Pablo Neira Ayuso0b21c1c2011-05-07 12:42:28 +0200256static struct osmo_timer_list rate_ctr_timer;
Harald Welte7b45d602010-05-13 11:35:30 +0200257static uint64_t timer_ticks;
258
259/* The one-second interval has expired */
260static void rate_ctr_group_intv(struct rate_ctr_group *grp)
261{
262 unsigned int i;
263
264 for (i = 0; i < grp->desc->num_ctr; i++) {
265 struct rate_ctr *ctr = &grp->ctr[i];
266
267 interval_expired(ctr, RATE_CTR_INTV_SEC);
268 if ((timer_ticks % 60) == 0)
269 interval_expired(ctr, RATE_CTR_INTV_MIN);
270 if ((timer_ticks % (60*60)) == 0)
271 interval_expired(ctr, RATE_CTR_INTV_HOUR);
272 if ((timer_ticks % (24*60*60)) == 0)
273 interval_expired(ctr, RATE_CTR_INTV_DAY);
274 }
275}
276
277static void rate_ctr_timer_cb(void *data)
278{
279 struct rate_ctr_group *ctrg;
280
281 /* Increment number of ticks before we calculate intervals,
282 * as a counter value of 0 would already wrap all counters */
283 timer_ticks++;
284
285 llist_for_each_entry(ctrg, &rate_ctr_groups, list)
286 rate_ctr_group_intv(ctrg);
287
Pablo Neira Ayuso0b21c1c2011-05-07 12:42:28 +0200288 osmo_timer_schedule(&rate_ctr_timer, 1, 0);
Harald Welte7b45d602010-05-13 11:35:30 +0200289}
290
Harald Welte53de0d32017-10-16 14:09:16 +0200291/*! Initialize the counter module. Call this once from your application.
292 * \param[in] tall_ctx Talloc context from which rate_ctr_group will be allocated
293 * \returns 0 on success; negative on error */
Harald Welte7b45d602010-05-13 11:35:30 +0200294int rate_ctr_init(void *tall_ctx)
295{
296 tall_rate_ctr_ctx = tall_ctx;
Pablo Neira Ayuso44f423f2017-05-08 18:00:28 +0200297 osmo_timer_setup(&rate_ctr_timer, rate_ctr_timer_cb, NULL);
Pablo Neira Ayuso0b21c1c2011-05-07 12:42:28 +0200298 osmo_timer_schedule(&rate_ctr_timer, 1, 0);
Harald Welte7b45d602010-05-13 11:35:30 +0200299
300 return 0;
301}
Daniel Willmann2d42dde2011-04-08 10:46:18 +0200302
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200303/*! Search for counter group based on group name and index
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200304 * \param[in] name Name of the counter group you're looking for
305 * \param[in] idx Index inside the counter group
306 * \returns \ref rate_ctr_group or NULL in case of error */
Daniel Willmann2d42dde2011-04-08 10:46:18 +0200307struct rate_ctr_group *rate_ctr_get_group_by_name_idx(const char *name, const unsigned int idx)
308{
309 struct rate_ctr_group *ctrg;
310
311 llist_for_each_entry(ctrg, &rate_ctr_groups, list) {
312 if (!ctrg->desc)
313 continue;
314
315 if (!strcmp(ctrg->desc->group_name_prefix, name) &&
316 ctrg->idx == idx) {
317 return ctrg;
318 }
319 }
320 return NULL;
321}
322
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200323/*! Search for counter based on group + name
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200324 * \param[in] ctrg pointer to \ref rate_ctr_group
325 * \param[in] name name of counter inside group
Harald Welte53de0d32017-10-16 14:09:16 +0200326 * \returns \ref rate_ctr or NULL in case of error
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200327 */
Holger Hans Peter Freythera9f526a2011-04-18 16:45:45 +0200328const struct rate_ctr *rate_ctr_get_by_name(const struct rate_ctr_group *ctrg, const char *name)
Daniel Willmann2d42dde2011-04-08 10:46:18 +0200329{
330 int i;
Holger Hans Peter Freythera9f526a2011-04-18 16:45:45 +0200331 const struct rate_ctr_desc *ctr_desc;
Daniel Willmann2d42dde2011-04-08 10:46:18 +0200332
333 if (!ctrg->desc)
334 return NULL;
335
336 for (i = 0; i < ctrg->desc->num_ctr; i++) {
337 ctr_desc = &ctrg->desc->ctr_desc[i];
338
339 if (!strcmp(ctr_desc->name, name)) {
340 return &ctrg->ctr[i];
341 }
342 }
343 return NULL;
344}
Harald Welte9327c6d2011-08-17 16:06:06 +0200345
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200346/*! Iterate over each counter in group and call function
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200347 * \param[in] counter group over whose counter to iterate
348 * \param[in] handle_counter function pointer
349 * \param[in] data Data to hand transparently to \ref handle_counter
350 * \returns 0 on success; negative otherwise
351 */
Jacob Erlbeck423c1e52015-10-19 13:45:42 +0200352int rate_ctr_for_each_counter(struct rate_ctr_group *ctrg,
353 rate_ctr_handler_t handle_counter, void *data)
354{
355 int rc = 0;
356 int i;
357
358 for (i = 0; i < ctrg->desc->num_ctr; i++) {
359 struct rate_ctr *ctr = &ctrg->ctr[i];
360 rc = handle_counter(ctrg,
361 ctr, &ctrg->desc->ctr_desc[i], data);
362 if (rc < 0)
363 return rc;
364 }
365
366 return rc;
367}
368
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200369/*! Iterate over all counter groups
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200370 * \param[in] handle_group function pointer of callback function
371 * \param[in] data Data to hand transparently to \ref handle_group
372 * \returns 0 on success; negative otherwise
373 */
Jacob Erlbeck423c1e52015-10-19 13:45:42 +0200374int rate_ctr_for_each_group(rate_ctr_group_handler_t handle_group, void *data)
375{
376 struct rate_ctr_group *statg;
377 int rc = 0;
378
379 llist_for_each_entry(statg, &rate_ctr_groups, list) {
380 rc = handle_group(statg, data);
381 if (rc < 0)
382 return rc;
383 }
384
385 return rc;
386}
387
Sylvain Munautdca7d2c2012-04-18 21:53:23 +0200388/*! @} */