| /* |
| * A tiny 'top' utility. |
| * |
| * This is written specifically for the linux /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 int process_status(const char *buf, const char *id, char *store, size_t size) |
| { |
| int len, i; |
| |
| /* check status field name */ |
| len = strlen(id); |
| if (strncmp(buf, id, len) != 0) { |
| if(store) |
| error_msg_and_die("ERROR status: line doesn't start with '%s' in:\n%s\n", id, buf); |
| else |
| return 0; |
| } |
| if (!store) { |
| /* ignoring this field */ |
| return 1; |
| } |
| 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'; |
| return 1; |
| } |
| |
| /* 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)); |
| s->pid[sizeof(s->pid)-1] = '\0'; |
| fclose(fp); |
| 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); |
| if(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); |
| if(process_status(buf, "TracerPid", 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); |
| if(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) { |
| fclose(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 - 5 lines for status */ |
| lines = 25 - 5; |
| |
| /* 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 - 5; |
| #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; |
| } |