#!/usr/bin/perl

use strict;
use warnings;

=head1 DESCRIPTION

This is a SNMP extend for monitoring /proc/net/softnet_stat on Linux for use with LibreNMS.

This just needs added to snmpd.conf like below.

    extend linux_softnet_stat /usr/lib64/librenms/snmp/linux_softnet_stat -b

=head1 SWITCHES

=head2 -p

Pretty print the JSON. If used with -b, this switch will be ignored.

=head2 -b

Gzip the output and convert to Base64.

=head1 VERSION

0.1.0

=cut

our $VERSION = '0.1.0';

use JSON;
use Getopt::Std;
use File::Slurp;
use MIME::Base64;
use IO::Compress::Gzip qw(gzip $GzipError);
use Pod::Usage;

$Getopt::Std::STANDARD_HELP_VERSION = 1;

sub main::VERSION_MESSAGE {
	print 'Linux softnet stats extend ' . $VERSION . "\n";
}

sub main::HELP_MESSAGE {
	pod2usage( -exitval => 255, -verbose => 2, -output => \*STDOUT, );
}

#this will be dumped to json at the end
my %tojson;
$tojson{totals} = {
	backlog_length => 0,
	cpu_collision  => 0,
	flow_limit     => 0,
	packet_dropped => 0,
	packets        => 0,
	received_rps   => 0,
	time_squeeze   => 0,
};
$tojson{cores}        = [];
$tojson{core_count}   = 0;
$tojson{budget}       = `sysctl net.core.netdev_budget 2> /dev/null`;
$tojson{budget_usecs} = `sysctl net.core.netdev_budget_usecs 2> /dev/null`;
chomp( $tojson{budget} );
chomp( $tojson{budget_usecs} );
$tojson{budget}       =~ s/.*\=[\ \t]*//;
$tojson{budget_usecs} =~ s/.*\=[\ \t]*//;

if ( $tojson{budget} !~ /^[0-9]+$/ ) {
	$tojson{budget} = 'unknown';
}
if ( $tojson{budget_usecs} !~ /^[0-9]+$/ ) {
	$tojson{budget_usecs} = 'unknown';
}

#gets the options
my %opts = ();
getopts( 'pb', \%opts );

my $j = JSON->new;

if ( $opts{p} && !$opts{b} ) {
	$j->pretty(1);
	$j->canonical(1);
}

##
## read it all in
##

my $lines_raw   = read_file('/proc/net/softnet_stat');
my @lines_split = split( /\n/, $lines_raw );

# not all linux kernel versions include softnet_backlog_len or index
my @to_total = keys( %{ $tojson{totals} } );
foreach my $line (@lines_split) {
	my %found;
	(
		$found{packets},       $found{packet_drop},  $found{time_squeeze}, $found{zero4},
		$found{zero5},         $found{zero6},        $found{zero7},        $found{zero8},
		$found{cpu_collision}, $found{received_rps}, $found{flow_limit},   $found{backlog_length},
		$found{index}
	) = split( /[\ \t]+/, $line );

	push(
		@{ $tojson{cores} },
		{
			core => $tojson{core_count},
		}
	);

	foreach my $item (@to_total) {
		if ( !defined( $found{$item} ) ) {
			$found{$item} = 0;
		} else {
			$found{$item} = hex( $found{$item} );
		}
		$tojson{totals}{$item} += $found{$item};
		$tojson{cores}[ $tojson{core_count} ]{$item} = $found{$item};
	}

	$tojson{core_count}++;
} ## end foreach my $line (@lines_split)

##
## print the results
##

my %head_hash;
$head_hash{'data'}        = \%tojson;
$head_hash{'version'}     = 1;
$head_hash{'error'}       = 0;
$head_hash{'errorString'} = '';

my $return_string = $j->encode( \%head_hash );

if ( !$opts{p} && !$opts{b} ) {
	print $return_string. "\n";
	exit 0;
} elsif ( !$opts{b} ) {
	print $return_string;
	exit 0;
}

my $toReturnCompressed;
gzip \$return_string => \$toReturnCompressed;
my $compressed = encode_base64($toReturnCompressed);
$compressed =~ s/\n//g;
$compressed = $compressed . "\n";
print $compressed;

exit 0;
