#!/usr/bin/perl
#
#  i3blocks-bandwidth.pl
#
#  Get network throughput from /sys/class/net and convert it to readable format.
#  Keeps the text at a constant width regardless of the magnitude or units. This
#  is useful for i3bar with you don't want widgets changing width, but it could
#  also be used for other indicators or just as a quick shell script.
#
#  Usage: i3blocks-bandwidth.pl INTERFACE
#                               test 9198 (can be any float or integer)
#                               test {1..1000} (using shell expansion)
#
#  Note that Perl's printf doesn't round properly for numbers like 0.95 (i.e.
#  0.95 -> 0.9b not 1.0b). However, it is a lot faster than implementing a
#  custom rounding function. Accuracy doesn't matter much in this case. Also,
#  this script works best with a refresh rate of 1 second. It doesn't have to
#  be exact, but larger/smaller values give misleading averages.
#

use strict;
use warnings;

my $temppath = "/dev/shm/bandwidth-$ARGV[0]";

offline() if ($#ARGV < 0);  # fail if no interface is specified

if ($ARGV[0] eq "test") {   # see comment at top of file
  shift;
  foreach (@ARGV) {
    parse($_);
    print "\n";
  }
  exit;
}

# Check if interface is up
open(STFILE, "/sys/class/net/$ARGV[0]/operstate") or offline();
my $state = <STFILE>;
close (STFILE);
$state =~  m/\A([^:\s]+)/;
offline() if ($1 ne "up");

sub offline {
  print "<span color='#ff0000'>";
  print "down";
  print "</span>";
  exit;
}

# Get rx bytes
open(BYTES, "/sys/class/net/$ARGV[0]/statistics/rx_bytes") or offline();
my $rx_bytes = <BYTES>;
close BYTES;
chomp $rx_bytes;

# Get tx bytes
open(BYTES, "/sys/class/net/$ARGV[0]/statistics/tx_bytes") or offline();
my $tx_bytes = <BYTES>;
close (BYTES);
chomp $tx_bytes;

# Get current time
my $time = time;

if (-f $temppath) {   # Read then overwrite file
  open(BYTES, $temppath);
  my $oldcontent = <BYTES>;   # read old data
  close (BYTES);
  open(my $fh, '>', $temppath);
  print $fh "$time $rx_bytes $tx_bytes";  # write new data
  close $fh;
  my ($oldtime, $oldrx, $oldtx) = split / /, $oldcontent;
  my $dt = $time - $oldtime;
  my $drxdt = ($rx_bytes - $oldrx) / $dt * 8; # *8 converts bytes to bits
  my $dtxdt = ($tx_bytes - $oldtx) / $dt * 8;
  parse($drxdt);
  print " / ";
  parse($dtxdt);
} else {    # Write data and exit
  open(my $fh, '>', $temppath);
  print $fh "$time $rx_bytes $tx_bytes";
  close $fh;
  chmod 0666, $temppath;
  exit;
}

sub parse {       # Determine best units and call round()
  my ($val) = @_;
  if ($val == 0) {
    print "000b";
  } elsif ($val < 999.5) {      # bits/second
    round($val, "b");
  } elsif ($val < 999500) {     # kilobits/second
    round($val / 1000, "k");
  } elsif ($val < 999500000) {  # megabits/second
    round($val / 1000000, "M");
  } else {
    round($val / 10000000000, "G");
  }
}

sub round {     # Format to the correct number of characters
  my ($val, $suffix) = @_;
  if ($val < 9.95) {
    printf("%.1f%s", $val, $suffix);  # *.*
  } elsif ($val < 99.5) {
    printf("0%.0f%s", $val, $suffix); # 0**
  } else {
    printf("%.0f%s", $val, $suffix);  # ***
  }
}
