619 lines
13 KiB
Perl
Executable File
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);
|