#!/usr/bin/perl -w # # boinc_wus - Munin plugin to monitor states of all BOINC WUs # # Run 'perldoc boinc_wus' for full man page # # Author: Palo M. # Modified by: Paul Saunders # License: GPLv3 # # # Parameters supported: # config # # # Configurable variables # boinccmd - command-line control program (default: boinc_cmd) # host - Host to query (default: none) # port - GUI RPC port (default: none = use BOINC-default) # boincdir - Directory containing appropriate password file # gui_rpc_auth.cfg (default: none) # verbose - Whether display more detailed states (default: 0) # password - Password for BOINC (default: none) !!! UNSAFE !!! # # # $Log$ # # Revision 1.1 2011/03/22 Paul Saunders # Update for BOINC 6.12 # Add colours from http://boinc.netsoft-online.com/e107_plugins/forum/forum_viewtopic.php?3 # Revision 1.0 2009/09/13 Palo M. # Add documentation and license information # Ready to publish on Munin Exchange # Revision 0.9 2009/09/13 Palo M. # Add possibility to read password from file # Revision 0.8 2009/09/12 Palo M. # Update default binary name: boinc_cmd -> boinccmd # Revision 0.7 2008/08/29 Palo M. # Creation - Attempt to port functionality from C++ code # # (Revisions 0.1 - 0.6) were done in C++ # # # # Magic markers: #%# family=contrib use strict; ######################################################################### # 1. Parse configuration variables # my $BOINCCMD = exists $ENV{'boinccmd'} ? $ENV{'boinccmd'} : "boinccmd"; my $HOST = exists $ENV{'host'} ? $ENV{'host'} : undef; my $PORT = exists $ENV{'port'} ? $ENV{'port'} : undef; my $PASSWORD = exists $ENV{'password'} ? $ENV{'password'} : undef; my $BOINCDIR = exists $ENV{'boincdir'} ? $ENV{'boincdir'} : undef; my $VERBOSE = exists $ENV{'verbose'} ? $ENV{'verbose'} : "0"; ######################################################################### # 2. Basic executable # if (defined $HOST) { $BOINCCMD .= " --host $HOST"; if (defined $PORT) { $BOINCCMD .= ":$PORT"; } } if (defined $PASSWORD) { $BOINCCMD .= " --passwd $PASSWORD"; } if (defined $BOINCDIR) { chdir $BOINCDIR; } ######################################################################### # 3. Initialize output structure # my $wu_states = { wu_run => 0, wu_pre => 0, wu_sus => 0, wu_dld => 0, wu_rtr => 0, wu_dlg => 0, wu_upl => 0, wu_err => 0, wu_abt => 0, wu_other => 0 }; ######################################################################### # 4. Fetch all needed data from BOINC-client with single call # my $prj_status = ""; my $results = ""; my $simpleGuiInfo = `$BOINCCMD --get_simple_gui_info 2>/dev/null`; if ($simpleGuiInfo ne "") { # Some data were retrieved, so let's split them my @sections; my @section1; @sections = split /=+ Projects =+\n/, $simpleGuiInfo; @section1 = split /=+ [A-z]+ =+\n/, $sections[1]; $prj_status = $section1[0]; @sections = split /=+ (?:Results|Tasks) =+\n/, $simpleGuiInfo; @section1 = split /=+ [A-z]+ =+\n/, $sections[1]; $results = $section1[0]; } ######################################################################### # 5. Parse BOINC data # # 5.a) Create project info structure my @prjInfos = split /\d+\) -+\n/, $prj_status; shift @prjInfos; # Throw out first empty line my @susp_projects; # array of suspended projects for my $prj_info (@prjInfos) { my @lines = split /\n/, $prj_info; my @prjURL = grep /^\s+master URL: /,@lines; if ($#prjURL != 0) {die "Unexpected output from boinccmd"; } my $prjURL =$prjURL[0]; $prjURL =~ s/^\s+master URL: //; my @suspGUI = grep /^\s+suspended via GUI: /,@lines; if ($#suspGUI != 0) {die "Unexpected output from boinccmd"; } my $suspGUI =$suspGUI[0]; $suspGUI =~ s/^\s+suspended via GUI: //; if ($suspGUI eq "yes") { push @susp_projects, $prjURL } } # 5.b) Parse results, check their states my @rsltInfos = split /\d+\) -+\n/, $results; shift @rsltInfos; # Throw out first empty line for my $rslt_info (@rsltInfos) { my @lines = split /\n/, $rslt_info; my @schedstat = grep /^\s+scheduler state: /,@lines; my $schedstat = $schedstat[0]; $schedstat =~ s/^\s+scheduler state: //; my @state = grep /^\s+state: /,@lines; my $state = $state[0]; $state =~ s/^\s+state: //; my @acttask = grep /^\s+active_task_state: /,@lines; my $acttask = $acttask[0]; $acttask =~ s/^\s+active_task_state: //; my @suspGUI = grep /^\s+suspended via GUI: /,@lines; my $suspGUI =$suspGUI[0]; $suspGUI =~ s/^\s+suspended via GUI: //; my @prjURL = grep /^\s+project URL: /,@lines; my $prjURL =$prjURL[0]; $prjURL =~ s/^\s+project URL: //; if ($suspGUI eq "yes") { $wu_states->{wu_sus} += 1; next; } my @suspPRJ = grep /^$prjURL$/,@susp_projects; if ($#suspPRJ == 0) { $wu_states->{wu_sus} += 1; next; } if ($state eq "1") { # RESULT_FILES_DOWNLOADING $wu_states->{wu_dlg} += 1; next; } if ($state eq "2") { # RESULT_FILES_DOWNLOADED if ($schedstat eq "0") { # CPU_SCHED_UNINITIALIZED 0 $wu_states->{wu_dld} += 1; next; } if ($schedstat eq "1") { # CPU_SCHED_PREEMPTED 1 $wu_states->{wu_pre} += 1; next; } if ($schedstat eq "2") { # CPU_SCHED_SCHEDULED 2 if ($acttask eq "1") { # PROCESS_EXECUTING 1 $wu_states->{wu_run} += 1; next; } if ( ($acttask eq "0") || ($acttask eq "9") ) { # PROCESS_UNINITIALIZED 0 # PROCESS_SUSPENDED 9 # suspended by "user active"? $wu_states->{wu_sus} += 1; next; } $wu_states->{wu_other} += 1; next; } $wu_states->{wu_other} += 1; next; } if ($state eq "3") { # RESULT_COMPUTE_ERROR $wu_states->{wu_err} += 1; next; } if ($state eq "4") { # RESULT_FILES_UPLOADING $wu_states->{wu_upl} += 1; next; } if ($state eq "5") { # RESULT_FILES_UPLOADED $wu_states->{wu_rtr} += 1; next; } if ($state eq "6") { # RESULT_ABORTED $wu_states->{wu_abt} += 1; next; } $wu_states->{wu_other} += 1; } ######################################################################### # 6. Display output # if ( (defined $ARGV[0]) && ($ARGV[0] eq "config") ) { # # 6.a) Display config # if (defined $HOST) { print "host_name $HOST\n"; } print "graph_title BOINC work status\n"; print "graph_category htc\n"; print "graph_args --base 1000 -l 0\n"; print "graph_vlabel Workunits\n"; print "graph_total total\n"; # First state is AREA, next are STACK print "wu_run.label Running\n"; print "wu_run.draw AREA\n"; print "wu_run.type GAUGE\n"; print "wu_pre.label Preempted\n"; print "wu_pre.draw STACK\n"; print "wu_pre.type GAUGE\n"; print "wu_sus.label Suspended\n"; print "wu_sus.draw STACK\n"; print "wu_sus.type GAUGE\n"; print "wu_dld.label Ready to run\n"; print "wu_dld.draw STACK\n"; print "wu_dld.type GAUGE\n"; print "wu_rtr.label Ready to report\n"; print "wu_rtr.draw STACK\n"; print "wu_rtr.type GAUGE\n"; print "wu_dlg.label Downloading\n"; print "wu_dlg.draw STACK\n"; print "wu_dlg.type GAUGE\n"; print "wu_upl.label Uploading\n"; print "wu_upl.draw STACK\n"; print "wu_upl.type GAUGE\n"; if ($VERBOSE ne "0") { print "wu_err.label Computation Error\n"; print "wu_err.draw STACK\n"; print "wu_err.type GAUGE\n"; print "wu_abt.label Aborted\n"; print "wu_abt.draw STACK\n"; print "wu_abt.type GAUGE\n"; } print "wu_other.label other states\n"; print "wu_other.draw STACK\n"; print "wu_other.type GAUGE\n"; exit 0; } # # 6.b) Display state of WUs # print "wu_run.value $wu_states->{wu_run}\n"; print "wu_pre.value $wu_states->{wu_pre}\n"; print "wu_sus.value $wu_states->{wu_sus}\n"; print "wu_dld.value $wu_states->{wu_dld}\n"; print "wu_rtr.value $wu_states->{wu_rtr}\n"; print "wu_dlg.value $wu_states->{wu_dlg}\n"; print "wu_upl.value $wu_states->{wu_upl}\n"; if ($VERBOSE ne "0") { print "wu_err.value $wu_states->{wu_err}\n"; print "wu_abt.value $wu_states->{wu_abt}\n"; print "wu_other.value $wu_states->{wu_other}\n"; } else { my $other = $wu_states->{wu_err} + $wu_states->{wu_abt} + $wu_states->{wu_other}; print "wu_other.value $other\n"; } exit 0; ######################################################################### # perldoc section =head1 NAME boinc_wus - Munin plugin to monitor states of all BOINC WUs =head1 APPLICABLE SYSTEMS Linux machines running BOINC and munin-node - or - Linux servers (running munin-node) used to collect data from other systems which are running BOINC, but not running munin-node (e.g. non-Linux systems) =head1 CONFIGURATION Following configuration variables are supported: =over 12 =item B command-line control program (default: boinccmd) =item B Host to query (default: none) =item B GUI RPC port (default: none = use BOINC-default) =item B Directory containing appropriate file gui_rpc_auth.cfg (default: none) =item B Display unusual states details (default: 0 = Summarize unusual states as C) =item B Password for BOINC (default: none) =back =head2 B Using of variable B poses a security risk. Even if the Munin configuration file for this plugin containing BOINC-password is properly protected, the password is exposed as environment variable and finally passed to boinccmd as a parameter. It is therefore possible for local users of the machine running this plugin to eavesdrop the BOINC password. Using of variable password is therefore strongly discouraged and is left here as a legacy option and for testing purposes. It should be always possible to use B variable instead - in such case the file gui_rpc_auth.cfg is read by boinccmd binary directly. If this plugin is used to fetch data from remote system, the gui_rpc_auth.cfg can be copied to special directory in a secure way (e.g. via scp) and properly protected by file permissions. =head1 INTERPRETATION This plugin shows how many BOINC workunits are in all the various states. The most important states C, C, C, C, C, C and C are always displayed. All other states are shown as C. If the variable B is used, additionally also states C and C are shown separately (they are included in C otherwise). =head1 EXAMPLES =head2 Local BOINC Example BOINC is running on local machine. The BOINC binaries are installed in F, the BOINC is running in directory F under username boinc, group boinc and the password is used to protect access to BOINC: [boinc_*] group boinc env.boinccmd /opt/boinc/custom-6.10.1/boinccmd env.boincdir /usr/local/boinc env.verbose 1 =head2 Remote BOINC Example BOINC is running on 2 remote machines C and C. On the local machine the binary of command-line interface is installed in directory F. The BOINC password used on the remote machine C is stored in file F. The BOINC password used on the remote machine C is stored in file F. These files are owned and readable by root, readable by group munin and not readable by others. There are 2 symbolic links to this plugin created in the munin plugins directory (usually F): F and F [snmp_foo_boinc*] group munin env.boinccmd /usr/local/bin/boinccmd env.host foo env.boincdir /etc/munin/boinc/foo [snmp_bar_boinc*] group munin env.boinccmd /usr/local/bin/boinccmd env.host bar env.boincdir /etc/munin/boinc/bar This way the plugin can be used by Munin the same way as the Munin plugins utilizng SNMP (although this plugin itself does not use SNMP). =head1 BUGS There is no C capability at the moment. This is due to the fact, that BOINC installations may vary over different systems, sometimes using default directory from distribution (e.g. F in Debian or Ubuntu), but often running in user directories or in other separate directories. Also the user-ID under which BOINC runs often differs. Under these circumstances the C would be either lame or too complicated. =head1 AUTHOR Palo M. =head1 LICENSE GPLv3 L =cut # vim:syntax=perl