rewrite i3blocks-bandwidth in perl
authorAndrew Lorimer <andrew@lorimer.id.au>
Thu, 6 Jun 2019 08:20:11 +0000 (18:20 +1000)
committerAndrew Lorimer <andrew@lorimer.id.au>
Thu, 6 Jun 2019 08:20:11 +0000 (18:20 +1000)
i3blocks-bandwidth.pl [new file with mode: 0755]
diff --git a/i3blocks-bandwidth.pl b/i3blocks-bandwidth.pl
new file mode 100755 (executable)
index 0000000..c32e0f5
--- /dev/null
@@ -0,0 +1,112 @@
+#!/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);  # ***
+  }
+}