The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl

use warnings;
use strict;
use Time::HiRes;
use Proc::ProcessTable;
use Scalar::Util::Numeric qw/isnan isinf/;
use List::Util qw/sum/;
use Mail::Sendmail;
use Data::Dumper;
unshift @{ $Mail::Sendmail::mailcfg{'smtp'} }, 'net.wur.nl';

use Pod::Usage;
use Getopt::Long qw(:config auto_help);

my %opt = (
  interval     => 60,
  mail         => ['joachim.bargsten@wur.nl'],
  'threshold'  => 0.005,
  'num_steps'  => 3,
  waiting_time => 30,
  subject      => 'Proc::ProcessTable process tracking'
);

GetOptions(
  \%opt,         'help|?',      'waiting_time=i', 'subject=s', 'interval=i', 'mail=s@',
  'threshold=s', 'num_steps=i', "verbose"
) or pod2usage(2);

pod2usage( -exitval => 0, -verbose => 2 ) if ( $opt{help} );

pod2usage(2)
  unless ( @ARGV && @ARGV > 0 );

my @pids           = @ARGV;
my $poll_intervall = $opt{interval} * 1000 * 1000;

my @start_time;

$SIG{CHLD} = 'IGNORE';

my @cpu_time;

my $ppt = Proc::ProcessTable->new;

my $pid_running = 1;

BIG:
while ($pid_running) {
  my $pt = parse_ppt( $ppt->table );
  my %childs;
  for my $pid (@pids) {
    map { $childs{$_} => 1 } subproc_ids( $pid, $pt );
    $childs{$pid} = 1;
  }

  my $sum_cpu   = 0;
  my $sum_start = 0;

  $pid_running = 0;
  for my $p (@$pt) {
    if ( $childs{ $p->[0] } ) {
      $pid_running = 1;
      #say STDERR "timepoint" unless(defined($time_point));
      #say STDERR "time" unless(defined($t));
      #say STDERR "process" unless((grep { defined $_ } @$p ) != @$p);
      $sum_cpu   += $p->[4];
      $sum_start += time - $p->[5];
    }
  }
  shift @cpu_time if ( @cpu_time > $opt{num_steps} );
  push @cpu_time, $sum_cpu;

  shift @start_time if ( @start_time > $opt{num_steps} );
  push @start_time, $sum_start;

  if ( @start_time >= $opt{num_steps} ) {
    my $diff_cpu   = ( $cpu_time[-1] - $cpu_time[0] ) / ( 1000 * 1000 );
    my $diff_start = ( $start_time[-1] - $start_time[0] );
    my $ratio      = $diff_cpu / $diff_start;
    printf "%3.2f ", $ratio if ( $opt{verbose} );
    if ( $ratio < $opt{threshold} ) {
      print "| " if ( $opt{verbose} );
      for my $mail ( @{ $opt{mail} } ) {
        send_mail(
          {
            To      => $mail,
            Subject => $opt{subject},
            Message => "blast2go has low cpu usage, probably it is finished and you can do the next step"
          }
        );

      }
      sleep $opt{waiting_time} * 60;

    }
  } else {
    print ". " if ( $opt{verbose} );
  }
  Time::HiRes::usleep($poll_intervall);
}
print "\n" if ( $opt{verbose} );

for my $mail ( @{ $opt{mail} } ) {
  send_mail(
    { To => $mail, Subject => $opt{subject}, Message => "blast2go is not running anymore and/or finished" } );
}

sub send_mail {
  my $c    = shift;
  my %mail = (
    From => 'joachim.bargsten@wur.nl',
    %$c
  );

  sendmail(%mail) or die $Mail::Sendmail::error;
}

sub parse_ppt {
  my $ppt_table = shift;
  my @table = map { [ $_->pid, $_->ppid, $_->rss, $_->size, $_->time, $_->start, $_->exec ] } @$ppt_table;
  return \@table;
}

sub subproc_ids {
  my ( $pid, $procs ) = @_;
  #[ pid, parentid ]
  my @childs;
  for my $c ( grep { $_->[1] == $pid } @$procs ) {
    push @childs, $c->[0];
    push @childs, subproc_ids( $c->[0], $procs );
  }
  return @childs;
}

__END__

=head1 NAME



=head1 SYNOPSIS

  track_b2g.pl [OPTIONS] <pid1> [<pid2 ... <pidn>]

=head1 DESCRIPTION

Track the CPU usage of processes and send a mail if the usage falls below a certain threshold.

=head1 OPTIONS

=over 4

=item B<< --waiting_time <minutes> >>

If the mail got send, wait C<< <minutes> >> minutes before resume tracking. This
prevents sending you hundreds of mails if the processes are below the
threshold.

=item B<< --subject <subject> >>

Set the subject of the mail.

=item B<< --interval <seconds> >>

Record every C<< <seconds> >> seconds the CPU usage.

=item B<<  --mail <address> >>

Send notification mails to C<< <address >>.

=item B<< --threshold <fraction> >>

Set he notification threshold to C<< <fraction> >>. 100% CPU usage corresponds to a fraction of 1.

=item B<< --num_steps <steps>  >>

The CPU usage is averaged over a time span of C<< <steps> * <interval time> >>.

=item B<< --verbose >>

Enable reporting of CPU usage fraction to STDOUT. If the number of steps is
smaller than the averaging window, "." is printed. If a mail is sent, "|" is
printed.

=back

=head1 SEE ALSO

=head1 AUTHOR

jw bargsten, C<< <jwb at cpan dot org> >>

=cut