Change in osmo-bsc[master]: add time_cc API: cumlative counter for time, reported as rate_ctr

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

neels gerrit-no-reply at lists.osmocom.org
Wed Nov 10 13:27:44 UTC 2021


neels has submitted this change. ( https://gerrit.osmocom.org/c/osmo-bsc/+/25973 )

Change subject: add time_cc API: cumlative counter for time, reported as rate_ctr
......................................................................

add time_cc API: cumlative counter for time, reported as rate_ctr

This is a candidate for adding to libosmocore (as osmo_time_cc), but
let's first use this in osmo-bsc to make sure that it works as intended.

I started out expecting to be done with this in half an hour, but I
found out that accumulating elapsed time to an integer counter has a
staggering amount of complexity to it, and a million pitfalls.

The intended use is to report allAvailableSDCCHAllocated and
allAvailableTCHAllocated performance indicators in OsmoBSC. Hopefully
this will also be generally useful elsewhere, to be worth the effort.

Related: SYS#4878
Change-Id: Icdd36f27cb54b2e1b940c9e6404ba9dd3692a310
---
M configure.ac
M include/osmocom/bsc/Makefile.am
A include/osmocom/bsc/time_cc.h
M src/osmo-bsc/Makefile.am
A src/osmo-bsc/time_cc.c
M tests/Makefile.am
M tests/testsuite.at
A tests/time_cc/Makefile.am
A tests/time_cc/time_cc_test.c
A tests/time_cc/time_cc_test.ok
10 files changed, 1,508 insertions(+), 0 deletions(-)

Approvals:
  neels: Looks good to me, approved
  Jenkins Builder: Verified



diff --git a/configure.ac b/configure.ac
index bcf91ba..989ad7d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -238,6 +238,7 @@
     tests/subscr/Makefile
     tests/nanobts_omlattr/Makefile
     tests/handover/Makefile
+    tests/time_cc/Makefile
     doc/Makefile
     doc/examples/Makefile
     doc/manuals/Makefile
diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am
index 3ddad45..8831533 100644
--- a/include/osmocom/bsc/Makefile.am
+++ b/include/osmocom/bsc/Makefile.am
@@ -58,6 +58,7 @@
 	signal.h \
 	system_information.h \
 	timeslot_fsm.h \
+	time_cc.h \
 	vty.h \
 	gsm_08_08.h \
 	penalty_timers.h \
diff --git a/include/osmocom/bsc/time_cc.h b/include/osmocom/bsc/time_cc.h
new file mode 100644
index 0000000..abddbcf
--- /dev/null
+++ b/include/osmocom/bsc/time_cc.h
@@ -0,0 +1,157 @@
+/* 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);
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
index 583fb79..cebbaeb 100644
--- a/src/osmo-bsc/Makefile.am
+++ b/src/osmo-bsc/Makefile.am
@@ -105,6 +105,7 @@
 	bssmap_reset.c \
 	system_information.c \
 	timeslot_fsm.c \
+	time_cc.c \
 	smscb.c \
 	cbch_scheduler.c \
 	cbsp_link.c \
diff --git a/src/osmo-bsc/time_cc.c b/src/osmo-bsc/time_cc.c
new file mode 100644
index 0000000..e78e48d
--- /dev/null
+++ b/src/osmo-bsc/time_cc.c
@@ -0,0 +1,208 @@
+/* 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 at sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr at 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);
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 762561e..aefaae8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -7,6 +7,7 @@
 	subscr \
 	nanobts_omlattr \
 	handover \
+	time_cc \
 	$(NULL)
 
 # The `:;' works around a Bash 3.2 bug when the output is not writeable.
diff --git a/tests/testsuite.at b/tests/testsuite.at
index e7eb76b..094117f 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -49,3 +49,9 @@
 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
diff --git a/tests/time_cc/Makefile.am b/tests/time_cc/Makefile.am
new file mode 100644
index 0000000..de407e7
--- /dev/null
+++ b/tests/time_cc/Makefile.am
@@ -0,0 +1,36 @@
+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
diff --git a/tests/time_cc/time_cc_test.c b/tests/time_cc/time_cc_test.c
new file mode 100644
index 0000000..4c5396d
--- /dev/null
+++ b/tests/time_cc/time_cc_test.c
@@ -0,0 +1,769 @@
+/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr at 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, T_ROUND_THRESH, OSMO_TDEF_US, -1), \
+		       osmo_tdef_get(g_my_obj_tdefs, T_FORGET_SUM, OSMO_TDEF_US, -1)); \
+	} while (0)
+
+	printf("\n----------- test T_defs\n\n");
+	now->tv_sec = 23000;
+	now->tv_nsec = 0;
+	osmo_gettimeofday_override_time = (struct timeval){23000, 0};
+
+	SET_TDEFS(100, 10, 0);
+
+	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(9);
+	/* flag has been true for 0.009s */
+	CHECK_RATE_CTRS(1, 0, 0);
+	ADD_MILLISECS(1);
+	/* flag has been true for 0.010s */
+	CHECK_RATE_CTRS(1, 1, 0);
+	ADD_MILLISECS(90);
+	/* flag has been true for 0.1s */
+	CHECK_RATE_CTRS(1, 1, 1);
+
+	SET_TDEFS(200, 190, 1);
+	/* gran is changed to 200ms, but still continues until the next scheduled event until the change is picked up.
+	 * For ceil(), it is 1 ms ahead.
+	 * For round(), it is 10 ms ahead.
+	 * For floor(), it is at the next full (previous) gran 100 ms ahead.
+	 * When T_defs change, all internal sums are reset to zero without reporting.
+	 */
+	CHECK_RATE_CTRS(1, 1, 1);
+	ADD_MILLISECS(1);
+	/* 1ms elapsed: ceil() picks up the T_gran change, starts anew. */
+	/* elapsed: ceil 0 ms */
+	CHECK_RATE_CTRS(1, 1, 1);
+	ADD_MILLISECS(1);
+	/* elapsed: ceil 1 ms */
+	/* ceil() increments because flag has been true for more than 1 us after reset */
+	CHECK_RATE_CTRS(2, 1, 1);
+	ADD_MILLISECS(8);
+	/* 10 ms elapsed: round() picks up the T_gran change, starts anew */
+	/* elapsed: ceil 9 ms, round 0 ms */
+	CHECK_RATE_CTRS(2, 1, 1);
+	ADD_MILLISECS(90);
+	/* 100 ms elapsed: floor() picks up the T_gran change, starts anew */
+	/* elapsed: ceil 99 ms, round 90 ms, floor 0 ms */
+	CHECK_RATE_CTRS(2, 1, 1);
+	ADD_MILLISECS(99);
+	/* elapsed: ceil 198 ms, round 189 ms, floor 99 ms */
+	CHECK_RATE_CTRS(2, 1, 1);
+	ADD_MILLISECS(1);
+	/* elapsed: ceil 199 ms, round 190 ms, floor 100 ms */
+	CHECK_RATE_CTRS(2, 2, 1);
+	ADD_MILLISECS(1);
+	/* elapsed: ceil 200 ms, round 191 ms, floor 101 ms */
+	CHECK_RATE_CTRS(2, 2, 1);
+	ADD_MILLISECS(1);
+	/* elapsed: ceil 201 ms, round 192 ms, floor 102 ms */
+	CHECK_RATE_CTRS(3, 2, 1);
+	ADD_MILLISECS(98);
+	/* elapsed: ceil 299 ms, round 290 ms, floor 200 ms */
+	CHECK_RATE_CTRS(3, 2, 2);
+	ADD_MILLISECS(99);
+	/* elapsed: ceil 398 ms, round 389 ms, floor 299 ms */
+	CHECK_RATE_CTRS(3, 2, 2);
+	ADD_MILLISECS(1);
+	/* elapsed: ceil 399 ms, round 390 ms, floor 300 ms */
+	CHECK_RATE_CTRS(3, 3, 2);
+	ADD_MILLISECS(1);
+	/* elapsed: ceil 400 ms, round 391 ms, floor 301 ms */
+	CHECK_RATE_CTRS(3, 3, 2);
+	ADD_MILLISECS(1);
+	/* elapsed: ceil 401 ms, round 392 ms, floor 302 ms */
+	CHECK_RATE_CTRS(4, 3, 2);
+	ADD_MILLISECS(98);
+	/* elapsed: ceil 499 ms, round 490 ms, floor 400 ms */
+	CHECK_RATE_CTRS(4, 3, 3);
+
+
+	SET_TDEFS(100, 0, 0);
+	/* T_defs change, but they only get picked up upon the next event:
+	 * For ceil(), it is 102 ms ahead.
+	 * For round(), it is 100 ms ahead (thresh is still 190, currently at 90).
+	 * For floor(), it is 200 ms ahead.
+	 * When T_defs change, all internal sums are reset to zero without reporting.
+	 */
+	CHECK_RATE_CTRS(4, 3, 3);
+	ADD_MILLISECS(100);
+	CHECK_RATE_CTRS(4, 3, 3);
+	/* round() picks up the new T_defs. Internal sum resets, nothing else happens yet.
+	 * round() schedules the next event 50 ms ahead. */
+	ADD_MILLISECS(2);
+	CHECK_RATE_CTRS(4, 3, 3);
+	/* ceil() picks up the change, its next event is 1 ms ahead. */
+	ADD_MILLISECS(1);
+	/* ceil: 0.001
+	 * round: 0.003
+	 * floor: still 97 ms until it picks up the change */
+	CHECK_RATE_CTRS(5, 3, 3);
+	ADD_MILLISECS(46);
+	CHECK_RATE_CTRS(5, 3, 3);
+	ADD_MILLISECS(1);
+	/* round() has first counter trigger after T_defs change. */
+	CHECK_RATE_CTRS(5, 4, 3);
+	/* ceil: 0.048
+	 * round: 0.050
+	 * floor: still 50 ms until it picks up the change */
+	ADD_MILLISECS(50);
+	/* floor() picks up the change. nothing happens yet. */
+	/* ceil: 0.098
+	 * round: 0.100
+	 * floor: 0.0 */
+	ADD_MILLISECS(2);
+	/* ceil: 0.100
+	 * round: 0.102
+	 * floor: 0.002 */
+	CHECK_RATE_CTRS(5, 4, 3);
+	ADD_MILLISECS(1);
+	/* ceil: 0.101
+	 * round: 0.103
+	 * floor: 0.003 */
+	CHECK_RATE_CTRS(6, 4, 3);
+	ADD_MILLISECS(46);
+	/* ceil: 0.147
+	 * round: 0.149
+	 * floor: 0.049 */
+	CHECK_RATE_CTRS(6, 4, 3);
+	ADD_MILLISECS(1);
+	/* ceil: 0.148
+	 * round: 0.150
+	 * floor: 0.050 */
+	CHECK_RATE_CTRS(6, 5, 3);
+
+	my_obj_destruct(&my_obj);
+	rate_ctr_group_reset(my_ctrg);
+
+	return 0;
+}
diff --git a/tests/time_cc/time_cc_test.ok b/tests/time_cc/time_cc_test.ok
new file mode 100644
index 0000000..ccf84d9
--- /dev/null
+++ b/tests/time_cc/time_cc_test.ok
@@ -0,0 +1,328 @@
+
+----------- cumulating time, without forget_sum
+
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+0 ADD_MILLISECS(100) --> 23000.100
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(0, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23000.101
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(99) --> 23000.200
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(100) --> 23000.300
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(100) --> 23000.400
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(300) --> 23000.700
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(299) --> 23000.999
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23001.000
+1 CHECK_RATE_CTRS(1, 1, 0) ok
+1 ADD_MILLISECS(499) --> 23001.499
+1 CHECK_RATE_CTRS(1, 1, 0) ok
+1 ADD_MILLISECS(1) --> 23001.500
+1 CHECK_RATE_CTRS(1, 1, 1) ok
+1 ADD_MILLISECS(1) --> 23001.501
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(299) --> 23001.800
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(2, 1, 1) ok
+0 ADD_MILLISECS(400) --> 23002.200
+0 CHECK_RATE_CTRS(2, 1, 1) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(199) --> 23002.399
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(2) --> 23002.401
+1 CHECK_RATE_CTRS(2, 2, 1) ok
+1 ADD_MILLISECS(498) --> 23002.899
+1 CHECK_RATE_CTRS(2, 2, 1) ok
+1 ADD_MILLISECS(2) --> 23002.901
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+1 ADD_MILLISECS(500) --> 23003.401
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(498) --> 23003.899
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(3) --> 23003.902
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(200) --> 23004.102
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(4, 3, 3) ok
+0 ADD_MILLISECS(4321) --> 23008.423
+0 CHECK_RATE_CTRS(4, 3, 3) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(5678) --> 23014.101
+1 CHECK_RATE_CTRS(9, 9, 8) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(9, 9, 8) ok
+
+----------- test forget_sum_usec
+
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(0, 0, 0) ok
+1 ADD_MILLISECS(100) --> 23000.100
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(1000) --> 23001.100
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(8999) --> 23010.099
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(1) --> 23010.100
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23010.101
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(399) --> 23010.500
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(99) --> 23010.599
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23010.600
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(400) --> 23011.000
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(99) --> 23011.099
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(1) --> 23011.100
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(300) --> 23011.400
+1 CHECK_RATE_CTRS(3, 1, 1) ok
+  flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23021.399
+  flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23021.424
+  flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23031.423
+  flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23031.448
+  flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23041.447
+  flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23041.472
+  flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23051.471
+  flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23051.496
+1 CHECK_RATE_CTRS(3, 1, 1) ok
+1 ADD_MILLISECS(100) --> 23051.596
+1 CHECK_RATE_CTRS(3, 2, 1) ok
+1 ADD_MILLISECS(500) --> 23052.096
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+1 ADD_MILLISECS(300) --> 23052.396
+1 CHECK_RATE_CTRS(4, 2, 2) ok
+  flag: TRUE -> FALSE
+0 ADD_MILLISECS(5000) --> 23057.396
+  flag: FALSE -> FALSE
+0 ADD_MILLISECS(5000) --> 23062.396
+0 CHECK_RATE_CTRS(4, 2, 2) ok
+  flag: FALSE -> TRUE
+1 ADD_MILLISECS(1) --> 23062.397
+1 CHECK_RATE_CTRS(5, 2, 2) ok
+1 ADD_MILLISECS(199) --> 23062.596
+1 CHECK_RATE_CTRS(5, 2, 2) ok
+1 ADD_MILLISECS(299) --> 23062.895
+1 CHECK_RATE_CTRS(5, 2, 2) ok
+1 ADD_MILLISECS(1) --> 23062.896
+1 CHECK_RATE_CTRS(5, 3, 2) ok
+1 ADD_MILLISECS(200) --> 23063.096
+1 CHECK_RATE_CTRS(5, 3, 2) ok
+1 ADD_MILLISECS(299) --> 23063.395
+1 CHECK_RATE_CTRS(5, 3, 2) ok
+1 ADD_MILLISECS(1) --> 23063.396
+1 CHECK_RATE_CTRS(5, 3, 3) ok
+
+----------- cumulating time, without forget_sum, when timer cb are invoked late
+
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+0 ADD_MILLISECS(100) --> 23000.100
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(0, 0, 0) ok
+1 ADD_MILLISECS(100) --> 23000.200
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(100) --> 23000.300
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(100) --> 23000.400
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(300) --> 23000.700
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(799) --> 23001.499
+1 CHECK_RATE_CTRS(1, 1, 0) ok
+1 ADD_MILLISECS(1) --> 23001.500
+1 CHECK_RATE_CTRS(1, 1, 1) ok
+1 ADD_MILLISECS(300) --> 23001.800
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(2, 1, 1) ok
+0 ADD_MILLISECS(400) --> 23002.200
+0 CHECK_RATE_CTRS(2, 1, 1) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(699) --> 23002.899
+1 CHECK_RATE_CTRS(2, 2, 1) ok
+1 ADD_MILLISECS(1) --> 23002.900
+1 CHECK_RATE_CTRS(2, 2, 2) ok
+1 ADD_MILLISECS(1) --> 23002.901
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+1 ADD_MILLISECS(499) --> 23003.400
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(499) --> 23003.899
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(1) --> 23003.900
+1 CHECK_RATE_CTRS(3, 3, 3) ok
+1 ADD_MILLISECS(200) --> 23004.100
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(4, 3, 3) ok
+0 ADD_MILLISECS(4321) --> 23008.421
+0 CHECK_RATE_CTRS(4, 3, 3) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(5678) --> 23014.099
+1 CHECK_RATE_CTRS(9, 9, 8) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(9, 9, 8) ok
+
+----------- test forget_sum, when timer cb are invoked late
+
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(0, 0, 0) ok
+1 ADD_MILLISECS(100) --> 23000.100
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(1000) --> 23001.100
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(8999) --> 23010.099
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(1) --> 23010.100
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23010.101
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(399) --> 23010.500
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(99) --> 23010.599
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23010.600
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(400) --> 23011.000
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(99) --> 23011.099
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(1) --> 23011.100
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(300) --> 23011.400
+1 CHECK_RATE_CTRS(3, 1, 1) ok
+  flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23021.399
+  flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23021.424
+  flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23031.423
+  flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23031.448
+  flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23041.447
+  flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23041.472
+  flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23051.471
+  flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23051.496
+1 CHECK_RATE_CTRS(3, 1, 1) ok
+1 ADD_MILLISECS(100) --> 23051.596
+1 CHECK_RATE_CTRS(3, 2, 1) ok
+1 ADD_MILLISECS(500) --> 23052.096
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+
+----------- test T_defs
+
+T_defs: T_gran=100000usec T_round_threshold=10000usec T_forget_sum=0usec
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+0 ADD_MILLISECS(100) --> 23000.100
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+  flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(0, 0, 0) ok
+1 ADD_MILLISECS(9) --> 23000.109
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23000.110
+1 CHECK_RATE_CTRS(1, 1, 0) ok
+1 ADD_MILLISECS(90) --> 23000.200
+1 CHECK_RATE_CTRS(1, 1, 1) ok
+T_defs: T_gran=200000usec T_round_threshold=190000usec T_forget_sum=1000000usec
+1 CHECK_RATE_CTRS(1, 1, 1) ok
+1 ADD_MILLISECS(1) --> 23000.201
+1 CHECK_RATE_CTRS(1, 1, 1) ok
+1 ADD_MILLISECS(1) --> 23000.202
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(8) --> 23000.210
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(90) --> 23000.300
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(99) --> 23000.399
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(1) --> 23000.400
+1 CHECK_RATE_CTRS(2, 2, 1) ok
+1 ADD_MILLISECS(1) --> 23000.401
+1 CHECK_RATE_CTRS(2, 2, 1) ok
+1 ADD_MILLISECS(1) --> 23000.402
+1 CHECK_RATE_CTRS(3, 2, 1) ok
+1 ADD_MILLISECS(98) --> 23000.500
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+1 ADD_MILLISECS(99) --> 23000.599
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+1 ADD_MILLISECS(1) --> 23000.600
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(1) --> 23000.601
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(1) --> 23000.602
+1 CHECK_RATE_CTRS(4, 3, 2) ok
+1 ADD_MILLISECS(98) --> 23000.700
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+T_defs: T_gran=100000usec T_round_threshold=0usec T_forget_sum=0usec
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(100) --> 23000.800
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(2) --> 23000.802
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(1) --> 23000.803
+1 CHECK_RATE_CTRS(5, 3, 3) ok
+1 ADD_MILLISECS(46) --> 23000.849
+1 CHECK_RATE_CTRS(5, 3, 3) ok
+1 ADD_MILLISECS(1) --> 23000.850
+1 CHECK_RATE_CTRS(5, 4, 3) ok
+1 ADD_MILLISECS(50) --> 23000.900
+1 ADD_MILLISECS(2) --> 23000.902
+1 CHECK_RATE_CTRS(5, 4, 3) ok
+1 ADD_MILLISECS(1) --> 23000.903
+1 CHECK_RATE_CTRS(6, 4, 3) ok
+1 ADD_MILLISECS(46) --> 23000.949
+1 CHECK_RATE_CTRS(6, 4, 3) ok
+1 ADD_MILLISECS(1) --> 23000.950
+1 CHECK_RATE_CTRS(6, 5, 3) ok

-- 
To view, visit https://gerrit.osmocom.org/c/osmo-bsc/+/25973
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-bsc
Gerrit-Branch: master
Gerrit-Change-Id: Icdd36f27cb54b2e1b940c9e6404ba9dd3692a310
Gerrit-Change-Number: 25973
Gerrit-PatchSet: 9
Gerrit-Owner: neels <nhofmeyr at sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge at osmocom.org>
Gerrit-Reviewer: neels <nhofmeyr at sysmocom.de>
Gerrit-Reviewer: osmith <osmith at sysmocom.de>
Gerrit-Reviewer: pespin <pespin at sysmocom.de>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20211110/a5c41b7b/attachment.htm>


More information about the gerrit-log mailing list