Files
android_kernel_samsung_sm87…/qcom/opensource/fst-manager/fst_tpoll.c
2025-08-12 22:13:00 +02:00

294 lines
8.4 KiB
C

/*
* 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);
}