CAKE w/ Adaptive Bandwidth [October 2021 to September 2022]

My rationale is as I explained above to give a reasonably simple example that can be packaged with sqm somehow, not a fully tricked out 100% solution (I also envision a big well-advertised link to the more complete lua implementation developed here). I consider this to be orthogonal to what is developed here :wink: (but still implementing the ideas refined in this thread)
Rate-finding wise, I am quite happy with a simple reactive control loop for my example code. IMHO the example only needs to give 80% and be good enough to be useful, allowing to keep things simpler.

But no matter what I will not have more time to actually work on that any time soon so I will heed the "hold off" advise by default :wink:

You guys are awesome thanks so much for all of the work!!! Love this very much lol...
This is the best I can get but lots better than it was. I have Spectrum Business internet docsis 3.1 with 600dl and 35 up. Consistent results on 5g even on congested network...

#!/bin/sh

# automatically adjust bandwidth for CAKE in dependence on detected load and RTT

# inspired by @moeller0 (OpenWrt forum)
# initial sh implementation by @Lynx (OpenWrt forum)
# requires packages: iputils-ping, coreutils-date and coreutils-sleep

debug=1

enable_verbose_output=1 # enable (1) or disable (0) output monitoring lines showing bandwidth changes

ul_if=wan # upload interface
dl_if=ifb4wan # download interface

base_ul_rate=35000 # steady state bandwidth for upload

base_dl_rate=40000 # steady state bandwidth for download

tick_duration=0.5 # seconds to wait between ticks

alpha_RTT_increase=0.001 # how rapidly baseline RTT is allowed to increase
alpha_RTT_decrease=0.9 # how rapidly baseline RTT is allowed to decrease

rate_adjust_RTT_spike=0.01 # how rapidly to reduce bandwidth upon detection of bufferbloat
rate_adjust_load_high=0.005 # how rapidly to increase bandwidth upon high load detected
rate_adjust_load_low=0.0025 # how rapidly to return to base rate upon low load detected

load_thresh=0.5 # % of currently set bandwidth for detecting high load

max_delta_RTT=16 # increase from baseline RTT for detection of bufferbloat

# verify these are correct using 'cat /sys/class/...'
case "${dl_if}" in
    \veth*)
        rx_bytes_path="/sys/class/net/${dl_if}/statistics/tx_bytes"
        ;;
    \ifb*)
        rx_bytes_path="/sys/class/net/${dl_if}/statistics/tx_bytes"
        ;;
    *)
        rx_bytes_path="/sys/class/net/${dl_if}/statistics/rx_bytes"
        ;;
esac

case "${ul_if}" in
    \veth*)
        tx_bytes_path="/sys/class/net/${ul_if}/statistics/rx_bytes"
        ;;
    \ifb*)
        tx_bytes_path="/sys/class/net/${ul_if}/statistics/rx_bytes"
        ;;
    *)
        tx_bytes_path="/sys/class/net/${ul_if}/statistics/tx_bytes"
        ;;
esac

if [ "$debug" ] ; then
    echo "rx_bytes_path: $rx_bytes_path"
    echo "tx_bytes_path: $tx_bytes_path"
fi


# list of reflectors to use
read -d '' reflectors << EOF
1.1.1.1
8.8.8.8
EOF

RTTs=$(mktemp)

# get minimum RTT across entire set of reflectors
get_RTT() {

for reflector in $reflectors;
do
        echo $(/usr/bin/ping -i 0.00 -c 10 $reflector | tail -1 | awk '{print $4}' | cut -d '/' -f 2) >> $RTTs&
done
wait
RTT=$(echo $(cat $RTTs) | awk 'min=="" || $1 < min {min=$1} END {print min}')
> $RTTs
}


call_awk() {
  printf '%s' "$(awk 'BEGIN {print '"${1}"'}')"
}

get_next_shaper_rate() {
    local cur_delta_RTT
    local cur_max_delta_RTT
    local cur_rate
    local cur_base_rate
    local cur_load
    local cur_load_thresh
    local cur_rate_adjust_RTT_spike
    local cur_rate_adjust_load_high
    local cur_rate_adjust_load_low

    local next_rate
    local cur_rate_decayed_down
    local cur_rate_decayed_up

    cur_delta_RTT=$1
    cur_max_delta_RTT=$2
    cur_rate=$3
    cur_base_rate=$4
    cur_load=$5
    cur_load_thresh=$6
    cur_rate_adjust_RTT_spike=$7
    cur_rate_adjust_load_high=$8
    cur_rate_adjust_load_low=$9

        # in case of supra-threshold RTT spikes decrease the rate so long as there is a load
        if awk "BEGIN {exit !(($cur_delta_RTT >= $cur_max_delta_RTT))}"; then
            next_rate=$( call_awk "int( ${cur_rate}*(1-${cur_rate_adjust_RTT_spike}) )" )
        else
            # ... otherwise determine whether to increase or decrease the rate in dependence on load
            # high load, so we would like to increase the rate
            if awk "BEGIN {exit !($cur_load >= $cur_load_thresh)}"; then
                next_rate=$( call_awk "int( ${cur_rate}*(1+${cur_rate_adjust_load_high}) )" )
            else
                # low load, so determine whether to decay down towards base rate, decay up towards base rate, or set as base rate
                cur_rate_decayed_down=$( call_awk "int( ${cur_rate}*(1-${cur_rate_adjust_load_low}) )" )
                cur_rate_decayed_up=$( call_awk "int( ${cur_rate}*(1+${cur_rate_adjust_load_low}) )" )

                # gently decrease to steady state rate
                if awk "BEGIN {exit !($cur_rate_decayed_down > $cur_base_rate)}"; then
                        next_rate=$cur_rate_decayed_down
                # gently increase to steady state rate
                elif awk "BEGIN {exit !($cur_rate_decayed_up < $cur_base_rate)}"; then
                        next_rate=$cur_rate_decayed_up
                # steady state has been reached
                else
                        next_rate=$cur_base_rate
        fi
        fi
        fi

        echo "${next_rate}"
}


# update download and upload rates for CAKE
function update_rates {
        cur_rx_bytes=$(cat $rx_bytes_path)
        cur_tx_bytes=$(cat $tx_bytes_path)
        t_cur_bytes=$(date +%s.%N)

        rx_load=$( call_awk "(8/1000)*(${cur_rx_bytes} - ${prev_rx_bytes}) / (${t_cur_bytes} - ${t_prev_bytes}) * (1/${cur_dl_rate}) " )
        tx_load=$( call_awk "(8/1000)*(${cur_tx_bytes} - ${prev_tx_bytes}) / (${t_cur_bytes} - ${t_prev_bytes}) * (1/${cur_ul_rate}) " )

        t_prev_bytes=$t_cur_bytes
        prev_rx_bytes=$cur_rx_bytes
        prev_tx_bytes=$cur_tx_bytes

        # calculate the next rate for dl and ul
        cur_dl_rate=$( get_next_shaper_rate "$delta_RTT" "$max_delta_RTT" "$cur_dl_rate" "$base_dl_rate" "$rx_load" "$load_thresh" "$rate_adjust_RTT_spike" "$rate_adjust_load_high" "$rate_adjust_load_low")
        cur_ul_rate=$( get_next_shaper_rate "$delta_RTT" "$max_delta_RTT" "$cur_ul_rate" "$base_ul_rate" "$tx_load" "$load_thresh" "$rate_adjust_RTT_spike" "$rate_adjust_load_high" "$rate_adjust_load_low")

        if [ $enable_verbose_output -eq 1 ]; then
                printf "%s;%14.2f;%14.2f;%14.2f;%14.2f;%14.2f;%14.2f;%14.2f;\n" $( date "+%Y%m%dT%H%M%S.%N" ) $rx_load $tx_load $baseline_RTT $RTT $delta_RTT $cur_dl_rate $cur_ul_rate
        fi
}

get_baseline_RTT() {
    local cur_RTT
    local cur_delta_RTT
    local last_baseline_RTT
    local cur_alpha_RTT_increase
    local cur_alpha_RTT_decrease

    local cur_baseline_RTT

    cur_RTT=$1
    cur_delta_RTT=$2
    last_baseline_RTT=$3
    cur_alpha_RTT_increase=$4
    cur_alpha_RTT_decrease=$5
        if awk "BEGIN {exit !($cur_delta_RTT >= 0)}"; then
                cur_baseline_RTT=$( call_awk "( 1 - ${cur_alpha_RTT_increase} ) * ${last_baseline_RTT} + ${cur_alpha_RTT_increase} * ${cur_RTT} " )
        else
                cur_baseline_RTT=$( call_awk "( 1 - ${cur_alpha_RTT_decrease} ) * ${last_baseline_RTT} + ${cur_alpha_RTT_decrease} * ${cur_RTT} " )
        fi

    echo "${cur_baseline_RTT}"
}



# set initial values for first run

get_RTT

baseline_RTT=$RTT;

cur_dl_rate=$base_dl_rate
cur_ul_rate=$base_ul_rate
# set the next different from the cur_XX_rates so that on the first round we are guaranteed to call tc
last_dl_rate=0
last_ul_rate=0


t_prev_bytes=$(date +%s.%N)

prev_rx_bytes=$(cat $rx_bytes_path)
prev_tx_bytes=$(cat $tx_bytes_path)

if [ $enable_verbose_output -eq 1 ]; then
        printf "%25s;%14s;%14s;%14s;%14s;%14s;%14s;%14s;\n" "log_time" "rx_load" "tx_load" "baseline_RTT" "RTT" "delta_RTT" "cur_dl_rate" "cur_ul_rate"
fi

# main loop runs every tick_duration seconds
while true
do
        t_start=$(date +%s.%N)
        get_RTT
        delta_RTT=$( call_awk "${RTT} - ${baseline_RTT}" )
        baseline_RTT=$( get_baseline_RTT "$RTT" "$delta_RTT" "$baseline_RTT" "$alpha_RTT_increase" "$alpha_RTT_decrease" )
        update_rates

        # only fire up tc if there are rates to change...
        if [ "$last_dl_rate" -ne "$cur_dl_rate" ] ; then
            #echo "tc qdisc change root dev ${dl_if} cake bandwidth ${cur_dl_rate}Kbit"
            tc qdisc change root dev ${dl_if} cake bandwidth ${cur_dl_rate}Kbit
        fi
        if [ "$last_ul_rate" -ne "$cur_ul_rate" ] ; then
            #echo "tc qdisc change root dev ${ul_if} cake bandwidth ${cur_ul_rate}Kbit"
            tc qdisc change root dev ${ul_if} cake bandwidth ${cur_ul_rate}Kbit
        fi
        # remember the last rates
        last_dl_rate=$cur_dl_rate
        last_ul_rate=$cur_ul_rate

        t_end=$(date +%s.%N)
        sleep_duration=$( call_awk "${tick_duration} - ${t_end} + ${t_start}" )
        if awk "BEGIN {exit !($sleep_duration > 0)}"; then
                sleep $sleep_duration
        fi
done
3 Likes

Nice! Would you be willing to run this same test using the experimental branch of the code?

It would help to have another real-world comparison between the two (shell vs Lua). Thanks!

Ooops, forgot to mention… the experimental code does take a little while to “stabilize” and learn your “safe” speeds. So if you get less than ideal tests right away, test it again after maybe 30-60 minutes and see how they look.

It’s still a WIP, but getting real feedback is hugely helpful.

3 Likes

DRAFT Autorate Testing Regime

This DRAFT is for brave souls only. It contains my understanding of what we are asking people to do now. I'm still makin' stuff up, so comments are appreciated.

The various implementations are being developed and changed as we speak. We don't promise that any of these control latency, or even that they function at all. They are certainly not nicely packaged, and some assembly is definitely required. If we haven't scared you away...

  1. Pick a candidate implementation and install it:

  2. Run and record results of any of these tests:

  3. Is this list close to useful? What else should we add?

  4. Did we mention that this is a DRAFT? Please send me comments and I'll update. After we have something more suitable/stable, I expect we'll choose a method to publish our experimental results. Thanks.

Changes:

  • 29Dec2021 - point at lua-threads version since it's now good enough to test
4 Likes

This applies to both the experimental branch and dansbranch, LuaJIT or not, both branches are using the same algorithm, they difference is now that experimental is based on coroutines and dansbranch is now using threads

Otherwise I think that's a good starting point yes : )

1 Like

IMHO, we need to take “dansbranch” out of the mix. It was never intended to be a “stable” test branch for general usage.

We aren’t ready for general multi-threaded testing. I was really only looking for a quick comparison of the algorithms from @Reeves0724 between shell and “experimental” and didn’t mean to open the doors for large scale testing.

@dlakelan and @Lochnair can certainly weigh in here if they have differing opinions.

1 Like

Sorry at work now my friend will post all the results tonight around 6 p.m. Chicago time... I tested I think all of them that work this Branch gave me the best results... the l u a says jit is missing wish I would have posted that screenshot I will tonight maybe you guys would assist me as I'm just a noob! Also forgive my grammar This is speech to text on the road

1 Like

No it's still a bit early I agree. Either way I think we should clean up the naming of our branches, so it's more clear which of them are at a point that it's worth trying out at all (like the experimental branch).

The branches we're actively working on are in a constant state of flux, so those tend to break a lot. I'd recommend staying away from those if you don't want to get your hands dirty.

3 Likes

Agreed, it's supposed to be where I tweak rate finding without worrying about breaking anyone's anything :-). We are close to having multithreading testable but I think when that's ready we can branch an testable-mt branch.

For one thing the install script for multithreading will need to pull in additional libraries.

3 Likes

Thanks for the comments. I updated the "DRAFT Autorate Testing Regime"

I'm still just "makin' stuff up", so please send me comments where I've missed the mark. Thanks again.

3 Likes

I am sorry this all looks interesting and I want to give it a shot. But I'm having a slight error while script is running.

ul_if=pppoe-wan # upload interface
dl_if=br-lan # download interface

My wan connection is pppoe and Is this what supposed to be? Options are br-lan, eth0, eth1, ifb4eth0, ifb4pppoe-wan, lo, pppoe-wan, teql0, wlan0 in /sys/class/net
I'm getting error:

root@OpenWrt:~# ./autorate.sh
rx_bytes_path: /sys/class/net/br-lan/statistics/rx_bytes
tx_bytes_path: /sys/class/net/pppoe-wan/statistics/tx_bytes
ping: ping: socketsocket: Address family not supported by protocol
: Address family not supported by protocol
                 log_time;       rx_load;       tx_load;  baseline_RTT;           RTT;     delta_RTT;   cur_dl_rate;   cur_ul_rate;
ping: ping: socketsocket: Address family not supported by protocol
: Address family not supported by protocol
20211229T001038.552218451;          0.00;          0.01;          1.94;          1.94;         -0.01;      11500.00;      11500.00;
RTNETLINK answers: No such file or directory
ping: ping: socketsocket: Address family not supported by protocol
: Address family not supported by protocol
20211229T001039.046409316;          0.00;          0.00;          1.94;          2.00;          0.06;      11500.00;      11500.00;
ping: ping: socketsocket: Address family not supported by protocol
: Address family not supported by protocol
20211229T001039.553800463;          0.00;          0.00;          1.94;          2.01;          0.07;      11500.00;      11500.00;

Three mentioned packages are installed. Didn't add ipv6 in my firmware since my ISP supports only ipv4. So what's this error "ping: socketsocket: Address family not supported by protocol"?

Could you confirm which version you are using?

Likely ifb4-pppoe-wan is the dl_if you want

This is imho a mistake. Ipv6 is here now and needs to be a thing people get used to. The future of openwrt imho is that the easiest and most supported administration method will be ipv6 ULA.

In any case in your specific case I believe In the lua version that we are listening for replies on :: which is an ipv6 address that represents all addresses BOTH ipv6 and ipv4. So I suspect that's causing your problem. Thanks for the report!

2 Likes

@richb-hanover-priv, I should mention that the branches are now cleaned up courtesy of @_FailSafe, so the experimental branch should now be replaced with testing/lua-threads

It's not quite ready yet, but @_FailSafe is hard at work getting it to a testable state.


I just updated & compiled the master version

Here we go!

Thanks to some quick development work by @Lochnair and @dlakelan over the past few days, the "official" Lua test branch (w/preemptive threading) is ready now. It is the testing/lua-threads branch and here is the README:

There are wget based installation instructions for those who want to try it out without pulling the project repo. It can be installed via:
sh -c "$(wget -q -O- https://raw.githubusercontent.com/Fail-Safe/sqm-autorate/testing/lua-threads/sqm-autorate-setup.sh)"

If you do choose to pull the project repo, you can still run the sqm-autorate-setup.sh file within the project and it will install the necessary pieces from the local files. This is useful for testing and tuning purposes.

Within the README are instructions for use, as well as descriptions for the available options and their purposes. Many thanks to @CharlesJC for his work to integrate a native OpenWrt config into this project.

Please refer to this evolving post by @richb-hanover-priv for testing expectations:

When issues are discovered, please open a bug report in the repo here: https://github.com/Fail-Safe/sqm-autorate/issues/new/choose

Feature/enhancement requests can be submitted via the same URL.

For anyone browsing my repo, please know that the develop/... branches are in no way expected to be stable. Please do not use them if you are not willing to help fix the issues you will uncover. Do NOT open bug reports for develop/... branch code.

The testing/... branches come with a reasonable expectation that obvious runtime bugs have already been squashed. Issues uncovered within testing/... branch code are fair game for bug reports.

Thanks to all who continue to contribute to the discussion and progressions here!

2 Likes

Thanks to @_FailSafe for the final push and cleaning up the repo to make it usable by others. Please do read the readme which has some caveats and expectations. Once this seems to run for people I do think it's fair game for people to fork the repo and try out different ratecontrol functions/algorithms.

3 Likes

If people are getting more speed on few sites like google drive, youtube etc. will the script manage individual ingress bandwidth while maintaining bufferbloat to minimum?

The script has a single speed and will raise the speed so long as buffer bloat remains low, but it won't give you "multiple lanes"

1 Like

I got this error then i tried to start script.

root@RaspberryCM4:~# lua /usr/lib/sqm-autorate/sqm-autorate.lua
lua: error loading module 'lanes.core' from file '/usr/lib/lua/lanes/core.so':
        Error relocating /usr/lib/lua/lanes/core.so: pthread_yield: symbol not found
stack traceback:
        [C]: ?
        [C]: in function 'require'
        /usr/lib/lua/lanes.lua:38: in main chunk
        [C]: in function 'require'
        /usr/lib/sqm-autorate/sqm-autorate.lua:9: in main chunk
        [C]: ?
root@RaspberryCM4:~#