443 lines
14 KiB
Plaintext
443 lines
14 KiB
Plaintext
|
#!/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 => 'shoutcast2',
|
||
|
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 => 'shoutcast2',
|
||
|
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
|
||
|
|
||
|
Thie 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);
|
||
|
}
|
||
|
}
|
||
|
|