/* * FST Manager traffic poller implementation * * Copyright (c) 2019, The Linux Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of The Linux Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "utils/includes.h" #include "utils/common.h" #include "utils/eloop.h" #define FST_MGR_COMPONENT "TPOLL" #include "fst_manager.h" #include "fst_tpoll.h" #include "fst_cfgmgr.h" #include "fst_rateupg.h" #include "fst_capconfigstore.h" #define FST_TPOLL_MIN_SAMPLES 5 #define Mbps2Bpms(t) (t * 1000 * 1000 / 1000 / 8) #define OS_RELTIME2MS(t) (t.sec * 1000 + t.usec / 1000) static const char FST_TPOLL_LOW_THRESH_KEY_NAME[] = "fst.tpoll.low.thresh.mbps"; #define FST_TPOLL_LOW_TRAFFIC_THRESH "10" static const char FST_TPOLL_HIGH_THRESH_KEY_NAME[] = "fst.tpoll.high.thresh.mbps"; #define FST_TPOLL_HIGH_TRAFFIC_THRESH "40" enum fst_tpoll_state { FST_TPOLL_STATE_DETECTED_NONE, FST_TPOLL_STATE_DETECTED_HIGH, FST_TPOLL_STATE_DETECTED_LOW, }; struct fst_tpoll { const struct fst_group_info *group; char mux_ifname[IFNAMSIZ + 1]; int interval_ms; int alpha; int low_thresh_Bpms; int high_thresh_Bpms; enum fst_tpoll_state state; struct os_reltime prev_sample_time; unsigned long long prev_total_bytes; int prev_avg; /* bytes/ms */ size_t num_samples; }; static struct fst_tpoll g_fst_tpoll; static int fst_tpoll_read_bytes(unsigned long long *total_bytes) { char fname[128]; FILE *f; unsigned long long rx_bytes, tx_bytes; int res = -1; if (snprintf(fname, sizeof(fname), "/sys/class/net/%s/statistics/rx_bytes", g_fst_tpoll.mux_ifname) < 0) return -1; f = fopen(fname, "r"); if (!f) { fst_mgr_printf(MSG_ERROR, "failed to open %s", fname); return -1; } if (fscanf(f, "%llu", &rx_bytes) != 1) { fst_mgr_printf(MSG_ERROR, "failed to read %s", fname); goto out; } fclose(f); if (snprintf(fname, sizeof(fname), "/sys/class/net/%s/statistics/tx_bytes", g_fst_tpoll.mux_ifname) < 0) return -1; f = fopen(fname, "r"); if (!f) { fst_mgr_printf(MSG_ERROR, "failed to open %s", fname); return -1; } if (fscanf(f, "%llu", &tx_bytes) != 1) { fst_mgr_printf(MSG_ERROR, "failed to read %s", fname); goto out; } *total_bytes = rx_bytes + tx_bytes; res = 0; out: fclose(f); return res; } static void fst_tpoll_timer(void *eloop_ctx, void *timeout_ctx) { unsigned long long total_bytes; int res, tput, new_avg = g_fst_tpoll.prev_avg; Boolean detected_high = FALSE, detected_low = FALSE; res = fst_tpoll_read_bytes(&total_bytes); if (res) goto out; if (total_bytes < g_fst_tpoll.prev_total_bytes) { fst_mgr_printf(MSG_WARNING, "invalid sample, total_bytes (%llu) < prev_total_bytes (%llu)", total_bytes, g_fst_tpoll.prev_total_bytes); goto out; } if (g_fst_tpoll.num_samples > 0) { struct os_reltime age; os_reltime_age(&g_fst_tpoll.prev_sample_time, &age); /* bytes/ms */ tput = (total_bytes - g_fst_tpoll.prev_total_bytes) / OS_RELTIME2MS(age); } else { /* in 1st sample, "prev" fields are uninitialized */ tput = 0; } /* Exponential Filtering */ new_avg = (g_fst_tpoll.alpha * tput + (100 - g_fst_tpoll.alpha) * g_fst_tpoll.prev_avg) / 100; if (++g_fst_tpoll.num_samples < FST_TPOLL_MIN_SAMPLES) { /* not enough samples to make decisions */ goto out; } switch (g_fst_tpoll.state) { case FST_TPOLL_STATE_DETECTED_NONE: if (new_avg > g_fst_tpoll.high_thresh_Bpms) detected_high = TRUE; else if (new_avg < g_fst_tpoll.low_thresh_Bpms) detected_low = TRUE; break; case FST_TPOLL_STATE_DETECTED_HIGH: if (new_avg < g_fst_tpoll.low_thresh_Bpms) detected_low = TRUE; break; case FST_TPOLL_STATE_DETECTED_LOW: if (new_avg > g_fst_tpoll.high_thresh_Bpms) detected_high = TRUE; break; } if (detected_high) { fst_mgr_printf(MSG_INFO, "detected high traffic, bytes %llu => %llu, tput %d, avg %d => %d", g_fst_tpoll.prev_total_bytes, total_bytes, tput * 8 / 1000, g_fst_tpoll.prev_avg * 8 / 1000, new_avg * 8 / 1000); fst_rate_upgrade_high_traffic(g_fst_tpoll.group); g_fst_tpoll.state = FST_TPOLL_STATE_DETECTED_HIGH; } else if (detected_low) { fst_mgr_printf(MSG_INFO, "detected low traffic, bytes %llu => %llu, tput %d, avg %d => %d", g_fst_tpoll.prev_total_bytes, total_bytes, tput * 8 / 1000, g_fst_tpoll.prev_avg * 8 / 1000, new_avg * 8 / 1000); fst_rate_upgrade_low_traffic(g_fst_tpoll.group); g_fst_tpoll.state = FST_TPOLL_STATE_DETECTED_LOW; } out: os_get_reltime(&g_fst_tpoll.prev_sample_time); g_fst_tpoll.prev_total_bytes = total_bytes; g_fst_tpoll.prev_avg = new_avg; eloop_register_timeout(0, g_fst_tpoll.interval_ms * 1000, fst_tpoll_timer, NULL, NULL); } int fst_tpoll_start(const struct fst_group_info *group) { int len; if (g_fst_tpoll.group != NULL) { fst_mgr_printf(MSG_ERROR, "tpoll already started for group %s", g_fst_tpoll.group->id); return -1; } len = fst_cfgmgr_get_mux_ifname(group->id, g_fst_tpoll.mux_ifname, sizeof(g_fst_tpoll.mux_ifname)-1); if (len == 0) { fst_mgr_printf(MSG_ERROR, "Cannot get mux ifname"); return -1; } fst_mgr_printf(MSG_INFO, "starting"); g_fst_tpoll.group = group; g_fst_tpoll.num_samples = 0; g_fst_tpoll.state = FST_TPOLL_STATE_DETECTED_NONE; g_fst_tpoll.prev_sample_time.sec = g_fst_tpoll.prev_sample_time.usec = 0; g_fst_tpoll.prev_total_bytes = 0; g_fst_tpoll.prev_avg = 0; eloop_register_timeout(0, 0, fst_tpoll_timer, NULL, NULL); return 0; } void fst_tpoll_stop() { fst_mgr_printf(MSG_INFO, "stopping"); g_fst_tpoll.group = NULL; eloop_cancel_timeout(fst_tpoll_timer, NULL, NULL); } void fst_tpoll_pause() { if (g_fst_tpoll.group == NULL) return; fst_mgr_printf(MSG_INFO, "pausing"); eloop_cancel_timeout(fst_tpoll_timer, NULL, NULL); } void fst_tpoll_resume() { if (g_fst_tpoll.group == NULL) return; fst_mgr_printf(MSG_INFO, "resuming"); g_fst_tpoll.num_samples = 0; g_fst_tpoll.state = FST_TPOLL_STATE_DETECTED_NONE; g_fst_tpoll.prev_sample_time.sec = g_fst_tpoll.prev_sample_time.usec = 0; g_fst_tpoll.prev_total_bytes = 0; g_fst_tpoll.prev_avg = 0; eloop_register_timeout(0, 0, fst_tpoll_timer, NULL, NULL); } int fst_tpoll_init(int interval, int alpha) { char buf[64] = { '\0' }; os_memset(&g_fst_tpoll, 0, sizeof(g_fst_tpoll)); g_fst_tpoll.interval_ms = interval; if (g_fst_tpoll.interval_ms <= 0) return -1; g_fst_tpoll.alpha = alpha; if (g_fst_tpoll.alpha <= 0 || g_fst_tpoll.alpha > 100) return -1; fst_get_config_string(FST_TPOLL_LOW_THRESH_KEY_NAME, FST_TPOLL_LOW_TRAFFIC_THRESH, buf, sizeof(buf)); g_fst_tpoll.low_thresh_Bpms = Mbps2Bpms(strtoul(buf, NULL, 0)); if (g_fst_tpoll.low_thresh_Bpms <= 0) return -1; fst_get_config_string(FST_TPOLL_HIGH_THRESH_KEY_NAME, FST_TPOLL_HIGH_TRAFFIC_THRESH, buf, sizeof(buf)); g_fst_tpoll.high_thresh_Bpms = Mbps2Bpms(strtoul(buf, NULL, 0)); if (g_fst_tpoll.high_thresh_Bpms <= g_fst_tpoll.low_thresh_Bpms) return -1; fst_mgr_printf(MSG_INFO, "interval %d, alpha %d, thresh (Bpms) low %d high %d", g_fst_tpoll.interval_ms, g_fst_tpoll.alpha, g_fst_tpoll.low_thresh_Bpms, g_fst_tpoll.high_thresh_Bpms); return 0; } void fst_tpoll_deinit() { eloop_cancel_timeout(fst_tpoll_timer, NULL, NULL); }