1
0
Fork 0
mirror of https://github.com/munin-monitoring/contrib.git synced 2025-08-03 06:38:24 +00:00

- have some dirs

This commit is contained in:
Steve Schnepp 2012-02-13 18:24:46 +01:00
parent 0b089ea777
commit 08346aac58
687 changed files with 0 additions and 0 deletions

234
plugins/boinc/boinc_credit Executable file
View file

@ -0,0 +1,234 @@
#!/usr/bin/perl
# -*- cperl -*-
=head1 NAME
boinc_credit - Munin plugin to monitor BOINC credit for a user
=head1 APPLICABLE SYSTEMS
Any
=head1 CONFIGURATION
All that should be needed is to add the following to your config:
[boinc_credit]
env.cpid 1234abcd....
Where the value is your Cross Project ID (CPID).
=head1 MAGIC MARKERS
#%# family=auto contrib
#%# capabilities=autoconf
=head1 VERSION
1.0
=head1 AUTHOR
Paul Saunders <darac+munin@darac.org.uk>
=cut
use strict;
use warnings;
use lib $ENV{'MUNIN_LIBDIR'};
use Munin::Plugin;
my $CPID = $ENV{cpid};
my $STATSURL = $ENV{statsurl}
|| "http://boinc.netsoft-online.com/get_user.php?cpid=$CPID";
my $TICK = $ENV{tick} || 60; # minutes
my $ret;
if ( !eval "require XML::Simple;" ) {
$ret += "Could not load XML::Simple; ";
}
if ( !eval "require LWP::Simple;" ) {
$ret += "Could not load LWP::Simple; ";
}
if ( defined $ARGV[0] and $ARGV[0] eq 'autoconf' ) {
# Can't auto configure at the moment.
# At least, until we can calculate CPID
print "no\n";
exit 0;
}
my $lastread;
sub save_data {
my @projdata = @_;
# Do we need to save this data?
if ( !defined $lastread or time >= $lastread + ( $TICK * 60 ) ) {
$lastread = time;
my @save_vector;
push @save_vector, $lastread;
foreach (@projdata) {
# Serialise the hash
my @tempbuf;
foreach my $key ( keys %{$_} ) {
push @tempbuf, $key . '¬' . $_->{$key};
}
push @save_vector, join( '^^', @tempbuf );
}
save_state(@save_vector);
}
}
sub load_data {
# Bring the data back in
my @save_vector = restore_state();
# Read the timestamp, Do we need to refresh the data?
$lastread = shift @save_vector;
my @projarray;
foreach (@save_vector) {
my $hashref;
foreach ( split /\^\^/ ) {
my ( $key, $value ) = split /¬/;
$hashref->{$key} = $value;
}
push @projarray, $hashref;
}
if ( !defined $lastread or time >= ( $lastread + ( $TICK * 60 ) ) ) {
# Data is stale
eval {
# Fetch the XML
my $content;
unless ( defined( $content = LWP::Simple::get $STATSURL) ) {
die "Could not get $STATSURL";
}
my $xmlref = XML::Simple::XMLin( $content, ForceArray => 1 );
my @temparray;
foreach ( @{ $xmlref->{project} } ) {
my $temphash;
$temphash->{name} = $_->{name}[0];
$temphash->{id} = $_->{project_id}[0];
$temphash->{credit} = $_->{total_credit}[0];
$temphash->{creditfract} =
$_->{total_credit}[0] / $xmlref->{total_credit}[0];
$temphash->{totalcredit} = $xmlref->{total_credit}[0];
$temphash->{rank} = $_->{project_rank_total_credit}[0];
push @temparray, $temphash;
}
# If the above threw an error, we won't overwrite the old data
@projarray = @temparray;
1;
} or do {
print $@;
}
}
return @projarray;
}
# Project Colours from http://boinc.netsoft-online.com/e107_plugins/forum/forum_viewtopic.php?3
sub rgb($$$) {
return sprintf( '%02x%02x%02x', shift, shift, shift );
}
my %project_colour = (
'climatepredition.net' => rgb( 0, 139, 69 ),
'Predictor@Home' => rgb( 135, 206, 235 ),
'SETI@home' => rgb( 65, 105, 225 ),
'Einstein@Home' => rgb( 255, 165, 0 ),
'Rosetta@home' => rgb( 238, 130, 238 ),
'PrimeGrid' => rgb( 205, 197, 191 ),
'LHC@home' => rgb( 255, 127, 80 ),
'World Community Grid' => rgb( 250, 128, 114 ),
'BURP' => rgb( 0, 255, 127 ),
'SZTAKI Desktop Grid' => rgb( 205, 79, 57 ),
'uFluids' => rgb( 0, 0, 0 ),
'SIMAP' => rgb( 143, 188, 143 ),
'Folding@Home' => rgb( 153, 50, 204 ),
'MalariaControl' => rgb( 30, 144, 255 ),
'The Lattice Project' => rgb( 0, 100, 0 ),
'Pirates@Home' => rgb( 127, 255, 0 ),
'BBC Climate Change Experiment' => rgb( 205, 173, 0 ),
'Leiden Classical' => rgb( 140, 34, 34 ),
'SETI@home Beta' => rgb( 152, 245, 255 ),
'RALPH@Home' => rgb( 250, 240, 230 ),
'QMC@HOME' => rgb( 144, 238, 144 ),
'XtremLab' => rgb( 130, 130, 130 ),
'HashClash' => rgb( 255, 105, 180 ),
'cpdn seasonal' => rgb( 255, 255, 255 ),
'Chess960@Home Alpha' => rgb( 165, 42, 42 ),
'vtu@home' => rgb( 255, 0, 0 ),
'LHC@home alpha' => rgb( 205, 133, 63 ),
'TANPAKU' => rgb( 189, 183, 107 ),
'other' => rgb( 255, 193, 37 ),
'Rectilinear Crossing Number' => rgb( 83, 134, 139 ),
'Nano-Hive@Home' => rgb( 193, 205, 193 ),
'Spinhenge@home' => rgb( 255, 240, 245 ),
'RieselSieve' => rgb( 205, 183, 158 ),
'Project Neuron' => rgb( 139, 58, 98 ),
'RenderFarm@Home' => rgb( 210, 105, 30 ),
'Docking@Home' => rgb( 178, 223, 238 ),
'proteins@home' => rgb( 0, 0, 255 ),
'DepSpid' => rgb( 139, 90, 43 ),
'ABC@home' => rgb( 222, 184, 135 ),
'BOINC alpha test' => rgb( 245, 245, 220 ),
'WEP-M+2' => rgb( 0, 250, 154 ),
'Zivis Superordenador Ciudadano' => rgb( 255, 239, 219 ),
'SciLINC' => rgb( 240, 248, 255 ),
'APS@Home' => rgb( 205, 91, 69 ),
'PS3GRID' => rgb( 0, 139, 139 ),
'Superlink@Technion' => rgb( 202, 255, 112 ),
'BRaTS@Home' => rgb( 255, 106, 106 ),
'Cosmology@Home' => rgb( 240, 230, 140 ),
'SHA 1 Collision Search' => rgb( 255, 250, 205 ),
);
if ( defined $ARGV[0] and $ARGV[0] eq 'config' ) {
if ($ret) {
print $ret;
exit 1;
}
my @projdata = load_data();
print <<EOF;
graph_args --base 1000 --logarithmic
graph_vlabel Cobblestones
graph_category boinc
graph_title BOINC Total Credit
EOF
foreach ( sort { $a->{id} <=> $b->{id} } @projdata ) {
my $fieldname = 'proj' . $_->{id};
print <<EOF;
$fieldname.label $_->{name}
$fieldname.type GAUGE
$fieldname.info Total Credit for project $_->{name}
EOF
if ( exists $project_colour{ $_->{name} } ) {
print "$fieldname.colour $project_colour{$_->{name}}\n";
}
}
save_data(@projdata);
exit 0;
}
my @projdata = load_data();
foreach ( sort { $a->{id} <=> $b->{id} } @projdata ) {
my $fieldname = 'proj' . $_->{id};
print "$fieldname.value $_->{credit}\n";
printf "$fieldname.extinfo %.2f%% of Total Credit (%.2f out of %.2f)\n",
$_->{creditfract} * 100, $_->{credit}, $_->{totalcredit};
}
save_data(@projdata);
exit 0;

438
plugins/boinc/boinc_estwk Executable file
View file

@ -0,0 +1,438 @@
#!/usr/bin/perl -w
#
# boinc_estwk - Munin plugin to monitor estimated time of BOINC WUs
#
# Run 'perldoc boinc_estwk' for full man page
#
# Author: Palo M. <palo.gm@gmail.com>
# License: GPLv3 <http://www.gnu.org/licenses/gpl-3.0.txt>
#
#
# Parameters supported:
# config
#
#
# Configurable variables
# boinccmd - command-line control program (default: boinccmd)
# 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)
# estwk_warn - Warning level - minimum estimated work (default: 24.00 hours)
# password - Password for BOINC (default: none) !!! UNSAFE !!!
#
#
# $Log$
#
# 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/30 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 $ESTWKWRN = exists $ENV{'estwk_warn'} ? $ENV{'estwk_warn'} : 24;
#########################################################################
# 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. Get host info, to retrieve number of CPUs
#
my $nCPUs;
my $hostInfo = `$BOINCCMD --get_host_info 2>/dev/null`;
if ($hostInfo ne "") {
my @hostInfo = split /\n/, $hostInfo;
my @nCPUs = grep /^\s+#CPUS: /,@hostInfo;
if ($#nCPUs != 0) { die "Unexpected output from boinccmd"; }
$nCPUs = $nCPUs[0];
$nCPUs =~ s/^\s+#CPUS: //;
no warnings; # for following line only
if ($nCPUs < 1) { die "Unexpected output from boinccmd"; }
}
else {
# No host info (e.g. client not running)
exit -1;
}
#print "$nCPUs\n";
#########################################################################
# 4. Display config if applicable
#
if ( (defined $ARGV[0]) && ($ARGV[0] eq "config") ) {
if (defined $HOST) {
print "host_name $HOST\n";
}
print "graph_title BOINC work cache estimation\n";
print "graph_category BOINC\n";
print "graph_args --base 1000 -l 0 --alt-autoscale-max\n";
print "graph_vlabel Hours\n";
print "graph_scale no\n";
# Longest WU is AREA, each CPU estimated is LINE2
print "longest.label Longest WU\n";
print "longest.draw AREA\n";
print "longest.type GAUGE\n";
for (my $i = 0; $i < $nCPUs; ++$i) {
print "cpu$i.label CPU$i\n";
print "cpu$i.draw LINE2\n";
print "cpu$i.type GAUGE\n";
printf "cpu$i.warning %.2f:\n",$ESTWKWRN;
print "cpu$i.critical 0:\n";
}
exit 0;
}
#########################################################################
# 5. 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 =+\n/, $simpleGuiInfo;
@section1 = split /=+ [A-z]+ =+\n/, $sections[1];
$results = $section1[0];
}
#########################################################################
# 6. Parse BOINC data
#
# 6.a) Get suspended projects
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
}
}
for my $i (@susp_projects) { print "$i\n"; }
# 6.b) Parse results, check their states
# Get those which are NOT suspended by GUI
my @rsltInfos = split /\d+\) -+\n/, $results;
shift @rsltInfos; # Throw out first empty line
my @rsltRemain;
for my $rslt_info (@rsltInfos) {
my @lines = split /\n/, $rslt_info;
my @estRemain = grep /^\s+estimated CPU time remaining: /,@lines;
my $estRemain = $estRemain[0];
$estRemain =~ s/^\s+estimated CPU time remaining: //;
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") {
# This result is not in work cache - at the moment
next;
}
my @suspPRJ = grep /^$prjURL$/,@susp_projects;
if ($#suspPRJ == 0) {
# This result is not in work cache - at the moment
next;
}
if ($state eq "2") {
# RESULT_FILES_DOWNLOADED
if ( ($schedstat eq "0") ||
($schedstat eq "1") ) {
# CPU_SCHED_UNINITIALIZED 0
# Not started yet: result is available in work cache
# CPU_SCHED_PREEMPTED 1
# preempted: result is available in work cache
push @rsltRemain,$estRemain;
next;
}
if ($schedstat eq "2") {
# CPU_SCHED_SCHEDULED 2
if ( ($acttask eq "1") ||
($acttask eq "0") ||
($acttask eq "9") ) {
# PROCESS_EXECUTING 1
# running
# PROCESS_UNINITIALIZED 0
# PROCESS_SUSPENDED 9
# suspended by "user active"/benchmark?
# available in work cache
push @rsltRemain,$estRemain;
next;
}
# other active-task-state - maybe failing/aborted WU
# => not in work cache
next;
}
# There should be no other scheduler state
next;
}
# RESULT_FILES_DOWNLOADING
# RESULT_COMPUTE_ERROR
# RESULT_FILES_UPLOADING
# RESULT_FILES_UPLOADED
# RESULT_ABORTED
# => not in work cache
}
#########################################################################
# 7. Distribute remaining results per CPUs
#
# 7.a) Sort remaining results descending
my @sortRemain = sort {$b <=> $a} @rsltRemain;
# 7.b) Assign to CPU with smallest workcache
my @CPUcache;
for (my $i = 0; $i < $nCPUs; ++$i) {
$CPUcache[$i] = 0;
}
for my $length (@sortRemain) {
# find CPU with smallest workcache:
my @sortedCPUs = sort {$a <=> $b} @CPUcache;
$sortedCPUs[0] = $sortedCPUs[0] + $length;
@CPUcache = @sortedCPUs;
}
# At the end, sort CPUs descending
@CPUcache = sort {$b <=> $a} @CPUcache;
#########################################################################
# 8. Display output
#
# Convert from seconds to hours
printf "longest.value %.2f\n",$sortRemain[0]/3600;
for (my $i = 0; $i < $nCPUs; ++$i) {
printf "cpu$i.value %.2f\n",$CPUcache[$i]/3600;
}
exit 0;
#########################################################################
# perldoc section
=head1 NAME
boinc_estwk - Munin plugin to monitor estimated time of 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<boinccmd>
command-line control program (default: boinccmd)
=item B<host>
Host to query (default: none)
=item B<port>
GUI RPC port (default: none = use BOINC-default)
=item B<boincdir>
Directory containing appropriate file gui_rpc_auth.cfg (default: none)
=item B<estwk_warn>
Warning level - minimum estimated work (default: 24.00 hours)
=item B<password>
Password for BOINC (default: none)
=back
=head2 B<Security Consideration:>
Using of variable B<password> 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<boincdir> 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 the estimated remaining computation time for all CPUs of
the machine and the estimated remaining computation time of longest workunit.
The estimation is based on assumption that the workunits of different lengths
will be distributed to the CPUs evenly (which is not always the case).
The warning level can be used to warn in forward about the risk of workunits
local cache depletion and BOINC client running out of the work.
Although such warning can be achieved by configuring Munin master, there is
also this option to configure it on munin-node side.
=head1 EXAMPLES
=head2 Local BOINC Example
BOINC is running on local machine. The BOINC binaries are installed in
F</opt/boinc/custom-6.10.1/>, the BOINC is running in directory
F</usr/local/boinc/> under username boinc, group boinc and the password is used
to protect access to BOINC.
Warning will be set when estimated work for any of CPUs will decrease under
48 hours:
[boinc_*]
group boinc
env.boinccmd /opt/boinc/custom-6.10.1/boinccmd
env.boincdir /usr/local/boinc
env.warn 48
=head2 Remote BOINC Example
BOINC is running on 2 remote machines C<foo> and C<bar>.
On the local machine the binary of command-line interface is installed in
directory F</usr/local/bin/>.
The BOINC password used on the remote machine C<foo> is stored in file
F</etc/munin/boinc/foo/gui_rpc_auth.cfg>.
The BOINC password used on the remote machine C<bar> is stored in file
F</etc/munin/boinc/bar/gui_rpc_auth.cfg>.
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</etc/munin/plugins/>): F<snmp_foo_boincestwk> and
F<snmp_bar_boincestwk>
[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
The estimation is based on simple assumption, that longest workunits will be
processed first. This is the case when work is distributed evenly among CPUs.
But this is not always the case, because various deadlines for various
workunits may fire the "panic mode" of BOINC and scheduling could be much
different.
For example, there can be 4 CPUs, and BOINC having downloaded 4 workunits
with estimated run-time 1 hour each and 3 workunits with estimated run-time
4 hours each.
This Munin plugin will report estimated work 4 hours for each CPU.
But if deadline of those 1-hour workunits will be much shorter than deadline
of those 4-hours workunits, BOINC will schedule short workunits first (for all
4 CPUs) and after finishing them it will schedule those long workunits.
This will result in real computation for 5 hours on 3 CPUs but only 1 hour on
remaining 4th CPU. So after 1 hour of computation 1 of CPUs will run out of
work.
There is no C<autoconf> 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</var/lib/boinc/> 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<autoconf> would be either lame or too
complicated.
=head1 AUTHOR
Palo M. <palo.gm@gmail.com>
=head1 LICENSE
GPLv3 L<http://www.gnu.org/licenses/gpl-3.0.txt>
=cut
# vim:syntax=perl

395
plugins/boinc/boinc_projs Executable file
View file

@ -0,0 +1,395 @@
#!/usr/bin/perl -w
#
# boinc_projs - Munin plugin to monitor actively running BOINC projects
#
# Run 'perldoc boinc_projs' for full man page
#
# Author: Palo M. <palo.gm@gmail.com>
# Modified by: Paul Saunders <darac+munin@darac.org.uk>
# License: GPLv3 <http://www.gnu.org/licenses/gpl-3.0.txt>
#
#
# Parameters supported:
# config
#
#
# Configurable variables
# boinccmd - command-line control program (default: boinccmd)
# host - Host to query (default: none = use local host)
# port - GUI RPC port (default: none = use BOINC-default)
# boincdir - Directory containing appropriate password file
# gui_rpc_auth.cfg (default: none)
# 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;
#########################################################################
# 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 || die "Could not chdir to $BOINCDIR";
}
#########################################################################
# 3. 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];
}
if ($prj_status eq "") { exit -1; }
# 3.a) Split --get_project_status into projects
my @prjInfos = split /\d+\) -+\n/, $prj_status;
shift @prjInfos; # Throw out first empty line
# 3.b) Fetch project infos
my %projects; # Store projects infos here
my @projects; # Just to keep the order of projects
for my $prj_info (@prjInfos) {
my @lines = split /\n/, $prj_info;
my $line1 = shift @lines; # get project name
if ($line1 !~ /^\s+name: /) { die "Unexpected output from boinccmd"; }
$line1 =~ s/^\s+name: //; # Make just the project name itself
my $line2 = shift @lines; # get project URL
if ($line2 !~ /^\s+master URL: /) { die "Unexpected output from boinccmd"; }
$line2 =~ s/^\s+master URL: //; # Make just the URL itself
my $prj_url = $line2;
my $prj_name = $line1;
$line1 =~ s/\@/at/g;
$line1 =~ s/[^0-9A-z]/_/g;
my $prj_var = "prj_" . $line1;
push @projects,$prj_url;
$projects{$prj_url} = {
prj_name => $prj_name,
prj_var => $prj_var,
prj_running => 0
};
}
#########################################################################
# 4. Parse results
#
# 4.a) Split --get_results
my @rsltInfos = split /\d+\) -+\n/, $results;
shift @rsltInfos; # Throw out first empty line
# 4.b) Parse results, find those which are running
for my $rslt_info (@rsltInfos) {
my @lines = split /\n/, $rslt_info;
my @url = grep /^\s+project URL: /,@lines;
my $url = $url[0];
$url =~ s/^\s+project URL: //; # Make just the URL itself
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: //;
if ( ($schedstat eq "2") && ($state eq "2") && ($acttask eq "1") ) {
# This is running task
$projects{$url}->{prj_running} += 1;
}
}
#########################################################################
# 5. Display output
#
# Project Colours from http://boinc.netsoft-online.com/e107_plugins/forum/forum_viewtopic.php?3
sub rgb($$$){
return sprintf ('%02x%02x%02x', shift, shift, shift);
}
my %project_colour = (
'climatepredition.net' => rgb(0,139,69),
'Predictor@Home' => rgb(135,206,235),
'SETI@home' => rgb(65,105,225),
'Einstein@Home' => rgb(255,165,0),
'Rosetta@home' => rgb(238,130,238),
'PrimeGrid' => rgb(205,197,191),
'LHC@home' => rgb(255,127,80),
'World Community Grid' => rgb(250,128,114),
'BURP' => rgb(0,255,127),
'SZTAKI Desktop Grid' => rgb(205,79,57),
'uFluids' => rgb(0,0,0),
'SIMAP' => rgb(143,188,143),
'Folding@Home' =>rgb(153,50,204),
'MalariaControl' => rgb(30,144,255),
'The Lattice Project' => rgb(0,100,0),
'Pirates@Home' => rgb(127,255,0),
'BBC Climate Change Experiment' => rgb(205,173,0),
'Leiden Classical' => rgb(140,34,34),
'SETI@home Beta' => rgb(152,245,255),
'RALPH@Home' => rgb(250,240,230),
'QMC@HOME' => rgb(144,238,144),
'XtremLab' => rgb(130,130,130),
'HashClash' => rgb(255,105,180),
'cpdn seasonal' => rgb(255,255,255),
'Chess960@Home Alpha' => rgb(165,42,42),
'vtu@home' => rgb(255,0,0),
'LHC@home alpha' => rgb(205,133,63),
'TANPAKU' => rgb(189,183,107),
'other' => rgb(255,193,37),
'Rectilinear Crossing Number' => rgb(83,134,139),
'Nano-Hive@Home' => rgb(193,205,193),
'Spinhenge@home' => rgb(255,240,245),
'RieselSieve' => rgb(205,183,158),
'Project Neuron' => rgb(139,58,98),
'RenderFarm@Home' => rgb(210,105,30),
'Docking@Home' => rgb(178,223,238),
'proteins@home' => rgb(0,0,255),
'DepSpid' => rgb(139,90,43),
'ABC@home' => rgb(222,184,135),
'BOINC alpha test' => rgb(245,245,220),
'WEP-M+2' => rgb(0,250,154),
'Zivis Superordenador Ciudadano' => rgb(255,239,219),
'SciLINC' => rgb(240,248,255),
'APS@Home' => rgb(205,91,69),
'PS3GRID' => rgb(0,139,139),
'Superlink@Technion' => rgb(202,255,112),
'BRaTS@Home' => rgb(255,106,106),
'Cosmology@Home' => rgb(240,230,140),
'SHA 1 Collision Search' => rgb(255,250,205),
);
if ( (defined $ARGV[0]) && ($ARGV[0] eq "config") ) {
#
# 5.a) Display config
#
if (defined $HOST) {
print "host_name $HOST\n";
}
print "graph_title Running BOINC processes\n";
print "graph_category BOINC\n";
print "graph_args --base 1000 -l 0\n";
print "graph_vlabel BOINC applications\n";
print "graph_total Total\n";
# First project is AREA, next are STACK
# Not nice, but fast:
my $prj1 = shift @projects;
print "$projects{$prj1}->{prj_var}.label $projects{$prj1}->{prj_name}\n";
if (exists $project_colour{$projects{$prj1}->{prj_name}}){
print "$projects{$prj1}->{prj_var}.colour $project_colour{$projects{$prj1}->{prj_name}}\n";
}
print "$projects{$prj1}->{prj_var}.draw AREA\n";
print "$projects{$prj1}->{prj_var}.type GAUGE\n";
for my $prjN (@projects) {
print "$projects{$prjN}->{prj_var}.label $projects{$prjN}->{prj_name}\n";
if (exists $project_colour{$projects{$prjN}->{prj_name}}){
print "$projects{$prjN}->{prj_var}.colour $project_colour{$projects{$prjN}->{prj_name}}\n";
}
print "$projects{$prjN}->{prj_var}.draw STACK\n";
print "$projects{$prjN}->{prj_var}.type GAUGE\n";
}
exit 0;
}
#
# 5.b) Display running state of projects
#
for my $prjN (@projects) {
print "$projects{$prjN}->{prj_var}.value $projects{$prjN}->{prj_running}\n";
}
exit 0;
#########################################################################
# perldoc section
=head1 NAME
boinc_projs - Munin plugin to monitor actively running BOINC projects.
=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<boinccmd>
command-line control program (default: boinccmd)
=item B<host>
Host to query (default: none)
=item B<port>
GUI RPC port (default: none = use BOINC-default)
=item B<boincdir>
Directory containing appropriate file gui_rpc_auth.cfg (default: none)
=item B<password>
Password for BOINC (default: none)
=back
=head2 B<Security Consideration:>
Using of variable B<password> 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<boincdir> 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 the number of currently running BOINC tasks on the machine.
If machine is attached to several BOINC projects, data for all these projects
are displayed.
=head1 EXAMPLES
=head2 Local BOINC Example
BOINC is running on local machine. The BOINC binaries are installed in
F</opt/boinc/custom-6.10.1/>, the BOINC is running in directory
F</usr/local/boinc/> 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
=head2 Remote BOINC Example
BOINC is running on 2 remote machines C<foo> and C<bar>.
On the local machine the binary of command-line interface is installed in
directory F</usr/local/bin/>.
The BOINC password used on the remote machine C<foo> is stored in file
F</etc/munin/boinc/foo/gui_rpc_auth.cfg>.
The BOINC password used on the remote machine C<bar> is stored in file
F</etc/munin/boinc/bar/gui_rpc_auth.cfg>.
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</etc/munin/plugins/>): F<snmp_foo_boincprojs> and
F<snmp_bar_boincprojs>
[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<autoconf> 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</var/lib/boinc/> 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<autoconf> would be either lame or too
complicated.
=head1 AUTHOR
Palo M. <palo.gm@gmail.com>
=head1 LICENSE
GPLv3
=cut
# vim:syntax=perl

448
plugins/boinc/boinc_wus Executable file
View file

@ -0,0 +1,448 @@
#!/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. <palo.gm@gmail.com>
# Modified by: Paul Saunders <darac+munin@darac.org.uk>
# License: GPLv3 <http://www.gnu.org/licenses/gpl-3.0.txt>
#
#
# 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 BOINC\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<boinccmd>
command-line control program (default: boinccmd)
=item B<host>
Host to query (default: none)
=item B<port>
GUI RPC port (default: none = use BOINC-default)
=item B<boincdir>
Directory containing appropriate file gui_rpc_auth.cfg (default: none)
=item B<verbose>
Display unusual states details (default: 0 = Summarize unusual states as C<other>)
=item B<password>
Password for BOINC (default: none)
=back
=head2 B<Security Consideration:>
Using of variable B<password> 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<boincdir> 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<Running>, C<Preempted>, C<Suspended>,
C<Ready to run>, C<Ready to report>, C<Downloading> and C<Uploading> are always
displayed. All other states are shown as C<other>.
If the variable B<verbose> is used, additionally also states
C<Computation Error> and C<Aborted> are shown separately (they are included in
C<other> otherwise).
=head1 EXAMPLES
=head2 Local BOINC Example
BOINC is running on local machine. The BOINC binaries are installed in
F</opt/boinc/custom-6.10.1/>, the BOINC is running in directory
F</usr/local/boinc/> 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<foo> and C<bar>.
On the local machine the binary of command-line interface is installed in
directory F</usr/local/bin/>.
The BOINC password used on the remote machine C<foo> is stored in file
F</etc/munin/boinc/foo/gui_rpc_auth.cfg>.
The BOINC password used on the remote machine C<bar> is stored in file
F</etc/munin/boinc/bar/gui_rpc_auth.cfg>.
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</etc/munin/plugins/>): F<snmp_foo_boincwus> and
F<snmp_bar_boincwus>
[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<autoconf> 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</var/lib/boinc/> 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<autoconf> would be either lame or too
complicated.
=head1 AUTHOR
Palo M. <palo.gm@gmail.com>
=head1 LICENSE
GPLv3 L<http://www.gnu.org/licenses/gpl-3.0.txt>
=cut
# vim:syntax=perl