aboutsummaryrefslogtreecommitdiffstats
path: root/src/time_cc_rate_ctr.c
blob: 53cc27774fe9b41877dceeff90a7c419d5fbd12c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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);
}