diff --git a/include/Makefile.am b/include/Makefile.am index 49402d0bc..e3246cf2c 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -60,6 +60,7 @@ nobase_include_HEADERS = \ osmocom/core/utils.h \ osmocom/core/write_queue.h \ osmocom/core/sockaddr_str.h \ + osmocom/core/time_cc.h \ osmocom/core/use_count.h \ osmocom/crypt/auth.h \ osmocom/crypt/gprs_cipher.h \ diff --git a/include/osmocom/core/time_cc.h b/include/osmocom/core/time_cc.h new file mode 100644 index 000000000..36fdee46d --- /dev/null +++ b/include/osmocom/core/time_cc.h @@ -0,0 +1,187 @@ +/*! \file time_cc.h + * 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 + * + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * 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 . + * + */ +#pragma once + +#include + +#include + +/*! \defgroup time_cc Cumulative counter of time as rate counter. + * @{ + * \file time_cc.h + */ + +struct osmo_tdef; +struct rate_ctr; + +/*! Configuration for osmo_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 osmo_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 osmo_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. When zero, use 1000000. */ + uint64_t gran_usec; + /*! Nr of microseconds above n * gran_usec at which to trigger a counter increment. When zero, use 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 osmo_time_cc_cfg for details on configuring. + * + * Usage: + * + * struct my_obj { + * struct osmo_time_cc flag_cc; + * }; + * + * void my_obj_init(struct my_obj *my_obj) + * { + * osmo_time_cc_init(&my_obj->flag_cc); + * my_obj->flag_cc.cfg = (struct osmo_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': + * // osmo_time_cc_set_flag(&my_obj->flag_cc, false); + * } + * + * void my_obj_event(struct my_obj *my_obj, bool flag) + * { + * osmo_time_cc_set_flag(&my_obj->flag_cc, flag); + * } + * + * void my_obj_destruct(struct my_obj *my_obj) + * { + * osmo_time_cc_cleanup(&my_obj->flag_cc); + * } + */ +struct osmo_time_cc { + struct osmo_time_cc_cfg cfg; + + bool flag_state; + + /*! Overall cumulative sum. Does not get reset for the entire lifetime of an osmo_time_cc. + * (Informational only, not used by the osmo_time_cc implementation.) */ + uint64_t total_sum; + + struct osmo_timer_list timer; + + /*! CLOCK_MONOTONIC reading in microseconds, at the time when the osmo_time_cc instance started counting. */ + uint64_t start_time; + /*! CLOCK_MONOTONIC reading in microseconds, at the time when the osmo_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 osmo_time_cc_init(struct osmo_time_cc *tc); +void osmo_time_cc_set_flag(struct osmo_time_cc *tc, bool flag); +void osmo_time_cc_cleanup(struct osmo_time_cc *tc); + +/*! @} */ diff --git a/src/Makefile.am b/src/Makefile.am index 328d2c730..6875aa59a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,6 +26,7 @@ libosmocore_la_SOURCES = context.c timer.c timer_gettimeofday.c timer_clockgetti isdnhdlc.c \ tdef.c \ thread.c \ + time_cc.c \ sockaddr_str.c \ use_count.c \ exec.c \ diff --git a/src/time_cc.c b/src/time_cc.c new file mode 100644 index 000000000..ae99b589f --- /dev/null +++ b/src/time_cc.c @@ -0,0 +1,229 @@ +/*! \file foo.c + * 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 + * + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * 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 . + * + */ + +/*! \addtogroup time_cc + * + * Report the cumulative counter of time for which a flag is true as rate counter. + * + * Useful for reporting cumulative time counters as defined in 3GPP TS 52.402, for example allAvailableSDCCHAllocated, + * allAvailableTCHAllocated, availablePDCHAllocatedTime. + * + * For a usage example, see the description of struct osmo_time_cc. + * + * @{ + * \file time_cc.c + */ +#include "config.h" +#ifdef HAVE_CLOCK_GETTIME + +#include +#include + +#include +#include +#include + +#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 osmo_time_cc_forget_sum(struct osmo_time_cc *tc, uint64_t now); + +static void osmo_time_cc_update_from_tdef(struct osmo_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) + osmo_time_cc_forget_sum(tc, now); +} + +static void osmo_time_cc_schedule_timer(struct osmo_time_cc *tc, uint64_t now); + +/*! Clear out osmo_timer and internal counting state of struct osmo_time_cc. The .cfg remains unaffected. After calling, + * the osmo_time_cc instance can be used again to accumulate state as if it had just been initialized. */ +void osmo_time_cc_cleanup(struct osmo_time_cc *tc) +{ + osmo_timer_del(&tc->timer); + *tc = (struct osmo_time_cc){ + .cfg = tc->cfg, + }; +} + +static void osmo_time_cc_start(struct osmo_time_cc *tc, uint64_t now) +{ + osmo_time_cc_cleanup(tc); + tc->start_time = now; + tc->last_counted_time = now; + osmo_time_cc_update_from_tdef(tc, now); + osmo_time_cc_schedule_timer(tc, now); +} + +static void osmo_time_cc_count_time(struct osmo_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 osmo_time_cc_report(struct osmo_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 osmo_time_cc_forget_sum(struct osmo_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 osmo_time_cc. Call this once before use, and before setting up the .cfg items. */ +void osmo_time_cc_init(struct osmo_time_cc *tc) +{ + *tc = (struct osmo_time_cc){0}; +} + +/*! Report state to be recorded by osmo_time_cc instance. Setting an unchanged state repeatedly has no effect. */ +void osmo_time_cc_set_flag(struct osmo_time_cc *tc, bool flag) +{ + uint64_t now = time_now_usec(); + if (!tc->start_time) + osmo_time_cc_start(tc, now); + /* No flag change == no effect */ + if (flag == tc->flag_state) + return; + /* Sum up elapsed time, report increments for that. */ + osmo_time_cc_count_time(tc, now); + osmo_time_cc_report(tc, now); + tc->flag_state = flag; + osmo_time_cc_schedule_timer(tc, now); +} + +static void osmo_time_cc_timer_cb(void *data) +{ + struct osmo_time_cc *tc = data; + uint64_t now = time_now_usec(); + + osmo_time_cc_update_from_tdef(tc, now); + + if (tc->flag_state) { + osmo_time_cc_count_time(tc, now); + osmo_time_cc_report(tc, now); + } else if (tc->cfg.forget_sum_usec && tc->sum + && (now >= tc->last_counted_time + tc->cfg.forget_sum_usec)) { + osmo_time_cc_forget_sum(tc, now); + } + osmo_time_cc_schedule_timer(tc, now); +} + +/*! Figure out the next time we should do anything, if the flag state remains unchanged. */ +static void osmo_time_cc_schedule_timer(struct osmo_time_cc *tc, uint64_t now) +{ + uint64_t next_event = UINT64_MAX; + + osmo_time_cc_update_from_tdef(tc, now); + + /* 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, osmo_time_cc_timer_cb, tc); + osmo_timer_del(&tc->timer); + osmo_timer_schedule(&tc->timer, next_event / 1000000, next_event % 1000000); +} + +#endif /* HAVE_CLOCK_GETTIME */ + +/*! @} */ diff --git a/tests/Makefile.am b/tests/Makefile.am index b72619f14..c44b6f08a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -42,6 +42,7 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \ bsslap/bsslap_test \ bssmap_le/bssmap_le_test \ it_q/it_q_test \ + time_cc/time_cc_test \ gsm48/rest_octets_test \ base64/base64_test \ $(NULL) @@ -324,6 +325,9 @@ bssmap_le_bssmap_le_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la it_q_it_q_test_SOURCES = it_q/it_q_test.c it_q_it_q_test_LDADD = $(LDADD) +time_cc_time_cc_test_SOURCES = time_cc/time_cc_test.c +time_cc_time_cc_test_LDADD = $(LDADD) + # The `:;' works around a Bash 3.2 bug when the output is not writeable. $(srcdir)/package.m4: $(top_srcdir)/configure.ac :;{ \ @@ -413,6 +417,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \ bsslap/bsslap_test.ok \ bssmap_le/bssmap_le_test.ok \ it_q/it_q_test.ok \ + time_cc/time_cc_test.ok \ gsm48/rest_octets_test.ok \ base64/base64_test.ok \ $(NULL) @@ -609,6 +614,8 @@ endif >$(srcdir)/bssmap_le/bssmap_le_test.ok it_q/it_q_test \ >$(srcdir)/it_q/it_q_test.ok + time_cc/time_cc_test \ + >$(srcdir)/time_cc/time_cc_test.ok check-local: atconfig $(TESTSUITE) [ -e /proc/cpuinfo ] && cat /proc/cpuinfo diff --git a/tests/testsuite.at b/tests/testsuite.at index 6ac597094..975b51e3f 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -452,3 +452,9 @@ AT_KEYWORDS([base64]) cat $abs_srcdir/base64/base64_test.ok > expout AT_CHECK([$abs_top_builddir/tests/base64/base64_test], [0], [expout], [ignore]) AT_CLEANUP + +AT_SETUP([time_cc]) +AT_KEYWORDS([time_cc]) +cat $abs_srcdir/time_cc/time_cc_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/time_cc/time_cc_test], [0], [expout], [ignore]) +AT_CLEANUP diff --git a/tests/time_cc/time_cc_test.c b/tests/time_cc/time_cc_test.c new file mode 100644 index 000000000..22ea7f651 --- /dev/null +++ b/tests/time_cc/time_cc_test.c @@ -0,0 +1,768 @@ +/* (C) 2021 by sysmocom - s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Neels Janosch Hofmeyr + * + * 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 . + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +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 osmo_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 osmo_time_cc flag_cc_ceil; + struct osmo_time_cc flag_cc_round; + struct osmo_time_cc flag_cc_floor; +}; + +void my_obj_init(struct my_obj *my_obj) +{ + osmo_time_cc_init(&my_obj->flag_cc_ceil); + my_obj->flag_cc_ceil.cfg = (struct osmo_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, + }; + + osmo_time_cc_init(&my_obj->flag_cc_round); + my_obj->flag_cc_round.cfg = (struct osmo_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, + }; + + osmo_time_cc_init(&my_obj->flag_cc_floor); + my_obj->flag_cc_floor.cfg = (struct osmo_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) +{ + osmo_time_cc_set_flag(&my_obj->flag_cc_ceil, flag); + osmo_time_cc_set_flag(&my_obj->flag_cc_round, flag); + osmo_time_cc_set_flag(&my_obj->flag_cc_floor, flag); +} + +void my_obj_destruct(struct my_obj *my_obj) +{ + osmo_time_cc_cleanup(&my_obj->flag_cc_ceil); + osmo_time_cc_cleanup(&my_obj->flag_cc_round); + osmo_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 osmo_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 000000000..ccf84d954 --- /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