#!/usr/bin/perl

# Nasty hack written by Juliusz Chroboczek.

# You are welcome to do anything you wish with this monster as long as
# any modified copies are clearly marked as such.  No guarantees of
# any kind.

# Warning: this code is written in Perl.  By proceeding further you
# certify that you are a consenting adult and that it is legal in your
# locality to view Perl code.



$slibtotal = $plibtotal = $rlibtotal = 0;
$sdevtotal = $pdevtotal = $rdevtotal = 0;
$sanontotal = $panontotal = $ranontotal = 0;
$sothertotal = $pothertotal = $rothertotal = 0;

$|=1;

open(TTY, '/dev/tty') || die "Couldn't open tty: $!\n";

sub pause {
  print "\nPress enter to continue: ";
  $line = <TTY>;
  print "\n\n";
}

$pid = $ARGV[0];

if($pid == 0) {
  print "Looking for XServer...\n";
  sleep(1);
  ($pid) = (qx(ps awx | grep '[ /][X][Ss]erver') =~ /^ *([0-9]*) .*/);
}
if(!$pid) {
  print "Looking for XFree86...\n";
  sleep(1);
  ($pid) = (qx(ps awx | grep '[ /][X]Free86') =~ /^ *([0-9]*) .*/);
}
if($pid == 0) {
  print "Looking for Xvesa...\n";
  sleep(1);
  ($pid) = (qx(ps awx | grep '[ /][X]vesa') =~ /^ *([0-9]*) .*/);
}
if($pid == 0) {
  print "Looking for Xfbdev...\n";
  sleep(1);
  ($pid) = (qx(ps awx | grep '[ /][X]fbdev') =~ /^ *([0-9]*) .*/);
}
if(!$pid) {
  print "Looking for XF86_*...\n";
  sleep(1);
  ($pid) = (qx(ps awx | grep '[ /][X]F86') =~ /^ *([0-9]*) .*/);
}
if($pid == 0) {
  print "Looking for X...\n";
  sleep(1);
  ($pid) = (qx(ps awx | grep '[ /][X]') =~ /^ *([0-9]*) .*/);
}
if($pid == 0) {
  print "Sorry, I couldn't find a running X server.\n";
  print "Please enter the pid of the X server: ";
  $pid = <TTY>;
  $pid > 0 || die "Couldn't find X server.\n";
}

print "\n\nOkay, analysing process $pid.\n\n";



print "Scavenging system core...";
sleep(1);
print " done.\n";
open(MAP, "/proc/$pid/maps") || die "Couldn't open map: $!\n";

print "Beginning analysis...\n";

while(<MAP>) {
  m/([0-9a-f]*)-([0-9a-f]*) (....) ([0-9a-f]*) (..:..) ([0-9]*)[ \t]*(.*)/
    || die "Couldn't parse process map\n";
  $from = hex $1;
  $to = hex $2;
  $mode = $3;
  $offset = hex $4;
  $inode = $6;
  $file = $7;

  $size = $to - $from;


  if($file =~ "\.so") {
    if($mode =~ ".*s") {
      $slibtotal += $size;
      $stotal += $size;
    } elsif($mode =~ "r\-") {
      $rlibtotal += $size;
      $rtotal += $size;
    } else {
      $plibtotal += $size;
      $ptotal += $size;
    }
  } elsif ($file =~ "/dev/") {
    if($mode =~ ".*s") {
      $sdevtotal += $size;
      $stotal += $size;
    } elsif($mode =~ "r\-") {
      $rdevtotal += $size;
      $rtotal += $size;
    } else {
      $pdevtotal += $size;
      $ptotal += $size;
    }
  } elsif ($file eq "") {
    if($mode =~ ".*s") {
      $sanontotal += $size;
      $stotal += $size;
    } elsif($mode =~ "r\-") {
      $ranontotal += $size;
      $rtotal += $size;
    } else {
      $panontotal += $size;
      $ptotal += $size;
    }
  } else {
    if($mode =~ ".*s") {
      $sothertotal += $size;
      $stotal += $size;
    } elsif($mode =~ "r\-") {
      $rothertotal += $size;
      $rtotal += $size;
    } else {
      $pothertotal += $size;
      $ptotal += $size;
    }
  }
}

$total = $ptotal + $stotal;

sleep(1);
print "Hmm, it's not as trivial as I thought...\n";
sleep(1);
print "Tricky...";
sleep(1);
print ".";
sleep(1);
print ".";
sleep(1);
print ".";
sleep(2);
print "\nAnalysis complete.  Phew!\n";

&pause;


print << "EOF";
The process is mapping a grand total of $total bytes.
Sounds like a lot?
EOF

&pause;

$another = 'Roughly';

if($stotal > 0) {

$another = 'Another';

$smisc = $stotal - $sdevtotal;

print << "EOF";
Well, actually, $stotal bytes of that are shared with other
processes.  Let me see what they are...

EOF

if($smisc > 0) {
print << "EOF";
Out of that figure, $sdevtotal bytes are devices: they are not real
memory, just a trick that XFree86 uses to speak to the hardware.

(If you are puzzled by this figure, remember that under some
circumstances XFree86 will map the same device multiple times.  You're
still not paying for any of that.)

The rest, $smisc bytes, are shared mappings that I was unable to
identify.
EOF
} else {
print << "EOF";
All of that memory, the whole $stotal bytes, are just mappings
of devices.  You're not paying for that -- it's only a trick that
XFree86 uses to speak to the hardware.

(If you are puzzled by this figure, remember that under some
circumstances XFree86 will map the same device multiple times.  You're
still not paying for any of that.)

EOF
}

&pause;
}

$rmisc = $rtotal - $rlibtotal - $rdevtotal;
print << "EOF";
$another $rtotal bytes are read-only mappings from files.  These
mappings are not very expensive: as they cannot be written to, the
kernel can discard them whenever it runs short of memory.  If they are
needed at a later time, it can just fetch them from the original file
again.

EOF

&pause;

print << "EOF";

But it's even better than that.  $rlibtotal bytes out of that are shared
libraries -- things like the standard C library, that are shared by
all processes, no matter how many.  Think of them as being part of the
system, not something that you should bill to the X server.
EOF

if($rdevtotal > 0) {
print << "EOF";
Another $rdevtotal bytes are read-only devices.  No idea what these are,
but you're definitely not paying for them.
EOF
}

print << "EOF";
  
The rest, $rmisc bytes, are the X server binary, the XFree86 drivers,
and other read-only mappings that I could not identify.  It's
difficult to quantify how much of that is actually using memory.
EOF

&pause;

$pmisc = $panontotal + $pdevtotal;
print "Now for the bad news.\n";

&pause;

print << "EOF";

There are $ptotal bytes that are private to the X server process.
This is memory that you're actually paying for.

Of that, $plibtotal bytes are the initialised data of the system libra-
ries.  $pothertotal bytes are the initialised data of the X server and
its drivers.

The rest, $pmisc bytes, are anonymous private mappings.  The kernel
maps them, but only really allocates them when they are first written
to, so it's very difficult to find out how much memory they really
cost.

Some of that is storage private to the system libraries.  Some of it
is storage private to the X server.  But most of it is probaly storage
that the X server has allocated on behalf of clients -- mostly pixmaps
(off-screen images allocated in the server), some of it is for fonts.

EOF

&pause;
print << "EOF";

If you still think this is a lot, here are a few hints for reducing
your memory usage.

Try to use clients that allocate less pixmaps.  Enlightenment and
Netscape, in particular, are known for allocating a lot of those.  Of
course, these applications have good reasons for allocating pixmaps.

You could also try to use a lower display depth -- a pixmap at depth
24 typically costs twice as much as a depth 15 pixmap of the same
size.  (Depth 24 costs either 4 or 3 bytes per pixel, depth 15 only
costs 2.)

(Did I mention that you should avoid depth 16?)



EOF

&pause;

print << "EOF";


Summary of collected information
********************************

Total mapped memory: $total bytes.

  Total shared: $stotal bytes.
    $sdevtotal shared devices.
EOF
if($smisc > 0) {
print << "EOF";
    $smisc other shared data.
EOF
}
print << "EOF";

  Read-only total: $rtotal.
    $rlibtotal read-only shared libraries.
    $rmisc other read-only.

  Private total: $ptotal.
    $plibtotal private library data.
    $pothertotal total private data of other files.
    $pmisc other private data.


EOF

print "Well, it was a pleasure chatting with you.  Bye.\n";

