diff --git a/snmp/poudriere b/snmp/poudriere new file mode 100755 index 0000000..b413fc8 --- /dev/null +++ b/snmp/poudriere @@ -0,0 +1,433 @@ +#!/usr/bin/env perl + +=head1 NAME + +poudriere - LibreNMS JSON style SNMP extend for monitoring Poudriere + +=head1 VERSION + +0.0.1 + +=head1 SYNOPSIS + +poudriere [B<-w>] [B<-b>] [B<-o> ] + +poudriere --help|-h + +poudriere --version|-v + +=head1 SNMPD CONFIG + + extend poudriere /etc/snmp/extends/poudriere -b + +or if using cron... + + extend poudriere cat /var/cache/poudriere.json.snmp + +=head1 FLAGS + +=head2 -w + +Write the results out. + +=head2 -b + +Print out the compressed data if GZip+Base64 is smaller. + +=head2 -o + +Where to write the results to. Defaults to '/var/cache/poudriere.json', +meaning it will be written out to the two locations. + + /var/cache/poudriere.json + /var/cache/poudriere.json.snmp + +The later is for use with returning data for SNMP. Will be compressed +if possible. + +=cut + +use strict; +use warnings; +use Getopt::Long; +use File::Slurp; +use MIME::Base64; +use IO::Compress::Gzip qw(gzip $GzipError); +use Pod::Usage; +use JSON; + +sub time_to_seconds { + my $time = $_[0]; + + if ( !defined($time) ) { + return 0; + } + + if ( $time =~ /^0\:[0-9]+\.[0-9]+$/ ) { + $time =~ s/^0\://; + return $time; + } elsif ( $time =~ /^[0-9]+\:[0-9]+\.[0-9]+$/ ) { + my $minutes = $time; + $minutes =~ s/\:.*//; + $time =~ s/.*\://; + $time = ( $minutes * 60 ) + $time; + return $time; + } elsif ( $time =~ /^[0-9]+D\:[0-9]+\:[0-9]+\.[0-9]+$/ ) { + my $days = $time; + $days =~ s/D\:.*$//; + my $minutes = $time; + $minutes =~ s/^.*D\://; + $minutes =~ s/\:.*//; + $time = ( $days * 86400 ) + ( $minutes * 60 ) + $time; + return $time; + } + + # return 0 for anything unknown + return 0; +} ## end sub time_to_seconds + +#the version of returned data +my $VERSION = 1; + +# ensure sbin is in the path +$ENV{PATH} = $ENV{PATH} . ':/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin'; + +my $pretty; +my $cache_base = '/var/cache/poudriere.json'; +my $write; +my $compress; +my $version; +my $help; +GetOptions( + 'o=s' => \$cache_base, + w => \$write, + b => \$compress, + v => \$version, + version => \$version, + h => \$help, + help => \$help, +); + +if ($version) { + pod2usage( -exitval => 255, -verbose => 99, -sections => qw(VERSION), -output => \*STDOUT, ); + exit 255; +} + +if ($help) { + pod2usage( -exitval => 255, -verbose => 2, -output => \*STDOUT, ); + exit 255; +} + +#the data to return +my $to_return = { + 'version' => $VERSION, + 'error' => '0', + 'errorString' => '', +}; +my $data = { + status => '', + build_info => '', + not_done => 0, + stats => { + 'copy-on-write-faults' => 0, + 'cpu-time' => 0, + 'data-size' => 0, + 'elapsed-times' => 0, + 'involuntary-context-switches' => 0, + 'job-control-count' => 0, + 'major-faults' => 0, + 'minor-faults' => 0, + 'percent-cpu' => 0, + 'percent-memory' => 0, + 'read-blocks' => 0, + 'received-messages' => 0, + 'rss' => 0, + 'sent-messages' => 0, + 'stack-size' => 0, + 'swaps' => 0, + 'system-time' => 0, + 'text-size' => 0, + 'threads' => 0, + 'user-time' => 0, + 'voluntary-context-switches' => 0, + 'written-blocks' => 0, + 'QUEUE' => 0, + 'BUILT' => 0, + 'FAIL' => 0, + 'SKIP' => 0, + 'IGNORE' => 0, + 'FETCH' => 0, + 'REMAIN' => 0, + 'TIME' => 0, + 'check-sanity' => 0, + 'pkg-depends' => 0, + 'fetch-depends' => 0, + 'fetch checksum' => 0, + 'extract-depends' => 0, + 'extract' => 0, + 'patch-depends' => 0, + 'patch' => 0, + 'build-depends' => 0, + 'lib-depends' => 0, + 'configure' => 0, + 'build' => 0, + 'run-depends' => 0, + 'stage' => 0, + 'package' => 0, + }, + jailANDportsANDset => {} +}; + +my @ps_stats = ( + 'copy-on-write-faults', 'cpu-time', 'data-size', 'elapsed-times', + 'involuntary-context-switches', 'job-control-count', 'major-faults', 'minor-faults', + 'percent-cpu', 'percent-memory', 'read-blocks', 'received-messages', + 'rss', 'sent-messages', 'stack-size', 'swaps', + 'system-time', 'text-size', 'threads', 'user-time', + 'voluntary-context-switches', +); + +my @poudriere_stats = ( 'QUEUE', 'BUILT', 'FAIL', 'SKIP', 'IGNORE', 'FETCH', 'REMAIN', 'TIME' ); + +### +### +### get basic info via calling poudriere status +### +### + +my $status_raw = `poudriere -N status -f 2> /dev/null`; +if ( $? == 0 ) { + $data->{status} = $status_raw; + $data->{build_info} = `poudriere -N status -f -b 2>&1`; + + my @status_split = split( /\n/, $status_raw ); + my $status_split_int = 1; + while ( defined( $status_split[$status_split_int] ) ) { + + my $jls; + eval { $jls = decode_json(`jls --libxo json`); }; + if ($@) { + $jls = { 'jail-information' => { jail => [] } }; + } + + my $found = { + 'copy-on-write-faults' => 0, + 'cpu-time' => 0, + 'data-size' => 0, + 'elapsed-times' => 0, + 'involuntary-context-switches' => 0, + 'job-control-count' => 0, + 'major-faults' => 0, + 'minor-faults' => 0, + 'percent-cpu' => 0, + 'percent-memory' => 0, + 'read-blocks' => 0, + 'received-messages' => 0, + 'rss' => 0, + 'sent-messages' => 0, + 'stack-size' => 0, + 'swaps' => 0, + 'system-time' => 0, + 'text-size' => 0, + 'threads' => 0, + 'user-time' => 0, + 'voluntary-context-switches' => 0, + 'check-sanity' => 0, + 'pkg-depends' => 0, + 'fetch-depends' => 0, + 'fetch checksum' => 0, + 'extract-depends' => 0, + 'extract' => 0, + 'patch-depends' => 0, + 'patch' => 0, + 'build-depends' => 0, + 'lib-depends' => 0, + 'configure' => 0, + 'build' => 0, + 'run-depends' => 0, + 'stage' => 0, + 'package' => 0, + }; + ( + $found->{SET}, $found->{PORTS}, $found->{JAIL}, $found->{BUILD}, $found->{STATUS}, + $found->{QUEUE}, $found->{BUILT}, $found->{FAIL}, $found->{SKIP}, $found->{IGNORE}, + $found->{FETCH}, $found->{REMAIN}, $found->{TIME}, $found->{LOGS} + ) = split( / +/, $status_split[$status_split_int], 14 ); + + if ( $found->{STATUS} ne 'done' ) { + $data->{not_done} = 1; + } + + my $jailANDportsANDset; + if ( $found->{SET} eq '-' ) { + $jailANDportsANDset = $found->{JAIL} . '-' . $found->{PORTS}; + } else { + $jailANDportsANDset = $found->{JAIL} . '-' . $found->{PORTS} . '-' . $found->{SET}; + } + + foreach my $item (@poudriere_stats) { + if ( $item eq 'TIME' ) { + $found->{$item} = time_to_seconds( $found->{$item} ); + } + $data->{stats}{$item} += $found->{$item}; + } + + ## + ## find the jails + ## + my @jails; + my $jail_regex = '^' . $jailANDportsANDset . '-job-[0-9]+'; + my $jls_int = 0; + while ( defined( $jls->{'jail-information'}{jail}[$jls_int] ) ) { + if ( $jls->{'jail-information'}{jail}[$jls_int]{hostname} eq $jailANDportsANDset + || $jls->{'jail-information'}{jail}[$jls_int]{hostname} =~ /$jail_regex/ ) + { + push( @jails, $jls->{'jail-information'}{jail}[$jls_int]{jid} ); + } + $jls_int++; + } + + ## + ## if we have found jails, grab the information via ps + ## + if ( defined( $jails[0] ) ) { + my $jails_string = join( ',', @jails ); + + my $ps; + eval { + $ps + = decode_json( + `ps -o 'jid %cpu %mem rss cow dsiz etimes inblk jobc majflt minflt msgrcv msgsnd nivcsw nlwp nsigs nswap nvcsw oublk ssiz systime time tsiz usertime' --libxo json -J $jails_string` + ); + }; + if ($@) { + $ps = { 'process-information' => { process => [] } }; + } + my $ps_int = 0; + while ( defined( $ps->{'process-information'}{process}[$ps_int] ) ) { + foreach my $item (@ps_stats) { + if ( $item eq 'user-time' || $item eq 'cpu-time' || $item eq 'system-time' ) { + $ps->{'process-information'}{process}[$ps_int]{$item} + = time_to_seconds( $ps->{'process-information'}{process}[$ps_int]{$item} ); + } + $data->{stats}{$item} += $ps->{'process-information'}{process}[$ps_int]{$item}; + $found->{$item} += $ps->{'process-information'}{process}[$ps_int]{$item}; + } + $ps_int++; + } ## end while ( defined( $ps->{'process-information'}...)) + } ## end if ( defined( $jails[0] ) ) + + $data->{jailANDportsANDset}{$jailANDportsANDset} = $found; + $status_split_int++; + } ## end while ( defined( $status_split[$status_split_int...])) + + my @build_info_split = split( /\n/, $data->{build_info} ); + my $current_section; + foreach my $line (@build_info_split) { + if ( $line =~ /^\[.*\]\ \[.*\] .*Queued.*Built/ ) { + $current_section = $line; + $current_section =~ s/^\[//; + $current_section =~ s/\].*$//; + } elsif ( $line =~ /^\[.*\].*\:.*\|.*\:/ ) { + my $type; + if ( $line =~ /[\ \t]check\-sanity[\ \t]/ ) { + $type = 'check-sanity'; + } elsif ( $line =~ /[\ \t]pkg-depends[\ \t]/ ) { + $type = 'pkg-depends'; + } elsif ( $line =~ /[\ \t]fetch-depends[\ \t]/ ) { + $type = 'fetch-depends'; + } elsif ( $line =~ /[\ \t]fetch[\ \t]/ ) { + $type = 'fetch'; + } elsif ( $line =~ /[\ \t]checksum[\ \t]/ ) { + $type = 'checksum'; + } elsif ( $line =~ /[\ \t]extract\-depends[\ \t]/ ) { + $type = 'extract-depends'; + } elsif ( $line =~ /[\ \t]extract[\ \t]/ ) { + $type = 'extract'; + } elsif ( $line =~ /[\ \t]patch-depends[\ \t]/ ) { + $type = 'patch-depends'; + } elsif ( $line =~ /[\ \t]lib\-depends[\ \t]/ ) { + $type = 'lib-depends'; + } elsif ( $line =~ /[\ \t]configure[\ \t]/ ) { + $type = 'configure'; + } elsif ( $line =~ /[\ \t]build[\ \t]/ ) { + $type = 'build'; + } elsif ( $line =~ /[\ \t]build\-depends[\ \t]/ ) { + $type = 'build-depends'; + } elsif ( $line =~ /[\ \t]lib\-depends[\ \t]/ ) { + $type = 'lib-depends'; + } elsif ( $line =~ /[\ \t]configure[\ \t]/ ) { + $type = 'configure'; + } elsif ( $line =~ /[\ \t]build[\ \t]/ ) { + $type = 'build'; + } elsif ( $line =~ /[\ \t]run\-depends[\ \t]/ ) { + $type = 'run-depends'; + } elsif ( $line =~ /[\ \t]stage[\ \t]/ ) { + $type = 'stage'; + } elsif ( $line =~ /[\ \t]package[\ \t]/ ) { + $type = 'package'; + } + if (defined($type)) { + $data->{stats}{$type}++; + if (defined($data->{jailANDportsANDset}{$current_section})) { + $data->{jailANDportsANDset}{$current_section}{$type}++; + } + } + } ## end elsif ( $line =~ /^\[[0-9]+\].*\/.*\|.*-.*\:/) + } ## end foreach my $line (@build_info_split) +} else { + $to_return->{error} = 1; + $to_return->{errorString} = 'non-zero exit for "poudriere status -f"'; +} + +### +### +### finalize it +### +### + +#add the data has to the return hash +$to_return->{data} = $data; + +#finally render the JSON +my $raw_json = encode_json($to_return); +if ($write) { + write_file( $cache_base, $raw_json ); + # compress and write to the cache file for it + my $compressed_string; + gzip \$raw_json => \$compressed_string; + my $compressed = encode_base64($compressed_string); + $compressed =~ s/\n//g; + $compressed = $compressed . "\n"; + my $print_compressed = 0; + if ( length($compressed) > length($raw_json) ) { + write_file( $cache_base . '.snmp', $raw_json ); + } else { + write_file( $cache_base . '.snmp', $compressed ); + $print_compressed = 1; + } + + if ( $compress && $print_compressed ) { + print $compressed; + } else { + print $raw_json; + } +} else { + if ( !$compress ) { + print $raw_json. "\n"; + exit; + } + + # compress and write to the cache file for it + my $compressed_string; + gzip \$raw_json => \$compressed_string; + my $compressed = encode_base64($compressed_string); + $compressed =~ s/\n//g; + $compressed = $compressed . "\n"; + my $print_compressed = 0; + if ( length($compressed) > length($raw_json) ) { + print $raw_json; + } else { + print $compressed; + } +} ## end else [ if ($write) ]