aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <nhofmeyr@sysmocom.de>2021-09-21 01:37:04 +0200
committerNeels Hofmeyr <nhofmeyr@sysmocom.de>2021-09-21 01:37:04 +0200
commit58b257ab252dedf7b731db907d3a44492fb062bb (patch)
tree227b4911b0c6d9ef04b0459d2f0a69ce55a32bc1
parentfcca9fed8656d139a0a982cd998c72c3aeb9b121 (diff)
time_cc wip
-rw-r--r--include/Makefile.am1
-rw-r--r--include/osmocom/core/time_cc_rate_ctr.h161
-rw-r--r--src/Makefile.am1
-rw-r--r--src/time_cc_rate_ctr.c190
4 files changed, 353 insertions, 0 deletions
diff --git a/include/Makefile.am b/include/Makefile.am
index e25ed48e..cf54b7f5 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -45,6 +45,7 @@ nobase_include_HEADERS = \
osmocom/core/process.h \
osmocom/core/rate_ctr.h \
osmocom/core/stat_item.h \
+ osmocom/core/time_cc_rate_ctr.h \
osmocom/core/select.h \
osmocom/core/sercomm.h \
osmocom/core/signal.h \
diff --git a/include/osmocom/core/time_cc_rate_ctr.h b/include/osmocom/core/time_cc_rate_ctr.h
new file mode 100644
index 00000000..746ecf0c
--- /dev/null
+++ b/include/osmocom/core/time_cc_rate_ctr.h
@@ -0,0 +1,161 @@
+/* Report the cumulative counter of time for which a flag is true as rate counter. */
+#pragma once
+
+/*! Configuration for an osmo_time_cc_rate_ctr.
+ * 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_rate_ctr_set_flag().
+ *
+ * The granularity defines how much time one rate counter increment represents:
+ * the "normal" configuration is gran_usec = 1000000, i.e. one rate counter increment represents one second.
+ *
+ * Reporting as rate counter is configurable by using either rate_ctr_early or rate_ctr_full,
+ * and by setting a nonzero reset_sum_usec value.
+ *
+ * Examples:
+ * - To see an increment for each gran_usec period where the flag was seen true, set reset_sum_usec = gran_usec and
+ * point rate_ctr_early at your rate_ctr instance.
+ * - To see an increment only for those gran_usec periods where the flag was true the entire time, set
+ * reset_sum_usec = gran_usec and point rate_ctr_full at your rate_ctr instance (leave rate_ctr_early = NULL).
+ * - To see an increment only when the cumulative time where the flag was true has surpassed another gran_usec step, no
+ * matter how long it takes to get there, set reset_sum_usec = 0 and use rate_ctr_full.
+ *
+ * Reporting modes in detail:
+ *
+ * - rate_ctr_early and rate_ctr_full report when the cumulative counter of time where the flag was true reaches a
+ * multiple of granularity period gran_usec, "early" at the start of a true flag, "full" only upon the sum reaching a full
+ * multiple of gran_usec.
+ *
+ * sum ^
+ * | ________
+ * | /
+ * | /
+ * | /
+ * 3*gran --+--------------------------------------+
+ * | /:
+ * | / :
+ * | / :
+ * | / :
+ * | / :
+ * 2*gran --+--------------------------------+ :
+ * | /: :
+ * | / : :
+ * | _________/ : :
+ * | / : :
+ * | / : :
+ * 1*gran --+-----------------+ : :
+ * | /: : :
+ * | / : : :
+ * | / : : :
+ * | / : : :
+ * | ....-------' : : :
+ * 0 +---------+---------+---------+---------+---------+----------------------> elapsed time
+ * 1*gran : 2*gran : :
+ * _ _ _______ ___________
+ * flag: __| |_| |____| : |________| : : |__________
+ * f t f t f t : f t : : f
+ * : : : :
+ * rate_ctr_early: 0 1 2 3 4 = inc when the sum starts the next granularity
+ * rate_ctr_full: 0 1 2 3 = inc when the sum becomes a full granularity
+ *
+ *
+ * - The sum can be reset periodically by setting a nonzero reset_sum_usec value:
+ * For example, with reset_sum_usec = 2 * gran_usec:
+ *
+ * sum ^
+ * |
+ * 2*gran --+
+ * | /|
+ * | / |
+ * | / |
+ * | /| / |
+ * | / | / |
+ * 1*gran --+-----------------+--|-------------+ |
+ * | /: | /: |
+ * | / : | / : |
+ * | / : | / : | ___
+ * | / : | / : | /
+ * | ....-------' : |________/ : |/
+ * 0 +---------+----------+---------+---------+-----> elapsed time
+ * 1*gran : 2*gran : 4*gran
+ * _ _ ______ ____________
+ * flag: __| |_| |____| : |_________| : : |__________
+ * f t f t f t : f t : : f
+ * : : : : :
+ * rate_ctr_early: 0 1 2 3 4 5
+ * rate_ctr_full: 0 1 2
+ *
+ * Like this,
+ * - rate_ctr_early increments once for each reset_sum_usec period in which the flag is seen true at any time,
+ * i.e. reports true flags quite sensitively, while guaranteeing a maximum of one increment per gran_usec.
+ * - rate_ctr_full increments only when there is a full gran_usec of "true" seen within one reset_sum_usec period,
+ * i.e. only reports counts for long durations of the flag being true, ignoring insignificantly short true flags.
+ */
+struct osmo_time_cc_rate_ctr_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 over which to accumulate for reporting, or zero to accumulate indefinitely.
+ * When this value is nonzero, it should be >= gran_usec. */
+ uint64_t reset_sum_usec;
+ /*! Rate counter that reports every true flag seen, at most one per gran_usec period, or NULL to not use it. */
+ struct rate_ctr *rate_ctr_early;
+ /*! Rate counter that reports a count only for every full gran_usec duration of true flag, or NULL. */
+ struct rate_ctr *rate_ctr_full;
+
+ /*! Update gran_usec from this T-timer value, or zero to not use any T timer. */
+ int T_gran_usec;
+ /*! Update reset_sum_usec from this T-timer value, or zero to not use any T timer. */
+ int T_reset_sum_usec;
+ /*! Look up T_gran_usec and T_reset_sum_usec in this list of timers, or NULL to not use any T timers. */
+ struct osmo_tdef *Tdefs;
+};
+
+/*! Report the cumulative counter of time for which a flag is true as rate counter.
+ * See also osmo_time_cc_rate_ctr_cfg for details on configuring an osmo_time_cc_rate_ctr.
+ *
+ * Usage:
+ *
+ * struct my_obj {
+ * struct osmo_time_cc_rate_ctr flag_cc;
+ * };
+ *
+ * void my_obj_init(struct my_obj *my_obj)
+ * {
+ * my_obj->flag_cc.cfg = (struct osmo_time_cc_rate_ctr_cfg){
+ * .gran_usec = 1000000,
+ * .reset_sum_usec = 1000000,
+ * .rate_ctr_early = rate_ctr_group_get_ctr(my_ctrg, MY_CTR_IDX),
+ * };
+ * osmo_time_cc_rate_ctr_set_flag(&my_obj->flag_cc, false);
+ * }
+ *
+ * void my_obj_event(struct my_obj *my_obj, bool flag)
+ * {
+ * osmo_time_cc_rate_ctr_set_flag(&my_obj->flag_cc, flag);
+ * }
+ *
+ * void my_obj_destruct(struct my_obj *my_obj)
+ * {
+ * osmo_time_cc_rate_ctr_cleanup(&my_obj->flag_cc);
+ * }
+ */
+struct osmo_time_cc_rate_ctr {
+ struct osmo_time_cc_rate_ctr_cfg cfg;
+
+ struct osmo_timer_list timer;
+
+ bool flag_state;
+ uint64_t start_time;
+ uint64_t last_counted_time;
+ uint64_t last_reset_time;
+
+ uint64_t total_sum;
+ uint64_t sum;
+ uint64_t reported_sum_early;
+ uint64_t reported_sum_full;
+};
+
+void osmo_time_cc_rate_ctr_set_flag(struct osmo_time_cc_rate_ctr *tcr, bool flag);
+void osmo_time_cc_rate_ctr_cleanup(struct osmo_time_cc_rate_ctr *tcr);
diff --git a/src/Makefile.am b/src/Makefile.am
index 2f18d092..5fccf1c4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -31,6 +31,7 @@ libosmocore_la_SOURCES = context.c timer.c timer_gettimeofday.c timer_clockgetti
exec.c \
it_q.c \
probes.d \
+ time_cc_rate_ctr.c \
$(NULL)
if HAVE_SSSE3
diff --git a/src/time_cc_rate_ctr.c b/src/time_cc_rate_ctr.c
new file mode 100644
index 00000000..53cc2777
--- /dev/null
+++ b/src/time_cc_rate_ctr.c
@@ -0,0 +1,190 @@
+/* Report the cumulative counter of time for which a flag is true as rate counter. */
+/* Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+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_rate_ctr_update_from_tdef(struct osmo_time_cc_rate_ctr *tcr)
+{
+ if (!tcr->cfg.Tdefs)
+ return;
+ if (tcr->cfg.T_gran_usec)
+ tcr->cfg.gran_usec = osmo_tdef_get(tcr->cfg.Tdefs, tcr->cfg.T_gran_usec, OSMO_TDEF_US, -1);
+ if (tcr->cfg.T_reset_sum_usec)
+ tcr->cfg.reset_sum_usec = osmo_tdef_get(tcr->cfg.Tdefs, tcr->cfg.T_reset_sum_usec, OSMO_TDEF_US, -1);
+}
+
+static void osmo_time_cc_rate_ctr_schedule_timer(struct osmo_time_cc_rate_ctr *tcr, uint64_t now);
+
+void osmo_time_cc_rate_ctr_cleanup(struct osmo_time_cc_rate_ctr *tcr)
+{
+ osmo_timer_del(&tcr->timer);
+ tcr->start_time = 0;
+ tcr->last_counted_time = 0;
+ tcr->last_reset_time = 0;
+ tcr->flag_state = false;
+ tcr->total_sum = 0;
+ tcr->sum = 0;
+ tcr->reported_sum_early = 0;
+ tcr->reported_sum_full = 0;
+}
+
+static void osmo_time_cc_rate_ctr_start(struct osmo_time_cc_rate_ctr *tcr, uint64_t now)
+{
+ osmo_time_cc_rate_ctr_cleanup(tcr);
+ tcr->start_time = now;
+ tcr->last_counted_time = now;
+ osmo_time_cc_rate_ctr_update_from_tdef(tcr);
+}
+
+static void osmo_time_cc_rate_ctr_count_time(struct osmo_time_cc_rate_ctr *tcr, uint64_t now)
+{
+ uint64_t time_delta;
+ if (!tcr->flag_state)
+ return;
+ /* Flag is currently true, cumulate the elapsed time */
+ time_delta = now - tcr->last_counted_time;
+ tcr->total_sum += time_delta;
+ tcr->sum += time_delta;
+ tcr->last_counted_time = now;
+}
+
+static void osmo_time_cc_rate_ctr_report_early(struct osmo_time_cc_rate_ctr *tcr, uint64_t now)
+{
+ uint64_t delta;
+ uint64_t n;
+ if (!tcr->cfg.rate_ctr_early)
+ return;
+ /* For rate_ctr_early, 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 (tcr->reported_sum_early > tcr->sum)
+ return;
+ delta = tcr->sum - tcr->reported_sum_early;
+ n = delta / tcr->cfg.gran_usec;
+ /* If the flag is true, increment the counter ahead of time. */
+ if (!n && tcr->flag_state)
+ n = 1;
+ if (!n)
+ return;
+ /* integer sanity, not that this would be a sane amount to add to a rate_ctr either... */
+ if (n > INT_MAX)
+ n = INT_MAX;
+ /* The flag is true and a new granularity period is here. Increment the counter. */
+ rate_ctr_add(tcr->cfg.rate_ctr_early, n);
+ /* Store the increments of gran_usec that were counted. That compensates for possible drift across granularity
+ * periods. */
+ tcr->reported_sum_early += n * tcr->cfg.gran_usec;
+}
+
+static void osmo_time_cc_rate_ctr_report_full(struct osmo_time_cc_rate_ctr *tcr, uint64_t now)
+{
+ uint64_t delta;
+ uint64_t n;
+ if (!tcr->cfg.rate_ctr_full)
+ return;
+ delta = tcr->sum - tcr->reported_sum_full;
+ n = delta / tcr->cfg.gran_usec;
+ if (!n)
+ return;
+ /* integer sanity, not that this would be a sane amount to add to a rate_ctr either... */
+ if (n > INT_MAX)
+ n = INT_MAX;
+ /* The flag was true for long enough to increment the counter. */
+ rate_ctr_add(tcr->cfg.rate_ctr_full, n);
+ /* Store the increments of gran_usec that were counted. That compensates for possible drift across granularity
+ * periods. */
+ tcr->reported_sum_full += n * tcr->cfg.gran_usec;
+}
+
+static void osmo_time_cc_rate_ctr_reset_sum(struct osmo_time_cc_rate_ctr *tcr)
+{
+ tcr->sum = 0;
+ tcr->reported_sum_early = 0;
+ tcr->reported_sum_full = 0;
+}
+
+void osmo_time_cc_rate_ctr_set_flag(struct osmo_time_cc_rate_ctr *tcr, bool flag)
+{
+ uint64_t now = time_now_usec();
+ if (!tcr->start_time)
+ osmo_time_cc_rate_ctr_start(tcr, now);
+ osmo_timer_del(&tcr->timer);
+ /* Sum up elapsed time, report increments for that. */
+ osmo_time_cc_rate_ctr_count_time(tcr, now);
+ osmo_time_cc_rate_ctr_report_full(tcr, now);
+ osmo_time_cc_rate_ctr_report_early(tcr, now);
+ tcr->flag_state = flag;
+ /* For rate_ctr_early, facilitate reporting a true flag increment ahead of time. */
+ if (tcr->flag_state)
+ osmo_time_cc_rate_ctr_report_early(tcr, now);
+ osmo_time_cc_rate_ctr_schedule_timer(tcr);
+}
+
+static void osmo_time_cc_rate_ctr_timer_cb(void *data)
+{
+ struct osmo_time_cc_rate_ctr *tcr = data;
+ uint64_t now = time_now_usec();
+ osmo_time_cc_rate_ctr_count_time(tcr, now);
+ osmo_time_cc_rate_ctr_report_full(tcr, now);
+ osmo_time_cc_rate_ctr_report_early(tc, now);
+ if (tcr->cfg.reset_sum_usec && (now - tcr->last_reset_time > tcr->cfg.reset_sum_usec)) {
+ osmo_time_cc_rate_ctr_reset_sum(tcr);
+ /* For rate_ctr_early, facilitate reporting a true flag increment ahead of time. */
+ if (tcr->flag_state)
+ osmo_time_cc_rate_ctr_report_early(tcr, now);
+ }
+}
+
+static void osmo_time_cc_rate_ctr_schedule_timer(struct osmo_time_cc_rate_ctr *tcr, uint64_t now)
+{
+ uint64_t next_event = UINT64_MAX;
+ /* Figure out the next time we should do anything, if the flag state remains unchanged. */
+ /* When will the next reset_sum happen? */
+ if (tcr->cfg.reset_sum_usec) {
+ uint64_t next_reset_time = tcr->last_reset_time + tcr->cfg.reset_sum_usec;
+ next_event = OSMO_MIN(next_event, next_reset_time);
+ }
+ /* If the flag is false, nothing else needs to happen until the user sets it to true again. */
+ if (tcr->flag_state) {
+ /* Next rate_ctr_early increment? */
+ if (tcr->cfg.rate_ctr_early) {
+ uint64_t next_early_inc = now + tcr->reported_sum_early - tcr->sum;
+ next_event = OSMO_MIN(next_event, next_early_inc);
+ }
+ /* Next rate_ctr_full increment? */
+ if (tcr->cfg.rate_ctr_full) {
+ uint64_t next_full_inc = now + tcr->reported_sum_full - tcr->sum + tcr->cfg.gran_usec;
+ next_event = OSMO_MIN(next_event, next_full_inc);
+ }
+ }
+ if (next_event <= now)
+ next_event = 0;
+ else
+ next_event -= now;
+ osmo_timer_setup(&tcr->timer, osmo_time_cc_rate_ctr_timer_cb, tcr);
+ osmo_timer_schedule(&tcr->timer, next_event / 1000000, next_event % 1000000);
+}