#!/usr/bin/perl -wi
use strict;
## Analyses a ps awxl listing from linux. Script is assumed to run this
## every N seconds and output has a "COUNT: N" line for which run it is.
## andyw - VISTORM 2006. Licensed under the GNU GPL V2.0
# user defines: {{{1
## (these are now defaults only)
my %config;
$config{CPUS} = 1; ## only important if --percentage requested
$config{SLEEP} = 15; ## time we slept (needed to work out start time)
$config{CPU_MIN} = 10; ## ignore processes using fewer than this many
## seconds of cpu over the whole test
$config{RECORD_MOD} = 8; ## number of records to skip. This is so we can see
## big changed in values, rather than trickly ones.
## This will illustrate the real cpu hogs. Default
## is 8, and we run every 15 seconds.
## use '1' to do every record.
$config{fname} = 'pslist'; ## filename to read
# variables: {{{1
my (%cur, %prev, $k, $l, $m, $s, $z, $pid, $vsz, $res, $stat, $time, $com);
my ($uid, $i, $rss);
my ($out, $c) = (0,0);
# init: {{{1
get_flags();
my ($coms, $scpu, $tcpu, $rc) = get_pids($config{fname}); # pre-filter the pids
my $initial_time = get_start($config{fname},$rc);
map { $cur{$_}{time}=0; } keys %$coms;
open(A,$config{fname});
# main: {{{1
while () {
if (m/COUNT\: (\d+)/o) {
next unless $1 % $config{RECORD_MOD} == 0;
if ($1 == 0) {
print 'START,' . join ',',map { $_ = "$_ $$coms{$_}" }
reverse sort { $$tcpu{$a} <=> $$tcpu{$b} } keys %$coms;
print "\n";
}
print STDERR;
if ($1 > 1) {
print sprintf("%02d:%02d,",
(localtime($initial_time + $1 * $config{SLEEP}))[2,1]);
print join',',map {
$z = $cur{$_}{'time'} - ($prev{$_}{'time'}||$$scpu{$_});
$z = sprintf("%3d",$z/$config{TSCPU}*100) if $config{PERCENTAGE}||0;
#print "PID $_ cur $cur{$_}{'time'} prev $prev{$_}{'time'}\n";
$_ = $z > 0 ? $z : 0;
} reverse sort { $$tcpu{$a} <=> $$tcpu{$b} } keys %$coms;
print "\n";
}
%prev = %cur;
#%cur = ();
next;
} else {
# COUNT: 0
#F UID PID PPID PRI NI VSZ\
#RSS WCHAN STAT TTY TIME COMMAND
#
# caputre: pid, vsz, rss, stat, time, command
next if /^F/o;
if ( ($uid, $pid, $vsz, $rss, $stat, $time, $com) =
(m/^\S+\s+(\S+)\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\d+)\s+(\d+)\s+(\S+)\s+\S+\s+\S+\s+(\S+)\s+(\S*)/o)) {
next if ! exists $$coms{$pid};
($m, $s) = ($time=~m/(\d+)\:(\d+)/o);
$time = $m*60+$s||0;
$com =~ s/,/_/og; # just in case, lose the commas
$cur{$pid} = {
'uid', $uid, 'vsz', $vsz, 'rss', $rss, 'stat', $stat,
'time', $time, 'com', $com
};
$prev{$pid}||=$cur{$pid}; # BODGE
$prev{$pid}{time}||=0; # BODGE to start time off
} else {
print STDERR "IRREGULAR LINE: $_";
print STDERR "$uid\n";
}
}
}
# get_pids: {{{1
sub get_pids {
my (%coms, %t, $rss, $pid, $uid, %scpu, %ecpu, $m, $s, $start, $end);
my ($fr, $c) = ('',0);
open(A,shift @_);
print STDERR "Working out which processes I can throw away: " .
"(Min utilisation = $config{CPU_MIN} seconds)\n";
while() {
if (/COUNT\: (\d+)/) { $fr = $1 unless length($fr); $c = $1; }
next if /^COUNT|^F/o;
if ( ($uid, $pid, $vsz, $rss, $stat, $time, $com) =
(m/^\S+\s+(\S+)\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\d+)\s+(\d+)\s+(\S+)\s+\S+\s+\S+\s+(\S+)\s+(.*)/o)) {
next if $com =~/sshd\:|\/sshd|bash$/o;
#next if $uid != 500;
if (!exists $scpu{$pid}) { $scpu{$pid} = $time; }
$coms{$pid} = $com;
$ecpu{$pid} = $time;
} else {
print STDERR "Irregular Line: $_";
}
}
close(A);
for $pid (keys %coms) {
($m, $s) = ($scpu{$pid}=~m/(\d+)\:(\d+)/o);
$start = $m*60+$s||0;
($m, $s) = ($ecpu{$pid}=~m/(\d+)\:(\d+)/o);
$end = $m*60+$s||0;
if ($end - $start < $config{CPU_MIN}) {
delete $coms{$pid};
} else {
$t{$pid} = $end - $start;
$scpu{$pid} = $start;
}
print STDERR (exists($coms{$pid}) ? 'Removing' : 'Keeping') .
" pid $pid since it used " . ($end - $start) .
"(S: $scpu{$pid} " . "[$start] E: $ecpu{$pid} [$end]) " .
" second(s) CPU time\n";
}
return \%coms, \%scpu, \%t, ($c - $fr);
}
# get_start {{{1
sub get_start {
my ($fname, $c) = @_;
return (stat($fname))[9] - $c * $config{SLEEP};
}
# usage {{{1
sub usage {
print "Usage: $0 --cpu-min=N --sleep=S --recordmerge=R --cpus=N --percentage\n";
print " Default is --sleep=15 --recordmerge=8\n";
print " percentage needs to know the number of CPU(s)\n";
exit 1;
}
# get_flags {{{1
sub get_flags {
for (@ARGV) {
if (/\-\-sleep=(\d+)/) {
$config{SLEEP} = $1; next;
}
if (/\-\-recordmerge=(\d+)/) {
$config{RECORD_MOD} = $1; next;
}
if (/\-\-cpu\-min=(\d+)/) {
$config{CPU_MIN} = $1; next;
}
if (/\-\-cpus=(\d+)/) {
$config{CPUS}=$1; next;
}
if (/\-\-percentage/) {
$config{PERCENTAGE}=1; next;
}
else { print "$0 @ARGV not understood '$_'\n"; usage() }
}
if ($config{PERCENTAGE} && ! int($config{CPUS})) { usage(); }
$config{TSCPU} = $config{SLEEP} * $config{RECORD_MOD} * $config{CPUS};
}
# vim: ts=4 fdm=marker