blob: 99ec5234420c4645396ed0d67e95d308fb07e13a [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 *
Harald Weltee08da972017-11-13 01:00:26 +09005 * SPDX-License-Identifier: GPL-2.0+
6 *
Harald Welte7b45d602010-05-13 11:35:30 +02007 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 */
22
Harald Welte9327c6d2011-08-17 16:06:06 +020023/*! \addtogroup rate_ctr
24 * @{
Neels Hofmeyr17518fe2017-06-20 04:35:06 +020025 * Counters about events and their event rates.
26 *
Harald Welte53de0d32017-10-16 14:09:16 +020027 * As \ref osmo_counter and \ref osmo_stat_item are concerned only with
28 * a single given value that may be increased/decreased, or the difference
29 * to one given previous value, this module adds some support for keeping
30 * long term information about a given event rate.
31 *
32 * A \ref rate_ctr keeps information on the amount of events per second,
33 * per minute, per hour and per day.
34 *
35 * \ref rate_ctr come in groups: An application describes a group of counters
36 * with their names and identities once in a (typically const) \ref
37 * rate_ctr_group_desc.
38 *
39 * As objects (such as e.g. a subscriber or a PDP context) are
40 * allocated dynamically at runtime, the application calls \ref
41 * rate_ctr_group_alloc with a refernce to the \ref
42 * rate_ctr_group_desc, which causes the library to allocate one set of
43 * \ref rate_ctr: One for each in the group.
44 *
45 * The application then uses functions like \ref rate_ctr_add or \ref
46 * rate_ctr_inc to increment the value as certain events (e.g. location
47 * update) happens.
48 *
49 * The library internally keeps a timer once per second which iterates
50 * over all registered counters and which updates the per-second,
51 * per-minute, per-hour and per-day averages based on the current
52 * value.
53 *
54 * The counters can be reported using \ref stats or by VTY
55 * introspection, as well as by any application-specific code accessing
56 * the \ref rate_ctr.intv array directly.
57 *
Neels Hofmeyr17518fe2017-06-20 04:35:06 +020058 * \file rate_ctr.c */
Harald Welte9327c6d2011-08-17 16:06:06 +020059
Harald Welteae510dc2017-10-03 17:46:14 +080060#include <stdbool.h>
Harald Welte7b45d602010-05-13 11:35:30 +020061#include <stdint.h>
Harald Welte7b45d602010-05-13 11:35:30 +020062#include <string.h>
63
Pablo Neira Ayuso83419342011-03-22 16:36:13 +010064#include <osmocom/core/utils.h>
65#include <osmocom/core/linuxlist.h>
66#include <osmocom/core/talloc.h>
67#include <osmocom/core/timer.h>
68#include <osmocom/core/rate_ctr.h>
Harald Welteae510dc2017-10-03 17:46:14 +080069#include <osmocom/core/logging.h>
Harald Welte7b45d602010-05-13 11:35:30 +020070
71static LLIST_HEAD(rate_ctr_groups);
Harald Welte9685a482019-05-08 16:58:34 +020072static struct osmo_timer_list rate_ctr_timer;
Harald Welte7b45d602010-05-13 11:35:30 +020073static void *tall_rate_ctr_ctx;
74
Harald Welteae510dc2017-10-03 17:46:14 +080075
Pau Espin Pedroldfc52a12018-06-27 16:53:31 +020076static bool rate_ctrl_group_desc_validate(const struct rate_ctr_group_desc *desc)
Harald Welteae510dc2017-10-03 17:46:14 +080077{
78 unsigned int i;
Harald Welteb48e82c2017-10-24 18:35:24 +020079 const struct rate_ctr_desc *ctr_desc;
Harald Welteae510dc2017-10-03 17:46:14 +080080
81 if (!desc) {
82 LOGP(DLGLOBAL, LOGL_ERROR, "NULL is not a valid counter group descriptor\n");
83 return false;
84 }
Harald Welteb48e82c2017-10-24 18:35:24 +020085 ctr_desc = desc->ctr_desc;
Harald Welteae510dc2017-10-03 17:46:14 +080086
87 DEBUGP(DLGLOBAL, "validating counter group %p(%s) with %u counters\n", desc,
88 desc->group_name_prefix, desc->num_ctr);
89
90 if (!osmo_identifier_valid(desc->group_name_prefix)) {
Pau Espin Pedroldfc52a12018-06-27 16:53:31 +020091 LOGP(DLGLOBAL, LOGL_ERROR, "'%s' is not a valid counter group identifier\n",
92 desc->group_name_prefix);
Harald Welteae510dc2017-10-03 17:46:14 +080093 return false;
94 }
95
96 for (i = 0; i < desc->num_ctr; i++) {
97 if (!osmo_identifier_valid(ctr_desc[i].name)) {
Pau Espin Pedroldfc52a12018-06-27 16:53:31 +020098 LOGP(DLGLOBAL, LOGL_ERROR, "'%s' is not a valid counter identifier\n",
99 ctr_desc[i].name);
Harald Welteae510dc2017-10-03 17:46:14 +0800100 return false;
101 }
102 }
103
104 return true;
105}
106
Pau Espin Pedroldfc52a12018-06-27 16:53:31 +0200107/* return 'in' if it doesn't contain any '.'; otherwise allocate a copy and
Harald Welteae510dc2017-10-03 17:46:14 +0800108 * replace all '.' with ':' */
109static char *mangle_identifier_ifneeded(const void *ctx, const char *in)
110{
111 char *out;
112 unsigned int i;
Pau Espin Pedroldfc52a12018-06-27 16:53:31 +0200113 bool modified = false;
Harald Welteae510dc2017-10-03 17:46:14 +0800114
115 if (!in)
116 return NULL;
117
118 if (!strchr(in, '.'))
119 return (char *)in;
120
121 out = talloc_strdup(ctx, in);
122 OSMO_ASSERT(out);
123
124 for (i = 0; i < strlen(out); i++) {
Pau Espin Pedroldfc52a12018-06-27 16:53:31 +0200125 if (out[i] == '.') {
Harald Welteae510dc2017-10-03 17:46:14 +0800126 out[i] = ':';
Pau Espin Pedroldfc52a12018-06-27 16:53:31 +0200127 modified = true;
128 }
Harald Welteae510dc2017-10-03 17:46:14 +0800129 }
130
Pau Espin Pedroldfc52a12018-06-27 16:53:31 +0200131 if (modified)
132 LOGP(DLGLOBAL, LOGL_NOTICE, "counter group name mangled: '%s' -> '%s'\n",
133 in, out);
134
Harald Welteae510dc2017-10-03 17:46:14 +0800135 return out;
136}
137
138/* "mangle" a rate counter group descriptor, i.e. replace any '.' with ':' */
139static struct rate_ctr_group_desc *
140rate_ctr_group_desc_mangle(void *ctx, const struct rate_ctr_group_desc *desc)
141{
142 struct rate_ctr_group_desc *desc_new = talloc_zero(ctx, struct rate_ctr_group_desc);
143 int i;
144
145 OSMO_ASSERT(desc_new);
146
Pau Espin Pedroldfc52a12018-06-27 16:53:31 +0200147 LOGP(DLGLOBAL, LOGL_INFO, "Needed to mangle counter group '%s' names: it is still using '.' as "
148 "separator, which is not allowed. please consider updating the application\n",
149 desc->group_name_prefix);
150
Harald Welteae510dc2017-10-03 17:46:14 +0800151 /* mangle the name_prefix but copy/keep the rest */
152 desc_new->group_name_prefix = mangle_identifier_ifneeded(desc_new, desc->group_name_prefix);
153 desc_new->group_description = desc->group_description;
154 desc_new->class_id = desc->class_id;
155 desc_new->num_ctr = desc->num_ctr;
156 desc_new->ctr_desc = talloc_array(desc_new, struct rate_ctr_desc, desc_new->num_ctr);
157 OSMO_ASSERT(desc_new->ctr_desc);
158
159 for (i = 0; i < desc->num_ctr; i++) {
160 struct rate_ctr_desc *ctrd_new = (struct rate_ctr_desc *) desc_new->ctr_desc;
161 const struct rate_ctr_desc *ctrd = desc->ctr_desc;
162
163 if (!ctrd[i].name) {
164 LOGP(DLGLOBAL, LOGL_ERROR, "counter group '%s'[%d] == NULL, aborting\n",
165 desc->group_name_prefix, i);
166 goto err_free;
167 }
168
169 ctrd_new[i].name = mangle_identifier_ifneeded(desc_new->ctr_desc, ctrd[i].name);
170 ctrd_new[i].description = ctrd[i].description;
171 }
172
Pau Espin Pedroldfc52a12018-06-27 16:53:31 +0200173 if (!rate_ctrl_group_desc_validate(desc_new)) {
Harald Welteae510dc2017-10-03 17:46:14 +0800174 /* simple mangling of identifiers ('.' -> ':') was not sufficient to render a valid
175 * descriptor, we have to bail out */
176 LOGP(DLGLOBAL, LOGL_ERROR, "counter group '%s' still invalid after mangling\n",
177 desc->group_name_prefix);
178 goto err_free;
179 }
180
Harald Welteae510dc2017-10-03 17:46:14 +0800181 return desc_new;
182err_free:
183 talloc_free(desc_new);
184 return NULL;
185}
186
Neels Hofmeyr554f7b82017-12-20 01:14:31 +0100187/*! Find an unused index for this rate counter group.
188 * \param[in] name Name of the counter group
189 * \returns the largest used index number + 1, or 0 if none exist yet. */
190static unsigned int rate_ctr_get_unused_name_idx(const char *name)
191{
192 unsigned int idx = 0;
193 struct rate_ctr_group *ctrg;
194
195 llist_for_each_entry(ctrg, &rate_ctr_groups, list) {
196 if (!ctrg->desc)
197 continue;
198
199 if (strcmp(ctrg->desc->group_name_prefix, name))
200 continue;
201
202 if (idx <= ctrg->idx)
203 idx = ctrg->idx + 1;
204 }
205 return idx;
206}
207
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200208/*! Allocate a new group of counters according to description
Vadim Yanitskiyed3a3c62019-03-26 00:57:45 +0700209 * \param[in] ctx parent talloc context
Harald Welte9327c6d2011-08-17 16:06:06 +0200210 * \param[in] desc Rate counter group description
211 * \param[in] idx Index of new counter group
212 */
Harald Welte7b45d602010-05-13 11:35:30 +0200213struct rate_ctr_group *rate_ctr_group_alloc(void *ctx,
214 const struct rate_ctr_group_desc *desc,
215 unsigned int idx)
216{
217 unsigned int size;
218 struct rate_ctr_group *group;
219
Harald Welte9685a482019-05-08 16:58:34 +0200220 if (!osmo_timer_pending(&rate_ctr_timer))
221 LOGP(DLGLOBAL, LOGL_ERROR, "your program appears to use libosmocore rate_ctr "
222 "without calling rate_ctr_init() at start-up. Rate counters won't work!\n");
223
Harald Welted589f1d2017-12-18 17:13:38 +0100224 if (rate_ctr_get_group_by_name_idx(desc->group_name_prefix, idx)) {
Neels Hofmeyr554f7b82017-12-20 01:14:31 +0100225 unsigned int new_idx = rate_ctr_get_unused_name_idx(desc->group_name_prefix);
226 LOGP(DLGLOBAL, LOGL_ERROR, "counter group '%s' already exists for index %u,"
227 " instead using index %u. This is a software bug that needs fixing.\n",
228 desc->group_name_prefix, idx, new_idx);
229 idx = new_idx;
Harald Welted589f1d2017-12-18 17:13:38 +0100230 }
Max3ef14a22017-12-15 20:19:10 +0100231
Harald Welte7b45d602010-05-13 11:35:30 +0200232 size = sizeof(struct rate_ctr_group) +
233 desc->num_ctr * sizeof(struct rate_ctr);
234
235 if (!ctx)
236 ctx = tall_rate_ctr_ctx;
237
238 group = talloc_zero_size(ctx, size);
239 if (!group)
240 return NULL;
241
Neels Hofmeyr10ee73a2017-11-16 18:31:57 +0100242 /* attempt to mangle all '.' in identifiers to ':' for backwards compat */
Pau Espin Pedroldfc52a12018-06-27 16:53:31 +0200243 if (!rate_ctrl_group_desc_validate(desc)) {
Neels Hofmeyr10ee73a2017-11-16 18:31:57 +0100244 desc = rate_ctr_group_desc_mangle(group, desc);
245 if (!desc) {
246 talloc_free(group);
247 return NULL;
248 }
249 }
250
Harald Welte7b45d602010-05-13 11:35:30 +0200251 group->desc = desc;
Harald Welte087fcff2010-05-13 12:16:17 +0200252 group->idx = idx;
Harald Welte7b45d602010-05-13 11:35:30 +0200253
254 llist_add(&group->list, &rate_ctr_groups);
255
256 return group;
257}
258
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200259/*! Free the memory for the specified group of counters */
Harald Welte7b45d602010-05-13 11:35:30 +0200260void rate_ctr_group_free(struct rate_ctr_group *grp)
261{
Maxfb6f43e2019-03-18 15:41:26 +0100262 if (!grp)
263 return;
264
265 if (!llist_empty(&grp->list))
266 llist_del(&grp->list);
Harald Welte7b45d602010-05-13 11:35:30 +0200267 talloc_free(grp);
268}
269
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200270/*! Add a number to the counter */
Harald Welte7b45d602010-05-13 11:35:30 +0200271void rate_ctr_add(struct rate_ctr *ctr, int inc)
272{
273 ctr->current += inc;
274}
275
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200276/*! Return the counter difference since the last call to this function */
Jacob Erlbeck423c1e52015-10-19 13:45:42 +0200277int64_t rate_ctr_difference(struct rate_ctr *ctr)
278{
279 int64_t result = ctr->current - ctr->previous;
280 ctr->previous = ctr->current;
281
282 return result;
283}
284
Jacob Erlbeckee702cd2015-11-10 11:38:25 +0100285/* TODO: support update intervals > 1s */
286/* TODO: implement this as a special stats reporter */
287
Harald Welte7b45d602010-05-13 11:35:30 +0200288static void interval_expired(struct rate_ctr *ctr, enum rate_ctr_intv intv)
289{
290 /* calculate rate over last interval */
291 ctr->intv[intv].rate = ctr->current - ctr->intv[intv].last;
292 /* save current counter for next interval */
293 ctr->intv[intv].last = ctr->current;
Harald Welted2dce6d2010-05-13 13:28:12 +0200294
295 /* update the rate of the next bigger interval. This will
296 * be overwritten when that next larger interval expires */
297 if (intv + 1 < ARRAY_SIZE(ctr->intv))
298 ctr->intv[intv+1].rate += ctr->intv[intv].rate;
Harald Welte7b45d602010-05-13 11:35:30 +0200299}
300
Harald Welte7b45d602010-05-13 11:35:30 +0200301static uint64_t timer_ticks;
302
303/* The one-second interval has expired */
304static void rate_ctr_group_intv(struct rate_ctr_group *grp)
305{
306 unsigned int i;
307
308 for (i = 0; i < grp->desc->num_ctr; i++) {
309 struct rate_ctr *ctr = &grp->ctr[i];
310
311 interval_expired(ctr, RATE_CTR_INTV_SEC);
312 if ((timer_ticks % 60) == 0)
313 interval_expired(ctr, RATE_CTR_INTV_MIN);
314 if ((timer_ticks % (60*60)) == 0)
315 interval_expired(ctr, RATE_CTR_INTV_HOUR);
316 if ((timer_ticks % (24*60*60)) == 0)
317 interval_expired(ctr, RATE_CTR_INTV_DAY);
318 }
319}
320
321static void rate_ctr_timer_cb(void *data)
322{
323 struct rate_ctr_group *ctrg;
324
325 /* Increment number of ticks before we calculate intervals,
326 * as a counter value of 0 would already wrap all counters */
327 timer_ticks++;
328
329 llist_for_each_entry(ctrg, &rate_ctr_groups, list)
330 rate_ctr_group_intv(ctrg);
331
Pablo Neira Ayuso0b21c1c2011-05-07 12:42:28 +0200332 osmo_timer_schedule(&rate_ctr_timer, 1, 0);
Harald Welte7b45d602010-05-13 11:35:30 +0200333}
334
Harald Welte53de0d32017-10-16 14:09:16 +0200335/*! Initialize the counter module. Call this once from your application.
336 * \param[in] tall_ctx Talloc context from which rate_ctr_group will be allocated
337 * \returns 0 on success; negative on error */
Harald Welte7b45d602010-05-13 11:35:30 +0200338int rate_ctr_init(void *tall_ctx)
339{
340 tall_rate_ctr_ctx = tall_ctx;
Pablo Neira Ayuso44f423f2017-05-08 18:00:28 +0200341 osmo_timer_setup(&rate_ctr_timer, rate_ctr_timer_cb, NULL);
Pablo Neira Ayuso0b21c1c2011-05-07 12:42:28 +0200342 osmo_timer_schedule(&rate_ctr_timer, 1, 0);
Harald Welte7b45d602010-05-13 11:35:30 +0200343
344 return 0;
345}
Daniel Willmann2d42dde2011-04-08 10:46:18 +0200346
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200347/*! Search for counter group based on group name and index
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200348 * \param[in] name Name of the counter group you're looking for
349 * \param[in] idx Index inside the counter group
350 * \returns \ref rate_ctr_group or NULL in case of error */
Daniel Willmann2d42dde2011-04-08 10:46:18 +0200351struct rate_ctr_group *rate_ctr_get_group_by_name_idx(const char *name, const unsigned int idx)
352{
353 struct rate_ctr_group *ctrg;
354
355 llist_for_each_entry(ctrg, &rate_ctr_groups, list) {
356 if (!ctrg->desc)
357 continue;
358
359 if (!strcmp(ctrg->desc->group_name_prefix, name) &&
360 ctrg->idx == idx) {
361 return ctrg;
362 }
363 }
364 return NULL;
365}
366
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200367/*! Search for counter based on group + name
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200368 * \param[in] ctrg pointer to \ref rate_ctr_group
369 * \param[in] name name of counter inside group
Harald Welte53de0d32017-10-16 14:09:16 +0200370 * \returns \ref rate_ctr or NULL in case of error
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200371 */
Holger Hans Peter Freythera9f526a2011-04-18 16:45:45 +0200372const struct rate_ctr *rate_ctr_get_by_name(const struct rate_ctr_group *ctrg, const char *name)
Daniel Willmann2d42dde2011-04-08 10:46:18 +0200373{
374 int i;
Holger Hans Peter Freythera9f526a2011-04-18 16:45:45 +0200375 const struct rate_ctr_desc *ctr_desc;
Daniel Willmann2d42dde2011-04-08 10:46:18 +0200376
377 if (!ctrg->desc)
378 return NULL;
379
380 for (i = 0; i < ctrg->desc->num_ctr; i++) {
381 ctr_desc = &ctrg->desc->ctr_desc[i];
382
383 if (!strcmp(ctr_desc->name, name)) {
384 return &ctrg->ctr[i];
385 }
386 }
387 return NULL;
388}
Harald Welte9327c6d2011-08-17 16:06:06 +0200389
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200390/*! Iterate over each counter in group and call function
Vadim Yanitskiyc7610442019-03-26 00:48:30 +0700391 * \param[in] ctrg counter group over which to iterate
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200392 * \param[in] handle_counter function pointer
Vadim Yanitskiyed3a3c62019-03-26 00:57:45 +0700393 * \param[in] data Data to hand transparently to handle_counter()
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200394 * \returns 0 on success; negative otherwise
395 */
Jacob Erlbeck423c1e52015-10-19 13:45:42 +0200396int rate_ctr_for_each_counter(struct rate_ctr_group *ctrg,
397 rate_ctr_handler_t handle_counter, void *data)
398{
399 int rc = 0;
400 int i;
401
402 for (i = 0; i < ctrg->desc->num_ctr; i++) {
403 struct rate_ctr *ctr = &ctrg->ctr[i];
404 rc = handle_counter(ctrg,
405 ctr, &ctrg->desc->ctr_desc[i], data);
406 if (rc < 0)
407 return rc;
408 }
409
410 return rc;
411}
412
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200413/*! Iterate over all counter groups
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200414 * \param[in] handle_group function pointer of callback function
Vadim Yanitskiyed3a3c62019-03-26 00:57:45 +0700415 * \param[in] data Data to hand transparently to handle_group()
Harald Welte2d2e2cc2016-04-25 12:11:20 +0200416 * \returns 0 on success; negative otherwise
417 */
Jacob Erlbeck423c1e52015-10-19 13:45:42 +0200418int rate_ctr_for_each_group(rate_ctr_group_handler_t handle_group, void *data)
419{
420 struct rate_ctr_group *statg;
421 int rc = 0;
422
423 llist_for_each_entry(statg, &rate_ctr_groups, list) {
424 rc = handle_group(statg, data);
425 if (rc < 0)
426 return rc;
427 }
428
429 return rc;
430}
431
Sylvain Munautdca7d2c2012-04-18 21:53:23 +0200432/*! @} */