1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
#!/usr/bin/env 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 => 1,
num_steps => 3,
);
GetOptions( \%opt, 'process_id|pid|p', 'help|?', 'interval=f', 'num_steps=i' ) or pod2usage(2);
pod2usage( -exitval => 0, -verbose => 2 ) if ( $opt{help} );
pod2usage(2) unless ( @ARGV && @ARGV > 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->new;
say STDERR "tracking PID $pid";
my $log_fh;
if ( $log_fn eq '-' ) {
$log_fh = \*STDOUT;
} else {
open $log_fh, '>', $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->table );
my @pids;
my $sum_rss = 0;
my $sum_vsz = 0;
my $sum_cpu = 0;
my $sum_start = 0;
my %childs = map { $_ => 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->[0] } ) {
$sum_rss += $p->[2];
$sum_vsz += $p->[3];
# utime + stime (cutime and cstime not needed, because we iterate through children
$sum_cpu += $p->[4];
push @pids, $p->[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->utime + prs->stime ) * 1/1e6 ) / (time(NULL) - prs->start_time);
shift @cpu_time if ( @cpu_time > $num_steps );
push @cpu_time, $sum_cpu;
shift @start_time if ( @start_time > $num_steps );
push @start_time, $t;
my $ratio = 0;
if ( @start_time >= $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 > 0 );
}
print $log_fh join( "\t", $time_point, $t, join( ",", @pids ), $sum_rss, $sum_vsz, $ratio ), "\n";
$log_fh->flush;
Time::HiRes::usleep($poll_intervall);
$time_point++;
}
$log_fh->close;
}
sub parse_ppt {
my $ppt_table = shift;
my @table = map { [ $_->pid, $_->ppid, $_->rss, $_->size, $_->time, $_->start ] } @$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
ppt_profile_cmd.pl - track the cpu and memory usage of a command
=head1 SYNOPSIS
ppt_profile_cmd.pl [OPTIONS] <log file> <command|process_id> [<arg1> <arg2> ... <argn>]
=head1 DESCRIPTION
=head1 OPTIONS
=head1 SEE ALSO
=head1 AUTHOR
jw bargsten, C<< <jwb at cpan dot org> >>
=cut
|