#!/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