blob: 40374a7c54a881b4870bf299b8e388df0364153e [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
Alexander Couzenscc72cc42019-04-27 23:19:55 +0200170/*! Increase the stat_item to the given value.
171 * This function adds a new value for the given stat_item at the end of
172 * the FIFO.
173 * \param[in] item The stat_item whose \a value we want to set
174 * \param[in] value The numeric value we want to store at end of FIFO
175 */
176void osmo_stat_item_inc(struct osmo_stat_item *item, int32_t value)
177{
178 int32_t oldvalue = item->values[item->last_offs].value;
179 osmo_stat_item_set(item, oldvalue + value);
180}
181
182/*! Descrease the stat_item to the given value.
183 * This function adds a new value for the given stat_item at the end of
184 * the FIFO.
185 * \param[in] item The stat_item whose \a value we want to set
186 * \param[in] value The numeric value we want to store at end of FIFO
187 */
188void osmo_stat_item_dec(struct osmo_stat_item *item, int32_t value)
189{
190 int32_t oldvalue = item->values[item->last_offs].value;
191 osmo_stat_item_set(item, oldvalue - value);
192}
193
Harald Welte781951b2017-10-15 19:24:57 +0200194/*! Set the a given stat_item to the given value.
195 * This function adds a new value for the given stat_item at the end of
196 * the FIFO.
197 * \param[in] item The stat_item whose \a value we want to set
198 * \param[in] value The numeric value we want to store at end of FIFO
199 */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100200void osmo_stat_item_set(struct osmo_stat_item *item, int32_t value)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200201{
Oliver Smith61401942021-03-26 10:18:37 +0100202 int32_t id = item->values[item->last_offs].id + 1;
203 if (id == OSMO_STAT_ITEM_NOVALUE_ID)
204 id++;
205
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200206 item->last_offs += 1;
207 if (item->last_offs >= item->desc->num_values)
208 item->last_offs = 0;
209
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200210 item->values[item->last_offs].value = value;
Oliver Smith61401942021-03-26 10:18:37 +0100211 item->values[item->last_offs].id = id;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200212}
213
Harald Welte781951b2017-10-15 19:24:57 +0200214/*! Retrieve the next value from the osmo_stat_item object.
Oliver Smithd3490bc2021-03-26 11:34:34 +0100215 * If a new value has been set, it is returned. The next_id is used to decide
Harald Welte781951b2017-10-15 19:24:57 +0200216 * which value to return.
Oliver Smithd3490bc2021-03-26 11:34:34 +0100217 * On success, *next_id is updated to refer to the next unread value. If
218 * values have been missed due to FIFO overflow, *next_id is incremented by
Harald Welte781951b2017-10-15 19:24:57 +0200219 * (1 + num_lost).
220 * This way, the osmo_stat_item object can be kept stateless from the reader's
221 * perspective and therefore be used by several backends simultaneously.
222 *
Oliver Smithd3490bc2021-03-26 11:34:34 +0100223 * \param val the osmo_stat_item object
224 * \param next_id identifies the next value to be read
225 * \param value a pointer to store the value
Harald Welte781951b2017-10-15 19:24:57 +0200226 * \returns the increment of the index (0: no value has been read,
227 * 1: one value has been taken,
228 * (1+n): n values have been skipped, one has been taken)
229 */
Oliver Smithd3490bc2021-03-26 11:34:34 +0100230int osmo_stat_item_get_next(const struct osmo_stat_item *item, int32_t *next_id,
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200231 int32_t *value)
232{
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100233 const struct osmo_stat_item_value *next_value;
234 const struct osmo_stat_item_value *item_value = NULL;
Oliver Smithd3490bc2021-03-26 11:34:34 +0100235 int id_delta;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200236 int next_offs;
237
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200238 next_offs = item->last_offs;
239 next_value = &item->values[next_offs];
240
Oliver Smithd3490bc2021-03-26 11:34:34 +0100241 while (next_value->id - *next_id >= 0 &&
Jacob Erlbeckac4ed172015-12-08 10:29:16 +0100242 next_value->id != OSMO_STAT_ITEM_NOVALUE_ID)
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200243 {
244 item_value = next_value;
245
246 next_offs -= 1;
247 if (next_offs < 0)
248 next_offs = item->desc->num_values - 1;
249 if (next_offs == item->last_offs)
250 break;
251 next_value = &item->values[next_offs];
252 }
253
254 if (!item_value)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200255 /* All items have been read */
256 return 0;
257
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200258 *value = item_value->value;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200259
Oliver Smithd3490bc2021-03-26 11:34:34 +0100260 id_delta = item_value->id + 1 - *next_id;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200261
Oliver Smithd3490bc2021-03-26 11:34:34 +0100262 *next_id = item_value->id + 1;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200263
Oliver Smith2623fca2021-03-26 10:42:09 +0100264 if (id_delta > 1) {
265 LOGP(DLSTATS, LOGL_ERROR, "%s: %d stats values skipped\n", item->desc->name, id_delta - 1);
266 }
267
Oliver Smithd3490bc2021-03-26 11:34:34 +0100268 return id_delta;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200269}
270
Oliver Smithd3490bc2021-03-26 11:34:34 +0100271/*! Skip/discard all values of this item and update \a next_id accordingly */
272int osmo_stat_item_discard(const struct osmo_stat_item *item, int32_t *next_id)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200273{
Oliver Smithd3490bc2021-03-26 11:34:34 +0100274 int discarded = item->values[item->last_offs].id + 1 - *next_id;
275 *next_id = item->values[item->last_offs].id + 1;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200276
277 return discarded;
278}
279
Oliver Smithd3490bc2021-03-26 11:34:34 +0100280/*! Skip all values of all items and update \a next_id accordingly */
281int osmo_stat_item_discard_all(int32_t *next_id)
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200282{
Oliver Smith61401942021-03-26 10:18:37 +0100283 LOGP(DLSTATS, LOGL_ERROR, "osmo_stat_item_discard_all is deprecated (no-op)\n");
284 return 0;
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200285}
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200286
Harald Welte781951b2017-10-15 19:24:57 +0200287/*! Initialize the stat item module. Call this once from your program.
288 * \param[in] tall_ctx Talloc context from which this module allocates */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100289int osmo_stat_item_init(void *tall_ctx)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200290{
291 tall_stat_item_ctx = tall_ctx;
292
293 return 0;
294}
295
Harald Welte781951b2017-10-15 19:24:57 +0200296/*! Search for item group based on group name and index
297 * \param[in] name Name of stats_item_group we want to find
298 * \param[in] idx Index of the group we want to find
299 * \returns pointer to group, if found; NULL otherwise */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100300struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idx(
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200301 const char *name, const unsigned int idx)
302{
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100303 struct osmo_stat_item_group *statg;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200304
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100305 llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200306 if (!statg->desc)
307 continue;
308
309 if (!strcmp(statg->desc->group_name_prefix, name) &&
310 statg->idx == idx)
311 return statg;
312 }
313 return NULL;
314}
315
Harald Welte781951b2017-10-15 19:24:57 +0200316/*! Search for item based on group + item name
317 * \param[in] statg group in which to search for the item
318 * \param[in] name name of item to search within \a statg
319 * \returns pointer to item, if found; NULL otherwise */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100320const struct osmo_stat_item *osmo_stat_item_get_by_name(
321 const struct osmo_stat_item_group *statg, const char *name)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200322{
323 int i;
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100324 const struct osmo_stat_item_desc *item_desc;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200325
326 if (!statg->desc)
327 return NULL;
328
329 for (i = 0; i < statg->desc->num_items; i++) {
330 item_desc = &statg->desc->item_desc[i];
331
332 if (!strcmp(item_desc->name, name)) {
333 return statg->items[i];
334 }
335 }
336 return NULL;
337}
338
Harald Welte781951b2017-10-15 19:24:57 +0200339/*! Iterate over all items in group, call user-supplied function on each
340 * \param[in] statg stat_item group over whose items to iterate
341 * \param[in] handle_item Call-back function, aborts if rc < 0
342 * \param[in] data Private data handed through to \a handle_item
343 */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100344int osmo_stat_item_for_each_item(struct osmo_stat_item_group *statg,
345 osmo_stat_item_handler_t handle_item, void *data)
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200346{
347 int rc = 0;
348 int i;
349
350 for (i = 0; i < statg->desc->num_items; i++) {
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100351 struct osmo_stat_item *item = statg->items[i];
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200352 rc = handle_item(statg, item, data);
353 if (rc < 0)
354 return rc;
355 }
356
357 return rc;
358}
359
Harald Welte781951b2017-10-15 19:24:57 +0200360/*! Iterate over all stat_item groups in system, call user-supplied function on each
361 * \param[in] handle_group Call-back function, aborts if rc < 0
362 * \param[in] data Private data handed through to \a handle_group
363 */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100364int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, void *data)
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200365{
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100366 struct osmo_stat_item_group *statg;
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200367 int rc = 0;
368
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100369 llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200370 rc = handle_group(statg, data);
371 if (rc < 0)
372 return rc;
373 }
374
375 return rc;
376}
377
Daniel Willmannea71b432020-07-14 18:10:20 +0200378
379/*! Remove all values of a stat item
380 * \param[in] item stat item to reset
381 */
382void osmo_stat_item_reset(struct osmo_stat_item *item)
383{
384 unsigned int i;
385
386 item->last_offs = item->desc->num_values - 1;
Oliver Smith61401942021-03-26 10:18:37 +0100387 item->stats_next_id = 1;
Daniel Willmannea71b432020-07-14 18:10:20 +0200388
389 for (i = 0; i <= item->last_offs; i++) {
390 item->values[i].value = item->desc->default_value;
391 item->values[i].id = OSMO_STAT_ITEM_NOVALUE_ID;
392 }
393}
394
395/*! Reset all osmo stat items in a group
396 * \param[in] statg stat item group to reset
397 */
398void osmo_stat_item_group_reset(struct osmo_stat_item_group *statg)
399{
400 int i;
401
402 for (i = 0; i < statg->desc->num_items; i++) {
403 struct osmo_stat_item *item = statg->items[i];
404 osmo_stat_item_reset(item);
405 }
406}
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200407/*! @} */