move time_cc to libosmocore osmo_time_cc

Related: SYS#4878
Depends: Iabb17a08e6e1a86f168cdb008fba05ecd4776bdd (libosmocore)
Change-Id: Ica9f908a1a30f334a24c59471affa11225117e12
changes/31/26231/4
Neels Hofmeyr 1 year ago
parent 7f7c83a7de
commit d13ef633d2
  1. 1
      TODO-RELEASE
  2. 1
      configure.ac
  3. 1
      include/osmocom/bsc/Makefile.am
  4. 8
      include/osmocom/bsc/bts.h
  5. 10
      include/osmocom/bsc/gsm_data.h
  6. 157
      include/osmocom/bsc/time_cc.h
  7. 1
      src/osmo-bsc/Makefile.am
  8. 8
      src/osmo-bsc/bsc_init.c
  9. 76
      src/osmo-bsc/bsc_stats.c
  10. 8
      src/osmo-bsc/bts.c
  11. 208
      src/osmo-bsc/time_cc.c
  12. 1
      tests/Makefile.am
  13. 6
      tests/testsuite.at
  14. 36
      tests/time_cc/Makefile.am
  15. 769
      tests/time_cc/time_cc_test.c
  16. 328
      tests/time_cc/time_cc_test.ok

@ -16,3 +16,4 @@ libosmosgsm >1.5.1 needs GSM_PCHAN_OSMO_DYN
libosmocore >1.5.1 RSL_IPAC_EIE_OSMO*, struct osmo_preproc_*
libosmocore >1.5.1 needs osmo_str_to_int()
libosmocore >1.5.1 needs new osmo_stat_item implementation (omits FIFO size for stat item)
libosmocore >=1.6 need osmo_time_cc

@ -238,7 +238,6 @@ AC_OUTPUT(
tests/subscr/Makefile
tests/nanobts_omlattr/Makefile
tests/handover/Makefile
tests/time_cc/Makefile
doc/Makefile
doc/examples/Makefile
doc/manuals/Makefile

@ -58,7 +58,6 @@ noinst_HEADERS = \
signal.h \
system_information.h \
timeslot_fsm.h \
time_cc.h \
vty.h \
gsm_08_08.h \
penalty_timers.h \

@ -622,10 +622,10 @@ struct gsm_bts {
/* At what point in the channel allocation sequence to dispatch the Immediate Assignment (Abis optimization) */
enum imm_ass_time imm_ass_time;
struct time_cc all_allocated_sdcch;
struct time_cc all_allocated_static_sdcch;
struct time_cc all_allocated_tch;
struct time_cc all_allocated_static_tch;
struct osmo_time_cc all_allocated_sdcch;
struct osmo_time_cc all_allocated_static_sdcch;
struct osmo_time_cc all_allocated_tch;
struct osmo_time_cc all_allocated_static_tch;
};
#define GSM_BTS_SI2Q(bts, i) (struct gsm48_system_information_type_2quater *)((bts)->si_buf[SYSINFO_TYPE_2quater][i])

@ -18,6 +18,7 @@
#include <osmocom/gsm/gsm48.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/time_cc.h>
#include <osmocom/crypt/auth.h>
@ -32,7 +33,6 @@
#include <osmocom/bsc/meas_rep.h>
#include <osmocom/bsc/acc.h>
#include <osmocom/bsc/osmux.h>
#include <osmocom/bsc/time_cc.h>
#define GSM_T3122_DEFAULT 10
@ -1262,10 +1262,10 @@ struct gsm_network {
struct smlc_config *smlc;
struct time_cc all_allocated_sdcch;
struct time_cc all_allocated_static_sdcch;
struct time_cc all_allocated_tch;
struct time_cc all_allocated_static_tch;
struct osmo_time_cc all_allocated_sdcch;
struct osmo_time_cc all_allocated_static_sdcch;
struct osmo_time_cc all_allocated_tch;
struct osmo_time_cc all_allocated_static_tch;
};
struct gsm_audio_support {

@ -1,157 +0,0 @@
/* Report the cumulative counter of time for which a flag is true as rate counter. */
#pragma once
#include <stdint.h>
#include <osmocom/core/timer.h>
struct osmo_tdef;
struct rate_ctr;
/*! Configuration for time_cc.
* Report the cumulative counter of time for which a flag is true as rate counter.
* For example, for each second that the flag is true, increment a rate counter.
*
* The flag to be monitored is reported by time_cc_set_flag().
*
* The granularity defines how much time one rate counter increment represents:
* the default configuration is gran_usec = 1000000, i.e. one rate counter increment represents one second.
*
* Reporting as rate counter is configurable by round_threshold_usec and forget_sum_usec, examples:
*
* round_threshold_usec:
* - To get "ceil()" behavior, set round_threshold_usec = 1. This increments the rate counter for each gran_usec period
* where the flag was seen true, even if it was true for only a very short fraction of a gran_usec period.
* - To get "round()" behavior, set round_threshold_usec = half of gran_usec. The rate counter increments when the flag
* has been true for 0.5 of a gran_usec (and then again at 1.5 * gran_usec of 'true' flag). round_threshold_usec = 0
* is a special value that means to use half of gran_usec.
* - To get "floor()" behavior, set round_threshold_usec >= gran_usec. The rate counter increments when reaching full
* gran_usec periods of the flag being true.
*
* forget_sum_usec:
* This is a tradeoff between the accuracy of the reported rate counter and making sure that the events reported are not
* irrelevantly long ago.
* - To keep sub-granularity-period surplus time forever, set forget_sum_usec = 0.
* - To keep surplus time for up to a minute, set forget_sum_usec = 60000000 (60 seconds).
* - To get rid of "leftover" time (almost) immediately after the flag goes false, set forget_sum_usec = 1.
* - If gran_usec is set to one second and forget_sum_usec is set to one minute, the reported rate counter has a
* possible inaccuracy of 1/60th, but makes sure that no timings older than a minute affect the current reports.
*
* Reporting modes in detail:
*
* The rate_ctr increments when the cumulative counter passes round_threshold_usec (default: half of gran_usec).
*
* sum ^
* | ________
* | /
* | /
* | /
* 3*gran --+--------------------------------------+
* | /:
* | / :
* | - - - - - - - - - - - - - - - - - / :
* | /. :
* | / . :
* 2*gran --+--------------------------------+ . :
* | /: . :
* | / : . :
* | - - - - - - - - - -_________/ : . :
* | / . : . :
* | / . : . :
* 1*gran --+-----------------+ . : . :
* | /: . : . :
* | / : . : . :
* | - - - - - - -/ : . : . :
* | /. : . : . :
* | ....-------' . : . : . :
* 0 +------------------------------------------------------------------------> elapsed time
* . : . : . :
* _ _ _______ ____________
* flag: __| |_| |____| . : |_______|. : . : |__________
* f t f t f t . : f t. : . : f
* round_threshold_usec : . : . : . :
* = 1 usec: 0 1 . :2 . :3 . :4 = "ceil()"
* = 0 == gran_usec/2: 0 1 : 2 : 3 : = "round()"
* >= gran_usec: 0 1 2 3 = "floor()"
*
*/
struct time_cc_cfg {
/*! Granularity in microseconds: nr of microseconds that one rate_ctr increment represents. A typical value is
* gran_usec = 1000000, meaning one rate counter increment represents one second. */
uint64_t gran_usec;
/*! Nr of microseconds above a full gran_usec at which to trigger rate_ctr_round. When zero, half a gran_usec. */
uint64_t round_threshold_usec;
/*! Forget counted sub-gran time after the flag was false for this long. */
uint64_t forget_sum_usec;
/*! Rate counter to report to, or NULL to not use it. */
struct rate_ctr *rate_ctr;
/*! Update gran_usec from this T timer value, or zero to not use any T timer. */
int T_gran;
/*! Update round_threshold_usec from this T timer value, or zero to not use any T timer. */
int T_round_threshold;
/*! Update forget_sum_usec from this T timer value, or zero to not use any T timer. */
int T_forget_sum;
/*! Look up T_gran and T_forget_sum in this list of timers, or NULL to not use any T timers. */
struct osmo_tdef *T_defs;
};
/*! Report the cumulative counter of time for which a flag is true as rate counter.
* See also time_cc_cfg for details on configuring.
*
* Usage:
*
* struct my_obj {
* struct time_cc flag_cc;
* };
*
* void my_obj_init(struct my_obj *my_obj)
* {
* time_cc_init(&my_obj->flag_cc);
* my_obj->flag_cc.cfg = (struct time_cc_cfg){
* .gran_usec = 1000000,
* .forget_sum_usec = 60000000,
* .rate_ctr = rate_ctr_group_get_ctr(my_ctrg, MY_CTR_IDX),
* };
* // optional: set initial flag state, default is 'false':
* // time_cc_set_flag(&my_obj->flag_cc, false);
* }
*
* void my_obj_event(struct my_obj *my_obj, bool flag)
* {
* time_cc_set_flag(&my_obj->flag_cc, flag);
* }
*
* void my_obj_destruct(struct my_obj *my_obj)
* {
* time_cc_cleanup(&my_obj->flag_cc);
* }
*/
struct time_cc {
struct time_cc_cfg cfg;
bool flag_state;
/** Overall cumulative sum. Does not get reset for the entire lifetime of a time_cc.
* (Informational only, not used by the time_cc implementation.) */
uint64_t total_sum;
struct osmo_timer_list timer;
/** CLOCK_MONOTONIC reading in microseconds, at the time when the time_cc instance started counting. */
uint64_t start_time;
/** CLOCK_MONOTONIC reading in microseconds, at the time when the time_cc last evaluated the flag state and
* possibly added to the cumulated sum. */
uint64_t last_counted_time;
/** Internal cumulative counter of time that flag_state was true. It may get reset to zero regularly, depending
* on cfg.forget_sum_usec. This is the basis for incrementing cfg.rate_ctr. */
uint64_t sum;
/** The amount of time that already reported cfg.rate_ctr increments account for. This may be ahead of or behind
* 'sum', depending on cfg.round_threshold_usec. */
uint64_t reported_sum;
};
void time_cc_init(struct time_cc *tc);
void time_cc_set_flag(struct time_cc *tc, bool flag);
void time_cc_cleanup(struct time_cc *tc);

@ -105,7 +105,6 @@ libbsc_la_SOURCES = \
bssmap_reset.c \
system_information.c \
timeslot_fsm.c \
time_cc.c \
smscb.c \
cbch_scheduler.c \
cbsp_link.c \

@ -120,7 +120,7 @@ static struct gsm_network *bsc_network_init(void *ctx)
if (!net->bts_unknown_statg)
goto err_free_all;
net->all_allocated_sdcch = (struct time_cc){
net->all_allocated_sdcch = (struct osmo_time_cc){
.cfg = {
.gran_usec = 1*1000000,
.forget_sum_usec = 60*1000000,
@ -131,7 +131,7 @@ static struct gsm_network *bsc_network_init(void *ctx)
.T_defs = net->T_defs,
},
};
net->all_allocated_static_sdcch = (struct time_cc){
net->all_allocated_static_sdcch = (struct osmo_time_cc){
.cfg = {
.gran_usec = 1*1000000,
.forget_sum_usec = 60*1000000,
@ -142,7 +142,7 @@ static struct gsm_network *bsc_network_init(void *ctx)
.T_defs = net->T_defs,
},
};
net->all_allocated_tch = (struct time_cc){
net->all_allocated_tch = (struct osmo_time_cc){
.cfg = {
.gran_usec = 1*1000000,
.forget_sum_usec = 60*1000000,
@ -153,7 +153,7 @@ static struct gsm_network *bsc_network_init(void *ctx)
.T_defs = net->T_defs,
},
};
net->all_allocated_static_tch = (struct time_cc){
net->all_allocated_static_tch = (struct osmo_time_cc){
.cfg = {
.gran_usec = 1*1000000,
.forget_sum_usec = 60*1000000,

@ -215,46 +215,46 @@ void bsc_update_time_cc_all_allocated(struct gsm_network *net)
chan_counts_add(&bts_counts, &trx_counts);
}
time_cc_set_flag(&bts->all_allocated_sdcch,
bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH]
&& !bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]);
time_cc_set_flag(&bts->all_allocated_static_sdcch,
bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH]
&& !bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]);
time_cc_set_flag(&bts->all_allocated_tch,
(bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F]
+ bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H])
&& !(bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]
+ bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]));
time_cc_set_flag(&bts->all_allocated_static_tch,
(bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F]
+ bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H])
&& !(bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]
+ bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]));
osmo_time_cc_set_flag(&bts->all_allocated_sdcch,
bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH]
&& !bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]);
osmo_time_cc_set_flag(&bts->all_allocated_static_sdcch,
bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH]
&& !bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]);
osmo_time_cc_set_flag(&bts->all_allocated_tch,
(bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F]
+ bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H])
&& !(bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]
+ bts_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]));
osmo_time_cc_set_flag(&bts->all_allocated_static_tch,
(bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F]
+ bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H])
&& !(bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]
+ bts_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]));
chan_counts_add(&bsc_counts, &bts_counts);
}
time_cc_set_flag(&net->all_allocated_sdcch,
bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH]
&& !bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]);
time_cc_set_flag(&net->all_allocated_static_sdcch,
bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH]
&& !bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]);
time_cc_set_flag(&net->all_allocated_tch,
(bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F]
+ bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H])
&& !(bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]
+ bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]));
time_cc_set_flag(&net->all_allocated_static_tch,
(bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F]
+ bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H])
&& !(bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]
+ bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]));
osmo_time_cc_set_flag(&net->all_allocated_sdcch,
bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH]
&& !bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]);
osmo_time_cc_set_flag(&net->all_allocated_static_sdcch,
bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH]
&& !bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]);
osmo_time_cc_set_flag(&net->all_allocated_tch,
(bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F]
+ bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H])
&& !(bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]
+ bsc_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]));
osmo_time_cc_set_flag(&net->all_allocated_static_tch,
(bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F]
+ bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H])
&& !(bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]
+ bsc_counts.val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]));
}

@ -211,7 +211,7 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, struct gsm_bts_sm *bts_sm
}
bts->bts_statg = osmo_stat_item_group_alloc(bts, &bts_statg_desc, bts->nr);
bts->all_allocated_sdcch = (struct time_cc){
bts->all_allocated_sdcch = (struct osmo_time_cc){
.cfg = {
.gran_usec = 1*1000000,
.forget_sum_usec = 60*1000000,
@ -222,7 +222,7 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, struct gsm_bts_sm *bts_sm
.T_defs = net->T_defs,
},
};
bts->all_allocated_static_sdcch = (struct time_cc){
bts->all_allocated_static_sdcch = (struct osmo_time_cc){
.cfg = {
.gran_usec = 1*1000000,
.forget_sum_usec = 60*1000000,
@ -233,7 +233,7 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, struct gsm_bts_sm *bts_sm
.T_defs = net->T_defs,
},
};
bts->all_allocated_tch = (struct time_cc){
bts->all_allocated_tch = (struct osmo_time_cc){
.cfg = {
.gran_usec = 1*1000000,
.forget_sum_usec = 60*1000000,
@ -244,7 +244,7 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, struct gsm_bts_sm *bts_sm
.T_defs = net->T_defs,
},
};
bts->all_allocated_static_tch = (struct time_cc){
bts->all_allocated_static_tch = (struct osmo_time_cc){
.cfg = {
.gran_usec = 1*1000000,
.forget_sum_usec = 60*1000000,

@ -1,208 +0,0 @@
/* Report the cumulative counter of time for which a flag is true as rate counter. */
/* Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <limits.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/bsc/time_cc.h>
#define GRAN_USEC(TIME_CC) ((TIME_CC)->cfg.gran_usec ? : 1000000)
#define ROUND_THRESHOLD_USEC(TIME_CC) ((TIME_CC)->cfg.round_threshold_usec ? \
OSMO_MIN((TIME_CC)->cfg.round_threshold_usec, GRAN_USEC(TIME_CC)) \
: (GRAN_USEC(TIME_CC) / 2))
static uint64_t time_now_usec()
{
struct timespec tp;
if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp))
return 0;
return (uint64_t)tp.tv_sec * 1000000 + tp.tv_nsec / 1000;
}
static void time_cc_forget_sum(struct time_cc *tc, uint64_t now);
static void time_cc_update_from_tdef(struct time_cc *tc, uint64_t now)
{
bool do_forget_sum = false;
if (!tc->cfg.T_defs)
return;
if (tc->cfg.T_gran) {
uint64_t was = GRAN_USEC(tc);
tc->cfg.gran_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_gran, OSMO_TDEF_US, -1);
if (was != GRAN_USEC(tc))
do_forget_sum = true;
}
if (tc->cfg.T_round_threshold)
tc->cfg.round_threshold_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_round_threshold,
OSMO_TDEF_US, -1);
if (tc->cfg.T_forget_sum) {
uint64_t was = tc->cfg.forget_sum_usec;
tc->cfg.forget_sum_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_forget_sum, OSMO_TDEF_US, -1);
if (tc->cfg.forget_sum_usec && was != tc->cfg.forget_sum_usec)
do_forget_sum = true;
}
if (do_forget_sum && tc->sum)
time_cc_forget_sum(tc, now);
}
static void time_cc_schedule_timer(struct time_cc *tc, uint64_t now);
/* Clear out osmo_timer and internal counting state of struct time_cc. The .cfg remains unaffected. After calling, the
* time_cc instance can be used again to accumulate state as if it had just been initialized. */
void time_cc_cleanup(struct time_cc *tc)
{
osmo_timer_del(&tc->timer);
*tc = (struct time_cc){
.cfg = tc->cfg,
};
}
static void time_cc_start(struct time_cc *tc, uint64_t now)
{
time_cc_cleanup(tc);
/* Set the default of 1 second of granularity */
tc->start_time = now;
tc->last_counted_time = now;
time_cc_update_from_tdef(tc, now);
time_cc_schedule_timer(tc, now);
}
static void time_cc_count_time(struct time_cc *tc, uint64_t now)
{
uint64_t time_delta = now - tc->last_counted_time;
tc->last_counted_time = now;
if (!tc->flag_state)
return;
/* Flag is currently true, cumulate the elapsed time */
tc->total_sum += time_delta;
tc->sum += time_delta;
}
static void time_cc_report(struct time_cc *tc, uint64_t now)
{
uint64_t delta;
uint64_t n;
if (!tc->cfg.rate_ctr)
return;
/* We report a sum "rounded up", ahead of time. If the granularity period has not yet elapsed after the last
* reporting, do not report again yet. */
if (tc->reported_sum > tc->sum)
return;
delta = tc->sum - tc->reported_sum;
/* elapsed full periods */
n = delta / GRAN_USEC(tc);
/* If the delta has passed round_threshold (normally half of gran_usec), increment. */
delta -= n * GRAN_USEC(tc);
if (delta >= ROUND_THRESHOLD_USEC(tc))
n++;
if (!n)
return;
/* integer sanity, since rate_ctr_add() takes an int argument. */
if (n > INT_MAX)
n = INT_MAX;
rate_ctr_add(tc->cfg.rate_ctr, n);
/* Store the increments of gran_usec that were counted. */
tc->reported_sum += n * GRAN_USEC(tc);
}
static void time_cc_forget_sum(struct time_cc *tc, uint64_t now)
{
tc->reported_sum = 0;
tc->sum = 0;
if (tc->last_counted_time < now)
tc->last_counted_time = now;
}
/* Initialize struct time_cc. Call this once before use, and before setting up the .cfg items. */
void time_cc_init(struct time_cc *tc)
{
*tc = (struct time_cc){0};
}
void time_cc_set_flag(struct time_cc *tc, bool flag)
{
uint64_t now = time_now_usec();
if (!tc->start_time)
time_cc_start(tc, now);
/* No flag change == no effect */
if (flag == tc->flag_state)
return;
/* Sum up elapsed time, report increments for that. */
time_cc_count_time(tc, now);
time_cc_report(tc, now);
tc->flag_state = flag;
time_cc_schedule_timer(tc, now);
}
static void time_cc_timer_cb(void *data)
{
struct time_cc *tc = data;
uint64_t now = time_now_usec();
time_cc_update_from_tdef(tc, now);
if (tc->flag_state) {
time_cc_count_time(tc, now);
time_cc_report(tc, now);
} else if (tc->cfg.forget_sum_usec && tc->sum
&& (now >= tc->last_counted_time + tc->cfg.forget_sum_usec)) {
time_cc_forget_sum(tc, now);
}
time_cc_schedule_timer(tc, now);
}
static void time_cc_schedule_timer(struct time_cc *tc, uint64_t now)
{
uint64_t next_event = UINT64_MAX;
time_cc_update_from_tdef(tc, now);
/* Figure out the next time we should do anything, if the flag state remains unchanged. */
/* If it is required, when will the next forget_sum happen? */
if (tc->cfg.forget_sum_usec && !tc->flag_state && tc->sum > 0) {
uint64_t next_forget_time = tc->last_counted_time + tc->cfg.forget_sum_usec;
next_event = OSMO_MIN(next_event, next_forget_time);
}
/* Next rate_ctr increment? */
if (tc->flag_state && tc->cfg.rate_ctr) {
uint64_t next_inc = now + (tc->reported_sum - tc->sum) + ROUND_THRESHOLD_USEC(tc);
next_event = OSMO_MIN(next_event, next_inc);
}
/* No event coming up? */
if (next_event == UINT64_MAX)
return;
if (next_event <= now)
next_event = 0;
else
next_event -= now;
osmo_timer_setup(&tc->timer, time_cc_timer_cb, tc);
osmo_timer_del(&tc->timer);
osmo_timer_schedule(&tc->timer, next_event / 1000000, next_event % 1000000);
}

@ -7,7 +7,6 @@ SUBDIRS = \
subscr \
nanobts_omlattr \
handover \
time_cc \
$(NULL)
# The `:;' works around a Bash 3.2 bug when the output is not writeable.

@ -49,9 +49,3 @@ AT_KEYWORDS([handover_tests])
cat $abs_srcdir/handover/handover_tests.ok > expout
AT_CHECK([$abs_srcdir/handover/handover_tests.sh $abs_srcdir/handover $abs_builddir/handover], [], [expout], [ignore])
AT_CLEANUP
AT_SETUP([time_cc_test])
AT_KEYWORDS([time_cc_test])
cat $abs_srcdir/time_cc/time_cc_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/time_cc/time_cc_test], [], [expout], [ignore])
AT_CLEANUP

@ -1,36 +0,0 @@
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
$(NULL)
AM_CFLAGS = \
-Wall \
-ggdb3 \
$(LIBOSMOCORE_CFLAGS) \
$(COVERAGE_CFLAGS) \
$(NULL)
AM_LDFLAGS = \
$(COVERAGE_LDFLAGS) \
$(NULL)
EXTRA_DIST = \
time_cc_test.ok \
$(NULL)
noinst_PROGRAMS = \
time_cc_test \
$(NULL)
time_cc_test_SOURCES = \
time_cc_test.c \
$(NULL)
time_cc_test_LDADD = \
$(top_builddir)/src/osmo-bsc/libbsc.la \
$(LIBOSMOCORE_LIBS) \
$(NULL)
.PHONY: update_exp
update_exp:
$(builddir)/time_cc_test >$(srcdir)/time_cc_test.ok

@ -1,769 +0,0 @@
/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <inttypes.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/application.h>
#include <osmocom/core/select.h>
#include <osmocom/core/tdef.h>
#include <osmocom/bsc/time_cc.h>
enum my_ctrs {
CTR_CEIL,
CTR_ROUND,
CTR_FLOOR,
};
const struct rate_ctr_desc my_ctr_desc[] = {
[CTR_CEIL] = {"ceil", "testing round_threshold_usec = 1"},
[CTR_ROUND] = {"round", "testing round_threshold_usec = 0 = gran_usec/2"},
[CTR_FLOOR] = {"floor", "testing round_threshold_usec = gran_usec"},
};
const struct rate_ctr_group_desc my_ctrg_desc = {
"time_cc_test",
"Counters for time_cc test",
0,
ARRAY_SIZE(my_ctr_desc),
my_ctr_desc,
};
struct rate_ctr_group *my_ctrg;
enum my_obj_timers {
T_GRAN = -23,
T_ROUND_THRESH = -24,
T_FORGET_SUM = -25,
};
struct osmo_tdef g_my_obj_tdefs[] = {
{ .T = T_GRAN, .default_val = 0, .unit = OSMO_TDEF_MS, .desc = "flag_cc granularity, or zero for 1 second" },
{ .T = T_ROUND_THRESH, .default_val = 0, .unit = OSMO_TDEF_MS,
.desc = "flag_cc rounding threshold, or zero for half a granularity" },
{ .T = T_FORGET_SUM, .default_val = 0, .unit = OSMO_TDEF_MS,
.desc = "flag_cc inactivity forget period, or zero to not forget any timings" },
{}
};
struct my_obj {
struct time_cc flag_cc_ceil;
struct time_cc flag_cc_round;
struct time_cc flag_cc_floor;
};
void my_obj_init(struct my_obj *my_obj)
{
time_cc_init(&my_obj->flag_cc_ceil);
my_obj->flag_cc_ceil.cfg = (struct time_cc_cfg){
.rate_ctr = rate_ctr_group_get_ctr(my_ctrg, CTR_CEIL),
.round_threshold_usec = 1,
.T_gran = T_GRAN,
.T_forget_sum = T_FORGET_SUM,
.T_defs = g_my_obj_tdefs,
};
time_cc_init(&my_obj->flag_cc_round);
my_obj->flag_cc_round.cfg = (struct time_cc_cfg){
.rate_ctr = rate_ctr_group_get_ctr(my_ctrg, CTR_ROUND),
.T_gran = T_GRAN,
.T_round_threshold = T_ROUND_THRESH,
.T_forget_sum = T_FORGET_SUM,
.T_defs = g_my_obj_tdefs,
};
time_cc_init(&my_obj->flag_cc_floor);
my_obj->flag_cc_floor.cfg = (struct time_cc_cfg){
.rate_ctr = rate_ctr_group_get_ctr(my_ctrg, CTR_FLOOR),
.round_threshold_usec = UINT64_MAX, /* always >= gran_usec */
.T_gran = T_GRAN,
.T_forget_sum = T_FORGET_SUM,
.T_defs = g_my_obj_tdefs,
};
}
void my_obj_event(struct my_obj *my_obj, bool flag)
{
time_cc_set_flag(&my_obj->flag_cc_ceil, flag);
time_cc_set_flag(&my_obj->flag_cc_round, flag);
time_cc_set_flag(&my_obj->flag_cc_floor, flag);
}
void my_obj_destruct(struct my_obj *my_obj)
{
time_cc_cleanup(&my_obj->flag_cc_ceil);
time_cc_cleanup(&my_obj->flag_cc_round);
time_cc_cleanup(&my_obj->flag_cc_floor);
}
static const struct log_info_cat log_categories[] = {
};
static const struct log_info log_info = {
.cat = log_categories,
.num_cat = ARRAY_SIZE(log_categories),
};
int main()
{
void *ctx = talloc_named_const(NULL, 0, "time_cc_test");
struct timespec *now;
struct my_obj my_obj = {0};
osmo_init_logging2(ctx, &log_info);
/* enable override for CLOCK_MONOTONIC */
osmo_clock_override_enable(CLOCK_MONOTONIC, true);
now = osmo_clock_override_gettimespec(CLOCK_MONOTONIC);
now->tv_sec = 23000;
now->tv_nsec = 0;
/* enable override for osmo_gettimeofday(), for osmo_timer_schedule() */
osmo_gettimeofday_override = true;
osmo_gettimeofday_override_time = (struct timeval){23000, 0};
my_ctrg = rate_ctr_group_alloc(ctx, &my_ctrg_desc, 0);
#define CHECK_RATE_CTRS(exp_ceil, exp_round, exp_floor) do { \
printf("%d CHECK_RATE_CTRS(" #exp_ceil ", " #exp_round ", " #exp_floor ")", \
my_obj.flag_cc_round.flag_state); \
while (osmo_select_main_ctx(1) > 0); \
if (exp_ceil != my_obj.flag_cc_ceil.cfg.rate_ctr->current \
|| exp_round != my_obj.flag_cc_round.cfg.rate_ctr->current \
|| exp_floor != my_obj.flag_cc_floor.cfg.rate_ctr->current) \
printf("\n ERROR on line %d: ctr_ceil=%"PRIu64" ctr_round=%"PRIu64" ctr_floor=%"PRIu64"\n", \
__LINE__, \
my_obj.flag_cc_ceil.cfg.rate_ctr->current, \
my_obj.flag_cc_round.cfg.rate_ctr->current, \
my_obj.flag_cc_floor.cfg.rate_ctr->current); \
else \
printf(" ok\n"); \
} while (0)
#define ADD_MILLISECS_NO_SELECT(ms) do { \
osmo_clock_override_add(CLOCK_MONOTONIC, ms / 1000, (uint64_t)(ms % 1000) * 1000000); \
osmo_gettimeofday_override_add(ms / 1000, (uint64_t)(ms % 1000) * 1000); \
printf("%d ADD_MILLISECS(" #ms ") --> %ld.%03ld", my_obj.flag_cc_round.flag_state, \
now->tv_sec, now->tv_nsec/1000000); \
printf("\n"); \
} while (0)
#define ADD_MILLISECS(ms) do { \
ADD_MILLISECS_NO_SELECT(ms); \
while (osmo_select_main_ctx(1) > 0); \
} while (0)
#define FLAG(VAL) do { \
printf(" flag: %s -> %s\n", my_obj.flag_cc_round.flag_state ? "TRUE" : "FALSE", VAL ? "TRUE" : "FALSE"); \
my_obj_event(&my_obj, VAL); \
} while (0)
/*
* sum ^
* | ________
* | /
* | /
* | /
* 3*gran --+--------------------------------------+
* | /:
* | / :
* | - - - - - - - - - - - - - - - - - / :
* | /. :
* | / . :
* 2*gran --+--------------------------------+ . :
* | /: . :
* | / : . :
* | - - - - - - - - - -_________/ : . :
* | / . : . :
* | / . : . :
* 1*gran --+-----------------+ . : . :
* | /: . : . :
* | / : . : . :
* | - - - - - - -/ : . : . :
* | /. : . : . :
* | ....-------' . : . : . :
* 0 +----------------------------------------------------------> elapsed time
* . : . : . :
* _ _ _______ ____________
* flag: __| |_| |____| . : |_______|. : . : |__________
* f t f t f t . : f t. : . : f
* round_threshold_usec : . : . : . :
* = 1 usec: 0 1 . :2 . :3 . :4 = "ceil()"
* = 0 == gran_usec/2: 0 1 : 2 : 3 : = "round()"
* = gran_usec: 0 1 2 3 = "floor()"
*/
printf("\n----------- cumulating time, without forget_sum\n\n");
my_obj_init(&my_obj);
CHECK_RATE_CTRS(0, 0, 0);
ADD_MILLISECS(100);
CHECK_RATE_CTRS(0, 0, 0);
FLAG(true);
/* flag has just turned true the first time */
CHECK_RATE_CTRS(0, 0, 0);
ADD_MILLISECS(1);
/* flag has been true for 0.001s */
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS(99);
/* flag has been true for 0.1s */
CHECK_RATE_CTRS(1, 0, 0);
FLAG(false);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS(100);
CHECK_RATE_CTRS(1, 0, 0);
FLAG(true);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS(100);
/* flag has been true for 0.2s */
CHECK_RATE_CTRS(1, 0, 0);
FLAG(false);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS(300);
CHECK_RATE_CTRS(1, 0, 0);
FLAG(true);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS(299);
/* flag has been true for 0.499s */
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS(1);
/* flag has been true for 0.5s */
CHECK_RATE_CTRS(1, 1, 0);
ADD_MILLISECS(499);
/* flag has been true for 0.999s */
CHECK_RATE_CTRS(1, 1, 0);
ADD_MILLISECS(1);
/* flag has been true for 1.0s */
CHECK_RATE_CTRS(1, 1, 1);
ADD_MILLISECS(1);
/* flag has been true for 1.001s */
CHECK_RATE_CTRS(2, 1, 1);
ADD_MILLISECS(299);
/* flag has been true for 1.3s */
CHECK_RATE_CTRS(2, 1, 1);
FLAG(false);
CHECK_RATE_CTRS(2, 1, 1);
ADD_MILLISECS(400);
CHECK_RATE_CTRS(2, 1, 1);
FLAG(true);
CHECK_RATE_CTRS(2, 1, 1);
ADD_MILLISECS(199);
/* flag has been true for 1.499s */
CHECK_RATE_CTRS(2, 1, 1);
ADD_MILLISECS(2);
/* flag has been true for 1.501s */
CHECK_RATE_CTRS(2, 2, 1);
ADD_MILLISECS(498);
/* flag has been true for 1.999s */
CHECK_RATE_CTRS(2, 2, 1);
ADD_MILLISECS(2);
/* flag has been true for 2.001s */
CHECK_RATE_CTRS(3, 2, 2);
ADD_MILLISECS(500);
/* flag has been true for 2.501s */
CHECK_RATE_CTRS(3, 3, 2);
ADD_MILLISECS(498);
/* flag has been true for 2.999s */
CHECK_RATE_CTRS(3, 3, 2);
ADD_MILLISECS(3);
/* flag has been true for 3.003s */
CHECK_RATE_CTRS(4, 3, 3);
ADD_MILLISECS(200);
/* flag has been true for 3.203s */
CHECK_RATE_CTRS(4, 3, 3);
FLAG(false);
CHECK_RATE_CTRS(4, 3, 3);
ADD_MILLISECS(4321);
CHECK_RATE_CTRS(4, 3, 3);
FLAG(true);
CHECK_RATE_CTRS(4, 3, 3);
ADD_MILLISECS(5678);
CHECK_RATE_CTRS(9, 9, 8);
FLAG(false);
CHECK_RATE_CTRS(9, 9, 8);
my_obj_destruct(&my_obj);
rate_ctr_group_reset(my_ctrg);
printf("\n----------- test forget_sum_usec\n\n");
osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, 10, OSMO_TDEF_S);
now->tv_sec = 23000;
now->tv_nsec = 0;
osmo_gettimeofday_override_time = (struct timeval){23000, 0};
my_obj_init(&my_obj);
CHECK_RATE_CTRS(0, 0, 0);
FLAG(true);
/* flag has just turned true the first time */
CHECK_RATE_CTRS(0, 0, 0);
ADD_MILLISECS(100);
/* flag has been true for 0.1s */
CHECK_RATE_CTRS(1, 0, 0);
FLAG(false);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS(1000);
/* 1 s of being false, forget_sum_usec has not yet occurred */
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS(8999);
/* 9.999 s of being false, forget_sum_usec has not yet occurred */
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS(1);
/* 10 s of being false, forget_sum_usec has occurred */
CHECK_RATE_CTRS(1, 0, 0);
FLAG(true);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS(1);
/* Since previous sums were forgotton, ceil() triggers again */
CHECK_RATE_CTRS(2, 0, 0);
/* If the sum had not been forgotten, adding 400 ms to the initial 100 ms would have triggered round(). Verify
* that this does not occur, since now full 500 ms are required */
ADD_MILLISECS(399);
CHECK_RATE_CTRS(2, 0, 0);
/* Adding another 100 ms will trigger round() */
ADD_MILLISECS(99);
CHECK_RATE_CTRS(2, 0, 0);
ADD_MILLISECS(1);
CHECK_RATE_CTRS(2, 1, 0);
/* If the sum had not been forgotten, adding 900 ms to the initial 100 ms would have triggered floor(). Verify
* that this does not occur, since now full 1000 ms are required. We already added 500 ms above. */
ADD_MILLISECS(400);
CHECK_RATE_CTRS(2, 1, 0);
/* Adding another 100 ms will trigger floor() */
ADD_MILLISECS(99);
CHECK_RATE_CTRS(2, 1, 0);
ADD_MILLISECS(1);
CHECK_RATE_CTRS(2, 1, 1);
/* Test that durations of false below forget_sum_usec never trigger a forget */
ADD_MILLISECS(300);
CHECK_RATE_CTRS(3, 1, 1);
/* internal counter is now at 0.3s above the last reported rate counter */
FLAG(false);
ADD_MILLISECS(9999);
FLAG(true);
ADD_MILLISECS(25);
FLAG(false);
ADD_MILLISECS(9999);
FLAG(true);
ADD_MILLISECS(25);
FLAG(false);
ADD_MILLISECS(9999);
FLAG(true);
ADD_MILLISECS(25);
FLAG(false);
ADD_MILLISECS(9999);
FLAG(true);
ADD_MILLISECS(25);
/* internal counter is now at 0.4s above the last reported rate counter */
CHECK_RATE_CTRS(3, 1, 1);
ADD_MILLISECS(100);
CHECK_RATE_CTRS(3, 2, 1);
ADD_MILLISECS(500);
CHECK_RATE_CTRS(3, 2, 2);
/* Test that repeated time_cc_set_flag(false) does not cancel a forget_sum_usec */
ADD_MILLISECS(300);
/* internal counter is now at 0.3s above the last reported rate counter */
CHECK_RATE_CTRS(4, 2, 2);
FLAG(false);
ADD_MILLISECS(5000);
/* Repeat 'false', must not affect forget_sum_usec */
FLAG(false);
ADD_MILLISECS(5000);
CHECK_RATE_CTRS(4, 2, 2);
/* 10 s have passed, forget_sum_usec has occurred.
* Hence ceil() will trigger again right away: */
FLAG(true);
ADD_MILLISECS(1);
CHECK_RATE_CTRS(5, 2, 2);
/* Adding 200 ms to the initial 300 ms would have triggered round(), but no more after forget_sum_usec */
ADD_MILLISECS(199);
CHECK_RATE_CTRS(5, 2, 2);
/* Adding another 300 ms will trigger round() */
ADD_MILLISECS(299);
CHECK_RATE_CTRS(5, 2, 2);
ADD_MILLISECS(1);
CHECK_RATE_CTRS(5, 3, 2);
/* Adding 700 ms to the initial 300 ms would have triggered ceil(), but no more after forget_sum_usec */
ADD_MILLISECS(200);
CHECK_RATE_CTRS(5, 3, 2);
/* Adding another 300 ms will trigger ceil() */
ADD_MILLISECS(299);
CHECK_RATE_CTRS(5, 3, 2);
ADD_MILLISECS(1);
CHECK_RATE_CTRS(5, 3, 3);
my_obj_destruct(&my_obj);
rate_ctr_group_reset(my_ctrg);
/* Verify correctness when select() lags and runs timer callbacks too late */
printf("\n----------- cumulating time, without forget_sum, when timer cb are invoked late\n\n");
osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, 0, OSMO_TDEF_S);
now->tv_sec = 23000;
now->tv_nsec = 0;
osmo_gettimeofday_override_time = (struct timeval){23000, 0};
my_obj_init(&my_obj);
CHECK_RATE_CTRS(0, 0, 0);
ADD_MILLISECS_NO_SELECT(100);
CHECK_RATE_CTRS(0, 0, 0);
FLAG(true);
/* flag has just turned true the first time */
CHECK_RATE_CTRS(0, 0, 0);
ADD_MILLISECS_NO_SELECT(100);
/* flag has been true for 0.1s */
CHECK_RATE_CTRS(1, 0, 0);
FLAG(false);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS_NO_SELECT(100);
CHECK_RATE_CTRS(1, 0, 0);
FLAG(true);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS_NO_SELECT(100);
/* flag has been true for 0.2s */
CHECK_RATE_CTRS(1, 0, 0);
FLAG(false);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS_NO_SELECT(300);
CHECK_RATE_CTRS(1, 0, 0);
FLAG(true);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS_NO_SELECT(799);
/* flag has been true for 0.999s */
CHECK_RATE_CTRS(1, 1, 0);
ADD_MILLISECS_NO_SELECT(1);
/* flag has been true for 1.0s */
CHECK_RATE_CTRS(1, 1, 1);
ADD_MILLISECS_NO_SELECT(300);
/* flag has been true for 1.3s */
CHECK_RATE_CTRS(2, 1, 1);
FLAG(false);
CHECK_RATE_CTRS(2, 1, 1);
ADD_MILLISECS_NO_SELECT(400);
CHECK_RATE_CTRS(2, 1, 1);
FLAG(true);
CHECK_RATE_CTRS(2, 1, 1);
ADD_MILLISECS_NO_SELECT(699);
/* flag has been true for 1.999s */
CHECK_RATE_CTRS(2, 2, 1);
ADD_MILLISECS_NO_SELECT(1);
/* flag has been true for 2.0s */
CHECK_RATE_CTRS(2, 2, 2);
ADD_MILLISECS_NO_SELECT(1);
/* flag has been true for 2.001s */
CHECK_RATE_CTRS(3, 2, 2);
ADD_MILLISECS_NO_SELECT(499);
/* flag has been true for 2.5s */
CHECK_RATE_CTRS(3, 3, 2);
ADD_MILLISECS_NO_SELECT(499);
/* flag has been true for 2.999s */
CHECK_RATE_CTRS(3, 3, 2);
ADD_MILLISECS_NO_SELECT(1);
/* flag has been true for 3.0s */
CHECK_RATE_CTRS(3, 3, 3);
ADD_MILLISECS_NO_SELECT(200);
/* flag has been true for 3.2s */
CHECK_RATE_CTRS(4, 3, 3);
FLAG(false);
CHECK_RATE_CTRS(4, 3, 3);
ADD_MILLISECS_NO_SELECT(4321);
CHECK_RATE_CTRS(4, 3, 3);
FLAG(true);
CHECK_RATE_CTRS(4, 3, 3);
ADD_MILLISECS_NO_SELECT(5678);
CHECK_RATE_CTRS(9, 9, 8);
FLAG(false);
CHECK_RATE_CTRS(9, 9, 8);
my_obj_destruct(&my_obj);
rate_ctr_group_reset(my_ctrg);
printf("\n----------- test forget_sum, when timer cb are invoked late\n\n");
osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, 10, OSMO_TDEF_S);
now->tv_sec = 23000;
now->tv_nsec = 0;
osmo_gettimeofday_override_time = (struct timeval){23000, 0};
my_obj_init(&my_obj);
CHECK_RATE_CTRS(0, 0, 0);
FLAG(true);
/* flag has just turned true the first time */
CHECK_RATE_CTRS(0, 0, 0);
ADD_MILLISECS_NO_SELECT(100);
/* flag has been true for 0.1s */
CHECK_RATE_CTRS(1, 0, 0);
FLAG(false);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS_NO_SELECT(1000);
/* 1 s of being false, forget_sum_usec has not yet occurred */
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS_NO_SELECT(8999);
/* 9.999 s of being false, forget_sum_usec has not yet occurred */
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS_NO_SELECT(1);
/* 10 s of being false, forget_sum_usec has occurred */
CHECK_RATE_CTRS(1, 0, 0);
FLAG(true);
CHECK_RATE_CTRS(1, 0, 0);
ADD_MILLISECS_NO_SELECT(1);
/* Since previous sums were forgotton, ceil() triggers again */
CHECK_RATE_CTRS(2, 0, 0);
/* If the sum had not been forgotten, adding 400 ms to the initial 100 ms would have triggered round(). Verify
* that this does not occur, since now full 500 ms are required */
ADD_MILLISECS_NO_SELECT(399);
CHECK_RATE_CTRS(2, 0, 0);
/* Adding another 100 ms will trigger round() */
ADD_MILLISECS_NO_SELECT(99);
CHECK_RATE_CTRS(2, 0, 0);
ADD_MILLISECS_NO_SELECT(1);
CHECK_RATE_CTRS(2, 1, 0);
/* If the sum had not been forgotten, adding 900 ms to the initial 100 ms would have triggered floor(). Verify
* that this does not occur, since now full 1000 ms are required. We already added 500 ms above. */
ADD_MILLISECS_NO_SELECT(400);
CHECK_RATE_CTRS(2, 1, 0);
/* Adding another 100 ms will trigger floor() */
ADD_MILLISECS_NO_SELECT(99);
CHECK_RATE_CTRS(2, 1, 0);
ADD_MILLISECS_NO_SELECT(1);
CHECK_RATE_CTRS(2, 1, 1);
/* Test that durations of false below forget_sum_usec never trigger a forget */
ADD_MILLISECS_NO_SELECT(300);
CHECK_RATE_CTRS(3, 1, 1);
/* internal counter is now at 0.3s above the last reported rate counter */
FLAG(false);
ADD_MILLISECS_NO_SELECT(9999);
FLAG(true);
ADD_MILLISECS_NO_SELECT(25);
FLAG(false);
ADD_MILLISECS_NO_SELECT(9999);
FLAG(true);
ADD_MILLISECS_NO_SELECT(25);
FLAG(false);
ADD_MILLISECS_NO_SELECT(9999);
FLAG(true);
ADD_MILLISECS_NO_SELECT(25);
FLAG(false);
ADD_MILLISECS_NO_SELECT(9999);
FLAG(true);
ADD_MILLISECS_NO_SELECT(25);
/* internal counter is now at 0.4s above the last reported rate counter */
CHECK_RATE_CTRS(3, 1, 1);
ADD_MILLISECS_NO_SELECT(100);
CHECK_RATE_CTRS(3, 2, 1);
ADD_MILLISECS_NO_SELECT(500);
CHECK_RATE_CTRS(3, 2, 2);
my_obj_destruct(&my_obj);
rate_ctr_group_reset(my_ctrg);
#define SET_TDEFS(gran, round_thresh, forget_sum) do { \
osmo_tdef_set(g_my_obj_tdefs, T_GRAN, gran, OSMO_TDEF_MS); \
osmo_tdef_set(g_my_obj_tdefs, T_ROUND_THRESH, round_thresh, OSMO_TDEF_MS); \
osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, forget_sum, OSMO_TDEF_S); \
printf("T_defs: T_gran=%luusec T_round_threshold=%luusec T_forget_sum=%luusec\n", \
osmo_tdef_get(g_my_obj_tdefs, T_GRAN, OSMO_TDEF_US, -1), \
osmo_tdef_get(g_my_obj_tdefs,