#!/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); } }