blob: a6f86cb97dab5a9ef2e39d2a10dd339507be8d80 [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
Alexander Couzenscc72cc42019-04-27 23:19:55 +0200180/*! Increase the stat_item to the given value.
181 * This function adds a new value for the given stat_item at the end of
182 * the FIFO.
183 * \param[in] item The stat_item whose \a value we want to set
184 * \param[in] value The numeric value we want to store at end of FIFO
185 */
186void osmo_stat_item_inc(struct osmo_stat_item *item, int32_t value)
187{
188 int32_t oldvalue = item->values[item->last_offs].value;
189 osmo_stat_item_set(item, oldvalue + value);
190}
191
192/*! Descrease the stat_item to the given value.
193 * This function adds a new value for the given stat_item at the end of
194 * the FIFO.
195 * \param[in] item The stat_item whose \a value we want to set
196 * \param[in] value The numeric value we want to store at end of FIFO
197 */
198void osmo_stat_item_dec(struct osmo_stat_item *item, int32_t value)
199{
200 int32_t oldvalue = item->values[item->last_offs].value;
201 osmo_stat_item_set(item, oldvalue - value);
202}
203
Harald Welte781951b2017-10-15 19:24:57 +0200204/*! Set the a given stat_item to the given value.
205 * This function adds a new value for the given stat_item at the end of
206 * the FIFO.
207 * \param[in] item The stat_item whose \a value we want to set
208 * \param[in] value The numeric value we want to store at end of FIFO
209 */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100210void osmo_stat_item_set(struct osmo_stat_item *item, int32_t value)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200211{
Oliver Smith61401942021-03-26 10:18:37 +0100212 int32_t id = item->values[item->last_offs].id + 1;
213 if (id == OSMO_STAT_ITEM_NOVALUE_ID)
214 id++;
215
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200216 item->last_offs += 1;
217 if (item->last_offs >= item->desc->num_values)
218 item->last_offs = 0;
219
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200220 item->values[item->last_offs].value = value;
Oliver Smith61401942021-03-26 10:18:37 +0100221 item->values[item->last_offs].id = id;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200222}
223
Harald Welte781951b2017-10-15 19:24:57 +0200224/*! Retrieve the next value from the osmo_stat_item object.
Oliver Smithd3490bc2021-03-26 11:34:34 +0100225 * If a new value has been set, it is returned. The next_id is used to decide
Harald Welte781951b2017-10-15 19:24:57 +0200226 * which value to return.
Oliver Smithd3490bc2021-03-26 11:34:34 +0100227 * On success, *next_id is updated to refer to the next unread value. If
228 * values have been missed due to FIFO overflow, *next_id is incremented by
Harald Welte781951b2017-10-15 19:24:57 +0200229 * (1 + num_lost).
230 * This way, the osmo_stat_item object can be kept stateless from the reader's
231 * perspective and therefore be used by several backends simultaneously.
232 *
Oliver Smithd3490bc2021-03-26 11:34:34 +0100233 * \param val the osmo_stat_item object
234 * \param next_id identifies the next value to be read
235 * \param value a pointer to store the value
Harald Welte781951b2017-10-15 19:24:57 +0200236 * \returns the increment of the index (0: no value has been read,
237 * 1: one value has been taken,
238 * (1+n): n values have been skipped, one has been taken)
239 */
Oliver Smithd3490bc2021-03-26 11:34:34 +0100240int osmo_stat_item_get_next(const struct osmo_stat_item *item, int32_t *next_id,
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200241 int32_t *value)
242{
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100243 const struct osmo_stat_item_value *next_value;
244 const struct osmo_stat_item_value *item_value = NULL;
Oliver Smithd3490bc2021-03-26 11:34:34 +0100245 int id_delta;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200246 int next_offs;
247
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200248 next_offs = item->last_offs;
249 next_value = &item->values[next_offs];
250
Oliver Smithd3490bc2021-03-26 11:34:34 +0100251 while (next_value->id - *next_id >= 0 &&
Jacob Erlbeckac4ed172015-12-08 10:29:16 +0100252 next_value->id != OSMO_STAT_ITEM_NOVALUE_ID)
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200253 {
254 item_value = next_value;
255
256 next_offs -= 1;
257 if (next_offs < 0)
258 next_offs = item->desc->num_values - 1;
259 if (next_offs == item->last_offs)
260 break;
261 next_value = &item->values[next_offs];
262 }
263
264 if (!item_value)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200265 /* All items have been read */
266 return 0;
267
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200268 *value = item_value->value;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200269
Oliver Smithd3490bc2021-03-26 11:34:34 +0100270 id_delta = item_value->id + 1 - *next_id;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200271
Oliver Smithd3490bc2021-03-26 11:34:34 +0100272 *next_id = item_value->id + 1;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200273
Oliver Smith2623fca2021-03-26 10:42:09 +0100274 if (id_delta > 1) {
275 LOGP(DLSTATS, LOGL_ERROR, "%s: %d stats values skipped\n", item->desc->name, id_delta - 1);
276 }
277
Oliver Smithd3490bc2021-03-26 11:34:34 +0100278 return id_delta;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200279}
280
Oliver Smithd3490bc2021-03-26 11:34:34 +0100281/*! Skip/discard all values of this item and update \a next_id accordingly */
282int osmo_stat_item_discard(const struct osmo_stat_item *item, int32_t *next_id)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200283{
Oliver Smithd3490bc2021-03-26 11:34:34 +0100284 int discarded = item->values[item->last_offs].id + 1 - *next_id;
285 *next_id = item->values[item->last_offs].id + 1;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200286
287 return discarded;
288}
289
Oliver Smithd3490bc2021-03-26 11:34:34 +0100290/*! Skip all values of all items and update \a next_id accordingly */
291int osmo_stat_item_discard_all(int32_t *next_id)
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200292{
Oliver Smith61401942021-03-26 10:18:37 +0100293 LOGP(DLSTATS, LOGL_ERROR, "osmo_stat_item_discard_all is deprecated (no-op)\n");
294 return 0;
Jacob Erlbeckb27b3522015-10-12 18:47:09 +0200295}
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200296
Harald Welte781951b2017-10-15 19:24:57 +0200297/*! Initialize the stat item module. Call this once from your program.
298 * \param[in] tall_ctx Talloc context from which this module allocates */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100299int osmo_stat_item_init(void *tall_ctx)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200300{
301 tall_stat_item_ctx = tall_ctx;
302
303 return 0;
304}
305
Harald Welte781951b2017-10-15 19:24:57 +0200306/*! Search for item group based on group name and index
307 * \param[in] name Name of stats_item_group we want to find
308 * \param[in] idx Index of the group we want to find
309 * \returns pointer to group, if found; NULL otherwise */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100310struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idx(
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200311 const char *name, const unsigned int idx)
312{
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100313 struct osmo_stat_item_group *statg;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200314
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100315 llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200316 if (!statg->desc)
317 continue;
318
319 if (!strcmp(statg->desc->group_name_prefix, name) &&
320 statg->idx == idx)
321 return statg;
322 }
323 return NULL;
324}
325
Harald Welte781951b2017-10-15 19:24:57 +0200326/*! Search for item based on group + item name
327 * \param[in] statg group in which to search for the item
328 * \param[in] name name of item to search within \a statg
329 * \returns pointer to item, if found; NULL otherwise */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100330const struct osmo_stat_item *osmo_stat_item_get_by_name(
331 const struct osmo_stat_item_group *statg, const char *name)
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200332{
333 int i;
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100334 const struct osmo_stat_item_desc *item_desc;
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200335
336 if (!statg->desc)
337 return NULL;
338
339 for (i = 0; i < statg->desc->num_items; i++) {
340 item_desc = &statg->desc->item_desc[i];
341
342 if (!strcmp(item_desc->name, name)) {
343 return statg->items[i];
344 }
345 }
346 return NULL;
347}
348
Harald Welte781951b2017-10-15 19:24:57 +0200349/*! Iterate over all items in group, call user-supplied function on each
350 * \param[in] statg stat_item group over whose items to iterate
351 * \param[in] handle_item Call-back function, aborts if rc < 0
352 * \param[in] data Private data handed through to \a handle_item
353 */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100354int osmo_stat_item_for_each_item(struct osmo_stat_item_group *statg,
355 osmo_stat_item_handler_t handle_item, void *data)
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200356{
357 int rc = 0;
358 int i;
359
360 for (i = 0; i < statg->desc->num_items; i++) {
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100361 struct osmo_stat_item *item = statg->items[i];
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200362 rc = handle_item(statg, item, data);
363 if (rc < 0)
364 return rc;
365 }
366
367 return rc;
368}
369
Harald Welte781951b2017-10-15 19:24:57 +0200370/*! Iterate over all stat_item groups in system, call user-supplied function on each
371 * \param[in] handle_group Call-back function, aborts if rc < 0
372 * \param[in] data Private data handed through to \a handle_group
373 */
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100374int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, void *data)
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200375{
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100376 struct osmo_stat_item_group *statg;
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200377 int rc = 0;
378
Jacob Erlbeckfc9533d2015-10-29 00:55:58 +0100379 llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
Jacob Erlbeckc6a71082015-10-19 14:04:38 +0200380 rc = handle_group(statg, data);
381 if (rc < 0)
382 return rc;
383 }
384
385 return rc;
386}
387
Daniel Willmannea71b432020-07-14 18:10:20 +0200388
389/*! Remove all values of a stat item
390 * \param[in] item stat item to reset
391 */
392void osmo_stat_item_reset(struct osmo_stat_item *item)
393{
394 unsigned int i;
395
396 item->last_offs = item->desc->num_values - 1;
Oliver Smith61401942021-03-26 10:18:37 +0100397 item->stats_next_id = 1;
Daniel Willmannea71b432020-07-14 18:10:20 +0200398
399 for (i = 0; i <= item->last_offs; i++) {
400 item->values[i].value = item->desc->default_value;
401 item->values[i].id = OSMO_STAT_ITEM_NOVALUE_ID;
402 }
403}
404
405/*! Reset all osmo stat items in a group
406 * \param[in] statg stat item group to reset
407 */
408void osmo_stat_item_group_reset(struct osmo_stat_item_group *statg)
409{
410 int i;
411
412 for (i = 0; i < statg->desc->num_items; i++) {
413 struct osmo_stat_item *item = statg->items[i];
414 osmo_stat_item_reset(item);
415 }
416}
Jacob Erlbeck9732cb42015-10-01 20:43:53 +0200417/*! @} */