blob: 43e41897587b997ca996b8aeb9fda6e6ce347c68 [file] [log] [blame]
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +02001/*
2 * Copyright (C) 2019 sysmocom - s.f.m.c. GmbH
3 * All Rights Reserved
4 *
Pau Espin Pedrole9ce77b2019-06-25 12:29:01 +02005 * SPDX-License-Identifier: AGPL-3.0+
6 *
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +02007 * Author: Pau Espin Pedrol <pespin@sysmocom.de>
8 *
Pau Espin Pedrole9ce77b2019-06-25 12:29:01 +02009 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +020012 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Pau Espin Pedrole9ce77b2019-06-25 12:29:01 +020017 * GNU Affero General Public License for more details.
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +020018 *
Pau Espin Pedrole9ce77b2019-06-25 12:29:01 +020019 * You should have received a copy of the GNU Affero General Public License
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +020020 * along with this program. If not, see <http://www.gnu.org/licenses/>.
Pau Espin Pedrole9ce77b2019-06-25 12:29:01 +020021 * See the COPYING file in the main directory for details.
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +020022 */
23
24/*
25 * rate_ctr API uses several osmocom select loop features, and as a result,
26 * calls to it must be done through the main thread (the one running the osmocom
27 * loop in osmo-trx).
28 * Since read/write from/to SDR is done in separate threads (even read and write
29 * each use a different thread), we must use some sort of message passing system
30 * between main thread feeding rate_ctr structures and the Rx/Tx threads
31 * generating the events.
32 * The idea is that upon read/write issues, lower layers (SDR APIs) provide us with
33 * underrun/overrun/droppedPackets information, and in that case we pass that up
34 * the stack through signal <SS_DEVICE,S_DEVICE_COUNTER_CHANGE> with signal_cb
35 * being a pointer to a "struct device_counters" structure, which contains
36 * device (implementation agnostic) statful counters for different kind of
37 * statistics.
38 * That signal is processed here in device_sig_cb, where a copy of the "struct
39 * device_counters" structure is held and the main thread is instructed through
40 * a timerfd to update rate_ctr APIs against this copy. All this is done inside
41 * a mutex to avoid different race conditons (between Rx andTx threads, and
42 * between Rx/Tx and main thread). For the same reason, callers of signal
43 * <SS_DEVICE,S_DEVICE_COUNTER_CHANGE> (device_sig_cb), that is Rx/Tx threads,
44 * must do so with PTHREAD_CANCEL_DISABLE, in order to avoid possible deadlocks
45 * in case the main thread decides to cancel other threads due to a shutdown
46 * operation (fi SIGKILL received)
47 */
48
49#include <string.h>
50#include <stdint.h>
51#include <inttypes.h>
52#include <netinet/in.h>
53#include <arpa/inet.h>
54
55extern "C" {
56#include <osmocom/core/talloc.h>
57#include <osmocom/core/utils.h>
58#include <osmocom/core/rate_ctr.h>
59#include <osmocom/core/select.h>
60#include <osmocom/core/stats.h>
Pau Espin Pedrol6a305fe2019-05-24 19:58:20 +020061#include <osmocom/core/timer.h>
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +020062
63#include "osmo_signal.h"
64#include "trx_vty.h"
65#include "trx_rate_ctr.h"
66}
67#include "Threads.h"
68#include "Logger.h"
69
70/* Used in ctrs_pending, when set it means that channel slot contains unused
71 (non-pending) counter data */
72#define PENDING_CHAN_NONE SIZE_MAX
73
Pau Espin Pedrol6a305fe2019-05-24 19:58:20 +020074static void *trx_rate_ctr_ctx;
75
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +020076static struct rate_ctr_group** rate_ctrs;
77static struct device_counters* ctrs_pending;
78static size_t chan_len;
79static struct osmo_fd rate_ctr_timerfd;
80static Mutex rate_ctr_mutex;
81
Pau Espin Pedrol6a305fe2019-05-24 19:58:20 +020082struct osmo_timer_list threshold_timer;
83static LLIST_HEAD(threshold_list);
84static int threshold_timer_sched_secs;
85static bool threshold_initied;
86
87const struct value_string rate_ctr_intv[] = {
88 { RATE_CTR_INTV_SEC, "per-second" },
89 { RATE_CTR_INTV_MIN, "per-minute" },
90 { RATE_CTR_INTV_HOUR, "per-hour" },
91 { RATE_CTR_INTV_DAY, "per-day" },
92 { 0, NULL }
93};
94
95const struct value_string trx_chan_ctr_names[] = {
96 { TRX_CTR_RX_UNDERRUNS, "rx_underruns" },
97 { TRX_CTR_RX_OVERRUNS, "rx_overruns" },
98 { TRX_CTR_TX_UNDERRUNS, "tx_underruns" },
99 { TRX_CTR_RX_DROP_EV, "rx_drop_events" },
100 { TRX_CTR_RX_DROP_SMPL, "rx_drop_samples" },
101 { 0, NULL }
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200102};
103
104static const struct rate_ctr_desc trx_chan_ctr_desc[] = {
105 [TRX_CTR_RX_UNDERRUNS] = { "device:rx_underruns", "Number of Rx underruns" },
106 [TRX_CTR_RX_OVERRUNS] = { "device:rx_overruns", "Number of Rx overruns" },
107 [TRX_CTR_TX_UNDERRUNS] = { "device:tx_underruns", "Number of Tx underruns" },
108 [TRX_CTR_RX_DROP_EV] = { "device:rx_drop_events", "Number of times Rx samples were dropped by HW" },
109 [TRX_CTR_RX_DROP_SMPL] = { "device:rx_drop_samples", "Number of Rx samples dropped by HW" },
110};
111
112static const struct rate_ctr_group_desc trx_chan_ctr_group_desc = {
113 .group_name_prefix = "trx:chan",
114 .group_description = "osmo-trx statistics",
115 .class_id = OSMO_STATS_CLASS_GLOBAL,
116 .num_ctr = ARRAY_SIZE(trx_chan_ctr_desc),
117 .ctr_desc = trx_chan_ctr_desc,
118};
119
120static int rate_ctr_timerfd_cb(struct osmo_fd *ofd, unsigned int what) {
121 size_t chan;
122 struct rate_ctr *ctr;
123 LOGC(DMAIN, NOTICE) << "Main thread is updating counters";
124 rate_ctr_mutex.lock();
125 for (chan = 0; chan < chan_len; chan++) {
126 if (ctrs_pending[chan].chan == PENDING_CHAN_NONE)
127 continue;
128 LOGCHAN(chan, DMAIN, INFO) << "rate_ctr update";
129 ctr = &rate_ctrs[chan]->ctr[TRX_CTR_RX_UNDERRUNS];
130 rate_ctr_add(ctr, ctrs_pending[chan].rx_underruns - ctr->current);
131 ctr = &rate_ctrs[chan]->ctr[TRX_CTR_RX_OVERRUNS];
132 rate_ctr_add(ctr, ctrs_pending[chan].rx_overruns - ctr->current);
133 ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TX_UNDERRUNS];
134 rate_ctr_add(ctr, ctrs_pending[chan].tx_underruns - ctr->current);
135 ctr = &rate_ctrs[chan]->ctr[TRX_CTR_RX_DROP_EV];
136 rate_ctr_add(ctr, ctrs_pending[chan].rx_dropped_events - ctr->current);
137 ctr = &rate_ctrs[chan]->ctr[TRX_CTR_RX_DROP_SMPL];
138 rate_ctr_add(ctr, ctrs_pending[chan].rx_dropped_samples - ctr->current);
139
140 /* Mark as done */
141 ctrs_pending[chan].chan = PENDING_CHAN_NONE;
142 }
143 if (osmo_timerfd_disable(&rate_ctr_timerfd) < 0)
144 LOGC(DMAIN, ERROR) << "Failed to disable timerfd";
145 rate_ctr_mutex.unlock();
146 return 0;
147}
148
149/* Callback function to be called every time we receive a signal from DEVICE */
150static int device_sig_cb(unsigned int subsys, unsigned int signal,
151 void *handler_data, void *signal_data)
152{
153 struct device_counters *ctr;
154 /* Delay sched around 20 ms, in case we receive several calls from several
155 * channels batched */
156 struct timespec next_sched = {.tv_sec = 0, .tv_nsec = 20*1000*1000};
157 /* no automatic re-trigger */
158 struct timespec intv_sched = {.tv_sec = 0, .tv_nsec = 0};
159
160 switch (signal) {
161 case S_DEVICE_COUNTER_CHANGE:
162 ctr = (struct device_counters *)signal_data;
163 LOGCHAN(ctr->chan, DMAIN, NOTICE) << "Received counter change from radioDevice";
164 rate_ctr_mutex.lock();
165 ctrs_pending[ctr->chan] = *ctr;
166 if (osmo_timerfd_schedule(&rate_ctr_timerfd, &next_sched, &intv_sched) < 0) {
167 LOGC(DMAIN, ERROR) << "Failed to schedule timerfd: " << errno << " = "<< strerror(errno);
168 }
169 rate_ctr_mutex.unlock();
170 break;
171 default:
172 break;
173 }
174 return 0;
175}
176
Pau Espin Pedrol6a305fe2019-05-24 19:58:20 +0200177/************************************
178 * ctr_threshold APIs
179 ************************************/
180static const char* ctr_threshold_2_vty_str(struct ctr_threshold *ctr)
181{
182 static char buf[256];
183 int rc = 0;
184 rc += snprintf(buf, sizeof(buf), "ctr-error-threshold %s", get_value_string(trx_chan_ctr_names, ctr->ctr_id));
185 rc += snprintf(buf + rc, sizeof(buf) - rc, " %d %s", ctr->val, get_value_string(rate_ctr_intv, ctr->intv));
186 return buf;
187}
188
189static void threshold_timer_cb(void *data)
190{
191 struct ctr_threshold *ctr_thr;
192 struct rate_ctr *rate_ctr;
193 size_t chan;
194 LOGC(DMAIN, DEBUG) << "threshold_timer_cb fired!";
195
196 llist_for_each_entry(ctr_thr, &threshold_list, list) {
197 for (chan = 0; chan < chan_len; chan++) {
198 rate_ctr = &rate_ctrs[chan]->ctr[ctr_thr->ctr_id];
199 LOGCHAN(chan, DMAIN, INFO) << "checking threshold: " << ctr_threshold_2_vty_str(ctr_thr)
200 << " ("<< rate_ctr->intv[ctr_thr->intv].rate << " vs " << ctr_thr->val << ")";
201 if (rate_ctr->intv[ctr_thr->intv].rate >= ctr_thr->val) {
202 LOGCHAN(chan, DMAIN, FATAL) << "threshold reached, stopping! " << ctr_threshold_2_vty_str(ctr_thr)
203 << " ("<< rate_ctr->intv[ctr_thr->intv].rate << " vs " << ctr_thr->val << ")";
204 osmo_signal_dispatch(SS_MAIN, S_MAIN_STOP_REQUIRED, NULL);
205 return;
206 }
207 }
208 }
209 osmo_timer_schedule(&threshold_timer, threshold_timer_sched_secs, 0);
210}
211
212static size_t ctr_threshold_2_seconds(struct ctr_threshold *ctr)
213{
214 size_t mult = 0;
215 switch (ctr->intv) {
216 case RATE_CTR_INTV_SEC:
217 mult = 1;
218 break;
219 case RATE_CTR_INTV_MIN:
220 mult = 60;
221 break;
222 case RATE_CTR_INTV_HOUR:
223 mult = 60*60;
224 break;
225 case RATE_CTR_INTV_DAY:
226 mult = 60*60*24;
227 break;
228 default:
229 OSMO_ASSERT(false);
230 }
231 return mult;
232}
233
234static void threshold_timer_update_intv() {
235 struct ctr_threshold *ctr, *min_ctr;
236 size_t secs, min_secs;
237
238 /* Avoid scheduling timer until itself and other structures are prepared
239 by trx_rate_ctr_init */
240 if (!threshold_initied)
241 return;
242
243 if (llist_empty(&threshold_list)) {
244 if (osmo_timer_pending(&threshold_timer))
245 osmo_timer_del(&threshold_timer);
246 return;
247 }
248
249 min_ctr = llist_first_entry(&threshold_list, struct ctr_threshold, list);
250 min_secs = ctr_threshold_2_seconds(min_ctr);
251
252 llist_for_each_entry(ctr, &threshold_list, list) {
253 secs = ctr_threshold_2_seconds(ctr);
254 if( min_secs > secs)
255 min_secs = secs;
256 }
257
258
259 threshold_timer_sched_secs = OSMO_MAX(min_secs / 2 - 1, 1);
260 LOGC(DMAIN, INFO) << "New ctr-error-threshold check interval: "
261 << threshold_timer_sched_secs << " seconds";
262 osmo_timer_schedule(&threshold_timer, threshold_timer_sched_secs, 0);
263}
264
265/* Init rate_ctr subsystem. Expected to be called during process start by main thread before VTY is ready */
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200266void trx_rate_ctr_init(void *ctx, struct trx_ctx* trx_ctx)
267{
268 size_t i;
Pau Espin Pedrol6a305fe2019-05-24 19:58:20 +0200269 trx_rate_ctr_ctx = ctx;
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200270 chan_len = trx_ctx->cfg.num_chans;
271 ctrs_pending = (struct device_counters*) talloc_zero_size(ctx, chan_len * sizeof(struct device_counters));
272 rate_ctrs = (struct rate_ctr_group**) talloc_zero_size(ctx, chan_len * sizeof(struct rate_ctr_group*));
273
274 for (i = 0; i < chan_len; i++) {
275 ctrs_pending[i].chan = PENDING_CHAN_NONE;
276 rate_ctrs[i] = rate_ctr_group_alloc(ctx, &trx_chan_ctr_group_desc, i);
277 if (!rate_ctrs[i]) {
278 LOGCHAN(i, DMAIN, ERROR) << "Failed to allocate rate ctr";
279 exit(1);
280 }
281 }
282 rate_ctr_timerfd.fd = -1;
283 if (osmo_timerfd_setup(&rate_ctr_timerfd, rate_ctr_timerfd_cb, NULL) < 0) {
284 LOGC(DMAIN, ERROR) << "Failed to setup timerfd";
285 exit(1);
286 }
287 osmo_signal_register_handler(SS_DEVICE, device_sig_cb, NULL);
Pau Espin Pedrol6a305fe2019-05-24 19:58:20 +0200288
289 /* Now set up threshold checks */
290 threshold_initied = true;
291 osmo_timer_setup(&threshold_timer, threshold_timer_cb, NULL);
292 threshold_timer_update_intv();
293}
294
295void trx_rate_ctr_threshold_add(struct ctr_threshold *ctr)
296{
297 struct ctr_threshold *new_ctr;
298
299 new_ctr = talloc_zero(trx_rate_ctr_ctx, struct ctr_threshold);
300 *new_ctr = *ctr;
301 LOGC(DMAIN, NOTICE) << "Adding new threshold check: " << ctr_threshold_2_vty_str(new_ctr);
302 llist_add(&new_ctr->list, &threshold_list);
303 threshold_timer_update_intv();
304}
305
306int trx_rate_ctr_threshold_del(struct ctr_threshold *del_ctr)
307{
308 struct ctr_threshold *ctr;
309
310 llist_for_each_entry(ctr, &threshold_list, list) {
311 if (ctr->intv != del_ctr->intv ||
312 ctr->ctr_id != del_ctr->ctr_id ||
313 ctr->val != del_ctr->val)
314 continue;
315
316 LOGC(DMAIN, NOTICE) << "Deleting threshold check: " << ctr_threshold_2_vty_str(del_ctr);
317 llist_del(&ctr->list);
318 talloc_free(ctr);
319 threshold_timer_update_intv();
320 return 0;
321 }
322 return -1;
323}
324
325void trx_rate_ctr_threshold_write_config(struct vty *vty, char *indent_prefix)
326{
327 struct ctr_threshold *ctr;
328
329 llist_for_each_entry(ctr, &threshold_list, list) {
330 vty_out(vty, "%s%s%s", indent_prefix, ctr_threshold_2_vty_str(ctr), VTY_NEWLINE);
331 }
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200332}