Simple SNMP bandwidth monitor

I’ve tried SNMP Bandwidth Monitor but it was way overcomplicated and generated a lot of entities that had to be hidden. So I’ve decided to do something much simpler:

configuration.yaml

sensor:
  - platform: command_line
    name: Internet Speed In
    command: "direct_path_to/bandwidth.php In 5"
    unit_of_measurement: mbps
    scan_interval: 10

  - platform: command_line
    name: Internet Speed Out
    command: "direct_path_to/bandwidth.php Out 5"
    unit_of_measurement: mbps
    scan_interval: 10

Second parameter is the time the script calculates the bandwidth, it should be lowered than scan_interval.

bandwidth.php

#!/usr/bin/php
<?
	$router		= "192.168.1.1";
	$interface	= 2;
	$time		= $argv[2];
	
	$A = (int) str_replace("Counter32: ", "", snmpget($router, "public", "if{$argv[1]}Octets.$interface"));
	sleep($time);
	$B = (int) str_replace("Counter32: ", "", snmpget($router, "public", "if{$argv[1]}Octets.$interface"));

	print(round((($B - $A) / $time) * 8 / 1024 / 1024, 2));
?>

If you want to have MB/s instead just remove “* 8” from the last line.

edit: A working solution to the problems outlined below has been posted here.

I’d like to try this out but can’t seem to make it work. I modified the script to use my router’s IP and read-only community string. The script wouldn’t execute at all until I modified line 2 like so:

<?php

Then, it couldn’t find snmpget() until I installed that module like this:

sudo apt-get install php-snmp

Now the script runs, but exits with the following errors:

homeassistant@hass:~/.homeassistant/bin$ ./bandwidth.php In 10
PHP Warning:  snmpget(): Invalid object identifier: ifInOctets.2 in /home/homeassistant/.homeassistant/bin/bandwidth.php on line 7
PHP Warning:  snmpget(): Invalid object identifier: ifInOctets.2 in /home/homeassistant/.homeassistant/bin/bandwidth.php on line 9

Interface 2 is confirmed to be the interface of interest, snmpwalk on that interface using the provided community string works like I would expect. Any idea of what I might be missing here?

edit: hardcoding the OID like snmpget($router, "public", "1.3.6.1.2.1.2.2.1.10.2") works like I would expect (but then I lose the In/Out capability). This feels like a MIB problem…

edit2: installed MIBs with

sudo apt-get install snmp-mibs-downloader
sudo wget http://pastebin.com/raw.php?i=p3QyuXzZ -O /usr/share/snmp/mibs/ietf/SNMPv2-PDU

and at the command line I can now issue the following:

snmpget -v1 -c public -m ALL 10.0.0.1 ifInOctets.2
IF-MIB::ifInOctets.2 = Counter32: 121468664

So I now have the system MIBs present, but PHP doesn’t seem to like it.

Here is the bash version of the script:

#!/bin/bash
ROUTER=192.168.1.1
INTERFACE=2
TIME=5

A=$(snmpget -v2c -c public 192.168.1.1 if$1Octets.$INTERFACE | awk '{print $4}')
sleep $TIME
B=$(snmpget -v2c -c public 192.168.1.1 if$1Octets.$INTERFACE | awk '{print $4}')

echo $( echo "scale=2; (($B - $A) / $TIME) * 8 / 1024 / 1024" | bc )

Or a modified PHP version:

#!/usr/bin/php
<?
	$router		= "192.168.1.1";
	$interface	= 2;
	$inout		= $argv[1] == "In" ? "10" : "16";
	$oid		= "1.3.6.1.2.1.2.2.1.$inout.2";
	$time		= 5;
	
	$A = (int) str_replace("Counter32: ", "", snmpget($router, "public", $oid));
	sleep($time);
	$B = (int) str_replace("Counter32: ", "", snmpget($router, "public", $oid));

	print(round((($B - $A) / $time) * 8 / 1024 / 1024, 2));
?>

FYI, I’ve just noticed an error in my initial post (I’ve corrected it already). This will give you mbps, if you want MB/s you have to remove “* 8” from the last line.

Allright I think I have this setup so that it works under my Ubuntu 17.10 + python venv installation:

bandwidth.php

sudo -s
apt-get install -y php-snmp snmp-mibs-downloader
cat << 'EOF' > /home/homeassistant/.homeassistant/bin/bandwidth.php
#!/usr/bin/php
<?php
  $router = "192.168.1.1";
  $interface = 2;
  $time = $argv[2];
  snmp_read_mib("/usr/share/snmp/mibs/ietf/IF-MIB");
  
  $A = (int) str_replace("Counter32: ", "", snmpget($router, "public", "if{$argv[1]}Octets.$interface"));
  sleep($time);
  $B = (int) str_replace("Counter32: ", "", snmpget($router, "public", "if{$argv[1]}Octets.$interface"));
  
  if ($A > $B) {
    print(round((((2**32 - $A) + $B) / $time) * 8 / 1024 / 1024, 2));
  } else {
    print(round((($B - $A) / $time) * 8 / 1024 / 1024, 2));
  }
?>
EOF
chmod +x /home/homeassistant/.homeassistant/bin/bandwidth.php
exit

configuration.yaml

sensor:
  - platform: command_line
    name: Internet Speed In
    command: '/home/homeassistant/.homeassistant/bin/bandwidth.php In 5'
    unit_of_measurement: mbps
    scan_interval: 10

  - platform: command_line
    name: Internet Speed Out
    command: '/home/homeassistant/.homeassistant/bin/bandwidth.php Out 5'
    unit_of_measurement: mbps
    scan_interval: 10

Now I’m seeing situations where the value is reading correctly, but the history graph shows negative values when running at ~150mbps:

image

edit I think what’s happening here is that there is no provision provided for the rollover of the counter, so you wind up with large negative value spikes in the data which is throwing the graph off.

It works for me. Maybe your router is misreporting the octets?

My router reports IfInOctets using a 32bit unsigned integer, same as any other RFC-compliant SNMP provider. How much data are you regularly moving? I have 150mbit service which will roll over a 32bit octet counter in a little under 4 minutes.

I think the solution here is to utilize ifHCInOctets/ifHCOutOctets pursuant to RFC 2233 which my router doesn’t support :frowning:

Instead I’ve applied this cheesy hack to simply ignore any sample where the counters have rolled over, which is working as expected.

edit: new hack to actually deal with rollover.

#!/usr/bin/php
<?php
  $router = "192.168.1.1";
  $interface = 2;
  $time = $argv[2];
  snmp_read_mib("/usr/share/snmp/mibs/ietf/IF-MIB");
  
  $A = (int) str_replace("Counter32: ", "", snmpget($router, "HkEjIVKQAp", "if{$argv[1]}Octets.$interface"));
  sleep($time);
  $B = (int) str_replace("Counter32: ", "", snmpget($router, "public", "if{$argv[1]}Octets.$interface"));
  
  if ($A > $B) {
    print(round((((2**32 - $A) + $B) / $time) * 8 / 1024 / 1024, 2));
  } else {
    print(round((($B - $A) / $time) * 8 / 1024 / 1024, 2));
  }
?>
1 Like

You’re right, that might be the case. I have 250mbps but didn’t upload or download any significant in last 24 hours so I might have missed the roll over. Thanks for sharing the “hack”.

I’ve fixed up the rollover hack to be a little less stupid, now the code above will check for a rollover condition and add up the octets appropriately in response (with the assumption that it has only happened once).

Thanks for sharing, I’ve updated my script.

Hi Cezex. I want to monitor my mikrotik snmp and I use your bash version but it doesn’t working. the output is:
pi@raspberrypi:~ $ ./band.sh
ifOctets.2: Unknown Object Identifier (Sub-id not found: (top) -> ifOctets)
ifOctets.2: Unknown Object Identifier (Sub-id not found: (top) -> ifOctets)
./band.sh: line 10: bc: command not found

is any way to fix ?
thanks

Just install bc (not sure where you are running the script, it should work on both linux and macos).

Trying to set it up but running into this problems when i run the php script.

PHP Notice: Undefined offset: 2 in /home/pi/.homeassistant/bandwidth.php on line 5
PHP Notice: Undefined offset: 1 in /home/pi/.homeassistant/bandwidth.php on line 8
PHP Warning: snmpget(): Invalid object identifier: ifOctets.2 in /home/pi/.homeassistant/bandwidth.php on line 8
PHP Notice: Undefined offset: 1 in /home/pi/.homeassistant/bandwidth.php on line 10
PHP Warning: snmpget(): Invalid object identifier: ifOctets.2 in /home/pi/.homeassistant/bandwidth.php on line 10
PHP Warning: Division by zero in /home/pi/.homeassistant/bandwidth.php on line 15

With script:

#!/usr/bin/php
<?php
  $router = "192.168.1.1";
  $interface = 2;
  $time = $argv[2];
  snmp_read_mib("/usr/share/snmp/mibs/ietf/IF-MIB");

  $A = (int) str_replace("Counter32: ", "", snmpget($router, "public", "if{$argv[1]}Octets.$interface"));
  sleep($time);
  $B = (int) str_replace("Counter32: ", "", snmpget($router, "public", "if{$argv[1]}Octets.$interface"));

  if ($A > $B) {
    print(round((((2**32 - $A) + $B) / $time) * 8 / 1024 / 1024, 2));
  } else {
    print(round((($B - $A) / $time) * 8 / 1024 / 1024, 2));
  }
?>

What am I doing wrong?