Hi List,

For all you memory resource watchers with larger scores than bank-accounts,
here's a little something to keep track of it all.  Use it to complain to
us about Lily's phenomenal resource appetite.

Greetings,

Jan.

PS: I'm not a system-level programmer, don't flame me (rather, send a patch :-)

/*
 proc-time.c -- Get resource usage from /proc for a command (GNU/Linux).

 awaiting PC's linux-mm patch for getrusage

 source file of the GNU LilyPond music typesetter
 Licence: GNU GPL
 
 (c) 2000 Jan Nieuwenhuizen <[EMAIL PROTECTED]>

 Translated from Python prototype

 GNU time doesn't report memory stuff on FreeBSD either,
 but I couldn't find an easy way like to get this info (like /proc).
*/
 
#include <getopt.h>
#include <stdio.h>

char const* name = "proc-time";
char const* version = "1.3.55";

char const* short_opts = "bhi:ntvV";
struct option long_opts[] =
{
  {"heartbeat", no_argument, 0, 'b'},
  {"help", no_argument, 0, 'h'},
  {"interval", required_argument, 0, 'i'},
  {"no-fork", no_argument, 0, 'n'},
  {"test", no_argument, 0, 't'},
  {"version", no_argument, 0, 'v'},
  {"verbose", no_argument, 0, 'V'},
  {0, no_argument, 0, 0}
};

int test = 0;
int nofork = 0;
int heartbeat = 0;
float interval = 0.5;
int status = 0;
int verbose = 0;

void
identify (FILE* f)
{
  fprintf (f, "%s from LilyPond %s\n", name, version);
}

void
print_usage ()
{
  identify (stdout);
  printf ("\n"
          "Usage: %s [OPTION]... COMMAND\n"
          "\n"
          "Get resource usage from /proc for COMMAND (GNU/Linux).\n"
          "\n"
          "Options:\n"
          "  -b, --heartbeat     show memory info at every heartbeat\n"
          "  -h, --help          this help\n"
          "  -i, --interval=TIME set heartbeat to TIME seconds\n"
          "  -n, --no-fork       don't fork\n"
          "  -t, --test          test mode\n"
          "  -V, --verbose       be verbose\n"
          "  -v, --version       version information\n"
          "\n", name);
}

void
print_version ()
{
  printf ("%s (GNU LilyPond) %s", name, version);
}

#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/unistd.h>

#define JIF_TO_SEC 0.01
#define PAGE_SIZE 4096
#define PAGE_TO_MEG PAGE_SIZE / (1024 * 1024)
#define MAX(a, b) (a < b ? b : a)

enum stat_enum
{
  STAT_USER_JIFFIES = 13,
  STAT_SYSTEM_JIFFIES,
  STAT_COUNTER = 17,
  STAT_MAX
};

enum statm_enum
{
  STATM_SIZE = 0,
  STATM_RSS,
  STATM_SHARED,
  STATM_CODE,
  STATM_DATA,
  STATM_LIB,
  STATM_DIRTY,
  STATM_MAX
};

struct process_info
{
  pid_t id;
  int t;
  char stat_name[81];
  char statm_name[81];
  int stat_i[STAT_MAX];
  int max_size;
  int cum_size;
  int max_rss;
  int cum_rss;
  struct timeval start_tv;
  struct timeval stop_tv;
};

struct process_info child_pi;
void
init (struct process_info* pi, pid_t pid)
{
  pi->id = pid;
  pi->t = 0;
  snprintf (pi->statm_name, sizeof (pi->statm_name) - 1,
            "/proc/%d/statm", pi->id);
  snprintf (pi->stat_name, sizeof (pi->stat_name) - 1,
            "/proc/%d/stat", pi->id);
  gettimeofday (&pi->start_tv, 0);
}

void
update_mem_stats (struct process_info* pi)
{
  FILE* f = fopen (pi->statm_name, "r");
  if (f)
    {
      int statm_i[STATM_MAX];
      if (fscanf (f, "%d %d %d %d %d %d %d",
                  &statm_i[0], 
                  &statm_i[1], 
                  &statm_i[2], 
                  &statm_i[3], 
                  &statm_i[4], 
                  &statm_i[5], 
                  &statm_i[6])
          >= STATM_MAX - 1)
        {
          pi->t++;
          pi->max_size = MAX (statm_i[STATM_SIZE], pi->max_size);
          pi->cum_size += statm_i[STATM_SIZE];
          pi->max_rss = MAX (statm_i[STATM_RSS], pi->max_rss);
          pi->cum_rss += statm_i[STATM_RSS];
        }
      else
        fprintf (stderr, "%s: scanf failed: %s", strerror (errno));
      fclose (f);
    }
  else
    fprintf (stderr, "%s: reading failed: %s", strerror (errno));
}

void
print_mem_stats (struct process_info* pi)
{
  if (pi->t)
    {
      int avg_size = pi->cum_size / pi->t;
      int avg_rss = pi->cum_rss / pi->t;
      fprintf (stderr, "MAXSIZE: %6.3fM(%d), MAXRSS: %6.3fM(%d)\n",
               (float)pi->max_size * PAGE_TO_MEG, pi->max_size, 
               (float)pi->max_rss * PAGE_TO_MEG, pi->max_rss);
      fprintf (stderr, "AVGSIZE: %6.3fM(%d), AVGRSS: %6.3fM(%d)\n",
               (float)avg_size * PAGE_TO_MEG, avg_size, 
               (float)avg_rss * PAGE_TO_MEG, avg_rss);
      fflush (stdout);
      fflush (stderr);
    }
}

void
update_time_stats (struct process_info* pi)
{
  FILE* f = fopen (pi->stat_name, "r");
  if (f)
    {
      char name[80];
      char s[80];
      if (fscanf (f,
                  "%d %s %s %d %d "
                  "%d %d %d %d %d "
                  "%d %d %d %d %d "
                  "%d %d",
                  &pi->stat_i[0],
                  &name,
                  &s,
                  &pi->stat_i[3],
                  &pi->stat_i[4],
                  &pi->stat_i[5],
                  &pi->stat_i[6],
                  &pi->stat_i[7],
                  &pi->stat_i[8],
                  &pi->stat_i[9],
                  &pi->stat_i[10],
                  &pi->stat_i[11],
                  &pi->stat_i[12],
                  &pi->stat_i[13],
                  &pi->stat_i[14],
                  &pi->stat_i[15])
          >= STAT_MAX - 1)
        ;
      fclose (f);
    }
  gettimeofday (&pi->stop_tv, 0);
}

void
print_time_stats (struct process_info* pi)
{
  fprintf (stderr, "user: %6.2f(%d) system: %6.2f(%d)\n",
           (float)pi->stat_i[STAT_USER_JIFFIES] * JIF_TO_SEC,
           pi->stat_i[STAT_USER_JIFFIES],
           (float)pi->stat_i[STAT_SYSTEM_JIFFIES] * JIF_TO_SEC,
           pi->stat_i[STAT_SYSTEM_JIFFIES]);
  fprintf (stderr, "elapsed: %6.2f\n",
           (float)(pi->stop_tv.tv_sec - pi->start_tv.tv_sec)
                   + (pi->stop_tv.tv_usec - pi->start_tv.tv_usec) * 1e-6);
}

void
handler (int arg)
{
  (void)arg;
  /* urg: child_pi */
  update_time_stats (&child_pi);
  update_mem_stats (&child_pi);
  fprintf (stderr, "\n");
  print_time_stats (&child_pi);
  print_mem_stats (&child_pi);
  if (test)
    fprintf (stderr, "handler\n");
  exit (status);
}

char**
getargs (int argc, char** argv)
{
  int c;
  while ((c = getopt_long (argc, argv, short_opts, long_opts, (int *) 0))
         != EOF)
    {
      switch (c)
        {
        case 'b':
          heartbeat = 1;
          break;
        case 'h':
          print_usage ();
          exit (0);
          break;
        case 'i':
          if (optarg)
            {
              float f;
              if (sscanf (optarg, "%f", &f))
                {
                  interval = f;
                }
            }
          break;
        case 'n':
          nofork = 1;
          break;
        case 't':
          test = 1;
          break;
        case 'v':
          identify (stdout);
          exit (0);
          break;
        case 'V':
          verbose = 1;
          break;
        default:
          print_usage ();
          exit (2);
          break;
        }
    }

  if (optind == argc)
    {
      print_usage ();
      exit (2);
    }

  return &argv[optind];
}

int
run (char** command)
{
  if (nofork)
    init (&child_pi, 1);
  else
    init (&child_pi, fork ());

  if (child_pi.id)
    {
      /* Parent */

      int input = 0;
      fd_set rfds;
      struct timeval tv;
      /* Watch stdin (fd 0) to see when it has input. */
      FD_ZERO (&rfds);
      FD_SET (0, &rfds);

      update_mem_stats (&child_pi);
      if (interval <= 0)
        {
          waitpid (child_pi.id, &status, 0);
          exit (status);
        }
      while (1)
        {
          if (waitpid (child_pi.id, &status, WNOHANG))
            break;
          
          /*
            should we use pause/alarm?
            when using the sleeptime of select, we don't see any input??
           */
#if 0
          /* man page says we should reset these */
          tv.tv_sec = 0;
          tv.tv_usec = (unsigned long)(interval*1e6);
#else
          usleep ((unsigned long)(interval*1e6));
          /* man page says we should reset these */
          tv.tv_sec = 0;
          tv.tv_usec = 0;
#endif
          input = select(1, &rfds, 0, 0, &tv);
          update_mem_stats (&child_pi);

          if (input)
            {
              fprintf (stderr, "\n");
              print_mem_stats (&child_pi);
              getc (stdin);
            }
          else if (heartbeat)
            {
              fprintf (stderr, "\n");
              print_mem_stats (&child_pi);
            }
        }
    }
  else
    {
      /* Child */
      execvp (command[0], command);
    }

  return status;
}

      
void
check_file (char const* name, char const* message)
{
  FILE* f;
  f = fopen (name, "r");
  if (f)
    {
      fclose (f);
    }
  else
    {
      fprintf (stderr, "%s: can't open: %s\n", name, message);
    }
}

void
check_sanity ()
{
  struct process_info init_pi;
  init (&init_pi, 1);

  check_file (init_pi.statm_name, "memory statistics unavailable");
  check_file (init_pi.stat_name, "detailed time statistics unavailable");
}

int
main (int argc, char** argv)
{ 
  int status = 0;
  char** command = getargs (argc, argv);
  identify (stderr);
  
  check_sanity ();

  signal (SIGINT, handler);
  signal (SIGCHLD, handler);
  signal (SIGTERM, handler);

  status = run (command);

  if (status)
    fprintf (stderr, "Non zero exit status: %d\n", status);

  return status;
}


-- 
Jan Nieuwenhuizen <[EMAIL PROTECTED]> | GNU LilyPond - The music typesetter
http://www.xs4all.nl/~jantien       | http://www.lilypond.org

Reply via email to