geekt has asked for the wisdom of the Perl Monks concerning the following question:

I'd like to grab the percentage of memory/CPU that is being used by the user, and return it to two scalars for use in a script. I would think this would be a fairly common task, however Google has failed me. I was trying to wrap my head around the best regex to use on the results from `top -b -n 1 -u $username`, but decided to ask here first.

Edit: Sorry, I figured that posting extremely basic code when the main piece was the regex would be pointless, especially when I wasn't sure I was doing it the best way, but someone hit me with a book, so I'll post some basic example code.
Note: Code not guaranteed or likely to be error free.

#!/usr/bin/perl use strict; use warnings; my $memlimit = "25.0"; # maximum percentage of memory user can use my $cpulimit = "25.0"; # maximum percentage of CPU user can use my $username = `whoami`; my ($usermem, $usercpu) = getUserMemCPUperc( '$username' ); if (($usermem < $memlimit) && ($usercpu < $cpulimit)){ # do stuff! } else { # memory or CPU limits reached exit(0); } exit(0); sub getUserMemCPUperc { my( $username ) = shift; my $top = `top -b -n 1 -u $username`; # gets top from the system my ($cpu, $mem); $_ = $top; # @processes =~ /.../g; # regex to extract each process line # foreach my $process (@processes) { # my ($procpu, $procmem) =~ /.../; # get cpu and memory percent # $cpu += $procpu; # add process cpu percent to total # $mem += $procmem; # add process memory percent to total # } # return ($mem, $cpu); }

So, how would you do it?

Edit: Code that seems to work is posted below in reply. Originally trying to do research to figure out how to do this, I saw that top gave me exactly what I wanted, however this seems like a convoluted and inefficient method.

Edit: Proc::ProcessTable will not seem to work on my CentOS version, so I'll be working on improving the version posted below. Suggestions would be appreciated.

Replies are listed 'Best First'.
Re: Percent of CPU/Mem Usage for User
by Anonymous Monk on Jun 11, 2010 at 10:34 UTC

        Thank you, however those appear to give processor information for the entire machine, or current script, not for the user as a whole.

        Edit: I might actually use one of those first ones. Gave me an idea for an added feature to also limit or send an alert based on total processor usage. Thank you yet again!

Re: Percent of CPU/Mem Usage for User
by Khen1950fx (Canon) on Jun 11, 2010 at 12:15 UTC
    Top may not be what you really need. Top is supposed to give a "dynamic real-time view of a running system". On the other hand, "ps selects all processes with the same effective user id (euid=EUID) as the current user and associated with the same terminal as the invoker".
      That is why I used the variation "top -b -n 1", so I could grab it as a string. However, Proc::ProcessTable mentioned by the other poster seems to return similar, as a Perl module. I am currently looking into calculating processor and memory usage from it.
Re: Percent of CPU/Mem Usage for User
by geekt (Novice) on Jun 12, 2010 at 01:30 UTC

    This code seems to work. I added CPU idle percentage, just because I think it's useful to know how little idle processor you have left, so you know when you need to upgrade a server, or if you should run an intense process.

    sub userMemCPUpercNidle { my @usernames = @_; my ($usercpu, $usermem, $cpuidle); # return values foreach my $username (@usernames){ my $top = `top -b -n 1 -u $username`; # gets top from the system $top =~ m/(\d+\.\d+)\%id/; # matches CPU idle time $cpuidle = $1; my @lines = split(/\n/, $top); # split each line into an array val +ue my ($cpucol,$memcol); foreach my $line (@lines){ if ($line =~ m/\%CPU/){ # checks if it's collumb headers my $colcount = 0; foreach my $col (split(' ', $line)){ if ($col eq '%CPU') { $cpucol = $colcount; # number of col that is the CPU val f +or process } elsif ($col eq '%MEM') { $memcol = $colcount; # number of col that is the mem val f +or process } if (defined($cpucol) && defined($memcol)){ last; } else { $colcount++; } } } else { if (!defined($cpucol) && !defined($memcol)){ $linecount++; } else { my @values = split(/\s+/, $line); $usercpu += $values[$cpucol]; # add process cpu percent to t +otal $usermem += $values[$memcol]; # add process memory percent t +o total } } } } return $usermem, $usercpu, $cpuidle; }

    Edit: This code was mainly written just to get it working. I didn't try to optimize it or think about how to do it better, just thought how it might be possible and did it to get it working. After looking at it later I noticed at least one major mistake. I tried to add the capability to check multiple users when first writing it, but then forgot that I had while I was writing. Will update with the code I am working on.

Re: Percent of CPU/Mem Usage for User
by deMize (Monk) on Jun 11, 2010 at 21:38 UTC
    Question: Instead of just grabbing % of cpu/mem usage, is it possible to restrict or cap the usage, but put the script in a wait until it can continue? I'm curious if someone developed a module to do this. For instance, if the user is approaching the mem limit, dump some mem onto the HD. Or if the CPU limit is reached, wait until other jobs are finished processing, if possible, otherwise timeout.


    Demize

      I was thinking about doing this. However, this would mean that the process would stay active, and I don't want that. Should be simple enough to do. I considered doing a slight wait, maybe a second or less, but considering this is actually going to be used in a CGI, not a good idea.

      You could use something similar to this to check if there are enough resources for a heavy task, and keep checking, with a maximum wait time...

        Response: Yes, the process would stay active, but isn't that what's needed, at least until the script ends, or the process is terminated? My guess would be to fork this check. I'm uncertain why there aren't any hooks already into the Linux kernel to do this... perhaps there are, afterall, I come from the MS world :)


        Demize
Re: Percent of CPU/Mem Usage for User
by geekt (Novice) on Jun 15, 2010 at 00:02 UTC

    Following is fixed code. Should now return data for multiple passed users, and return it in a hash ref, so you can access it by using ${$hash}{users}{username}{cpu}, etc.

    Code is a bit cleaned up, but there are many ways I can improve it. Any suggestions are appreciated.

    sub userMemCPUpercNidle { my @usernames = @_; my $topcommand = "top -b -n 1 -u "; my $top; # top result my $countusers = 0; my $rethash; foreach my $username (@usernames){ $topcommand .= $username; if ($countusers < $#usernames){ # if there are more users $topcommand .= ", "; } $top = `$topcommand`; # gets top from the system } $top =~ m/(\d+\.\d+)\%id/; # matches CPU idle time $rethash = {'cpuidle' => $1}; # assigns cpu idle to hash $top =~ s/^.+?Swap[^\n]+\s+//s; # removes everything before the proc +esses my @lines = split(/\n/, $top); # split each line into an array value my ($cpucol,$memcol,$usrcol); my $headers = shift(@lines); foreach my $col (split(' ', $headers)){ # determines which collumn e +ach value is in if ($col eq 'USER') { $usrcol = $colcount; # number of col that is the USER val for pr +ocess } elsif ($col eq '%CPU') { $cpucol = $colcount; # number of col that is the CPU val for pro +cess } elsif ($col eq '%MEM') { $memcol = $colcount; # number of col that is the mem val for pro +cess } if (defined($usrcol) && defined($cpucol) && defined($memcol)){ last; } else { $colcount++; } } foreach my $line (@lines){ if (defined($cpucol) && defined($memcol) && defined($usrcol)){ my @values = split(' ', $line); ${$rethash}{users}{$values[$usrcol]}{cpu} += $values[$cpucol]; # + add process cpu percent to total ${$rethash}{users}{$values[$usrcol]}{mem} += $values[$memcol]; # + add process memory percent to total } } return $rethash; }