munin-contrib/plugins/shoutcast/shoutcast2_multi

445 lines
14 KiB
Perl
Executable File

#!/usr/bin/perl
#
=head1 Shoutcast 2.0.x Plugin
A Plugin for monitoring a Shoutcast 2.0.x Server (Multigraph)
=head1 Munin Configuration
[shoutcast2_multi]
env.host 127.0.0.1 *default*
env.port 8000 *default*
env.pass changeme *default*
=head2 Munin Configuration Explanation
host = host we are attempting to connection to, can be ip, or hostname
port = port we need to connect to in order to get to admin.cgi
pass = password to use to authenticate as admin user
=head1 AUTHOR
Matt West < https://github.com/mhwest13 >
=head1 License
GPLv2
=head1 Magic Markers
#%# family=auto
#%# capabilities=autoconf
=cut
use strict;
use warnings;
use LWP::UserAgent;
use XML::Simple;
use Munin::Plugin;
need_multigraph();
=head1 Variable Declarations
This section is mainly for importing / declaring our environment variables.
This is were we will import the data from our plugin-conf.d file so we can
override the default settings which will only work for Shoutcast test configs.
=cut
my $host = $ENV{host} || '127.0.0.1';
my $port = $ENV{port} || 8000;
my $pass = $ENV{pass} || 'changeme';
# Initialize hashref for storing results information...
my $dataRef;
# Create a hashref for our graph information that we will call up later...
my $graphsRef;
my $ua = LWP::UserAgent->new();
$ua->agent('Munin Shoutcast Plugin/0.1');
$ua->timeout(5);
$ua->credentials($host.':'.$port, 'Shoutcast Server', 'admin'=>$pass);
=head1 Graphs Declarations
The following section of code contains our graph information. This is the data
provided to Munin, so that it may properly draw our graphs based on the values
the plugin returns.
While you are free to change colors or labels changing the type, min, or max
could cause unfortunate problems with your graphs.
=cut
$graphsRef->{active} = {
config => {
args => '--base 1000 --lower-limit 0',
vlabel => 'Is a DJ Actively Connected?',
category => 'streaming',
title => 'Active States',
info => 'This graph shows us the active state or not, depending on if a DJ is connected',
},
datasrc => [
{ name => 'active', draw => 'AREA', min => '0', max => '1', label => 'On or Off', type => 'GAUGE' },
],
};
$graphsRef->{listeners} = {
config => {
args => '--base 1000 --lower-limit 0',
vlabel => 'Listener Count',
category => 'streaming',
title => 'Listeners',
info => 'This graph shows us the various counts for listener states we are tracking',
},
datasrc => [
{ name => 'maxlisteners', draw => 'AREA', min => '0', label => 'Max Listeners', type => 'GAUGE' },
{ name => 'currlisteners', draw => 'STACK', min => '0', label => 'Current Listeners', type => 'GAUGE' },
],
};
$graphsRef->{sid_active} = {
config => {
args => '--base 1000 --lower-limit 0',
vlabel => 'Is a DJ Actively Connected?',
title => 'Active State',
info => 'This graph shows us the active state or not, depending on if a DJ is connected',
},
datasrc => [
{ name => 'active', draw => 'AREA', min => '0', max => '1', label => 'On or Off', type => 'GAUGE', xmlkey => 'STREAMSTATUS' },
],
};
$graphsRef->{sid_listeners} = {
config => {
args => '--base 1000 --lower-limit 0',
vlabel => 'Listener Count',
title => 'Listeners',
info => 'This graph shows us the various counts for listener states we are tracking',
},
datasrc => [
{ name => 'maxlisteners', draw => 'AREA', min => '0', label => 'Max Listeners', type => 'GAUGE', xmlkey => 'MAXLISTENERS' },
{ name => 'currlisteners', draw => 'STACK', min => '0', label => 'Current Listeners', type => 'GAUGE', xmlkey => 'CURRENTLISTENERS' },
{ name => 'peaklisteners', draw => 'LINE2', min => '0', label => 'Peak Listeners', type => 'GAUGE', xmlkey => 'PEAKLISTENERS' },
{ name => 'uniqlisteners', draw => 'LINE2', min => '0', label => 'Unique Listeners', type => 'GAUGE', xmlkey => 'UNIQUELISTENERS' },
],
};
if (defined($ARGV[0]) && ($ARGV[0] eq 'config')) {
config();
exit;
}
if (defined($ARGV[0]) && (($ARGV[0] eq 'autoconf') || ($ARGV[0] eq 'suggest'))) {
check_autoconf();
exit;
}
# I guess we are collecting stats to return, execute main subroutine.
main();
exit;
=head1 Subroutines
The following is a description of what each subroutine is for and does
=head2 main
This subroutine is our main routine should we not be calling up autoconf
or config. Ultimately this routine will print out the values for each graph
and graph data point we are tracking.
=cut
sub main {
my ($returnBit,$adminRef) = fetch_admin_data();
if ($returnBit == 0) {
exit;
}
my $streamConfigRef = $adminRef->{STREAMCONFIGS}->{STREAMCONFIG};
my $sidDataRef;
if ($adminRef->{STREAMCONFIGS}->{TOTALCONFIGS} == 1) {
my $sid = $streamConfigRef->{id};
my ($return,$tmpSidRef) = fetch_sid_data($sid);
if ($return == 0) {
# Only one stream, and we didn't get a good response.
exit;
}
$sidDataRef->{$sid} = $tmpSidRef;
} else {
foreach my $sid (keys %{$streamConfigRef}) {
my ($return,$tmpSidRef) = fetch_sid_data($sid);
if ($return == 0) {
next;
}
$sidDataRef->{$sid} = $tmpSidRef;
}
}
print_active_data($sidDataRef);
print_listener_data($adminRef->{STREAMCONFIGS}->{SERVERMAXLISTENERS}, $sidDataRef);
return;
}
=head2 print_active_data
The subroutine prints out the active graph values for each stream and ultimately for
the entire shoutcast service. Should 1 Stream be active, but 5 streams available,
the global graph should show the state as active for the service, but clicking into
that graph, should give you a stream level view of which stream was in use during
what time periods.
=cut
sub print_active_data {
my ($sidDataRef) = (@_);
my $globalActive = 0;
foreach my $sid (sort keys %{$sidDataRef}) {
print "multigraph shoutcast2_active.active_sid_$sid\n";
foreach my $dsrc (@{$graphsRef->{sid_active}->{datasrc}}) {
print "$dsrc->{name}.value $sidDataRef->{$sid}->{$dsrc->{xmlkey}}\n";
if ($sidDataRef->{$sid}->{$dsrc->{xmlkey}} == 1) {
$globalActive = 1;
}
}
}
print "multigraph shoutcast2_active\n";
foreach my $dsrc (@{$graphsRef->{active}->{datasrc}}) {
print "$dsrc->{name}.value $globalActive\n";
}
return;
}
=head2 print_listener_data
This subroutine prints out the listener graph values for each stream and ultimately
adds all of the current users together to show that against the maxserver count in
the global graph. Clicking on the global graph will reveal a bit more information
about the users on a stream by stream basis.
=cut
sub print_listener_data {
my ($maxListeners,$sidDataRef) = (@_);
my $globalListeners = 0;
foreach my $sid (sort keys %{$sidDataRef}) {
print "multigraph shoutcast2_listeners.listeners_sid_$sid\n";
foreach my $dsrc (@{$graphsRef->{sid_listeners}->{datasrc}}) {
print "$dsrc->{name}.value $sidDataRef->{$sid}->{$dsrc->{xmlkey}}\n";
if ($dsrc->{name} eq 'currlisteners') {
$globalListeners += $sidDataRef->{$sid}->{$dsrc->{xmlkey}};
}
}
}
print "multigraph shoutcast2_listeners\n";
foreach my $dsrc (@{$graphsRef->{listeners}->{datasrc}}) {
if ($dsrc->{name} eq 'maxlisteners') {
print "$dsrc->{name}.value $maxListeners\n";
} else {
print "$dsrc->{name}.value $globalListeners\n";
}
}
return;
}
=head2 config
The config subroutine can be seen as the main config routine, which
will call up to your shoutcast server to figure out how many streams
you have running, and then print out the appropriate multigraph info.
Ultimately this subroutine will call two more routines to print out
the graph args / configuration information.
=cut
sub config {
my ($returnBit,$adminRef) = fetch_admin_data();
if ($returnBit == 0) {
# $adminRef returned a string, we'll just print it out.
print "no (error response: $adminRef)\n";
exit;
}
my $streamConfigRef = $adminRef->{STREAMCONFIGS}->{STREAMCONFIG};
my $sidDataRef;
if ($adminRef->{STREAMCONFIGS}->{TOTALCONFIGS} == 1) {
my $sid = $streamConfigRef->{id};
my ($return,$tmpSidRef) = fetch_sid_data($sid);
if ($return == 0) {
# Only one stream, and we didn't get a good response.
exit;
}
$sidDataRef->{$sid} = $tmpSidRef;
} else {
foreach my $sid (keys %{$streamConfigRef}) {
my ($return,$tmpSidRef) = fetch_sid_data($sid);
if ($return == 0) {
next;
}
$sidDataRef->{$sid} = $tmpSidRef;
}
}
print_active_config($sidDataRef);
print_listener_config($sidDataRef);
return;
}
=head2 print_active_config
This subroutine prints out the graph information for our active graphs.
It prints the sub-multigraphs first based on stream id, and finally the
root active graph. Its not suggested that you mess with this routine
unless you fully understand what its doing and how munin graph_args work.
=cut
sub print_active_config {
my ($sidDataRef) = (@_);
foreach my $sid (sort keys %{$sidDataRef}) {
# Print the Config Info First
print "multigraph shoutcast2_active.active\_sid\_$sid\n";
print "graph_title ".$graphsRef->{sid_active}->{config}->{title}." for StreamID: $sid\n";
print "graph_args ".$graphsRef->{sid_active}->{config}->{args}."\n";
print "graph_vlabel ".$graphsRef->{sid_active}->{config}->{vlabel}."\n";
print "graph_category streamid_$sid\n";
print "graph_info ".$graphsRef->{sid_active}->{config}->{info}."\n";
# Print the Data Value Info
foreach my $dsrc (@{$graphsRef->{sid_active}->{datasrc}}) {
while ( my ($key, $value) = each (%{$dsrc})) {
next if ($key eq 'name');
next if ($key eq 'xmlkey');
print "$dsrc->{name}.$key $value\n";
}
}
}
print "multigraph shoutcast2_active\n";
print "graph_title ".$graphsRef->{active}->{config}->{title}."\n";
print "graph_args ".$graphsRef->{active}->{config}->{args}."\n";
print "graph_vlabel ".$graphsRef->{active}->{config}->{vlabel}."\n";
print "graph_category ".$graphsRef->{active}->{config}->{category}."\n";
print "graph_info ".$graphsRef->{active}->{config}->{info}."\n";
# Print the Data Value Info
foreach my $dsrc (@{$graphsRef->{active}->{datasrc}}) {
while ( my ($key, $value) = each (%{$dsrc})) {
next if ($key eq 'name');
print "$dsrc->{name}.$key $value\n";
}
}
return;
}
=head2 print_listener_config
This subroutine prints out the graph information for our listeners graphs.
It prints the sub-multigraphs first based on stream id, and finally the
root listeners graph. Its not suggested that you mess with this routine
unless you fully understand what its doing and how munin graph_args work.
=cut
sub print_listener_config {
my ($sidDataRef) = (@_);
foreach my $sid (sort keys %{$sidDataRef}) {
# Print the Config Info First
print "multigraph shoutcast2_listeners.listeners\_sid\_$sid\n";
print "graph_title ".$graphsRef->{sid_listeners}->{config}->{title}." for StreamID: $sid\n";
print "graph_args ".$graphsRef->{sid_listeners}->{config}->{args}."\n";
print "graph_vlabel ".$graphsRef->{sid_listeners}->{config}->{vlabel}."\n";
print "graph_category streamid_$sid\n";
print "graph_info ".$graphsRef->{sid_listeners}->{config}->{info}."\n";
# Print the Data Value Info
foreach my $dsrc (@{$graphsRef->{sid_listeners}->{datasrc}}) {
while ( my ($key, $value) = each (%{$dsrc})) {
next if ($key eq 'name');
next if ($key eq 'xmlkey');
print "$dsrc->{name}.$key $value\n";
}
}
}
print "multigraph shoutcast2_listeners\n";
print "graph_title ".$graphsRef->{listeners}->{config}->{title}."\n";
print "graph_args ".$graphsRef->{listeners}->{config}->{args}."\n";
print "graph_vlabel ".$graphsRef->{listeners}->{config}->{vlabel}."\n";
print "graph_category ".$graphsRef->{listeners}->{config}->{category}."\n";
print "graph_info ".$graphsRef->{listeners}->{config}->{info}."\n";
# Print the Data Value Info
foreach my $dsrc (@{$graphsRef->{listeners}->{datasrc}}) {
while ( my ($key, $value) = each (%{$dsrc})) {
next if ($key eq 'name');
print "$dsrc->{name}.$key $value\n";
}
}
return;
}
=head2 check_autoconf
This subroutine is called up when we intercept autoconf specified in ARGV[0]
If we are able to connect to the shoutcast service as admin and fetch the main
admin stats page, we will return ok, otherwise we will return no and the error
response we got from LWP::UserAgent.
=cut
sub check_autoconf {
my ($returnBit,$adminRef) = fetch_admin_data();
if ($returnBit == 0) {
# $adminRef returned a string, we'll just print it out.
print "no (error response: $adminRef)\n";
} else {
print "yes\n";
}
return;
}
=head2 fetch_sid_data
This subroutine is called up to fetch information on a per stream mentality.
If we are able to connect to the shoutcast service and get the stats we will
return 1 and a hashref of the de-coded xml information, otherwise we return 0
so that we know that we have failed and can handle it gracefully.
=cut
sub fetch_sid_data {
my ($sid) = (@_);
my $url = 'http://'.$host.':'.$port.'/stats?sid='.$sid;
my $response = $ua->get($url);
if ($response->is_success) {
my $returnRef = XMLin($response->decoded_content);
return (1, $returnRef);
} else {
return (0, $response->status_line);
}
}
=head2 fetch_admin_data
This subroutine is called up to fetch information from the admin page to get stream ids.
This subroutine is also used to test that we can connect to the shoutcast service
and if not we can fail gracefully. If we are able to connect to the shoutcast service
and get the stats we will return 1 and a hashref of the de-coded xml information,
otherwise we return 0 so that we know that we have failed and can handle it gracefully.
=cut
sub fetch_admin_data {
my $url = 'http://'.$host.':'.$port.'/admin.cgi?sid=1&mode=viewxml&page=6';
my $response = $ua->get($url);
if ($response->is_success) {
my $returnRef = XMLin($response->decoded_content);
if (($returnRef->{STREAMCONFIGS}->{TOTALCONFIGS} > 0) && (defined($returnRef->{STREAMCONFIGS}->{STREAMCONFIG}))) {
return (1, $returnRef);
} else {
return (0, 'Unable to Detect any Stream Configurations');
}
} else {
return (0, $response->status_line);
}
}
# for Munin Plugin Gallery
# graph_category streaming