Bash script to find out the summary of user memory usage [not working]

Giorgos Keramidas keramida at ceid.upatras.gr
Mon Dec 17 07:51:02 PST 2007


On 2007-12-17 06:00, Patrick Dung <patrick_dkt at yahoo.com.hk> wrote:
> I have correction with the script but still doesn't work:
>
> #!/usr/local/bin/bash
> for user in `ps -A -o user | sort | uniq | tail +2`
>  do
>         echo "user: $user"
>
>    ps aux -U $user | tail +2 | while read line
>    do
>
>     mem=`echo $line | awk {'print $4'}`
>         echo "mem: $mem"
>         TMPSUMMEM=`awk -v x=$mem -v y=$TMPSUMMEM 'BEGIN{printf
> "%.2f\n",x+y}'`
>         echo "summem: $TMPSUMMEM"
>    done
>         echo "finalsummem: $SUMMEM"
>         export SUMMEM=$TMPSUMMEM
>  done
>
>         echo "finalsummem: $SUMMEM"

There are *many* race conditions in that script.  For example, there's
no guarantee that once you get a snapshot of the "ps -A -o user" output,
then the same users will be listed in the loop you are running for each
username.

The script is also a bit 'sub-optimal' because it calls ps(1) and parses
its output many times (at least as many times as there are users).  A
much better way to `design' something like this would be to keep a hash
of the usernames, and keep incrementing the hash entry for each user as
you hit ps(1) output lines.

I'm not going to even bother writing a script to use a hash in bash(1),
because there are much better languages to work with hashes,
dictionaries or even simple arrays.

Here's for example a Python script which does what I described:

     1  #!/usr/bin/env python
     2
     3  import os
     4  import re
     5  import sys
     6
     7  try:
     8      input = os.popen('ps xauwww', 'r')
     9  except:
    10      print "Cannot open pipe for ps(1) output"
    11      sys.exit(1)
    12
    13  # Start with an empty dictionary.
    14  stats = {}
    15
    16  # Regexp to strip the ps(1) output header.
    17  header = re.compile('USER')
    18
    19  for line in input.readlines():
    20      if header.match(line):
    21          continue
    22      fields = line.split()
    23      if not fields or len(fields) < 4:
    24          continue
    25
    26      (username, mem) = (fields[0], float(fields[3]))
    27      value = None
    28      try:
    29          value = stats[username]
    30      except KeyError:
    31          pass
    32
    33      if not value:
    34          stats[username] = 0.0
    35      stats[username] += mem
    36
    37  # Print all the stats we have collected so far.
    38  keys = stats.keys()
    39  if len(keys) > 0:
    40      total = 0.0
    41      print "%-15s %5s" % ('USERNAME', 'MEM%')
    42      for k in stats.keys():
    43          print "%-15s %5.2f" % (k, stats[k])
    44          total += stats[k]
    45      # Finally print a grand total of all users.
    46      print "%-15s %5.2f" % ('TOTAL', total)

It's not the shortest Python script one could write to do what you
describe, but I've gone for readability rather than speed or
conciseness.

Running this script should produce:

    $ ./foo.py
    USERNAME         MEM%
    _pflogd          0.10
    daemon           0.00
    bind             1.10
    _dhcp            0.10
    keramida        38.60
    smmsp            0.10
    root            10.10
    build            0.00
    TOTAL           50.10
    $

PS: Yes, you could probably do the same in bash, with sed, awk and a bit
of superglue, but I prefer Perl and/or Python for anything which
involves something a bit more involved than simple string substitution
these days...



More information about the freebsd-questions mailing list