#!/usr/bin/perl
# -*- perl -*-

# Copyright (C) 2015-2023 Pirx Developers - https://pirx.dev/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

=head1 NAME

hq_rtt_ - Munin wildcard plugin to monitor RTT between hosts.

=head1 APPLICABLE SYSTEMS

Linux systems with ping command available.

=head1 CONFIGURATION

This plugin requires special configuration to work properly. Lets
assume that you want to monitor RTT between your Linux router (IP
address 10.10.10.1) and WAN gateway (IP address 10.20.20.1). You
can do this with following configuration:

[hq_rtt_*]
  env.wan_src_ip 10.10.10.1
  env.wan_dst_ip 10.20.20.1
  env.wan_title WAN gateway RTT
  env.wan_rtt_warning 100
  env.wan_rtt_critical 500
  env.wan_loss_warning 5
  env.wan_loss_critical 10

Now symlink hq_rtt_ as hq_rtt_wan in your munin plugins directory
and thats all.

You can also specify number of pings and packet size. More pings gives
more accurate results, but also require more time to test. This may cause
node processing timeout when more than 10 hosts are tested.

[hq_rtt_*]
  env.ping_count 5
  env.packet_size 500

Additionally on some systems root privileges may be required for running
ping command. Add following to your munin-node config if necessary:

[hq_rtt_*]
  user root

=head1 VERSION

  20230309

=head1 MAGIC MARKERS

  #%# family=manual
  #%# capabilities=autoconf

=head1 BUGS

None.

=head1 AUTHOR

Pirx Developers - https://pirx.dev/

=head1 LICENSE

GPLv3

=cut

use strict;
use warnings;

# Handle autoconf
if(defined($ARGV[0]) and $ARGV[0] eq 'autoconf') {
  print("no\n");
  exit(0);
}

my ($graph_name) = $0 =~ /.*\/hq_rtt_(.*)$/;

if(!defined($graph_name) or $graph_name eq "") {
  print("Error: invalid symlink name.\n");
  exit(1);
}

# Set warning and critical thresholds
my $rtt_warning = 100;
my $rtt_critical = 500;
my $loss_warning = 5;
my $loss_critical = 10;
if(defined($ENV{$graph_name . '_rtt_warning'})) {
  $rtt_warning = $ENV{$graph_name . '_rtt_warning'};
}
if(defined($ENV{$graph_name . '_rtt_critical'})) {
  $rtt_critical = $ENV{$graph_name . '_rtt_critical'};
}
if(defined($ENV{$graph_name . '_loss_warning'})) {
  $loss_warning = $ENV{$graph_name . '_loss_warning'};
}
if(defined($ENV{$graph_name . '_loss_critical'})) {
  $loss_critical = $ENV{$graph_name . '_loss_critical'};
}
# Read config from ENV
my $src_ip;
my $dst_ip;
my $title;
my $ping_count = 5;
my $packet_size = 500;
if(defined($ENV{$graph_name . '_src_ip'})) {
  $src_ip = $ENV{$graph_name . '_src_ip'};
}
if(defined($ENV{$graph_name . '_dst_ip'})) {
  $dst_ip = $ENV{$graph_name . '_dst_ip'};
}
if(defined($ENV{$graph_name . '_title'})) {
  $title = $ENV{$graph_name . '_title'};
}
if(defined($ENV{'ping_count'})) {
  $ping_count = $ENV{'ping_count'};
}
if(defined($ENV{'packet_size'})) {
  $packet_size = $ENV{'packet_size'};
}

if(!defined($src_ip) or $src_ip eq "" or !defined($dst_ip) or $dst_ip eq "" or !defined($title) or $title eq "") {
  print("Error: incomplete configuration.\n");
  exit(1);
}

# Handle config
if(defined($ARGV[0]) and $ARGV[0] eq 'config') {
  print <<EOF;
graph_title $title
graph_args --base 1000 --lower-limit 0
graph_scale no
graph_vlabel time in ms / loss in %
graph_category rtt

loss.label Packet loss
loss.type GAUGE
loss.min 0
loss.draw LINE1
loss.colour ff0000
loss.line $loss_warning:ffff00:Packet loss warning threshold
loss.warning $loss_warning
loss.critical $loss_critical

rtt.label RTT
rtt.type GAUGE
rtt.min 0
rtt.draw LINE1
rtt.colour 228b22
rtt.line $rtt_warning:40ff40:RTT warning threshold
rtt.warning $rtt_warning
rtt.critical $rtt_critical
EOF
  exit(0);
}

my $rtt;
my $loss;

$rtt = "U";
$loss = "U";

# Read and parse df output
my $exec_fd;
if(open($exec_fd, "ping -c " . $ping_count . " -s " . $packet_size . " -I " . $src_ip . " " . $dst_ip . " -q 2>/dev/null |")) {
  PING_READ_LOOP: while(defined(my $line=<$exec_fd>)) {
    if($line =~ /.*transmitted.*received.*\s+([0-9\.]+)% packet loss/) {
      $loss = $1;
    }
    if($line =~ m!^rtt min/avg/max/mdev = [0-9\.]+/([0-9\.]+)/[0-9\.]+!) {
      $rtt = $1;
    }
  }
  close($exec_fd);
}
else {
  print("Error running ping command. Exiting.\n");
  exit(1);
}

print("loss.value " . $loss . "\n");
if($loss == 100) {
  print("rtt.value 0\n");
}
else {
  print("rtt.value " . $rtt . "\n");
}

exit(0);
