aboutsummaryrefslogtreecommitdiffstats
path: root/src/time_cc_rate_ctr.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/time_cc_rate_ctr.c')
-rw-r--r--src/time_cc_rate_ctr.c190
1 files changed, 190 insertions, 0 deletions
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);
+}