diff --git a/plugins/shoutcast/shoutcast2_multi b/plugins/shoutcast/shoutcast2_multi new file mode 100755 index 00000000..79ba142f --- /dev/null +++ b/plugins/shoutcast/shoutcast2_multi @@ -0,0 +1,442 @@ +#!/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); + } +} +