<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/perl

use warnings;
use strict;
use Time::HiRes;
use Proc::ProcessTable;

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

my %opt = (
  interval  =&gt; 1,
  num_steps =&gt; 3,
);

GetOptions( \%opt, 'process_id|pid|p', 'help|?', 'interval=f', 'num_steps=i' ) or pod2usage(2);

pod2usage( -exitval =&gt; 0, -verbose =&gt; 2 ) if ( $opt{help} );
pod2usage(2) unless ( @ARGV &amp;&amp; @ARGV &gt; 1 );

my ( $log_fn, @cmd ) = @ARGV;

my $poll_intervall    = int($opt{interval} * 1000 * 1000);
my $num_steps         = $opt{num_steps};
my $script_start_time = [ Time::HiRes::gettimeofday() ];

my $pid;

if ( $opt{process_id} ) {
  $pid = shift @cmd;
} else {
  $SIG{CHLD} = 'IGNORE';

  $pid = fork;
  die "cannot fork" unless defined $pid;
}

if ( $pid == 0 ) {
  #child

  system(@cmd);
  exit;

} else {
  #main

  my $time_point = 1;
  my @start_time;
  my @cpu_time;

  my $ppt = Proc::ProcessTable-&gt;new;

  say STDERR "tracking PID $pid";
  my $log_fh;
  if ( $log_fn eq '-' ) {
    $log_fh = \*STDOUT;
  } else {
    open $log_fh, '&gt;', $log_fn or die "Can't open filehandle: $!";
  }

  print $log_fh join( "\t", qw/tp time pids rss vsz pcpu/ ), "\n";

  while ( kill( 0, $pid ) ) {
    my $t  = Time::HiRes::tv_interval($script_start_time);
    my $pt = parse_ppt( $ppt-&gt;table );

    my @pids;
    my $sum_rss   = 0;
    my $sum_vsz   = 0;
    my $sum_cpu   = 0;
    my $sum_start = 0;

    my %childs = map { $_ =&gt; 1 } subproc_ids( $pid, $pt );
    $childs{$pid}++ if ( $opt{process_id} );
    for my $p (@$pt) {
      #[0] pid
      #[1] ppid
      #[2] rss
      #[3] size
      #[4] time
      #[5] start
      if ( $childs{ $p-&gt;[0] } ) {
        $sum_rss += $p-&gt;[2];
        $sum_vsz += $p-&gt;[3];
        # utime + stime (cutime and cstime not needed, because we iterate through children
        $sum_cpu += $p-&gt;[4];
        push @pids, $p-&gt;[0];
      }
    }

# calc pct cpu per interval:
# we need seconds since
#https://stackoverflow.com/questions/16726779/how-do-i-get-the-total-cpu-usage-of-an-application-from-proc-pid-stat

    #pctcpu = ( 100.0f * sum over all (prs-&gt;utime + prs-&gt;stime ) * 1/1e6 ) / (time(NULL) - prs-&gt;start_time);

    shift @cpu_time if ( @cpu_time &gt; $num_steps );
    push @cpu_time, $sum_cpu;

    shift @start_time if ( @start_time &gt; $num_steps );
    push @start_time, $t;

    my $ratio = 0;
    if ( @start_time &gt;= $num_steps ) {
      my $diff_cpu   = ( $cpu_time[-1] - $cpu_time[0] ) / 1e6;
      my $diff_start = ( $start_time[-1] - $start_time[0] );
      $ratio = $diff_cpu / $diff_start if ( $diff_start &gt; 0 );
    }
    print $log_fh join( "\t", $time_point, $t, join( ",", @pids ), $sum_rss, $sum_vsz, $ratio ), "\n";
    $log_fh-&gt;flush;

    Time::HiRes::usleep($poll_intervall);
    $time_point++;
  }
  $log_fh-&gt;close;
}

sub parse_ppt {
  my $ppt_table = shift;

  my @table = map { [ $_-&gt;pid, $_-&gt;ppid, $_-&gt;rss, $_-&gt;size, $_-&gt;time, $_-&gt;start ] } @$ppt_table;
  return \@table;
}

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

__END__

=head1 NAME

ppt_profile_cmd.pl - track the cpu and memory usage of a command

=head1 SYNOPSIS

ppt_profile_cmd.pl [OPTIONS] &lt;log file&gt; &lt;command|process_id&gt; [&lt;arg1&gt; &lt;arg2&gt; ... &lt;argn&gt;]

=head1 DESCRIPTION

=head1 OPTIONS

=head1 SEE ALSO

=head1 AUTHOR

jw bargsten, C&lt;&lt; &lt;jwb at cpan dot org&gt; &gt;&gt;

=cut
</pre></body></html>