Changeset 1388

Show
Ignore:
Timestamp:
01/18/08 13:25:54 (4 years ago)
Author:
janl
Message:

* New functions in Munin::Plugin (state retention, tail functions)
* Some preparations for same in plugin.sh

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/node/Plugin.pm.in

    r1184 r1388  
    44# Utility functions for perl munin plugins. 
    55# 
    6 # Copyright (C) 2003-2004 Jimmy Olsen, Audun Ytterdal 
     6# Copyright (C) 2007-2008 Nicolai Langfeldt 
    77# 
    88# This program is free software; you can redistribute it and/or 
     
    2323# 
    2424 
     25=head1 Munin::Plugin 
     26 
     27=head2 Usage 
     28 
     29  use lib $ENV{'MUNIN_LIBDIR'}; 
     30  use Munin::Plugin; 
     31 
     32If your Munin installation predates the MUNIN_* environment variables 
     33(introduced in 1.3.3) you can put this in your plugin configutation: 
     34 
     35  [*] 
     36      env.MUNIN_PLUGSTATE /lib/munin/plugin-state 
     37      env.MUNIN_LIBDIR /usr/share/munin 
     38 
     39IF, indeed that is the munin plugin state directory and library 
     40directory. 
     41 
     42The module exports these functions: clean_fieldname, 
     43set_state_name, save_state, restore_state, tail_open, tail_close. 
     44 
     45=cut 
     46 
    2547use Exporter; 
    2648@ISA = ('Exporter'); 
    27 @EXPORT = qw(clean_fieldname); 
     49@EXPORT = qw(clean_fieldname set_state_name save_state restore_state 
     50             tail_open, tail_close); 
    2851 
    2952use strict; 
     53use VARS qw($me $pluginstatedir $statefile $DEBUG); 
     54 
     55=head2 Variables 
     56 
     57The module instanciates a number of variables in the $Munin::Plugin 
     58scope.  None of these are exported, and they must be referenced by the 
     59full names shown here. 
     60 
     61=head3 $Munin::Plugin::me 
     62 
     63The name of the plugin without any prefixing directory names and so 
     64on.  Same as "basename $0" in a shell.  It is a very good idea to use 
     65this in warning and/or error messages so that the logs show clearly 
     66what plugin the error message comes from. 
     67 
     68=cut 
     69 
     70my @dircomponents = split('/',$0); 
     71$me = pop(@dircomponents); 
     72 
     73=head3 $Munin::Plugin::pluginstatedir 
     74 
     75Identical to the environment variable MUNIN_PLUGSTATE (if available, is 
     76in Muinin 1.3.3) or the install time @Z<>@PLUGSTATE@Z<>@ 'constant'. 
     77You can use this if you need to save several different state files. 
     78But there is also a function to change the state file name so the 
     79state file support functions can be used for several state files. 
     80 
     81If it's value cannot be determined the plugin will be aborted at once 
     82with an explanatory message.  The most likely causes are 
     83 
     84=over 8 
     85 
     86=item * 
     87that the plugin is run directly and not from munin-node or munin-run or 
     88 
     89=item * 
     90that your munin-node is too old or that 
     91 
     92=item * 
     93munin-node was installed incorrectly somehow. 
     94 
     95=back 
     96 
     97The two last points can be worked around by the plugin configuration 
     98shown at the beginning of this document. 
     99 
     100=cut 
     101# This is because perl mode is confused: ' 
     102 
     103$pluginstatedir = $ENV{'MUNIN_PLUGSTATE'} || '@@PLUGSTATE@@'; 
     104 
     105if ($statedir =~ /^@@/) { 
     106    die "# Unable to determine PLUGSTATE, please see plugindoc Munin::Plugin-"; 
     107}; 
     108 
     109=head3 $Munin::Plugin::statefile 
     110 
     111The automatically calculated name for the plugins state file.  This is 
     112simply a concatenation of the $statedir and the $me variables (with a 
     113slash between).  To change the value of this please use the 
     114C<set_state_name ($)> procedure (see below). 
     115 
     116=cut 
     117 
     118$statefile = "$statedir/$me"; 
     119 
     120=head3 $Munin::Plugin::DEBUG 
     121 
     122Set to true if the plugin should emit debug output.  There are some 
     123(but not many) debug print statements in the Module as well, which all 
     124obey this variable.  Set from the MUNIN_DEBUG environment variable. 
     125Defaults to false (0). 
     126 
     127=cut 
     128 
     129my $DEBUG = $ENV{'MUNIN_DEBUG'} || 0; 
     130 
     131=head2 Functions 
     132 
     133=head3 $fieldname = clean_fieldname($input_fieldname) 
     134 
     135Munin plugin field names are restricted with regards to what 
     136characters they may use: The characters must be C<[a-zA-Z0-9_]>, while 
     137the first character must be C<[a-zA-Z_]>.  To satisfy these demands 
     138the function replaces illegal characters with a '_'. 
     139 
     140Additionally the field name is only allowed to be 19 characters long. 
     141This is also enforced, by S<front> trunkating the string, as the most 
     142interesting/significant bits of the strings will typically be at the 
     143end and not at the start. 
     144 
     145See also 
     146L<http://munin.projects.linpro.no/wiki/notes_on_datasource_names> 
     147 
     148=cut 
    30149 
    31150sub clean_fieldname ($) { 
    32     # Clean up field name so it complies with munin requirements. 
    33151    my $name = shift; 
    34152 
    35     $name =~ s/^[^A-Za-z_]/_/; 
     153    # Replace a sequence of illegal leading chars with a single _ 
     154    $name =~ s/^[^A-Za-z_]+/_/; 
     155    # Replace remaining illegals with _ 
    36156    $name =~ s/[^A-Za-z0-9_]/_/g; 
    37157 
     158    # And use only the last 19 chars 
     159    $name = substr($name,-19); 
     160 
    38161    return $name; 
    39162} 
     163 
     164 
     165=head3 set_state_name ($statefile_name) 
     166 
     167Override the default statefile name.  This only overrides the filename 
     168part, not the directory name. 
     169 
     170Calling this function is not normaly needed and is not recommended. 
     171 
     172=cut 
     173 
     174sub set_state_name ($) { 
     175    # Set (override) the default statefile name. 
     176    my ($filename) = @_; 
     177 
     178    $statefile = "$pluginstatedir/$filename"; 
     179}; 
     180 
     181 
     182sub _encode_string { 
     183    # Internal function: URL encode a few characters that save_state 
     184    # breaks on otherwise 
     185 
     186    my ($s) = @_; 
     187 
     188    # This is to do a general URL encode 
     189    # $str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; 
     190 
     191    # But we do a restricted because that's all we need.  I hope O:-) 
     192    $s =~ s/%/%25/g; 
     193    $s =~ s/\n/%0A/g; 
     194    $s =~ s/\r/%0D/g; 
     195 
     196    return $s; 
     197} 
     198 
     199 
     200sub _decode_string { 
     201    # Internal function: URL decode a string 
     202    my ($s) = @_; 
     203 
     204    # General URL decode, just in case.  "Be gracefull about what you 
     205    # accept" you know. 
     206 
     207    $s =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg; 
     208 
     209    return $s; 
     210}; 
     211 
     212 
     213sub _encode_state (@) { 
     214    # Internal function: Return an encoded instance of the state vector 
     215    my @ns; 
     216 
     217    @ns = map { _encode_string($_); } @_; 
     218 
     219    return @ns; 
     220}; 
     221 
     222 
     223sub _decode_state (@) { 
     224    # Internal function: Return a decoded instance of the state vector 
     225    my @ns = @_; 
     226 
     227    my $encmagic=shift(@ns); 
     228 
     229    @ns = map { _decode_string($_); } @_; 
     230 
     231    return @ns; 
     232}; 
     233 
     234 
     235=head3 save_state(@state_vector) 
     236 
     237Save the passed state vector to the state file approproate for the 
     238plugin.  The state vector should contain only strings (or numbers), 
     239and absolutely no objects.  The strings may contain newlines without 
     240ill effect. 
     241 
     242If the file cannot be opened for writing the plugin will abort the 
     243program in the interest of error-obviousness. 
     244 
     245The state file name is determined automatically based on the 
     246name of the process we're running as.  Se L<$Munin::Plugin::me>, 
     247L<$Munin::Plugin::statefile> and L<set_state_name> above about the 
     248file name. 
     249 
     250The file will contain a starting line with a magic number so that the 
     251library can se the difference between an actuall state file and a file 
     252containing rubish.  Currently this magic number is 
     253'%MUNIN-STATE1.0\n'. Files with this magic number will contain the 
     254vector verbatim with \r, \n and % URL encoded. 
     255 
     256=cut 
     257 
     258sub save_state (@) { 
     259    print "State file: $statefile\n" if $DEBUG; 
     260 
     261    open(STATE,"> $statefile") or 
     262      die "$me: Could not open statefile '$statefile' for writing: $!\n"; 
     263 
     264    # Munin-state 1.0 encodes %, \n and \r in URL encoding and leaves 
     265    # the rest. 
     266    print STATE "%MUNIN-STATE1.0\n"; 
     267    print STATE join("\n",_encode_state(@_)),"\n"; 
     268 
     269    close(STATE); 
     270} 
     271 
     272=head3 @state_vector = restore_state() 
     273 
     274Read state from the state file written by L<save_state(@)>. If 
     275everything is OK the state vector will be returned. 
     276 
     277If the file cannot be opened undef will be returned.  Likewise, if the 
     278file does not have a recognized magic number undef will be returned 
     279(and a warning printed, which will appear in the munin-node logs). 
     280 
     281=cut 
     282 
     283sub restore_state { 
     284    # Read a state vector from a plugin appropriate state file 
     285    local $/; 
     286 
     287    open(STATE,"<$statefile") or return undef; 
     288 
     289    my @state = split(/\n/,<STATE>); 
     290 
     291    my $filemagic = shift(@state); 
     292 
     293    if ($filemagic ne '%MUNIN-STATE1.0') { 
     294        warn "$me: Statefile $statefile has unrecognized magic number: '$filemagic'\n"; 
     295        return undef; 
     296    } 
     297 
     298    return _decode_state(@state); 
     299} 
     300 
     301 
     302=head ($file_handle,$rotated) = tail_open($file_name,$position) 
     303 
     304Open the given file and seek to the given position.  If this position 
     305is beyond the end of the file the function assumes that the file has 
     306been rotated, and the file position will be at the start of the file. 
     307 
     308If the file is opened OK the function returns a tuple consisting of 
     309the file handle and a file rotation indicator.  $rotated will be 1 if 
     310the file has been rotated and 0 otherwise.  Also, if the file was 
     311rotated a warning is printed (this can be found in the munin-node log 
     312or seen in the terminal when using munin-run). 
     313 
     314At this point the plugin can read from the file with <$file_hanle> in 
     315loop as usual until EOF is encountered. 
     316 
     317If the file cannot be stat'ed C<(undef,undef)> is returned.  If the 
     318file cannot be opened for reading the plugin is aborted with a error 
     319in the interest of error-obviousness. 
     320 
     321=cut 
     322 
     323sub tail_open ($$) { 
     324    my ($file,$position) = @_; 
     325 
     326    my $fh; 
     327 
     328    my $filereset=0; 
     329 
     330    my $size = (stat($file))[7]; 
     331 
     332    warn "**Size is $size\n" if $DEBUG; 
     333 
     334    if (!defined($size)) { 
     335        warn "$me: Could not stat input file '$file': $!\n"; 
     336        return (undef,undef); 
     337    } 
     338 
     339    open($fh,"<$file") or 
     340      die "$me: Could not open input file '$file' for reading: $!\n"; 
     341 
     342    if ($position > $size) { 
     343        warn "$me: File rotated, starting at start\n"; 
     344        $filereset=1; 
     345    } elsif (!seek($fh,$position,0)) { 
     346        die "$me: Seek to position $position of '$file' failed: $!\n"; 
     347    } 
     348    return ($fh,$filereset); 
     349} 
     350 
     351=head $position = tail_close($file_handle) 
     352 
     353Close the the file and return the current position in the file.  This 
     354position should be put in a state vector and stored in a state file 
     355until the next time the plugin runs. 
     356 
     357If the C<close> system call fails print a warning (which can be found 
     358in the munin-node log or seen when using munin-run). 
     359 
     360=cut 
     361 
     362 
     363sub tail_close ($) { 
     364    my ($fh) = @_; 
     365 
     366    my $position = tell($fh); 
     367 
     368    # If this ever hits us I'll be amazed. 
     369    close($fh) or 
     370      warn "$me: Could not close input file: $!\n"; 
     371 
     372    return $position; 
     373} 
     374 
     375 
     376 
     377sub _test () { 
     378    my $pos = 0; 
     379    my $fh; 
     380 
     381    do { 
     382        $fh = tail_open('/var/log/messages',$pos); 
     383        while (<$fh>) { 
     384            print; 
     385        } 
     386        $pos = tail_close($fh); 
     387 
     388        print "**Position is $pos\n"; 
     389    } while sleep 1; 
     390} 
  • trunk/node/plugin.sh.in

    r1184 r1388  
    1111} 
    1212 
     13# janl_: can I in a shell script save STDOUT so I can restore it after 
     14#        a "exec >>somefile"? 
     15# james: exec 2>&4 etc. 
     16# janl_: this saves handle 2 in handle 4? 
     17# james: yes, that's basically the same as dup 
     18# james: dup2, even 
     19# janl_: so... ... "exec 4>&2" to restore? 
     20# james: Actually you can do: exec 4>&2- ... which closes 4 afterwards ... 
     21#        I think that's historical behaviour and not a newish extension 
     22