Add a new top applet
authorEric Andersen <andersen@codepoet.org>
Tue, 17 Sep 2002 22:14:58 +0000 (22:14 -0000)
committerEric Andersen <andersen@codepoet.org>
Tue, 17 Sep 2002 22:14:58 +0000 (22:14 -0000)
 -Erik

include/applets.h
include/usage.h
procps/Makefile.in
procps/config.in
procps/top.c [new file with mode: 0644]

index de07cc96062405cdcb31d200fde764e0d5ce324d..9508c3a4b328589cba7c6fb42acf6d46288e8f36 100644 (file)
 #ifdef CONFIG_TIME
        APPLET(time, time_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER)
 #endif
+#ifdef CONFIG_TOP
+       APPLET(top, top_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER)
+#endif
 #ifdef CONFIG_TOUCH
        APPLET(touch, touch_main, _BB_DIR_BIN, _BB_SUID_NEVER)
 #endif
index 1cdaa668121c671a6c21264b4da7d8cec087d0af..33a81c4671b4c3f2e40c39f958617e0e6ce9ce2e 100644 (file)
        "Options:\n" \
        "\t-v\tDisplays verbose resource usage information."
 
+#define top_trivial_usage \
+       "[-d <seconds>]"
+#define top_full_usage \
+       "top provides an view of processor activity in real time.\n" \
+       "This utility reads the status for all processes in /proc each <seconds>\n" \
+       "and shows the status for however many processes will fit on the screen.\n" \
+       "This utility will not show processes that are started after program startup,\n" \
+       "but it will show the EXIT status for and PIDs that exit while it is running."
+
 #define touch_trivial_usage \
        "[-c] FILE [FILE ...]"
 #define touch_full_usage \
index 1851d8953390b9252442e6a30b821e8374e53712..150d2c31d839ec40a288d099fc911a357646aaad 100644 (file)
@@ -28,6 +28,7 @@ PROCPS-$(CONFIG_KILL)         += kill.o
 PROCPS-$(CONFIG_PIDOF)         += pidof.o
 PROCPS-$(CONFIG_PS)            += ps.o
 PROCPS-$(CONFIG_RENICE)                += renice.o
+PROCPS-$(CONFIG_TOP)           += top.o
 PROCPS-$(CONFIG_UPTIME)                += uptime.o
 
 libraries-y+=$(PROCPS_DIR)$(PROCPS_AR)
index 94d76b6065828618de1f3af6f0b3c7d66c6a0391..be2dd090ed443654935f2ea1eebc2ad778a60408 100644 (file)
@@ -18,6 +18,7 @@ if [ "$CONFIG_PS" = "y" ] ; then
     bool '  Use devps instead of /proc (needs a patched kernel)'       CONFIG_FEATURE_USE_DEVPS_PATCH
 fi
 bool 'renice'      CONFIG_RENICE
+bool 'top'         CONFIG_TOP
 bool 'uptime'      CONFIG_UPTIME
 endmenu
 
diff --git a/procps/top.c b/procps/top.c
new file mode 100644 (file)
index 0000000..38211b3
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+ * A tiny 'top' utility.
+ *
+ * This is written specifically for the linux 2.4 /proc/<PID>/status
+ * file format, but it checks that the file actually conforms to the
+ * format that this utility expects.
+ * This reads the PIDs of all processes at startup and then shows the
+ * status of those processes at given intervals.  User can give
+ * maximum number of processes to show.  If a process exits, it's PID
+ * is shown as 'EXIT'.  If new processes are started while this works,
+ * it doesn't add them to the list of shown processes.
+ * 
+ * NOTES:
+ * - At startup this changes to /proc, all the reads are then
+ *   relative to that.
+ * - Includes code from the scandir() manual page.
+ * 
+ * TODO:
+ * - ppid, uid etc could be read only once when program starts
+ *   and rest of the information could be gotten from the
+ *   /proc/<PID>/statm file.
+ * - Add process CPU and memory usage *percentages*.
+ * 
+ * (C) Eero Tamminen <oak at welho dot com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include "busybox.h"
+
+
+/* process information taken from /proc,
+ * The code takes into account how long the fields below are,
+ * starting from copying the file from 'status' file to displaying it!
+ */
+typedef struct {
+       char uid[6];    /* User ID */
+       char pid[6];    /* Pid */
+       char ppid[6];   /* Parent Pid */
+       char name[12];  /* Name */
+       char cmd[20];   /* command line[read/show size] */
+       char state[2];  /* State: S, W... */
+       char size[9];   /* VmSize */
+       char lck[9];    /* VmLck */
+       char rss[9];    /* VmRSS */
+       char data[9];   /* VmData */
+       char stk[9];    /* VmStk */
+       char exe[9];    /* VmExe */
+       char lib[9];    /* VmLib */
+} status_t;
+
+/* display generic info (meminfo / loadavg) */
+static void display_generic(void)
+{
+       FILE *fp;
+       char buf[80];
+       float avg1, avg2, avg3;
+       unsigned long total, used, mfree, shared, buffers, cached;
+
+       /* read memory info */
+       fp = fopen("meminfo", "r");
+       if (!fp) {
+               perror("fopen('meminfo')");
+               return;
+       }
+       fgets(buf, sizeof(buf), fp);    /* skip first line */
+
+       if (fscanf(fp, "Mem: %lu %lu %lu %lu %lu %lu",
+                  &total, &used, &mfree, &shared, &buffers, &cached) != 6) {
+               fprintf(stderr, "Error: failed to read 'meminfo'");
+               fclose(fp);
+       }
+       fclose(fp);
+       
+       /* read load average */
+       fp = fopen("loadavg", "r");
+       if (!fp) {
+               perror("fopen('loadavg')");
+                       return;
+       }
+       if (fscanf(fp, "%f %f %f", &avg1, &avg2, &avg3) != 3) {
+               fprintf(stderr, "Error: failed to read 'loadavg'");
+               fclose(fp);
+               return;
+       }
+       fclose(fp);
+
+       /* convert to kilobytes */
+       if (total) total /= 1024;
+       if (used) used /= 1024;
+       if (mfree) mfree /= 1024;
+       if (shared) shared /= 1024;
+       if (buffers) buffers /= 1024;
+       if (cached) cached /= 1024;
+       
+       /* output memory info and load average */
+       printf("Mem: %ldK, %ldK used, %ldK free, %ldK shrd, %ldK buff, %ldK cached\n",
+              total, used, mfree, shared, buffers, cached);
+       printf("Load average: %.2f, %.2f, %.2f    (State: S=sleeping R=running, W=waiting)\n",
+              avg1, avg2, avg3);
+}
+
+
+/* display process statuses */
+static void display_status(int count, const status_t *s)
+{
+       const char *fmt, *cmd;
+       
+       /* clear screen & go to top */
+       printf("\e[2J\e[1;1H");
+
+       display_generic();
+       
+       /* what info of the processes is shown */
+       printf("\n%*s %*s %*s %*s %*s %*s  %-*s\n",
+              sizeof(s->pid)-1, "Pid:",
+              sizeof(s->state)-1, "",
+              sizeof(s->ppid)-1, "PPid:",
+              sizeof(s->uid)-1, "UID:",
+              sizeof(s->size)-1, "WmSize:",
+              sizeof(s->rss)-1, "WmRSS:",
+              sizeof(s->cmd)-1, "command line:");
+
+       while (count--) {
+               if (s->cmd[0]) {
+                       /* normal process, has command line */
+                       cmd = s->cmd;
+                       fmt = "%*s %*s %*s %*s %*s %*s  %s\n";
+               } else {
+                       /* no command line, show only process name */
+                       cmd = s->name;
+                       fmt = "%*s %*s %*s %*s %*s %*s  [%s]\n";
+               }
+               printf(fmt,
+                      sizeof(s->pid)-1, s->pid,
+                      sizeof(s->state)-1, s->state,
+                      sizeof(s->ppid)-1, s->ppid,
+                      sizeof(s->uid)-1, s->uid,
+                      sizeof(s->size)-1, s->size,
+                      sizeof(s->rss)-1, s->rss,
+                      cmd);
+               s++;
+       }
+}
+
+
+/* checks if given 'buf' for process starts with 'id' + ':' + TAB
+ * and stores rest of the buf to 'store' with max size 'size'
+ */
+static void process_status(const char *buf, const char *id, char *store, size_t size)
+{
+       int len, i;
+       
+       if (!store) {
+               /* ignoring this field */
+               return;
+       }
+
+       /* check status field name */
+       len = strlen(id);
+       if (strncmp(buf, id, len) != 0) {
+               error_msg_and_die("ERROR status: line doesn't start with '%s' in:\n%s\n", id, buf);
+       }
+       buf += len;
+       
+       /* check status field format */
+       if ((*buf++ != ':') || (*buf++ != '\t')) {
+               error_msg_and_die("ERROR status: field '%s' not followed with ':' + TAB in:\n%s\n", id, buf);
+       }
+       
+       /* skip whitespace in Wm* fields */
+       if (id[0] == 'V' && id[1] == 'm') {
+               i = 3;
+               while (i--) {
+                       if (*buf == ' ') {
+                               buf++;
+                       } else {
+                               error_msg_and_die("ERROR status: can't skip whitespace for "
+                                       "'%s' field in:\n%s\n", id, buf);
+                       }
+               }
+       }
+       
+       /* copy at max (size-1) chars and force '\0' to the end */
+       while (--size) {
+               if (*buf < ' ') {
+                       break;
+               }
+               *store++ = *buf++;
+       }
+       *store = '\0';
+}
+
+
+/* read process statuses */
+static void read_status(int num, status_t *s)
+{
+       char status[20];
+       char buf[80];
+       FILE *fp;
+       
+       while (num--) {
+               sprintf(status, "%s/status", s->pid);
+
+               /* read the command line from 'cmdline' in PID dir */
+               fp = fopen(status, "r");
+               if (!fp) {
+                       strncpy(s->pid, "EXIT", sizeof(s->pid));
+                       continue;
+               }
+
+               /* get and process the information */
+               fgets(buf, sizeof(buf), fp);
+               process_status(buf, "Name", s->name, sizeof(s->name));
+               fgets(buf, sizeof(buf), fp);
+               process_status(buf, "State", s->state, sizeof(s->state));
+               fgets(buf, sizeof(buf), fp);
+               process_status(buf, "Tgid", NULL, 0);
+               fgets(buf, sizeof(buf), fp);
+               process_status(buf, "Pid", NULL, 0);
+               fgets(buf, sizeof(buf), fp);
+               process_status(buf, "PPid", s->ppid, sizeof(s->ppid));
+               fgets(buf, sizeof(buf), fp);
+               process_status(buf, "TracePid", NULL, 0);
+               fgets(buf, sizeof(buf), fp);
+               process_status(buf, "Uid", s->uid, sizeof(s->uid));
+               fgets(buf, sizeof(buf), fp);
+               process_status(buf, "Gid", NULL, 0);
+               fgets(buf, sizeof(buf), fp);
+               process_status(buf, "FDSize", NULL, 0);
+               fgets(buf, sizeof(buf), fp);
+               process_status(buf, "Groups", NULL, 0);
+               fgets(buf, sizeof(buf), fp);
+               /* only user space processes have command line
+                * and memory statistics
+                */
+               if (s->cmd[0]) {
+                       process_status(buf, "VmSize", s->size, sizeof(s->size));
+                       fgets(buf, sizeof(buf), fp);
+                       process_status(buf, "VmLck", s->lck, sizeof(s->lck));
+                       fgets(buf, sizeof(buf), fp);
+                       process_status(buf, "VmRSS", s->rss, sizeof(s->rss));
+                       fgets(buf, sizeof(buf), fp);
+                       process_status(buf, "VmData", s->data, sizeof(s->data));
+                       fgets(buf, sizeof(buf), fp);
+                       process_status(buf, "VmStk", s->stk, sizeof(s->stk));
+                       fgets(buf, sizeof(buf), fp);
+                       process_status(buf, "VmExe", s->exe, sizeof(s->exe));
+                       fgets(buf, sizeof(buf), fp);
+                       process_status(buf, "VmLib", s->lib, sizeof(s->lib));
+               }
+               fclose(fp);
+               
+               /* next process */
+               s++;
+       }
+}
+
+
+/* allocs statuslist and reads process command lines, frees namelist,
+ * returns filled statuslist or NULL in case of error.
+ */
+static status_t *read_info(int num, struct dirent **namelist)
+{
+       status_t *statuslist, *s;
+       char cmdline[20];
+       FILE *fp;
+       int idx;
+
+       /* allocate & zero status for each of the processes */
+       statuslist = calloc(num, sizeof(status_t));
+       if (!statuslist) {
+               return NULL;
+       }
+       
+       /* go through the processes */
+       for (idx = 0; idx < num; idx++) {
+
+               /* copy PID string to status struct and free name */
+               s = &(statuslist[idx]);
+               if (strlen(namelist[idx]->d_name) > sizeof(s->pid)-1) {
+                       fprintf(stderr, "PID '%s' too long\n", namelist[idx]->d_name);
+                       return NULL;
+               }
+               strncpy(s->pid, namelist[idx]->d_name, sizeof(s->pid));
+               s->pid[sizeof(s->pid)-1] = '\0';
+               free(namelist[idx]);
+
+               /* read the command line from 'cmdline' in PID dir */
+               sprintf(cmdline, "%s/cmdline", s->pid);
+               fp = fopen(cmdline, "r");
+               if (!fp) {
+                       perror("fopen('cmdline')");
+                       return NULL;
+               }
+               fgets(statuslist[idx].cmd, sizeof(statuslist[idx].cmd), fp);
+               fclose(fp);
+       }
+       free(namelist);
+       return statuslist;
+}
+
+
+/* returns true for file names which are PID dirs
+ * (i.e. start with number)
+ */
+static int filter_pids(const struct dirent *dir)
+{
+       status_t dummy;
+       char *name = dir->d_name;
+
+       if (*name >= '0' && *name <= '9') {
+               if (strlen(name) > sizeof(dummy.pid)-1) {
+                       fprintf(stderr, "PID name '%s' too long\n", name);
+                       return 0;
+               }
+               return 1;
+       }
+       return 0;
+}
+
+
+/* compares two directory entry names as numeric strings
+ */
+static int num_sort(const void *a, const void *b)
+{
+       int ia = atoi((*(struct dirent **)a)->d_name);
+       int ib = atoi((*(struct dirent **)b)->d_name);
+       
+       if (ia == ib) {
+               return 0;
+       }
+       /* NOTE: by switching the check, you change the process sort order */
+       if (ia < ib) {
+               return -1;
+       } else {
+               return 1;
+       }
+}
+
+
+int top_main(int argc, char **argv)
+{
+       status_t *statuslist;
+       struct dirent **namelist;
+       int opt, num, interval, lines;
+#if defined CONFIG_FEATURE_AUTOWIDTH && defined CONFIG_FEATURE_USE_TERMIOS
+       struct winsize win = { 0, 0, 0, 0 };
+#endif
+       /* Default update rate is 5 seconds */
+       interval = 5;
+       /* Default to 25 lines */
+       lines = 25;
+
+       /* do normal option parsing */
+       while ((opt = getopt(argc, argv, "d:")) > 0) {
+           switch (opt) {
+               case 'd':
+                   interval = atoi(optarg);
+                   break;
+               default:
+                   show_usage();
+           }
+       }
+
+#if defined CONFIG_FEATURE_AUTOWIDTH && defined CONFIG_FEATURE_USE_TERMIOS
+       ioctl(fileno(stdout), TIOCGWINSZ, &win);
+       if (win.ws_row > 4)
+           lines = win.ws_row - 6;
+#endif
+       
+       /* change to proc */
+       if (chdir("/proc") < 0) {
+               perror_msg_and_die("chdir('/proc')");
+       }
+       
+       /* read process IDs for all the processes from the procfs */
+       num = scandir(".", &namelist, filter_pids, num_sort);
+       if (num < 0) {
+               perror_msg_and_die("scandir('/proc')");
+       }
+       if (lines > num) {
+               lines = num;
+       }
+
+       /* read command line for each of the processes */
+       statuslist = read_info(num, namelist);
+       if (!statuslist) {
+               return EXIT_FAILURE;
+       }
+
+       while (1) {
+               /* read status for each of the processes */
+               read_status(num, statuslist);
+
+               /* display status */
+               display_status(lines, statuslist);
+               
+               sleep(interval);
+       }
+       
+       free(statuslist);
+       return EXIT_SUCCESS;
+}