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