| | 53 | use VARS qw($me $pluginstatedir $statefile $DEBUG); |
|---|
| | 54 | |
|---|
| | 55 | =head2 Variables |
|---|
| | 56 | |
|---|
| | 57 | The module instanciates a number of variables in the $Munin::Plugin |
|---|
| | 58 | scope. None of these are exported, and they must be referenced by the |
|---|
| | 59 | full names shown here. |
|---|
| | 60 | |
|---|
| | 61 | =head3 $Munin::Plugin::me |
|---|
| | 62 | |
|---|
| | 63 | The name of the plugin without any prefixing directory names and so |
|---|
| | 64 | on. Same as "basename $0" in a shell. It is a very good idea to use |
|---|
| | 65 | this in warning and/or error messages so that the logs show clearly |
|---|
| | 66 | what plugin the error message comes from. |
|---|
| | 67 | |
|---|
| | 68 | =cut |
|---|
| | 69 | |
|---|
| | 70 | my @dircomponents = split('/',$0); |
|---|
| | 71 | $me = pop(@dircomponents); |
|---|
| | 72 | |
|---|
| | 73 | =head3 $Munin::Plugin::pluginstatedir |
|---|
| | 74 | |
|---|
| | 75 | Identical to the environment variable MUNIN_PLUGSTATE (if available, is |
|---|
| | 76 | in Muinin 1.3.3) or the install time @Z<>@PLUGSTATE@Z<>@ 'constant'. |
|---|
| | 77 | You can use this if you need to save several different state files. |
|---|
| | 78 | But there is also a function to change the state file name so the |
|---|
| | 79 | state file support functions can be used for several state files. |
|---|
| | 80 | |
|---|
| | 81 | If it's value cannot be determined the plugin will be aborted at once |
|---|
| | 82 | with an explanatory message. The most likely causes are |
|---|
| | 83 | |
|---|
| | 84 | =over 8 |
|---|
| | 85 | |
|---|
| | 86 | =item * |
|---|
| | 87 | that the plugin is run directly and not from munin-node or munin-run or |
|---|
| | 88 | |
|---|
| | 89 | =item * |
|---|
| | 90 | that your munin-node is too old or that |
|---|
| | 91 | |
|---|
| | 92 | =item * |
|---|
| | 93 | munin-node was installed incorrectly somehow. |
|---|
| | 94 | |
|---|
| | 95 | =back |
|---|
| | 96 | |
|---|
| | 97 | The two last points can be worked around by the plugin configuration |
|---|
| | 98 | shown 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 | |
|---|
| | 105 | if ($statedir =~ /^@@/) { |
|---|
| | 106 | die "# Unable to determine PLUGSTATE, please see plugindoc Munin::Plugin-"; |
|---|
| | 107 | }; |
|---|
| | 108 | |
|---|
| | 109 | =head3 $Munin::Plugin::statefile |
|---|
| | 110 | |
|---|
| | 111 | The automatically calculated name for the plugins state file. This is |
|---|
| | 112 | simply a concatenation of the $statedir and the $me variables (with a |
|---|
| | 113 | slash between). To change the value of this please use the |
|---|
| | 114 | C<set_state_name ($)> procedure (see below). |
|---|
| | 115 | |
|---|
| | 116 | =cut |
|---|
| | 117 | |
|---|
| | 118 | $statefile = "$statedir/$me"; |
|---|
| | 119 | |
|---|
| | 120 | =head3 $Munin::Plugin::DEBUG |
|---|
| | 121 | |
|---|
| | 122 | Set 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 |
|---|
| | 124 | obey this variable. Set from the MUNIN_DEBUG environment variable. |
|---|
| | 125 | Defaults to false (0). |
|---|
| | 126 | |
|---|
| | 127 | =cut |
|---|
| | 128 | |
|---|
| | 129 | my $DEBUG = $ENV{'MUNIN_DEBUG'} || 0; |
|---|
| | 130 | |
|---|
| | 131 | =head2 Functions |
|---|
| | 132 | |
|---|
| | 133 | =head3 $fieldname = clean_fieldname($input_fieldname) |
|---|
| | 134 | |
|---|
| | 135 | Munin plugin field names are restricted with regards to what |
|---|
| | 136 | characters they may use: The characters must be C<[a-zA-Z0-9_]>, while |
|---|
| | 137 | the first character must be C<[a-zA-Z_]>. To satisfy these demands |
|---|
| | 138 | the function replaces illegal characters with a '_'. |
|---|
| | 139 | |
|---|
| | 140 | Additionally the field name is only allowed to be 19 characters long. |
|---|
| | 141 | This is also enforced, by S<front> trunkating the string, as the most |
|---|
| | 142 | interesting/significant bits of the strings will typically be at the |
|---|
| | 143 | end and not at the start. |
|---|
| | 144 | |
|---|
| | 145 | See also |
|---|
| | 146 | L<http://munin.projects.linpro.no/wiki/notes_on_datasource_names> |
|---|
| | 147 | |
|---|
| | 148 | =cut |
|---|
| | 163 | |
|---|
| | 164 | |
|---|
| | 165 | =head3 set_state_name ($statefile_name) |
|---|
| | 166 | |
|---|
| | 167 | Override the default statefile name. This only overrides the filename |
|---|
| | 168 | part, not the directory name. |
|---|
| | 169 | |
|---|
| | 170 | Calling this function is not normaly needed and is not recommended. |
|---|
| | 171 | |
|---|
| | 172 | =cut |
|---|
| | 173 | |
|---|
| | 174 | sub set_state_name ($) { |
|---|
| | 175 | # Set (override) the default statefile name. |
|---|
| | 176 | my ($filename) = @_; |
|---|
| | 177 | |
|---|
| | 178 | $statefile = "$pluginstatedir/$filename"; |
|---|
| | 179 | }; |
|---|
| | 180 | |
|---|
| | 181 | |
|---|
| | 182 | sub _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 | |
|---|
| | 200 | sub _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 | |
|---|
| | 213 | sub _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 | |
|---|
| | 223 | sub _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 | |
|---|
| | 237 | Save the passed state vector to the state file approproate for the |
|---|
| | 238 | plugin. The state vector should contain only strings (or numbers), |
|---|
| | 239 | and absolutely no objects. The strings may contain newlines without |
|---|
| | 240 | ill effect. |
|---|
| | 241 | |
|---|
| | 242 | If the file cannot be opened for writing the plugin will abort the |
|---|
| | 243 | program in the interest of error-obviousness. |
|---|
| | 244 | |
|---|
| | 245 | The state file name is determined automatically based on the |
|---|
| | 246 | name of the process we're running as. Se L<$Munin::Plugin::me>, |
|---|
| | 247 | L<$Munin::Plugin::statefile> and L<set_state_name> above about the |
|---|
| | 248 | file name. |
|---|
| | 249 | |
|---|
| | 250 | The file will contain a starting line with a magic number so that the |
|---|
| | 251 | library can se the difference between an actuall state file and a file |
|---|
| | 252 | containing rubish. Currently this magic number is |
|---|
| | 253 | '%MUNIN-STATE1.0\n'. Files with this magic number will contain the |
|---|
| | 254 | vector verbatim with \r, \n and % URL encoded. |
|---|
| | 255 | |
|---|
| | 256 | =cut |
|---|
| | 257 | |
|---|
| | 258 | sub 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 | |
|---|
| | 274 | Read state from the state file written by L<save_state(@)>. If |
|---|
| | 275 | everything is OK the state vector will be returned. |
|---|
| | 276 | |
|---|
| | 277 | If the file cannot be opened undef will be returned. Likewise, if the |
|---|
| | 278 | file 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 | |
|---|
| | 283 | sub 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 | |
|---|
| | 304 | Open the given file and seek to the given position. If this position |
|---|
| | 305 | is beyond the end of the file the function assumes that the file has |
|---|
| | 306 | been rotated, and the file position will be at the start of the file. |
|---|
| | 307 | |
|---|
| | 308 | If the file is opened OK the function returns a tuple consisting of |
|---|
| | 309 | the file handle and a file rotation indicator. $rotated will be 1 if |
|---|
| | 310 | the file has been rotated and 0 otherwise. Also, if the file was |
|---|
| | 311 | rotated a warning is printed (this can be found in the munin-node log |
|---|
| | 312 | or seen in the terminal when using munin-run). |
|---|
| | 313 | |
|---|
| | 314 | At this point the plugin can read from the file with <$file_hanle> in |
|---|
| | 315 | loop as usual until EOF is encountered. |
|---|
| | 316 | |
|---|
| | 317 | If the file cannot be stat'ed C<(undef,undef)> is returned. If the |
|---|
| | 318 | file cannot be opened for reading the plugin is aborted with a error |
|---|
| | 319 | in the interest of error-obviousness. |
|---|
| | 320 | |
|---|
| | 321 | =cut |
|---|
| | 322 | |
|---|
| | 323 | sub 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 | |
|---|
| | 353 | Close the the file and return the current position in the file. This |
|---|
| | 354 | position should be put in a state vector and stored in a state file |
|---|
| | 355 | until the next time the plugin runs. |
|---|
| | 356 | |
|---|
| | 357 | If the C<close> system call fails print a warning (which can be found |
|---|
| | 358 | in the munin-node log or seen when using munin-run). |
|---|
| | 359 | |
|---|
| | 360 | =cut |
|---|
| | 361 | |
|---|
| | 362 | |
|---|
| | 363 | sub 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 | |
|---|
| | 377 | sub _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 | } |
|---|