blob: 55aa95105e25a6eaa1e82196a1691a731e5d6ed0 [file] [log] [blame]
Neels Hofmeyr17518fe2017-06-20 04:35:06 +02001/*! \file stat_item.c
Harald Welte781951b2017-10-15 19:24:57 +02002 * utility routines for keeping statistical values */
Neels Hofmeyr17518fe2017-06-20 04:35:06 +02003/*
Jacob Erlbeck9732cb42015-10-01 20:43:53 +02004 * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
Harald Weltee08da972017-11-13 01:00:26 +09005 * (C) 2015 by sysmocom - s.f.m.c. GmbH
Jacob Erlbeck9732cb42015-10-01 20:43:53 +02006 *
7 * All Rights Reserved
8 *
Harald Weltee08da972017-11-13 01:00:26 +09009 * SPDX-License-Identifier: GPL-2.0+
10 *
Jacob Erlbeck9732cb42015-10-01 20:43:53 +020011 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 *
25 */
26
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +010027/*! \addtogroup osmo_stat_item
Jacob Erlbeck9732cb42015-10-01 20:43:53 +020028 * @{
Harald Welte781951b2017-10-15 19:24:57 +020029 *
30 * This osmo_stat_item module adds instrumentation capabilities to
31 * gather measurement and statistical values in a similar fashion to
32 * what we have as \ref osmo_counter_group.
33 *
34 * As opposed to counters, osmo_stat_item do not increment but consist
35 * of a configurable-sized FIFO, which can store not only the current
36 * (most recent) value, but also historic values.
37 *
38 * The only supported value type is an int32_t.
39 *
Oliver Smith61401942021-03-26 10:18:37 +010040 * Getting values from osmo_stat_item is usually done at a high level
41 * through the stats API (stats.c). It uses item->stats_next_id to
42 * store what has been sent to all enabled reporters. It is also
43 * possible to read from osmo_stat_item directly, without modifying
44 * its state, by storing next_id outside of osmo_stat_item.
Harald Welte781951b2017-10-15 19:24:57 +020045 *
46 * Each value stored in the FIFO of an osmo_stat_item has an associated
Oliver Smith61401942021-03-26 10:18:37 +010047 * value_id. The value_id is increased with each value, so (until the
48 * counter wraps) more recent values will have higher values.
Harald Welte781951b2017-10-15 19:24:57 +020049 *
50 * When a new value is set, the oldest value in the FIFO gets silently
51 * overwritten. Lost values are skipped when getting values from the
52 * item.
53 *
54 */
Jacob Erlbeck9732cb42015-10-01 20:43:53 +020055
Oliver Smith43686da2021-03-26 14:33:21 +010056/* Struct overview:
57 * Each osmo_stat_item is attached to an osmo_stat_item_group, which is
58 * attached to the global osmo_stat_item_groups list.
59 *
60 * osmo_stat_item_groups
61 * / \
62 * group1 group2
63 * / \
64 * item1 item2
65 * |
66 * values
67 * / \
68 * 1 2
69 * |-id |-id
70 * '-value '-value
71 */
72
Jacob Erlbeck9732cb42015-10-01 20:43:53 +020073#include <stdint.h>
74#include <string.h>
75
76#include <osmocom/core/utils.h>
77#include <osmocom/core/linuxlist.h>
Oliver Smith61401942021-03-26 10:18:37 +010078#include <osmocom/core/logging.h>
Jacob Erlbeck9732cb42015-10-01 20:43:53 +020079#include <osmocom/core/talloc.h>
80#include <osmocom/core/timer.h>
81#include <osmocom/core/stat_item.h>
82
Harald Welte781951b2017-10-15 19:24:57 +020083/*! global list of stat_item groups */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +010084static LLIST_HEAD(osmo_stat_item_groups);
Jacob Erlbeck9732cb42015-10-01 20:43:53 +020085
Harald Welte781951b2017-10-15 19:24:57 +020086/*! talloc context from which we allocate */
Jacob Erlbeck9732cb42015-10-01 20:43:53 +020087static void *tall_stat_item_ctx;
88
Harald Welte781951b2017-10-15 19:24:57 +020089/*! Allocate a new group of counters according to description.
90 * Allocate a group of stat items described in \a desc from talloc context \a ctx,
91 * giving the new group the index \a idx.
Jacob Erlbeck9732cb42015-10-01 20:43:53 +020092 * \param[in] ctx \ref talloc context
93 * \param[in] desc Statistics item group description
94 * \param[in] idx Index of new stat item group
95 */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +010096struct osmo_stat_item_group *osmo_stat_item_group_alloc(void *ctx,
97 const struct osmo_stat_item_group_desc *desc,
Jacob Erlbeck9732cb42015-10-01 20:43:53 +020098 unsigned int idx)
99{
100 unsigned int group_size;
Harald Welteb32a1942015-11-20 10:22:14 +0100101 unsigned long items_size = 0;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200102 unsigned int item_idx;
103 void *items;
104
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100105 struct osmo_stat_item_group *group;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200106
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100107 group_size = sizeof(struct osmo_stat_item_group) +
108 desc->num_items * sizeof(struct osmo_stat_item *);
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200109
110 if (!ctx)
111 ctx = tall_stat_item_ctx;
112
113 group = talloc_zero_size(ctx, group_size);
114 if (!group)
115 return NULL;
116
117 group->desc = desc;
118 group->idx = idx;
119
120 /* Get combined size of all items */
121 for (item_idx = 0; item_idx < desc->num_items; item_idx++) {
122 unsigned int size;
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100123 size = sizeof(struct osmo_stat_item) +
124 sizeof(struct osmo_stat_item_value) *
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200125 desc->item_desc[item_idx].num_values;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200126 /* Align to pointer size */
127 size = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
128
129 /* Store offsets into the item array */
130 group->items[item_idx] = (void *)items_size;
131
132 items_size += size;
133 }
134
135 items = talloc_zero_size(group, items_size);
136 if (!items) {
137 talloc_free(group);
138 return NULL;
139 }
140
141 /* Update item pointers */
142 for (item_idx = 0; item_idx < desc->num_items; item_idx++) {
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100143 struct osmo_stat_item *item = (struct osmo_stat_item *)
Harald Welteb32a1942015-11-20 10:22:14 +0100144 ((uint8_t *)items + (unsigned long)group->items[item_idx]);
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200145 unsigned int i;
146
147 group->items[item_idx] = item;
148 item->last_offs = desc->item_desc[item_idx].num_values - 1;
Oliver Smith61401942021-03-26 10:18:37 +0100149 item->stats_next_id = 1;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200150 item->desc = &desc->item_desc[item_idx];
151
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200152 for (i = 0; i <= item->last_offs; i++) {
153 item->values[i].value = desc->item_desc[item_idx].default_value;
Jacob Erlbeckac4ed172015-12-08 10:29:16 +0100154 item->values[i].id = OSMO_STAT_ITEM_NOVALUE_ID;
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200155 }
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200156 }
157
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100158 llist_add(&group->list, &osmo_stat_item_groups);
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200159
160 return group;
161}
162
Harald Welte781951b2017-10-15 19:24:57 +0200163/*! Free the memory for the specified group of stat items */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100164void osmo_stat_item_group_free(struct osmo_stat_item_group *grp)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200165{
166 llist_del(&grp->list);
167 talloc_free(grp);
168}
169
Pau Espin Pedrol5fe3de52021-05-31 13:39:07 +0200170/*! Get statistics item from group, identified by index idx
171 * \param[in] grp Rate counter group
172 * \param[in] idx Index of the counter to retrieve
173 * \returns rate counter requested
174 */
175struct osmo_stat_item *osmo_stat_item_group_get_item(struct osmo_stat_item_group *grp, unsigned int idx)
176{
177 return grp->items[idx];
178}
179
Pau Espin Pedrol09f075f2021-05-31 13:10:24 +0200180/*! Set a name for the statistics item group to be used instead of index value
181 at report time.
182 * \param[in] statg Statistics item group
183 * \param[in] name Name identifier to assign to the statistics item group
184 */
185void osmo_stat_item_group_set_name(struct osmo_stat_item_group *statg, const char *name)
186{
187 osmo_talloc_replace_string(statg, &statg->name, name);
188}
189
Alexander Couzenscc72cc42019-04-27 23:19:55 +0200190/*! Increase the stat_item to the given value.
191 * This function adds a new value for the given stat_item at the end of
192 * the FIFO.
193 * \param[in] item The stat_item whose \a value we want to set
194 * \param[in] value The numeric value we want to store at end of FIFO
195 */
196void osmo_stat_item_inc(struct osmo_stat_item *item, int32_t value)
197{
198 int32_t oldvalue = item->values[item->last_offs].value;
199 osmo_stat_item_set(item, oldvalue + value);
200}
201
202/*! Descrease the stat_item to the given value.
203 * This function adds a new value for the given stat_item at the end of
204 * the FIFO.
205 * \param[in] item The stat_item whose \a value we want to set
206 * \param[in] value The numeric value we want to store at end of FIFO
207 */
208void osmo_stat_item_dec(struct osmo_stat_item *item, int32_t value)
209{
210 int32_t oldvalue = item->values[item->last_offs].value;
211 osmo_stat_item_set(item, oldvalue - value);
212}
213
Harald Welte781951b2017-10-15 19:24:57 +0200214/*! Set the a given stat_item to the given value.
215 * This function adds a new value for the given stat_item at the end of
216 * the FIFO.
217 * \param[in] item The stat_item whose \a value we want to set
218 * \param[in] value The numeric value we want to store at end of FIFO
219 */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100220void osmo_stat_item_set(struct osmo_stat_item *item, int32_t value)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200221{
Oliver Smith61401942021-03-26 10:18:37 +0100222 int32_t id = item->values[item->last_offs].id + 1;
223 if (id == OSMO_STAT_ITEM_NOVALUE_ID)
224 id++;
225
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200226 item->last_offs += 1;
227 if (item->last_offs >= item->desc->num_values)
228 item->last_offs = 0;
229
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200230 item->values[item->last_offs].value = value;
Oliver Smith61401942021-03-26 10:18:37 +0100231 item->values[item->last_offs].id = id;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200232}
233
Harald Welte781951b2017-10-15 19:24:57 +0200234/*! Retrieve the next value from the osmo_stat_item object.
Oliver Smithd3490bc2021-03-26 11:34:34 +0100235 * If a new value has been set, it is returned. The next_id is used to decide
Harald Welte781951b2017-10-15 19:24:57 +0200236 * which value to return.
Oliver Smithd3490bc2021-03-26 11:34:34 +0100237 * On success, *next_id is updated to refer to the next unread value. If
238 * values have been missed due to FIFO overflow, *next_id is incremented by
Harald Welte781951b2017-10-15 19:24:57 +0200239 * (1 + num_lost).
240 * This way, the osmo_stat_item object can be kept stateless from the reader's
241 * perspective and therefore be used by several backends simultaneously.
242 *
Oliver Smithd3490bc2021-03-26 11:34:34 +0100243 * \param val the osmo_stat_item object
244 * \param next_id identifies the next value to be read
245 * \param value a pointer to store the value
Harald Welte781951b2017-10-15 19:24:57 +0200246 * \returns the increment of the index (0: no value has been read,
247 * 1: one value has been taken,
248 * (1+n): n values have been skipped, one has been taken)
249 */
Oliver Smithd3490bc2021-03-26 11:34:34 +0100250int osmo_stat_item_get_next(const struct osmo_stat_item *item, int32_t *next_id,
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200251 int32_t *value)
252{
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100253 const struct osmo_stat_item_value *next_value;
254 const struct osmo_stat_item_value *item_value = NULL;
Oliver Smithd3490bc2021-03-26 11:34:34 +0100255 int id_delta;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200256 int next_offs;
257
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200258 next_offs = item->last_offs;
259 next_value = &item->values[next_offs];
260
Oliver Smithd3490bc2021-03-26 11:34:34 +0100261 while (next_value->id - *next_id >= 0 &&
Jacob Erlbeckac4ed172015-12-08 10:29:16 +0100262 next_value->id != OSMO_STAT_ITEM_NOVALUE_ID)
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200263 {
264 item_value = next_value;
265
266 next_offs -= 1;
267 if (next_offs < 0)
268 next_offs = item->desc->num_values - 1;
269 if (next_offs == item->last_offs)
270 break;
271 next_value = &item->values[next_offs];
272 }
273
274 if (!item_value)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200275 /* All items have been read */
276 return 0;
277
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200278 *value = item_value->value;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200279
Oliver Smithd3490bc2021-03-26 11:34:34 +0100280 id_delta = item_value->id + 1 - *next_id;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200281
Oliver Smithd3490bc2021-03-26 11:34:34 +0100282 *next_id = item_value->id + 1;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200283
Oliver Smith2623fca2021-03-26 10:42:09 +0100284 if (id_delta > 1) {
285 LOGP(DLSTATS, LOGL_ERROR, "%s: %d stats values skipped\n", item->desc->name, id_delta - 1);
286 }
287
Oliver Smithd3490bc2021-03-26 11:34:34 +0100288 return id_delta;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200289}
290
Oliver Smithd3490bc2021-03-26 11:34:34 +0100291/*! Skip/discard all values of this item and update \a next_id accordingly */
292int osmo_stat_item_discard(const struct osmo_stat_item *item, int32_t *next_id)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200293{
Oliver Smithd3490bc2021-03-26 11:34:34 +0100294 int discarded = item->values[item->last_offs].id + 1 - *next_id;
295 *next_id = item->values[item->last_offs].id + 1;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200296
297 return discarded;
298}
299
Oliver Smithd3490bc2021-03-26 11:34:34 +0100300/*! Skip all values of all items and update \a next_id accordingly */
301int osmo_stat_item_discard_all(int32_t *next_id)
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200302{
Oliver Smith61401942021-03-26 10:18:37 +0100303 LOGP(DLSTATS, LOGL_ERROR, "osmo_stat_item_discard_all is deprecated (no-op)\n");
304 return 0;
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200305}
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200306
Harald Welte781951b2017-10-15 19:24:57 +0200307/*! Initialize the stat item module. Call this once from your program.
308 * \param[in] tall_ctx Talloc context from which this module allocates */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100309int osmo_stat_item_init(void *tall_ctx)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200310{
311 tall_stat_item_ctx = tall_ctx;
312
313 return 0;
314}
315
Harald Welte781951b2017-10-15 19:24:57 +0200316/*! Search for item group based on group name and index
317 * \param[in] name Name of stats_item_group we want to find
318 * \param[in] idx Index of the group we want to find
319 * \returns pointer to group, if found; NULL otherwise */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100320struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idx(
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200321 const char *name, const unsigned int idx)
322{
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100323 struct osmo_stat_item_group *statg;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200324
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100325 llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200326 if (!statg->desc)
327 continue;
328
329 if (!strcmp(statg->desc->group_name_prefix, name) &&
330 statg->idx == idx)
331 return statg;
332 }
333 return NULL;
334}
335
Harald Welte781951b2017-10-15 19:24:57 +0200336/*! Search for item based on group + item name
337 * \param[in] statg group in which to search for the item
338 * \param[in] name name of item to search within \a statg
339 * \returns pointer to item, if found; NULL otherwise */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100340const struct osmo_stat_item *osmo_stat_item_get_by_name(
341 const struct osmo_stat_item_group *statg, const char *name)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200342{
343 int i;
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100344 const struct osmo_stat_item_desc *item_desc;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200345
346 if (!statg->desc)
347 return NULL;
348
349 for (i = 0; i < statg->desc->num_items; i++) {
350 item_desc = &statg->desc->item_desc[i];
351
352 if (!strcmp(item_desc->name, name)) {
353 return statg->items[i];
354 }
355 }
356 return NULL;
357}
358
Harald Welte781951b2017-10-15 19:24:57 +0200359/*! Iterate over all items in group, call user-supplied function on each
360 * \param[in] statg stat_item group over whose items to iterate
361 * \param[in] handle_item Call-back function, aborts if rc < 0
362 * \param[in] data Private data handed through to \a handle_item
363 */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100364int osmo_stat_item_for_each_item(struct osmo_stat_item_group *statg,
365 osmo_stat_item_handler_t handle_item, void *data)
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200366{
367 int rc = 0;
368 int i;
369
370 for (i = 0; i < statg->desc->num_items; i++) {
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100371 struct osmo_stat_item *item = statg->items[i];
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200372 rc = handle_item(statg, item, data);
373 if (rc < 0)
374 return rc;
375 }
376
377 return rc;
378}
379
Harald Welte781951b2017-10-15 19:24:57 +0200380/*! Iterate over all stat_item groups in system, call user-supplied function on each
381 * \param[in] handle_group Call-back function, aborts if rc < 0
382 * \param[in] data Private data handed through to \a handle_group
383 */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100384int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, void *data)
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200385{
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100386 struct osmo_stat_item_group *statg;
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200387 int rc = 0;
388
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100389 llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200390 rc = handle_group(statg, data);
391 if (rc < 0)
392 return rc;
393 }
394
395 return rc;
396}
397
Daniel Willmannea71b432020-07-14 18:10:20 +0200398
399/*! Remove all values of a stat item
400 * \param[in] item stat item to reset
401 */
402void osmo_stat_item_reset(struct osmo_stat_item *item)
403{
404 unsigned int i;
405
406 item->last_offs = item->desc->num_values - 1;
Oliver Smith61401942021-03-26 10:18:37 +0100407 item->stats_next_id = 1;
Daniel Willmannea71b432020-07-14 18:10:20 +0200408
409 for (i = 0; i <= item->last_offs; i++) {
410 item->values[i].value = item->desc->default_value;
411 item->values[i].id = OSMO_STAT_ITEM_NOVALUE_ID;
412 }
413}
414
415/*! Reset all osmo stat items in a group
416 * \param[in] statg stat item group to reset
417 */
418void osmo_stat_item_group_reset(struct osmo_stat_item_group *statg)
419{
420 int i;
421
422 for (i = 0; i < statg->desc->num_items; i++) {
423 struct osmo_stat_item *item = statg->items[i];
424 osmo_stat_item_reset(item);
425 }
426}
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200427/*! @} */