2011-12-16

Perl: report total size of memory used by certain processes in Linux.

Nothing fancy in this script. I merely have to write down my scripts somewhere, right?

I originally wrote this to tell me how much memory a certain Apache instance was using. I made it slightly more general purpose. You pass it a regular expression that matches the processes which you want to see the total memory usage of. You can use a debug option to get more verbose output.

I'm aware that the binary itself is not counted multiple times when totaling memory consumption, but in Apache this always comes to less than 10% of the total, so I don't even bother anymore.

Updated version after taking John's excellent suggestions below.

use strict;
use warnings;
use Getopt::Long;

my $debug;
GetOptions('debug|verbose|d|v' => \$debug);

die "Missing regular expression.\n" if ! $ARGV[0];

my $regex = shift;

my @ps_out = qx{ ps -eo pid,vsz,args };

my @proc_lines = grep { m/$regex/ } @ps_out;

my %pids = map { (split)[0,1] } @proc_lines;

delete $pids{$$};

my $total;
my $len = length((sort { $b <=> $a } values %pids)[0]);

for my $pid (keys %pids) {
    $total += $pids{$pid};
    printf "%s = %${len}s\n", $pid, $pids{$pid} if $debug;
}

$len = length($total);
print "=" x ($len + 11);
print "\n";

print "total KB = $total\n" if $debug;

my $mb_total = int($total / 1024);
printf "total MB = %${len}s\n", $mb_total;

My original and very inefficient and inelegant version.

use strict;
use warnings;
use Getopt::Long;

my $debug;
GetOptions('debug|verbose|d|v' => \$debug);

die "Missing regular expression.\n" if ! $ARGV[0];

my $regex = shift;

my @ps_out = qx{ ps -ef };

my @proc_lines = grep { m/$regex/ } @ps_out;

my %pids;

for my $line (@proc_lines) {
    my $pid = (split /\s+/, $line)[1];
    my @out = qx{ ps -o vsz -p $pid };
    chomp @out;
    my $vsz = $out[-1];
    $vsz =~ s/\s+//;
    $pids{$pid} = $vsz;
}

my $total;
my $len = length((sort { $b <=> $a } values %pids)[0]);

for my $pid (keys %pids) {
    $total += $pids{$pid};
    printf "%s = %${len}s\n", $pid, $pids{$pid} if $debug;
}

$len = length($total);
print "=" x ($len + 11);
print "\n";

print "total KB = $total\n" if $debug;

my $mb_total = int($total / 1024);
printf "total MB = %${len}s\n", $mb_total;

Here is me using it.

$ ./1.pl --debug '/httpd\.conf'
5293 =  10696
5298 = 290096
5294 =   7408
5296 = 289124
=================
total KB = 597324
total MB =    583

2 comments:

  1. Would it not be a lot more efficient to get the size in the 1st ps command rather than running ps for each matching process ? A bad regex could dump hundreds of fork execs on a busy system.

    perl -e'map { printf "%-8d = %8d\n",(split)[0,1] } grep { /$ARGV[0]/ } qx{ ps -eo pid,sz,args };' apache

    ReplyDelete
  2. Thanks a bunch, John, for pointing out all my wasteful use of ps. I've updated the original post.

    ReplyDelete