Netspeed in Waybar 🤩

Has anyone already created a netspeed display (download/upload speed Kbit/s, Mbit/s) for the waybar?

First for Sway would be enough for me. :grin:

Sorry if the terminology is wrong. :smiley:

I move it from Garuda Community to FAQ and Tutorials .

I use the tooltip,
2023-02-08T01:35:38,654537456+01:00
but I think you want a custom module with the numbers in the bar, right?
Not promising anything but I'll look into it.

3 Likes

Yes :slight_smile:


From i3wm.

That is exactly my motto as well. :slight_smile:

1 Like

Quick and dirty (v2):

~/.config/waybar/scripts/network_traffic.sh
#!/bin/bash

# network_traffic.sh NETWORK_INTERFACE [POLLING_INTERVAL]

iface=${1:-lo}
isecs=${2:-1}

# `snore` adapted from https://blog.dhampir.no/content/sleeping-without-a-subprocess-in-bash-and-how-to-sleep-forever
# without MacOS workaround, TODO: with _snore_fd initialized separatedly, also i dont touch IFS so dont bother with it
snore() {
    local IFS
    [[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null
    read ${1:+-t "$1"} -u $_snore_fd || :
}

# `human_readable` adapted from https://gist.github.com/cjsewell/96463db7fec6faeab291
human_readable() {
  local value=$1
  local units=( B K M G T P )
  local index=0
  while (( value > 1000 && index < 5 )); do
        (( value /= 1000, index++ ))
  done
  echo "$value${units[$index]}"
}

# sanity checking, timing here is not an issue anymore -- TODO: check how waybar reacts to `exit 1`
test -n "${iface}" && grep -q "${iface}:" /proc/net/dev || { printf '{"text": "%s"}\n' "${iface} not found"; exit 1; }
test "$isecs" -gt 0                                     || { printf '{"text": "%s"}\n' "${isecs} not valid"; exit 1; }

# NOTE: `/proc/net/dev` format is:
#       RX  bytes packets errs drop fifo  frame compressed multicast
#       TX  bytes packets errs drop fifo  colls carrier compressed

declare -a traffic_prev traffic_curr traffic_delta
# NOTE: array items are:
# 0=rx_bytes 1=rx_packets 2=rx_errs 3=rx_drop
# 4=tx_bytes 5=tx_packets 6=tx_errs 7=tx_drop

# TODO: rearrange the loop, do not show bogus on first iteration
traffic_prev=( 0 0 0 0  0 0 0 0 )
while snore ${isecs} ;do
  traffic_curr=( $(awk '/^ *'${iface}':/{print $2 " " $3 " " $4 " " $5 " " $10 " " $11 " " $12 " " $13}' /proc/net/dev) )
  for i in {0..7}; do
    (( traffic_delta[i] = ( traffic_curr[i] - traffic_prev[i] ) / isecs ))
  done
  traffic_prev=(${traffic_curr[@]})
  printf '{"text": "%5s⇣ %5s⇡"}\n' $(human_readable ${traffic_delta[0]}) $(human_readable ${traffic_delta[4]})
  #printf '{"text": "%5s⇣ %5s⇡", "alt": "%s", "tooltip": "%s", "class": "%s", "percentage": %d }\n' $(human_readable ${traffic_delta[0]}) $(human_readable ${traffic_delta[4]}) '_alt' '_tooltip' '_class' 0
done

# TODO: handle errors
# TODO: aggregate interfaces (default to all from `ls /sys/class/net | grep -E '^(eth|wlan|enp|wlp)'`)
# TODO: tooltip with details per each interface
# TODO: colors (?)
# TODO: styling (in waybar .css, using {percent})
# TODO: unicode meter (" ","▁","▁","▂","▃","▄","▅","▆","▇","█")
# TODO: split rx/tx (?)
# TODO: test and optimize

# NOTE: in waybar config (do NOT use "interval"):
#         "custom/network_traffic": {
#             "exec": "~/.config/waybar/scripts/network_traffic.sh enp14s0",
#         },

image

Updates may or may not come but let me know of bugs.
Maybe it's better parsing ip -s -c link show? Let me know.

Credits: adapted from traffic.sh · GitHub


edit: replaced script with better version, no more bogus results nor problems with more than one.
future updates when I post to gitlab the whole dots (a few weeks if nothing gets in the way)
@SGS

4 Likes

Thank you so much :slight_smile:

Now it could, please, @OdiousImp implemented for the general public in Sway :slight_smile:

2 Likes

Note that, just like the pacman one and any custom waybar script, if you have the bar on more than one monitor the two scripts stomp onto each other and give outdated (or, in this case) bogus results.
I worked around that in my pacman script but I'm not sure we want subshells talking to each other for something that refresh so often.
I hope to find time coming week to clean it up a bit before I share.


And the first bug is... sometimes it spits a negative number. crap.


Curiosity: does anyone use eww?

1 Like

That would fit well with my ISP :smiley: there it also jams more often. :rofl:

I move this posts to Sway :slight_smile:

2 Likes

This is nice, how did you set this up in ~/.config/waybar/config? Is it a one-liner or does it source an external script from somewhere?

1 Like

A few alternatives from my dots:

        "format-wifi": "{icon} {essid}", // takes too much space and i dont want it in the screenshots
        "format-wifi": "{icon} {ifname}",
        //"format-alt": "{icon} {ifname}: {ipaddr}/{cidr}", // overridden by on-click below (click also toggles format/format-alt)
        "tooltip-format": "{ifname}\t{ipaddr}/{cidr}\ngateway\t{gwaddr}",
        "tooltip-format": "{ifname}\t{ipaddr}/{cidr}\ngateway\t{gwaddr}\n\t{essid}\n{icon}  ⇣{bandwidthDownBytes}  ⇡{bandwidthUpBytes}",
4 Likes

Work fine, interval I set to 1, correct?

"interval": 1,
 "exec": "~/.config/waybar/scripts/network_traffic.sh wlp1s0 1",

Fine tuning :slight_smile:
From i3 netspeed.sh, I add for my hardware wlp.

# Auto detect interfaces
ifaces=$(ls /sys/class/net | grep -E '^(eth|wlan|enp|wlp)')

@meanruse
Maybe you would like to become active here? :slight_smile:

3 Likes

Shorter interval, more responsive but even less precise.
I'll try adding some averaging after I fix current issues, it's giving nonsense readings too often.
Next on the to-do list, combined traffic from all the interfaces and details in the tooltip (thanks for the hint about that).
Yep of course I'll share my stuff at some point, I want to clean it up first.


Reading Module: Custom · Alexays/Waybar Wiki · GitHub with more attention, I suspect I missed something and the "two scripts out of sync" may be entirely my fault... wouldn't be the first time I jump into some over-engineered solution to a non problem. Time to make coffee and have some fun!


OK, not entirely but for the most part. pacman next.
Turns out when I tried to use a "continuous script" last year I either done something wrong or hit a bug and gave up.

2 Likes

This has turned out well. :+1: :star_struck:
I do not like the { }"" :slight_smile: :wink: in the waybar, so ...

printf 'Speed:%5s⇣ %5s⇡\n' $(human_readable ${traffic_delta[0]}) $(human_readable ${traffic_delta[4]})

2023-02-09T15:03:45,055163180+01:00

Please, just post your fixes in a new post. :slight_smile:

3 Likes

Darn, looks like I forgot to mention"return-type": "json" in the waybar config.

New version, shows aggregate totals for multiple interfaces.
Default interval: 1 second, optional -tN for N seconds.
Default interfaces: all except lo, optional list as positional arguments.
Tooltip commented out because weird bug. Other features still under construction.

Summary
#!/bin/bash

# network_traffic.sh [-tPOLLING_INTERVAL] [NETWORK_INTERFACE...]

getopts t: __ && shift
isecs=${OPTARG:-1}
ifaces=($@)
: ${rate_max:=1000000} # maximum transfer rate for {percent}, can be overridden setting the env var

# `snore` adapted from https://blog.dhampir.no/content/sleeping-without-a-subprocess-in-bash-and-how-to-sleep-forever
# without MacOS workaround, TODO: with _snore_fd initialized separatedly, also i dont touch IFS so dont bother with it
snore() {
    local IFS
    [[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null
    read ${1:+-t "$1"} -u $_snore_fd || :
}

# `human_readable` adapted from https://gist.github.com/cjsewell/96463db7fec6faeab291
human_readable() {
  local value=$1
  local units=( B K M G T P )
  local index=0
  while (( value > 1000 && index < 5 )); do
        (( value /= 1000, index++ ))
  done
  echo "$value${units[$index]}"
}

exit_err() {
  printf '{"text": "⚠ %s", "tooltip": "%s", "class": "error"}\n' "$@"
  exit
}

if test ${#ifaces[@]} -gt 0; then
# sanity check the interface names
  for iface in ${ifaces[@]}; do
    test -h "/sys/class/net/${iface}" || exit_err "${iface}" "${iface} is not an existing network interface name"
  done
else
# default to all interfaces except `lo`
  ifaces=(/sys/class/net/*)
  ifaces=(${ifaces[@]##*/})
  ifaces=(${ifaces[@]//lo/})
# TODO: check that filtering out `lo` is enough, else `^(eth|wlan|enp|wlp)` as suggested
fi

# sanity check polling interval
if test ${isecs} -lt 1; then
  exit_err "${isecs}" "${isecs} is not a valid polling interval"
fi
# NOTE: `snore` would take a decimal interval but bash arithmetic does not
#if test $(echo "${isecs} >= 0.2" |bc) -eq 0; then
#  # TODO: set the class and tooltip
#  exit_err "${isecs}" "${isecs} is not a valid polling interval"
#fi

# NOTE: `/proc/net/dev` format is:
#       RX  bytes packets errs drop fifo  frame compressed multicast
#       TX  bytes packets errs drop fifo  colls carrier compressed

# NOTE: array items are:
# 0=rx_bytes 1=rx_packets 2=rx_errs 3=rx_drop
# 4=tx_bytes 5=tx_packets 6=tx_errs 7=tx_drop
for iface in ${ifaces[@]} aggregate; do
  declare -a traffic_prev_${iface} traffic_curr_${iface} traffic_delt_${iface}
  declare -n traffic_prev=traffic_prev_${iface}
  traffic_prev=( 0 0 0 0  0 0 0 0 )
done

# TODO: rearrange the loop, do not show bogus on first iteration
while snore ${isecs} ;do
  tooltip=""
  traffic_delt_aggregate=( 0 0 0 0  0 0 0 0 )
  for iface in ${ifaces[@]}; do
    declare -n traffic_prev=traffic_prev_${iface}
    declare -n traffic_curr=traffic_curr_${iface}
    declare -n traffic_delt=traffic_delt_${iface}
    traffic_curr=( $(awk '/^ *'${iface}':/{print $2 " " $3 " " $4 " " $5 " " $10 " " $11 " " $12 " " $13}' /proc/net/dev) )
    #FIXME: delayed one iteration wrt main display
    #printf -v tooltip_hr_rx '%4s⇣' $(human_readable ${traffic_delt[0]})
    #printf -v tooltip_hr_tx '%4s⇡' $(human_readable ${traffic_delt[4]})
    #tooltip="${tooltip:+${tooltip}\r}${iface}\t${tooltip_hr_rx}\t${tooltip_hr_tx}"
    for i in {0..7}; do
      (( traffic_delt[i] = ( traffic_curr[i] - traffic_prev[i] ) / isecs ))
      (( traffic_delt_aggregate[i] += traffic_delt[i] ))
    done
    traffic_prev=(${traffic_curr[@]})
  done
  printf '{"text": "%4s⇣ %4s⇡", "tooltip": "%s",  "percent": %d}\n'   \
    $(human_readable ${traffic_delt_aggregate[0]})  \
    $(human_readable ${traffic_delt_aggregate[4]})  \
    "${tooltip}"                                    \
    $(( ( traffic_delt_aggregate[0] + traffic_delt_aggregate[4] ) / rate_max ))
  #printf '{"text": "%5s⇣ %5s⇡", "alt": "%s", "tooltip": "%s", "class": "%s", "percentage": %d }\n' $(human_readable ${traffic_delt[0]}) $(human_readable ${traffic_delt[4]}) '_alt' '_tooltip' '_class' 0
done

# DONE: aggregate interfaces (default to all from `/sys/class/net` except `lo`)
# TODO: handle errors  (partially done)
# TODO: tooltip with details per each interface  (partially done, FIXME: tooltip is delayed compared to the main display, what the hell???)
# TODO: styling in waybar .css, using {percent} and "class"  (partially done, "error" class but percent not working)
# TODO: unicode meter (" ","▁","▁","▂","▃","▄","▅","▆","▇","█")
# TODO: split rx/tx  (?)
# TODO: colors  (? unlikely)
# TODO: test and optimize

# NOTE: in waybar config (do NOT use "interval"):
#         "custom/network_traffic": {
#             "exec": "~/.config/waybar/scripts/network_traffic.sh",
#             "return-type": "json",
#             "format": "Speed: {}",    // optional
#         },
2 Likes

Yep. Sorry for the late response. I'll get it done tommrow. Thanks @meanruse!

3 Likes

Hold on, useless use of awk and one unnecessary loop got taken care of.
One small bug I'll fix in half an hour or so, then I post.
Apologies, I still could not find the time to upload a repo, my stuff is a bloody mess.

edit: it was just a variable not initialized, here it is. Unfinished but acceptable.


network_traffic.sh
#!/bin/bash

# network_traffic.sh [-tPOLLING_INTERVAL] [NETWORK_INTERFACE...]

getopts t: __ && shift
isecs=${OPTARG:-1}
ifaces=($@)
: ${rate_max:=1000000} # maximum transfer rate for {percent}, can be overridden setting the env var

# `snore` adapted from https://blog.dhampir.no/content/sleeping-without-a-subprocess-in-bash-and-how-to-sleep-forever
# without MacOS workaround, TODO: with _snore_fd initialized separatedly, also i dont touch IFS so dont bother with it
snore() {
    local IFS
    [[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null
    read ${1:+-t "$1"} -u $_snore_fd || :
}

human_readable() {
  local hrunits=( B K M G T P )
  local ndigits=${#1}
  local idxunit=$(( (2 + ndigits) / 3 - 1))
  local lentrim=$(( ndigits - (idxunit * 3 ) ))
  echo ${1::$lentrim}${hrunits[$idxunit]}
}

exit_err() {
  printf '{"text": "⚠ %s", "tooltip": "%s", "class": "error"}\n' "$@"
  exit
}

if test ${#ifaces[@]} -gt 0; then
# sanity check the interface names
  for iface in ${ifaces[@]}; do
    test -h "/sys/class/net/${iface}" || exit_err "${iface}" "${iface} is not an existing network interface name"
  done
else
# default to all interfaces except `lo`
  ifaces=(/sys/class/net/*)
  ifaces=(${ifaces[@]##*/})
  ifaces=(${ifaces[@]//lo/})
# TODO: check that filtering out `lo` is enough, else `^(eth|wlan|enp|wlp)` as suggested
fi

# sanity check polling interval
if test ${isecs} -lt 1; then
  exit_err "${isecs}" "${isecs} is not a valid polling interval"
fi
# NOTE: `snore` would take a decimal interval but bash arithmetic does not
#if test $(echo "${isecs} >= 0.2" |bc) -eq 0; then
#  exit_err "${isecs}" "${isecs} is not a valid polling interval"
#fi

# NOTE: `/proc/net/dev` format is:
#   interface:
#       RX  bytes packets errs drop fifo  frame compressed multicast
#       TX  bytes packets errs drop fifo  colls carrier compressed

# NOTE: array items are:
# 0=rx_bytes 1=rx_packets 2=rx_errs 3=rx_drop
# 4=tx_bytes 5=tx_packets 6=tx_errs 7=tx_drop
for iface in ${ifaces[@]} aggregate; do
  declare -a traffic_prev_${iface} traffic_curr_${iface} traffic_delt_${iface}
  declare -n traffic_prev=traffic_prev_${iface}
  declare -n traffic_curr=traffic_curr_${iface}
  declare -n traffic_delt=traffic_delt_${iface}
  traffic_prev=( 0 0 0 0  0 0 0 0 )
  traffic_curr=( 0 0 0 0  0 0 0 0 )
  traffic_delt=( 0 0 0 0  0 0 0 0 )
done

# TODO: rearrange the loop, do not show bogus on first iteration
while snore ${isecs} ;do
  tooltip=""
  traffic_delt_aggregate=( 0 0 0 0  0 0 0 0 )

  readarray -s2 proc_net_dev </proc/net/dev
  while read -a data; do
    iface=${data[0]%:}
    test "${ifaces[*]}" = "${ifaces[*]//${iface}/}" && continue
    declare -n traffic_prev=traffic_prev_${iface}
    declare -n traffic_curr=traffic_curr_${iface}
    declare -n traffic_delt=traffic_delt_${iface}
    traffic_curr=(${data[@]:1:4} ${data[@]:9:4})
    #FIXME: tooltip is delayed one iteration wrt main display (but why?)
    #printf -v tooltip_hr_rx '%4s⇣' $(human_readable ${traffic_delt[0]})
    #printf -v tooltip_hr_tx '%4s⇡' $(human_readable ${traffic_delt[4]})
    #tooltip="${tooltip:+${tooltip}\r}${iface}\t${tooltip_hr_rx}\t${tooltip_hr_tx}"
    for i in {0..7}; do
      (( traffic_delt[i] = ( traffic_curr[i] - traffic_prev[i] ) / isecs ))
      (( traffic_delt_aggregate[i] += traffic_delt[i] ))
    done
    traffic_prev=(${traffic_curr[@]})
  done <<<"${proc_net_dev[@]}"

  printf '{"text": "%4s⇣ %4s⇡", "tooltip": "%s",  "percentage": %d}\n'   \
    $(human_readable ${traffic_delt_aggregate[0]})  \
    $(human_readable ${traffic_delt_aggregate[4]})  \
    "${tooltip}"                                    \
    $(( ( traffic_delt_aggregate[0] + traffic_delt_aggregate[4] ) / rate_max ))
  #printf '{"text": "%5s⇣ %5s⇡", "alt": "%s", "tooltip": "%s", "class": "%s", "percentage": %d }\n' $(human_readable ${traffic_delt[0]}) $(human_readable ${traffic_delt[4]}) '_alt' '_tooltip' '_class' 0
done

# DONE: aggregate interfaces (default to all from `/sys/class/net` except `lo`)
# DONE: get rid of "useless use of awk" (should be faster, but isn't -- still ~0.7 cpu on my box)
# DONE: avoid loop in human_readable (this makes it somewhat faster)
# TODO: handle errors  (partially done)
# TODO: tooltip with details per each interface  (partially done, FIXME: tooltip is delayed one iteration, ???)
# TODO: styling in waybar .css, using {percent} and "class"  (partially done, "error" class but percent not working)
# TODO: unicode meter (" ","▁","▁","▂","▃","▄","▅","▆","▇","█")
# TODO: split rx/tx  (?)
# TODO: colors  (? unlikely)
# TODO: test and optimize

# NOTE: in waybar config (do NOT use "interval"):
#         "custom/network_traffic": {
#             "exec": "~/.config/waybar/scripts/network_traffic.sh",
#             "return-type": "json",
#             "format": "Speed: {}",    // optional
#         },

3 Likes

Done! netspeed on waybar (9a73a1a5) · Commits · Garuda Linux / Themes and Settings / Settings / garuda-sway-settings · GitLab

@meanruse is it ok for me to add this to the bar for EndeavourOS sway as well?

4 Likes

Of course it is, I just hope I actually works without problems.
I've been running it these last days, I noticed it is still a bit too CPU hungry for my taste.
At times, I see it (or waybar itself) spike over 1% . I think it's waybar redrawing though, I commented out the whole loop contents without noticeable difference.
I you have advice for that, let me know.

2 Likes

Facepalm time.
"format-ethernet": "{icon} {ifname} ⇣{bandwidthDownBytes} ⇡{bandwidthUpBytes}",
Some days, I really wish the internet could forget.
Something is wrong with me... and Depeche Mode made a song for me.
@OdiousImp @SGS

1 Like

Done, correct? :slight_smile: