Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 sysmocom - s.f.m.c. GmbH |
| 3 | * All Rights Reserved |
| 4 | * |
Pau Espin Pedrol | e9ce77b | 2019-06-25 12:29:01 +0200 | [diff] [blame] | 5 | * SPDX-License-Identifier: AGPL-3.0+ |
| 6 | * |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 7 | * Author: Pau Espin Pedrol <pespin@sysmocom.de> |
| 8 | * |
Pau Espin Pedrol | e9ce77b | 2019-06-25 12:29:01 +0200 | [diff] [blame] | 9 | * 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 Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 12 | * (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 Pedrol | e9ce77b | 2019-06-25 12:29:01 +0200 | [diff] [blame] | 17 | * GNU Affero General Public License for more details. |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 18 | * |
Pau Espin Pedrol | e9ce77b | 2019-06-25 12:29:01 +0200 | [diff] [blame] | 19 | * You should have received a copy of the GNU Affero General Public License |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 20 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
Pau Espin Pedrol | e9ce77b | 2019-06-25 12:29:01 +0200 | [diff] [blame] | 21 | * See the COPYING file in the main directory for details. |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 22 | */ |
| 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 | |
| 55 | extern "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 Pedrol | 6a305fe | 2019-05-24 19:58:20 +0200 | [diff] [blame] | 61 | #include <osmocom/core/timer.h> |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 62 | |
| 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 Pedrol | 6a305fe | 2019-05-24 19:58:20 +0200 | [diff] [blame] | 74 | static void *trx_rate_ctr_ctx; |
| 75 | |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 76 | static struct rate_ctr_group** rate_ctrs; |
| 77 | static struct device_counters* ctrs_pending; |
| 78 | static size_t chan_len; |
| 79 | static struct osmo_fd rate_ctr_timerfd; |
| 80 | static Mutex rate_ctr_mutex; |
| 81 | |
Pau Espin Pedrol | 6a305fe | 2019-05-24 19:58:20 +0200 | [diff] [blame] | 82 | struct osmo_timer_list threshold_timer; |
| 83 | static LLIST_HEAD(threshold_list); |
| 84 | static int threshold_timer_sched_secs; |
| 85 | static bool threshold_initied; |
| 86 | |
| 87 | const 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 | |
| 95 | const 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 Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 102 | }; |
| 103 | |
| 104 | static 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 | |
| 112 | static 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 | |
| 120 | static 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 */ |
| 150 | static 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 Pedrol | 6a305fe | 2019-05-24 19:58:20 +0200 | [diff] [blame] | 177 | /************************************ |
| 178 | * ctr_threshold APIs |
| 179 | ************************************/ |
| 180 | static 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 | |
| 189 | static 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 | |
| 212 | static 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 | |
| 234 | static 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 Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 266 | void trx_rate_ctr_init(void *ctx, struct trx_ctx* trx_ctx) |
| 267 | { |
| 268 | size_t i; |
Pau Espin Pedrol | 6a305fe | 2019-05-24 19:58:20 +0200 | [diff] [blame] | 269 | trx_rate_ctr_ctx = ctx; |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 270 | 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 Pedrol | 6a305fe | 2019-05-24 19:58:20 +0200 | [diff] [blame] | 288 | |
| 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 | |
| 295 | void 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 | |
| 306 | int 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 | |
| 325 | void 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 Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 332 | } |