aprsc/src/aprsc_munin

619 lines
13 KiB
Perl
Executable File

#!/usr/bin/perl
#
# Markers for munin's automagical configuration:
#%# family=auto
#%# capabilities=autoconf
#
=head1 NAME
aprsc_munin - Munin plugin which monitors an aprsc server instance
=head CONFIGURATION
This plugin needs to be able to request http://localhost:14501/status.json.
The URL can be changed in the configuration to point to another server URL.
This configuration example shows the default settings for the plugin:
[aprsc_munin]
env.url http://127.0.0.1:14501/status.json
=head1 LICENSE
BSD
=cut
use strict;
use warnings;
use File::Basename;
use Data::Dumper;
my $in_autoconf = (defined $ARGV[0] && $ARGV[0] eq "autoconf");
my $in_config = (defined $ARGV[0] && $ARGV[0] eq "config");
my $in_makelinks = (defined $ARGV[0] && $ARGV[0] eq "makelinks");
my $title_add;
$title_add = $ENV{'title'} if (defined $ENV{'title'});
# present fail message in the right format depending on mode
sub fail($)
{
my($s) = @_;
if ($in_autoconf) {
print "no ($s)\n";
exit(1);
}
warn "aprsc_munin failed: $s\n";
exit(1);
}
# print a config set for a single graph
sub print_config($$)
{
my($graph, $gr) = @_;
my $attrs = $gr->{'config_attrs'};
my $srcs = $gr->{'sources'};
my @order;
if (defined $gr->{'order'}) {
@order = @{ $gr->{'order'} };
} else {
@order = sort keys %{ $srcs };
}
if (defined $title_add) {
$attrs->{'graph_title'} = $title_add . " - " . $attrs->{'graph_title'};
$attrs->{'graph_category'} .= ' ' . $title_add;
}
foreach my $k (keys %{ $attrs }) {
printf("%s %s\n", $k, $attrs->{$k});
}
foreach my $src (@order) {
foreach my $k (keys %{ $srcs->{$src}->{'attrs'} }) {
printf("%s.%s %s\n", $src, $k, $srcs->{$src}->{'attrs'}->{$k});
}
}
}
# find a value by key from the JSON object
sub find_val($$)
{
my($needle, $j) = @_;
my $r = $j;
foreach my $p (split('\.', $needle)) {
return 'U' if (ref($r) ne 'HASH' || !defined $r->{$p});
$r = $r->{$p};
}
return $r;
}
# print data values
sub print_data($$$)
{
my($graph, $gr, $j) = @_;
my $srcs = $gr->{'sources'};
foreach my $k (keys %{ $srcs }) {
my $val = find_val($srcs->{$k}->{'k'}, $j);
print "$k.value $val\n";
}
}
#
##### main
#
# require some unusual modules
if (!eval "require LWP::UserAgent;")
{
fail("LWP::UserAgent not found");
}
if (!eval "require JSON::XS;")
{
fail("JSON::XS not found");
}
# configuration
my $status_url = defined $ENV{'url'} ? $ENV{'url'} : "http://127.0.0.1:14501/status.json";
# definitions for graphs
my $category = 'aprsc';
my %graphs = (
'0clients' => {
'config_attrs' => {
'graph_title' => 'Clients allocated',
'graph_vlabel' => 'clients + pseudoclients allocated',
'graph_args' => '--base 1000',
'graph_category' => $category,
},
'sources' => {
'clients_total' => {
'k' => 'totals.clients',
'attrs' => {
'label' => 'Total',
'min' => 0,
'type' => 'GAUGE',
'colour' => 'dd00ff'
}
}
}
},
'1connects' => {
'config_attrs' => {
'graph_title' => 'Connections',
'graph_vlabel' => 'connections/s',
'graph_args' => '--base 1000',
'graph_category' => $category,
},
'sources' => {
'conns' => {
'k' => 'totals.connects',
'attrs' => {
'label' => 'Incoming connections',
'min' => 0,
'max' => 100000,
'type' => 'DERIVE',
'colour' => '25a7fd'
}
}
}
},
'dupecheck' => {
'config_attrs' => {
'graph_title' => 'Dupechecked packets',
'graph_vlabel' => 'packets/s',
'graph_args' => '--base 1000 --lower-limit 0',
'graph_category' => $category,
},
'sources' => {
'uniq' => {
'k' => 'dupecheck.uniques_out',
'attrs' => {
'label' => 'Unique packets',
'min' => 0,
'type' => 'DERIVE',
'warning' => '30:200',
'critical' => '10:400'
}
},
'dup' => {
'k' => 'dupecheck.dupes_dropped',
'attrs' => {
'label' => 'Duplicates dropped',
'min' => 0,
'type' => 'DERIVE',
'colour' => 'e47bfd'
}
}
}
},
'dpktstcp' => {
'config_attrs' => {
'graph_title' => 'APRS packets over TCP',
'graph_vlabel' => 'packets/s in (-) / out (+)',
'graph_args' => '--base 1000',
'graph_order' => 'rx tx',
'graph_category' => $category,
},
'sources' => {
'rx' => {
'k' => 'totals.tcp_pkts_rx',
'attrs' => {
'label' => 'Packets RX',
'min' => 0,
'type' => 'DERIVE',
'graph' => 'no',
'draw' => 'AREA',
'colour' => '16b5ff'
}
},
'tx' => {
'k' => 'totals.tcp_pkts_tx',
'attrs' => {
'label' => 'Packets',
'min' => 0,
'type' => 'DERIVE',
'negative' => 'rx',
'draw' => 'AREA',
'colour' => 'fd745c'
}
},
}
},
'dpktsudp' => {
'config_attrs' => {
'graph_title' => 'APRS packets over UDP',
'graph_vlabel' => 'packets/s in (-) / out (+)',
'graph_args' => '--base 1000',
'graph_order' => 'rx tx',
'graph_category' => $category,
},
'sources' => {
'rx' => {
'k' => 'totals.udp_pkts_rx',
'attrs' => {
'label' => 'Packets RX',
'min' => 0,
'type' => 'DERIVE',
'graph' => 'no',
'draw' => 'AREA',
'colour' => '16b5ff'
}
},
'tx' => {
'k' => 'totals.udp_pkts_tx',
'attrs' => {
'label' => 'Packets',
'min' => 0,
'type' => 'DERIVE',
'negative' => 'rx',
'draw' => 'AREA',
'colour' => 'fd745c'
}
},
}
},
'ddatatcp' => {
'config_attrs' => {
'graph_title' => 'APRS data over TCP',
'graph_vlabel' => 'bits/s in (-) / out (+)',
'graph_args' => '--base 1000',
'graph_order' => 'rx tx',
'graph_category' => $category,
},
'sources' => {
'rx' => {
'k' => 'totals.tcp_bytes_rx',
'attrs' => {
'label' => 'Bits/s RX',
'min' => 0,
'type' => 'DERIVE',
'graph' => 'no',
'cdef' => 'rx,8,*',
'draw' => 'AREA',
'colour' => '16b5ff'
}
},
'tx' => {
'k' => 'totals.tcp_bytes_tx',
'attrs' => {
'label' => 'Bits/s',
'min' => 0,
'type' => 'DERIVE',
'negative' => 'rx',
'cdef' => 'tx,8,*',
'draw' => 'AREA',
'colour' => 'fd745c'
}
},
}
},
'ddataudp' => {
'config_attrs' => {
'graph_title' => 'APRS data over UDP',
'graph_vlabel' => 'bits/s in (-) / out (+)',
'graph_args' => '--base 1000',
'graph_order' => 'rx tx',
'graph_category' => $category,
},
'sources' => {
'rx' => {
'k' => 'totals.udp_bytes_rx',
'attrs' => {
'label' => 'Bits/s RX',
'min' => 0,
'type' => 'DERIVE',
'graph' => 'no',
'cdef' => 'rx,8,*',
'draw' => 'AREA',
'colour' => '16b5ff'
}
},
'tx' => {
'k' => 'totals.udp_bytes_tx',
'attrs' => {
'label' => 'Bits/s',
'min' => 0,
'type' => 'DERIVE',
'negative' => 'rx',
'cdef' => 'tx,8,*',
'draw' => 'AREA',
'colour' => 'fd745c'
}
},
}
},
'memcellu' => {
'config_attrs' => {
'graph_title' => 'Memory cells used',
'graph_vlabel' => 'cells',
'graph_args' => '--base 1024 --lower-limit 0',
'graph_category' => $category,
},
'keytail' => 'cells_used',
'sources' => {
}
},
'memcellub' => {
'config_attrs' => {
'graph_title' => 'Memory bytes used',
'graph_vlabel' => 'bytes',
'graph_args' => '--base 1024 --lower-limit 0',
'graph_category' => $category,
},
'keytail' => 'used_bytes',
'sources' => {
}
},
'memcelluba' => {
'config_attrs' => {
'graph_title' => 'Memory bytes allocated',
'graph_vlabel' => 'bytes',
'graph_args' => '--base 1024 --lower-limit 0',
'graph_category' => $category,
},
'keytail' => 'allocated_bytes',
'sources' => {
}
},
'clientlist' => {
'config_attrs' => {
'graph_title' => 'Clients per listener',
'graph_vlabel' => 'clients connected',
'graph_args' => '--base 1000',
'graph_category' => $category,
},
'keytail' => 'clients',
'type' => 'GAUGE',
'sources' => {
}
},
'clientx0pin' => {
'config_attrs' => {
'graph_title' => 'Packets in per listener',
'graph_vlabel' => 'packets/s',
'graph_args' => '--base 1000',
'graph_category' => $category,
},
'keytail' => 'pkts_rx',
'type' => 'DERIVE',
'sources' => {
}
},
'clientx0dout' => {
'config_attrs' => {
'graph_title' => 'Data out per listener',
'graph_vlabel' => 'bits/s',
'graph_args' => '--base 1000',
'graph_category' => $category,
},
'keytail' => 'bytes_tx',
'type' => 'DERIVE',
'cdef' => ',8,*',
'sources' => {
}
},
'peerinpkts' => {
'config_attrs' => {
'graph_title' => 'Packets in per peer',
'graph_vlabel' => 'packets/s',
'graph_args' => '--base 1000 --lower-limit 0',
'graph_category' => $category,
},
'keytail' => 'pkts_rx',
'type' => 'DERIVE',
'sources' => {
}
},
'rxerrstcp' => {
'config_attrs' => {
'graph_title' => 'Packets dropped from TCP clients',
'graph_vlabel' => 'packets/min',
'graph_args' => '--base 1000',
'graph_category' => $category,
},
'key' => 'totals.tcp_rx_errs',
'type' => 'DERIVE',
'cdef' => ',60,*',
'sources' => {
}
},
'rxerrsudp' => {
'config_attrs' => {
'graph_title' => 'Packets dropped from UDP clients',
'graph_vlabel' => 'packets/min',
'graph_args' => '--base 1000',
'graph_category' => $category,
},
'key' => 'totals.udp_rx_errs',
'type' => 'DERIVE',
'cdef' => ',60,*',
'sources' => {
}
},
);
if ($in_makelinks) {
my $instance = '';
$instance = '_' . $ARGV[1] if (defined $ARGV[1]);
foreach my $graph (sort keys %graphs) {
my $d = "aprsc" . $instance . "_" . $graph;
next if (-e $d);
symlink($0, $d) || die "Failed to symlink $0 to $d: $!\n";
}
exit(0);
}
# initialize our hammers and fetch JSON from server
my $json = new JSON::XS;
my $ua = LWP::UserAgent->new(timeout => 5);
my $res = $ua->request(HTTP::Request->new('GET', $status_url));
if (!$res->is_success) {
# todo: print result error messages
fail("HTTP request to aprsc failed");
}
#print $res->content;
# decode and validate
my $j = $json->decode($res->content);
if (!defined $j) {
fail("JSON parsing of status object failed");
}
# if we're just checking if this doable, say so
if ($in_autoconf) {
print "yes\n";
exit(0);
}
my $graph = basename($0);
$graph =~ s/.*_//;
if (!defined $graphs{$graph}) {
fail("No such graph available: $graph");
}
my $gr = $graphs{$graph};
# for memcell graphs, get a dynamic list of sources
if ($graph =~ /^memcell/) {
my @ord;
foreach my $k (sort grep(/cells_used$/, keys %{ $j->{'memory'} })) {
$k =~ s/_cells_used$//;
push @ord, $k;
$gr->{'sources'}->{$k} = {
'k' => 'memory.' . $k . '_' . $gr->{'keytail'},
'attrs' => {
'label' => $k,
'min' => 0,
'type' => 'GAUGE',
}
};
}
$gr->{'order'} = \@ord;
} elsif ($graph =~ /^client(list|x)/) {
my @ord;
# convert array to hash
my $h = {};
foreach my $l (@{ $j->{'listeners'} }) {
my $k = sprintf("%s_%s", $l->{'proto'}, $l->{'addr'});
$k =~ s/[^\w]/_/g;
$h->{$k} = $l;
}
$j->{'listeners'} = $h;
my $draw = 'AREA';
foreach my $k (sort { $j->{'listeners'}->{$a}->{'clients'} <=> $j->{'listeners'}->{$b}->{'clients'} } keys %{ $j->{'listeners'} }) {
next if ($graph eq 'clientlist' && $j->{'listeners'}->{$k}->{'proto'} eq 'udp');
push @ord, $k;
$gr->{'sources'}->{$k} = {
'k' => 'listeners.' . $k . '.' . $gr->{'keytail'},
'attrs' => {
'label' => $j->{'listeners'}->{$k}->{'proto'} . '/' . $j->{'listeners'}->{$k}->{'addr'},
'min' => 0,
'type' => $gr->{'type'},
'draw' => $draw
}
};
if ($gr->{'cdef'}) {
$gr->{'sources'}->{$k}->{'attrs'}->{'cdef'} = $k . $gr->{'cdef'};
}
$draw = 'STACK';
}
$gr->{'order'} = \@ord;
} elsif ($graph =~ /^peer/) {
my @ord;
# convert array to hash
my $h = {};
foreach my $l (@{ $j->{'peers'} }) {
my $k = sprintf("p%s", $l->{'addr_rem'});
$k =~ s/[^\w]/_/g;
$h->{$k} = $l;
if ($graph =~ /^peerinpkts/ && !defined $h->{'out'}) {
$h->{'out'} = $l;
}
}
$j->{'peers'} = $h;
my $draw = 'AREA';
foreach my $k (sort keys %{ $j->{'peers'} }) {
push @ord, $k;
my $keytail = $gr->{'keytail'};
my $label = ((defined $j->{'peers'}->{$k}->{'username'}) ? $j->{'peers'}->{$k}->{'username'} . ' ' : '')
. $j->{'peers'}->{$k}->{'addr_rem'};
if ($k eq 'out') {
$label = 'Packets out per peer';
$keytail = 'pkts_tx';
}
$gr->{'sources'}->{$k} = {
'k' => 'peers.' . $k . '.' . $keytail,
'attrs' => {
'label' => $label,
'min' => 0,
'type' => $gr->{'type'},
'draw' => $draw,
'warning' => '0.2:100',
'critical' => '0.05:200',
}
};
if ($gr->{'cdef'}) {
$gr->{'sources'}->{$k}->{'attrs'}->{'cdef'} = $k . $gr->{'cdef'};
}
$draw = 'STACK';
}
$gr->{'order'} = \@ord;
if ($#ord == -1) {
$gr->{'config_attrs'}->{'update'} = 'no';
$gr->{'config_attrs'}->{'graph'} = 'no';
}
} elsif ($graph =~ /^rxerrs(tcp|udp)/) {
my @ord;
my $keys = $j->{'rx_errs'};
# convert array to hash
my $h = {};
my $vals = find_val($gr->{'key'}, $j);
my $draw = 'AREA';
foreach my $k (@$keys) {
push @ord, $k;
#$k =~ s/[^\w]/_/g;
$h->{$k} = shift @$vals;
$gr->{'sources'}->{$k} = {
'k' => 'use.' . $k,
'attrs' => {
'label' => $k,
'min' => 0,
'type' => $gr->{'type'},
'draw' => $draw
}
};
if ($gr->{'cdef'}) {
$gr->{'sources'}->{$k}->{'attrs'}->{'cdef'} = $k . $gr->{'cdef'};
}
$draw = 'STACK';
}
$j->{'use'} = $h;
$gr->{'order'} = \@ord;
}
if ($in_config) {
# default title from the server's ServerId
$title_add = $j->{'server'}->{'server_id'} if (!defined $title_add && defined $j->{'server'});
print_config($graph, $gr);
exit(0);
}
print_data($graph, $gr, $j);
exit(0);