diff --git a/include/applets.h b/include/applets.h index de07cc960..9508c3a4b 100644 --- a/include/applets.h +++ b/include/applets.h @@ -473,6 +473,9 @@ #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 diff --git a/include/usage.h b/include/usage.h index 1cdaa6681..33a81c467 100644 --- a/include/usage.h +++ b/include/usage.h @@ -1860,6 +1860,15 @@ "Options:\n" \ "\t-v\tDisplays verbose resource usage information." +#define top_trivial_usage \ + "[-d ]" +#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 \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 \ diff --git a/procps/Makefile.in b/procps/Makefile.in index 1851d8953..150d2c31d 100644 --- a/procps/Makefile.in +++ b/procps/Makefile.in @@ -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) diff --git a/procps/config.in b/procps/config.in index 94d76b606..be2dd090e 100644 --- a/procps/config.in +++ b/procps/config.in @@ -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 index 000000000..38211b345 --- /dev/null +++ b/procps/top.c @@ -0,0 +1,408 @@ +/* + * A tiny 'top' utility. + * + * This is written specifically for the linux 2.4 /proc//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//statm file. + * - Add process CPU and memory usage *percentages*. + * + * (C) Eero Tamminen + */ +#include +#include +#include +#include +#include +#include +#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; +}