diff options
author | Rob Landley <rob@landley.net> | 2016-01-20 16:51:17 -0600 |
---|---|---|
committer | Rob Landley <rob@landley.net> | 2016-01-20 16:51:17 -0600 |
commit | 941f9d6cc09adaa0155ee71ac34e6f9b773a90f1 (patch) | |
tree | 12620e41d1dae88099e84bbf487215cb9c56d902 | |
parent | 59781de0c0512ed3c862bef5735d569b2adcd4a3 (diff) | |
download | toybox-941f9d6cc09adaa0155ee71ac34e6f9b773a90f1.tar.gz |
Add basic top support to ps.c, delete toys/pending/top.c.
While I'm there, pack the ps help text, break out TOP_COMMON (todo: update
config2help to collate that properly), add -o CPU showing which processor
this pid is running on, implement -o C (it's %cpu without the fractional
part), add -o %MEM, fix header padding.
-rw-r--r-- | toys/pending/top.c | 848 | ||||
-rw-r--r-- | toys/posix/ps.c | 296 |
2 files changed, 161 insertions, 983 deletions
diff --git a/toys/pending/top.c b/toys/pending/top.c deleted file mode 100644 index fd7e879c..00000000 --- a/toys/pending/top.c +++ /dev/null @@ -1,848 +0,0 @@ -/* top.c - Provide a view of process activity in real time. - * - * Copyright 2013 Bilal Qureshi <bilal.jmi@gmail.com> - * Copyright 2013 Ashwini Kumar <ak.ashwini@gmail.com> - * Copyright 2013 Kyungwan Han <asura321@gmail.com> - * - * No Standard - -USE_TOP(NEWTOY(top, ">0d#=3n#<1mb", TOYFLAG_USR|TOYFLAG_BIN)) - -config TOP - bool "top" - default n - help - - usage: top [-mb] [ -d seconds ] [ -n iterations ] - - Provide a view of process activity in real time. - Keys - N/M/P/T show CPU usage, sort by pid/mem/cpu/time - S show memory - R reverse sort - H toggle threads - C,1 toggle SMP - Q,^C exit - - Options - -n Iterations before exiting - -d Delay between updates - -m Same as 's' key - -b Batch mode -*/ - -#define FOR_top -#include "toys.h" -#include <signal.h> -#include <poll.h> - -GLOBALS( - long iterations; - long delay; - - long cmp_field; - long reverse; - long rows; - long smp; - long threads; - long m_flag; - long num_new_procs; - long scroll_offset; - struct termios inf; -) - -#define PROC_NAME_LEN 512 //For long cmdline. -#define INIT_PROCS 50 - -struct cpu_info { - long unsigned utime, ntime, stime, itime; - long unsigned iowtime, irqtime, sirqtime, steal; - unsigned long long total; -}; - -struct keycode_map_s { - char *key; - int code; -}; - -struct proc_info { - struct proc_info *next; - pid_t pid, ppid; - uid_t uid; - char name[PROC_NAME_LEN]; - char tname[PROC_NAME_LEN]; - char state[4]; - int prs; - unsigned long utime, stime, delta_utime, delta_stime, delta_time; - unsigned long vss, vssrw, rss, rss_shr, drt, drt_shr, stack; -}; - -static struct proc_info *free_procs, **old_procs, **new_procs; -static struct cpu_info old_cpu[10], new_cpu[10]; //1 total, 8 cores, 1 null -static int (*proc_cmp)(const void *a, const void *b); - -static struct proc_info *find_old_proc(pid_t pid) -{ - int i; - - for (i = 0; old_procs && old_procs[i]; i++) - if (old_procs[i]->pid == pid) return old_procs[i]; - - return NULL; -} - -static void read_stat(char *filename, struct proc_info *proc) -{ - int nice; - FILE *file; - char *open_paren, *close_paren; - - if (!(file = fopen(filename, "r"))) return; - fgets(toybuf, sizeof(toybuf), file); - fclose(file); - - // Split at first '(' and last ')' to get process name. - open_paren = strchr(toybuf, '('); - close_paren = strrchr(toybuf, ')'); - if (!open_paren || !close_paren) return; - - *open_paren = *close_paren = '\0'; - snprintf(proc->tname, PROC_NAME_LEN, "[%s]",open_paren + 1); - - // Scan rest of string. - sscanf(close_paren + 1, " %c %d %*d %*d %*d %*d %*d %*d %*d %*d %*d " - "%lu %lu %*d %*d %*d %d %*d %*d %*d %lu %ld " - "%*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d", - &proc->state[0], &proc->ppid, &proc->utime, &proc->stime, &nice, - &proc->vss, &proc->rss, &proc->prs); - if (!proc->vss && proc->state[0] != 'Z') proc->state[1] = 'W'; - else proc->state[1] = ' '; - if (nice < 0 ) proc->state[2] = '<'; - else if (nice) proc->state[2] = 'N'; - else proc->state[2] = ' '; -} - -static void read_status(char *filename, struct proc_info *proc) -{ - FILE *file; - - if (!(file = fopen(filename, "r"))) return; - while (fgets(toybuf, sizeof(toybuf), file)) - if (sscanf(toybuf, "Uid: %u", &(proc->uid)) == 1) break; - - fclose(file); -} - -static void read_cmdline(char *filename, struct proc_info *proc) -{ - int fd, len, rbytes = 0; - char *ch, *base, tname[PROC_NAME_LEN]; - - if ((fd = open(filename, O_RDONLY)) == -1) return; - rbytes = readall(fd, toybuf, sizeof(toybuf)); - close(fd); - if (rbytes <= 0) { - strcpy(proc->name, proc->tname); - return; - } - toybuf[rbytes] = '\0'; - while (--rbytes >= 0 && toybuf[rbytes] == '\0') continue; - - snprintf(tname, PROC_NAME_LEN, "%s", proc->tname+1); - tname[strlen(tname) - 1] = '\0'; - ch = strchr(toybuf, ' '); - if (ch) *ch = '\0'; - base = strrchr(toybuf, '/'); - if (base) base++; - else base = toybuf; - - for (; rbytes >= 0; rbytes--) - if ((unsigned char)toybuf[rbytes] < ' ') toybuf[rbytes] = ' '; - - if (*base == '-') base++; - len = strlen(tname); - if (strncmp(base, tname, len)) { - len +=3; //{,}, \0 - rbytes = strlen(toybuf); - memmove(toybuf+ len, toybuf, rbytes+1); - snprintf(toybuf, sizeof(toybuf), "{%s}", tname); - toybuf[len-1] = ' '; - } - snprintf(proc->name, PROC_NAME_LEN, "%s", toybuf); -} - -static void add_proc(int proc_num, struct proc_info *proc) -{ - int i; - - if (proc_num >= TT.num_new_procs-1) { - new_procs = xrealloc(new_procs, (INIT_PROCS + TT.num_new_procs) - * sizeof(struct proc_info *)); - for (i = TT.num_new_procs; i < (INIT_PROCS + TT.num_new_procs); i++) - new_procs[i] = NULL; - TT.num_new_procs += INIT_PROCS; - } - new_procs[proc_num] = proc; -} - -void signal_handler(int sig) -{ - tcsetattr(STDIN_FILENO, TCSANOW, &TT.inf); - xputc('\n'); - signal(sig, SIG_DFL); - raise(sig); - _exit(sig | 128); -} - -static int get_key_code(char *ch, int i) -{ - static struct keycode_map_s type2[] = { - {"OA",KEY_UP}, {"OB",KEY_DOWN}, {"OH",KEY_HOME}, - {"OF",KEY_END}, {"[A",KEY_UP}, {"[B",KEY_DOWN}, - {"[H",KEY_HOME}, {"[F",KEY_END}, {NULL, 0} - }; - - static struct keycode_map_s type3[] = { - {"[1~", KEY_HOME}, {"[4~", KEY_END}, {"[5~", KEY_PGUP}, - {"[6~", KEY_PGDN}, {"[7~", KEY_HOME}, {"[8~", KEY_END}, - {NULL, 0} - }; - struct keycode_map_s *table, *keytable[3] = {type2, type3, NULL}; - int j; - - if ( i > 3 || i < 1) return -1; - - for (j=0; (table = keytable[j]); j++) { - while (table->key) { - if (!strncmp(ch, table->key, i)) break; - table++; - } - if (table->key) { - if (i == 1 || (i == 2 && j)) return 1; - return table->code; - } - } - return -1; -} - -static int read_input(int delay) -{ - struct pollfd pfd[1]; - int ret, fret = 0, cnt = 0, escproc = 0, timeout = delay * 1000; - char ch, seq[4] = {0,}; - struct termios newf; - - tcgetattr(0, &TT.inf); - if (toys.optflags & FLAG_b) { - sleep(delay); - return 0; - } - pfd[0].fd = 0; - pfd[0].events = POLLIN; - - //prepare terminal for input, without Enter of Carriage return - memcpy(&newf, &TT.inf, sizeof(struct termios)); - newf.c_lflag &= ~(ICANON | ECHO | ECHONL); - newf.c_cc[VMIN] = 1; - newf.c_cc[VTIME] = 0; - tcsetattr(0, TCSANOW, &newf); - - while (1) { - if ((ret = poll(pfd, 1, timeout)) >= 0) break; - else { - if (timeout > 0) timeout--; - if (errno == EINTR) continue; - perror_exit("poll"); - } - } - - while (ret) { - if (read(STDIN_FILENO, &ch, 1) != 1) toys.optflags |= FLAG_b; - else if (ch == '\033' || escproc) { - int code; - //process ESC keys - if (!escproc) { - if (!poll(pfd, 1, 50)) break; //no more chars - escproc = 1; - continue; - } - seq[cnt++] = ch; - code = get_key_code(seq, cnt); - switch(code) { - case -1: //no match - fret = 0; - break; - case 1: //read more - continue; - default: // got the key - fret = code; - break; - } - } else if ((ch == TT.inf.c_cc[VINTR]) - || (ch == TT.inf.c_cc[VEOF])) - fret = 'q'; - else fret = ch | 0x20; - break; - } - tcsetattr(0, TCSANOW, &TT.inf); - return fret; -} - -// Allocation for Processes -static struct proc_info *alloc_proc(void) -{ - struct proc_info *proc; - - if (free_procs) { - proc = free_procs; - free_procs = free_procs->next; - memset(proc, 0, sizeof(*proc)); - } else proc = xzalloc(sizeof(*proc)); - - return proc; -} - -static void free_proc_list(struct proc_info *procs) -{ - struct proc_info *tmp = procs; - - for (;tmp; tmp = procs) { - procs = procs->next; - free(tmp); - } -} - -// Free allocated Processes in order to avoid memory leaks -static void free_proc(struct proc_info *proc) -{ - proc->next = free_procs; - free_procs = proc; -} - -static struct proc_info *add_new_proc(pid_t pid, pid_t tid) -{ - char filename[64]; - struct proc_info *proc = alloc_proc(); - - proc->pid = (tid)? tid : pid; - if (!tid) { - sprintf(filename, "/proc/%d/stat", pid); - read_stat(filename, proc); - sprintf(filename, "/proc/%d/cmdline", pid); - read_cmdline(filename, proc); - sprintf(filename, "/proc/%d/status", pid); - read_status(filename, proc); - } else{ - sprintf(filename, "/proc/%d/task/%d/stat", pid,tid); - read_stat(filename, proc); - sprintf(filename, "/proc/%d/task/%d/cmdline", pid, tid); - read_cmdline(filename, proc); - } - return proc; -} - -static void read_smaps(pid_t pid, struct proc_info *p) -{ - FILE *fp; - char *line; - size_t len; - long long start, end, val, prvcl, prvdr, shrdr, shrcl; - int count; - - p->vss = p->rss = 0; - start = end = val = prvcl = prvdr = shrdr = shrcl = 0; - sprintf(toybuf, "/proc/%u/smaps", pid); - if (!(fp = fopen(toybuf, "r"))) { - error_msg("No %ld\n", (long)pid); - return; - } - for (;;) { - int off; - - line = 0; - if (0 >= getline(&line, &len, fp)) break; - count = sscanf(line, "%llx-%llx %s %*s %*s %*s %n", - &start, &end, toybuf, &off); - - if (count == 3) { - end = end - start; - if (strncmp(line+off, "/dev/", 5) || !strcmp(line+off, "/dev/zero\n")) { - p->vss += end; - if (toybuf[1] == 'w') p->vssrw += end; - } - if (line[off] && !strncmp(line+off, "[stack]",7)) p->stack += end; - } else { - if (0<sscanf(line, "Private_Clean: %lld", &val)) prvcl += val; - if (0<sscanf(line, "Private_Dirty: %lld", &val)) prvdr += val; - if (0<sscanf(line, "Shared_Dirty: %lld", &val)) shrdr += val; - if (0<sscanf(line, "Shared_Clean: %lld", &val)) shrcl += val; - } - free(line); - } - free(line); //incase it broke out. - p->rss_shr = shrdr + shrcl; - p->drt = prvdr + shrdr; - p->drt_shr = shrdr; - p->rss = p->rss_shr + prvdr + prvcl; - fclose(fp); -} - -static void read_procs(void) // Read Processes -{ - DIR *proc_dir, *thr_dir; - struct dirent *pid_dir, *t_dir; - struct proc_info *proc; - pid_t pid, tid; - int proc_num = 0; - - proc_dir = opendir("/proc"); - if (!proc_dir) perror_exit("Could not open /proc"); - - new_procs = xzalloc(INIT_PROCS * sizeof(struct proc_info *)); - TT.num_new_procs = INIT_PROCS; - - while ((pid_dir = readdir(proc_dir))) { - if (!isdigit(pid_dir->d_name[0])) continue; - - pid = atoi(pid_dir->d_name); - proc = add_new_proc(pid, 0); - if (TT.m_flag) { - read_smaps(pid, proc); - if (!proc->vss) { - free(proc); - continue; - } - } - add_proc(proc_num++, proc); - - if (TT.threads) { - char filename[64]; - uid_t uid = proc->uid; - - sprintf(filename,"/proc/%d/task",pid); - if ((thr_dir = opendir(filename))) { - while ((t_dir = readdir(thr_dir))) { - if (!isdigit(t_dir->d_name[0])) continue; - - tid = atoi(t_dir->d_name); - if (pid == tid) continue; - proc = add_new_proc(pid, tid); - proc->uid = uid; //child will have same uid as parent. - add_proc(proc_num++, proc); - } - closedir(thr_dir); - } - } - } - - closedir(proc_dir); - TT.num_new_procs = proc_num; -} - -//calculate percentage. -static char* show_percent(long unsigned num, long unsigned den) -{ - long res; - static char ch, buff[12]={'\0'}; - - if(num > den) num = den; - res = (num * 100)/den; - sprintf(buff,"%ld", (num * 100)% den); - ch = *buff; - sprintf(buff, "%ld.%c",res, ch); - return buff; -} - -static int print_header(struct sysinfo *info, unsigned int cols) -{ - int fd, j, k, rows =0; - long unsigned total, meminfo_cached, anon, meminfo_mapped, - meminfo_slab, meminfo_dirty, meminfo_writeback, swapT, swapF; - char *buff; - - fd = xopen("/proc/meminfo", O_RDONLY); - while ((buff = get_line(fd))) { - if (!strncmp(buff, "Cached", 6)) - sscanf(buff,"%*s %lu\n",&meminfo_cached); - else if (!strncmp(buff, "AnonPages", 9)) - sscanf(buff,"%*s %lu\n",&anon); - else if (!strncmp(buff, "Mapped", 6)) - sscanf(buff,"%*s %lu\n",&meminfo_mapped); - else if (!strncmp(buff, "Slab", 4)) - sscanf(buff,"%*s %lu\n",&meminfo_slab); - else if (!strncmp(buff, "Dirty", 5)) - sscanf(buff,"%*s %lu\n",&meminfo_dirty); - else if (!strncmp(buff, "Writeback", 9)) - sscanf(buff,"%*s %lu\n",&meminfo_writeback); - else if (!strncmp(buff, "SwapTotal", 9)) - sscanf(buff,"%*s %lu\n",&swapT); - else if (!strncmp(buff, "SwapFree", 8)) - sscanf(buff,"%*s %lu\n",&swapF); - free(buff); - } - close(fd); - - if (!(toys.optflags & FLAG_b)) printf("\033[H\033[J"); - - if (TT.m_flag){ - sprintf(toybuf, "Mem total:%lu anon:%lu map:%lu free:%lu", - ((info->totalram) >> 10), anon, meminfo_mapped, - ((info->freeram) >> 10)); - printf("%.*s\n", cols, toybuf); - - sprintf(toybuf, "slab:%lu buf:%lu cache:%lu dirty:%lu write:%lu", - meminfo_slab, ((info->bufferram) >>10), meminfo_cached, - meminfo_dirty,meminfo_writeback); - printf("%.*s\n", cols, toybuf); - - sprintf(toybuf, "Swap total:%lu free:%lu",swapT, swapF); - printf("%.*s\n", cols, toybuf); - rows += 3; - } else { - sprintf(toybuf,"Mem: %luK used, %luK free, %luK shrd, %luK buff, %luK cached", - (info->totalram-info->freeram) >>10, (info->freeram) >>10, - (info->sharedram) >>10, (info->bufferram) >>10, meminfo_cached); - printf("%.*s\n", cols, toybuf); - - for (k = 1; new_cpu[k].total; k++) { - j = 0; - if (!TT.smp) { - k = 0; - j = sprintf(toybuf,"CPU:"); - } else j = sprintf(toybuf,"CPU%d:", k-1); - - total = (new_cpu[k].total) - (old_cpu[k].total); - if (!total) total = 1; //avoid denominator as 0, FPE - j += sprintf(toybuf + j," %s%% usr", - show_percent((new_cpu[k].utime - old_cpu[k].utime), total)); - j += sprintf(toybuf+j," %s%% sys", - show_percent((new_cpu[k].stime - old_cpu[k].stime), total)); - j += sprintf(toybuf+j," %s%% nic", - show_percent(new_cpu[k].ntime - old_cpu[k].ntime, total)); - j += sprintf(toybuf+j," %s%% idle", - show_percent(new_cpu[k].itime - old_cpu[k].itime, total)); - j += sprintf(toybuf+j," %s%% io", - show_percent((new_cpu[k].iowtime - old_cpu[k].iowtime), total)); - j += sprintf(toybuf+j," %s%% irq", - show_percent(new_cpu[k].irqtime - old_cpu[k].irqtime, total)); - j += sprintf(toybuf+j," %s%% sirq", - show_percent(new_cpu[k].sirqtime - old_cpu[k].sirqtime, total)); - printf("%.*s\n", cols, toybuf); - if (!TT.smp) break; - } - - if ((buff = readfile("/proc/loadavg", NULL, 0))) { - buff[strlen(buff) -1] = '\0'; //removing '\n' at end - sprintf(toybuf, "Load average: %s", buff); - printf("%.*s\n", cols, toybuf); - free(buff); - } - rows += 2 + ((TT.smp) ? k-1 : 1); - } - return rows; -} - -static void print_procs(void) -{ - int i, j = 0; - struct proc_info *old_proc, *proc; - long unsigned total_delta_time; - struct passwd *user; - char *user_str, user_buf[20]; - struct sysinfo info; - unsigned int cols=0, rows =0; - - terminal_size(&cols, &rows); - if (!rows){ - rows = 24; //on serial consoles setting default - cols = 79; - } - if (toys.optflags & FLAG_b) rows = INT_MAX; - TT.rows = rows; - - for (i = 0; i < TT.num_new_procs; i++) { - if (new_procs[i]) { - old_proc = find_old_proc(new_procs[i]->pid); - if (old_proc) { - new_procs[i]->delta_utime = new_procs[i]->utime - old_proc->utime; - new_procs[i]->delta_stime = new_procs[i]->stime - old_proc->stime; - } else { - new_procs[i]->delta_utime = 0; - new_procs[i]->delta_stime = 0; - } - new_procs[i]->delta_time = new_procs[i]->delta_utime - + new_procs[i]->delta_stime; - } - } - - total_delta_time = new_cpu[0].total - old_cpu[0].total; - if (!total_delta_time) total_delta_time = 1; - - qsort(new_procs, TT.num_new_procs, sizeof(struct proc_info *), proc_cmp); - - //Memory details - sysinfo(&info); - info.totalram *= info.mem_unit; - info.freeram *= info.mem_unit; - info.sharedram *= info.mem_unit; - info.bufferram *= info.mem_unit; - - rows -= print_header(&info, cols); - - if (TT.m_flag) { - sprintf(toybuf, "%5s %5s %5s %5s %5s %5s %5s %5s %s", "PID", "VSZ", "VSZRW", - "RSS", "(SHR)", "DIRTY", "(SHR)", "STACK", "COMMAND"); - toybuf[11 + TT.cmp_field*6] = (TT.reverse)?'_':'^'; //11 for PID,VSZ fields - } else sprintf(toybuf, "%5s %5s %-8s %4s %5s %5s %4s %5s %s", "PID", "PPID", - "USER", "STAT", "VSZ", "%VSZ", "CPU" , "%CPU", "COMMAND"); - - printf((toys.optflags & FLAG_b)?"%.*s\n":"\033[7m%.*s\033[0m\n",cols, toybuf); - rows--; - for (i = TT.scroll_offset; i < TT.num_new_procs; i++) { - j = 0; - proc = new_procs[i]; - - user = getpwuid(proc->uid); - if (user && user->pw_name) { - user_str = user->pw_name; - } else { - snprintf(user_buf, 20, "%d", proc->uid); - user_str = user_buf; - } - - if (!TT.m_flag ) - { - float vss_percentage = (float)(proc->vss)/info.totalram * 100; - - j = sprintf(toybuf, "%5d %5d %-8.8s %-4s",proc->pid, proc->ppid, user_str, - proc->state); - - if ((proc->vss >> 10) >= 100000) - j += sprintf(toybuf + j, " %4lum", ((proc->vss >> 10) >> 10)); - else j += sprintf(toybuf+j, " %5lu", (proc->vss >> 10)); - - sprintf(toybuf + j," %5.1f %4d %5s %s", vss_percentage, proc->prs, - show_percent(proc->delta_time, total_delta_time), - ((proc->name[0])? proc->name : proc->tname)); - printf("%.*s", cols, toybuf); - } else { - j = sprintf(toybuf, "%5d",proc->pid); - - if ((proc->vss >> 10) >= 100000) - j += sprintf(toybuf + j, " %4lum", ((proc->vss >> 10) >> 10)); - else j += sprintf(toybuf+j, " %5lu", (proc->vss >> 10)); - if ((proc->vssrw >>10) >= 100000) - j += sprintf(toybuf + j, " %4lum", ((proc->vssrw >> 10) >> 10)); - else j += sprintf(toybuf+j, " %5lu", (proc->vssrw >> 10)); - if (proc->rss >= 100000) - j += sprintf(toybuf + j, " %4lum", ((proc->rss >> 10))); - else j += sprintf(toybuf+j, " %5lu", proc->rss); - if (proc->rss_shr >= 100000) - j += sprintf(toybuf + j, " %4lum", (proc->rss_shr >> 10)); - else j += sprintf(toybuf+j, " %5lu", proc->rss_shr); - if (proc->drt >= 100000) - j += sprintf(toybuf + j, " %4lum", (proc->drt >> 10)); - else j += sprintf(toybuf+j, " %5lu", proc->drt); - if (proc->drt_shr >= 100000) - j += sprintf(toybuf + j, " %4lum", (proc->drt_shr >> 10)); - else j += sprintf(toybuf+j, " %5lu", proc->drt_shr); - if ((proc->stack >>10) >= 100000) - j += sprintf(toybuf + j, " %4lum", ((proc->stack >> 10) >> 10)); - else j += sprintf(toybuf+j, " %5lu", (proc->stack >> 10)); - - sprintf(toybuf + j," %s",((proc->name[0])? proc->name : proc->tname)); - printf("%.*s", cols, toybuf); - } - rows--; - if (!rows) { - xputc('\r'); - break; //don't print any more process details. - } else xputc('\n'); - } -} - -/* - * Free old processes(displayed in old iteration) in order to - * avoid memory leaks - */ -static void free_procs_arr(struct proc_info **procs) -{ - int i; - for (i = 0; procs && procs[i]; i++) - free_proc(procs[i]); - - free(procs); -} - -static int numcmp(long long a, long long b) -{ - if (a < b) return (TT.reverse)?-1 : 1; - if (a > b) return (TT.reverse)?1 : -1; - return 0; -} - -static int top_mem_cmp(const void *a, const void *b) -{ - char *pa, *pb; - - int n = offsetof(struct proc_info, vss) + TT.cmp_field * sizeof(unsigned long); - pa = *((char **)a); pb = *((char **)b); - return numcmp(*(unsigned long*)(pa+n), *(unsigned long*)(pb+n)); -} - -static int proc_time_cmp(const void *a, const void *b) -{ - struct proc_info *pa, *pb; - - pa = *((struct proc_info **)a); pb = *((struct proc_info **)b); - return numcmp(pa->utime + pa->stime, pb->utime+pa->stime); -} - -/* - * Function to compare CPU usgae % while displaying processes - * according to CPU usage - */ -static int proc_cpu_cmp(const void *a, const void *b) -{ - struct proc_info *pa, *pb; - - pa = *((struct proc_info **)a); pb = *((struct proc_info **)b); - return numcmp(pa->delta_time, pb->delta_time); -} - -/* - * Function to compare memory taking by a process at the time of - * displaying processes according to Memory usage - */ -static int proc_vss_cmp(const void *a, const void *b) -{ - struct proc_info *pa, *pb; - - pa = *((struct proc_info **)a); pb = *((struct proc_info **)b); - return numcmp(pa->vss, pb->vss); -} - -static int proc_pid_cmp(const void *a, const void *b) -{ - struct proc_info *pa, *pb; - - pa = *((struct proc_info **)a); pb = *((struct proc_info **)b); - return numcmp(pa->pid, pb->pid); -} - -/* Read CPU stats for all the cores, assuming max 8 cores - * to be present here. - */ -static void read_cpu_stat() -{ - int i; - size_t len; - char *line = 0, *params = "%lu %lu %lu %lu %lu %lu %lu %lu"; - FILE *fp = xfopen("/proc/stat", "r"); - - for (i = 0; i<=8 && getline(&line, &len, fp) > 0; i++) { - if (i) sprintf(toybuf, "cpu%d %s", i-1, params); - else sprintf(toybuf, "cpu %s", params); - len = sscanf(line, toybuf, &new_cpu[i].utime, &new_cpu[i].ntime, - &new_cpu[i].stime, &new_cpu[i].itime, &new_cpu[i].iowtime, - &new_cpu[i].irqtime, &new_cpu[i].sirqtime, &new_cpu[i].steal); - if (len == 8) - new_cpu[i].total = new_cpu[i].utime + new_cpu[i].ntime + new_cpu[i].stime - + new_cpu[i].itime + new_cpu[i].iowtime + new_cpu[i].irqtime - + new_cpu[i].sirqtime + new_cpu[i].steal; - - free(line); - line = 0; - } - fclose(fp); -} - -void top_main(void ) -{ - int get_key; - - proc_cmp = &proc_cpu_cmp; - if ( TT.delay < 0) TT.delay = 3; - if (toys.optflags & FLAG_m) { - proc_cmp = &top_mem_cmp; - TT.m_flag = 1; - } - - sigatexit(signal_handler); - read_cpu_stat(); - get_key = read_input(0); - - while (!(toys.optflags & FLAG_n) || TT.iterations--) { - old_procs = new_procs; - memcpy(old_cpu, new_cpu, sizeof(old_cpu)); - read_procs(); - read_cpu_stat(); - print_procs(); - free_procs_arr(old_procs); - if ((toys.optflags & FLAG_n) && !TT.iterations) break; - - get_key = read_input(TT.delay); - if (get_key == 'q') break; - - switch(get_key) { - case 'n': - proc_cmp = &proc_pid_cmp; - TT.m_flag = 0; - break; - case 'h': - if (!TT.m_flag) TT.threads ^= 1; - break; - case 'm': - proc_cmp = &proc_vss_cmp; - TT.m_flag = 0; - break; - case 'r': - TT.reverse ^= 1; - break; - case 'c': - case '1': - TT.smp ^= 1; - break; - case 's': - TT.m_flag = 1; - TT.cmp_field = (TT.cmp_field + 1) % 7;//7 sort fields, vss,vssrw... - proc_cmp = &top_mem_cmp; - break; - case 'p': - proc_cmp = &proc_cpu_cmp; - TT.m_flag = 0; - break; - case 't': - proc_cmp = &proc_time_cmp; - TT.m_flag = 0; - break; - case KEY_UP: - TT.scroll_offset--; - break; - case KEY_DOWN: - TT.scroll_offset++; - break; - case KEY_HOME: - TT.scroll_offset = 0; - break; - case KEY_END: - TT.scroll_offset = TT.num_new_procs - TT.rows/2; - break; - case KEY_PGUP: - TT.scroll_offset -= TT.rows/2; - break; - case KEY_PGDN: - TT.scroll_offset += TT.rows/2; - break; - } - if (TT.scroll_offset >= TT.num_new_procs) TT.scroll_offset = TT.num_new_procs-1; - if (TT.scroll_offset < 0) TT.scroll_offset = 0; - } - xputc('\n'); - if (CFG_TOYBOX_FREE) { - free_proc_list(free_procs); - free_procs = NULL; - free_procs_arr(new_procs); - free_proc_list(free_procs); - } -} diff --git a/toys/posix/ps.c b/toys/posix/ps.c index 139c9d01..bddb3a9d 100644 --- a/toys/posix/ps.c +++ b/toys/posix/ps.c @@ -34,11 +34,11 @@ * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l") * TODO: iotop: Window size change: respond immediately. Why not padding * at right edge? (Not adjusting to screen size at all? Header wraps?) - * TODO: utf8 fontmetrics USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE)) -USE_TTOP(NEWTOY(ttop, ">0d#=3n#<1mb", TOYFLAG_USR|TOYFLAG_BIN)) -USE_IOTOP(NEWTOY(iotop, "Aabkoqp*u*d#n#", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE)) +// stayroot because iotop needs root to read other process' proc/$$/io +USE_TOP(NEWTOY(top, ">0m" "p*u*d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE)) +USE_IOTOP(NEWTOY(iotop, ">0Aako" "p*u*d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE)) USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:", TOYFLAG_USR|TOYFLAG_BIN)) USE_PKILL(NEWTOY(pkill, "Vu*U*t*s*P*g*G*fnovxl:", TOYFLAG_USR|TOYFLAG_BIN)) @@ -80,60 +80,39 @@ config PS Available -o FIELDs: - ADDR Instruction pointer - ARGS Command line (argv[0-X] minus path) - CMD COMM without -f, ARGS with -f - CMDLINE Command line (argv[0-X]) - COMM Original command name - COMMAND Original command path - CPU Which processor running on - ETIME Elapsed time since process start - F Flags (1=FORKNOEXEC, 4=SUPERPRIV) - GID Group id - GROUP Group name - LABEL Security label - MAJFL Major page faults - MINFL Minor page faults - NAME Command name (argv[0]) - NI Niceness (lower is faster) - PCPU Percentage of CPU time used - PGID Process Group ID - PID Process ID - PPID Parent Process ID - PRI Priority (higher is faster) - PSR Processor last executed on - RGID Real (before sgid) group ID - RGROUP Real (before sgid) group name - RSS Resident Set Size (memory in use, 4k pages) - RTPRIO Realtime priority - RUID Real (before suid) user ID - RUSER Real (before suid) user name - S Process state: - R (running) S (sleeping) D (device I/O) T (stopped) t (traced) - Z (zombie) X (deader) x (dead) K (wakekill) W (waking) - SCHED Scheduling policy (0=other, 1=fifo, 2=rr, 3=batch, 4=iso, 5=idle) - STAT Process state (S) plus: - < high priority N low priority L locked memory - s session leader + foreground l multithreaded - STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss) - SZ Memory Size (4k pages needed to completely swap out process) - TIME CPU time consumed - TTY Controlling terminal - UID User id - USER User name - VSZ Virtual memory size (1k units) - WCHAN Waiting in kernel for - - You can put a % after SZ, RSS - to get percentage - -config TTOP - bool "ttop" - default n + ADDR Instruction pointer ARGS Command line (argv[] -path) + CMD COMM without -f, ARGS with -f CMDLINE Command line (argv[]) + COMM Original command name COMMAND Original command path + CPU Which processor running on ETIME Elapsed time since PID start + F Flags (1=FORKNOEXEC 4=SUPERPRIV) GID Group id + GROUP Group name LABEL Security label + MAJFL Major page faults MINFL Minor page faults + NAME Command name (argv[0]) NI Niceness (lower is faster) + PCPU Percentage of CPU time used PGID Process Group ID + PID Process ID PPID Parent Process ID + PRI Priority (higher is faster) PSR Processor last executed on + RGID Real (before sgid) group ID RGROUP Real (before sgid) group name + RSS Resident Set Size (pages in use) RTPRIO Realtime priority + RUID Real (before suid) user ID RUSER Real (before suid) user name + S Process state: + R (running) S (sleeping) D (device I/O) T (stopped) t (traced) + Z (zombie) X (deader) x (dead) K (wakekill) W (waking) + SCHED Scheduling policy (0=other, 1=fifo, 2=rr, 3=batch, 4=iso, 5=idle) + STAT Process state (S) plus: + < high priority N low priority L locked memory + s session leader + foreground l multithreaded + STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss) + SZ Memory Size (4k pages needed to completely swap out process) + TIME CPU time consumed TTY Controlling terminal + UID User id USER User name + VSZ Virtual memory size (1k units) %VSZ VSZ as % of physical memory + WCHAN Waiting in kernel for + +config TOP + bool "top" + default y help - usage: ttop [-mb] [ -d seconds ] [ -n iterations ] - - todo: implement top + usage: top [-m] [ -d seconds ] [ -n iterations ] Provide a view of process activity in real time. Keys @@ -148,31 +127,38 @@ config TTOP -n Iterations before exiting -d Delay between updates -m Same as 's' key - -b Batch mode # Requires CONFIG_IRQ_TIME_ACCOUNTING in the kernel for /proc/$$/io config IOTOP bool "iotop" default y help - usage: iotop [-Aabkoq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,] + usage: iotop [-Aako] - Rank processes by I/O. Cursor left/right to change sort, Q to exit. + Rank processes by I/O. -A All I/O, not just disk -a Accumulated I/O (not percentage) + -k Kilobytes + -o Only show processes doing I/O + + Cursor left/right to change sort, space to update, Q to exit. + +config TOP_COMMON + bool + default y + help + usage: COMMON [-bq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,] -b Batch mode (no tty) -d Delay SECONDS between each cycle (default 3) - -k Kilobytes -n Exit after NUMBER iterations - -o Only show processes doing I/O -p Show these PIDs - -q Quiet (no header lines) -u Show these USERs + -q Quiet (no header lines) config PGREP bool "pgrep" - default n + default y depends on PGKILL_COMMON help usage: pgrep [-cL] [-d DELIM] [-L SIGNAL] [PATTERN] @@ -234,13 +220,9 @@ GLOBALS( struct { long n; long d; - } ttop; - struct { - long n; - long d; struct arg_list *u; struct arg_list *p; - } iotop; + } top; struct{ char *L; struct arg_list *G; @@ -329,7 +311,7 @@ struct typography { {"PID", 5, 0}, {"PPID", 5, 1}, {"PRI", 3, 15}, {"NI", 3, 16}, {"ADDR", 4+sizeof(long), 27}, {"SZ", 5, 20}, {"RSS", 5, 21}, {"PGID", 5, 2}, {"VSZ", 6, 20}, {"MAJFL", 6, 9}, {"MINFL", 6, 7}, {"PR", 2, 15}, - {"PSR", 3, 36}, {"RTPRIO", 6, 37}, {"SCH", 3, 38}, + {"PSR", 3, 36}, {"RTPRIO", 6, 37}, {"SCH", 3, 38}, {"CPU", 1, 36}, // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP {"UID", 5, 31}, {"USER", -8, 64|31}, {"RUID", 4, 32}, {"RUSER", -8, 64|32}, @@ -344,10 +326,11 @@ struct typography { {"TIME", 8, 11}, {"ELAPSED", 11, 19}, {"TIME+", 9, 11}, // Remaining ungrouped - {"STIME", 5, 19}, {"F", 1, 64|6}, {"S", -1, 64}, {"C", 1, 0}, {"%CPU", 4, 64}, + {"STIME", 5, 19}, {"F", 1, 64|6}, {"S", -1, 64}, {"C", 1, 64|11}, {"%CPU", 4, 64|11}, {"STAT", -5, 64}, {"%VSZ", 5, 23}, {"VIRT", 4, 47}, {"RES", 4, 48}, {"SHR", 4, 49}, {"READ", 6, 50}, {"WRITE", 6, 51}, {"IO", 6, 28}, - {"DREAD", 6, 52}, {"DWRITE", 6, 53}, {"SWAP", 6, 54}, {"DIO", 6, 29} + {"DREAD", 6, 52}, {"DWRITE", 6, 53}, {"SWAP", 6, 54}, {"DIO", 6, 29}, + {"%MEM", 5, 21} ); // Return 0 to discard, nonzero to keep @@ -398,9 +381,6 @@ static char *string_field(struct carveup *tb, struct strawberry *field) int which = field->which, sl = typos[which].slot; long long *slot = tb->slot, ll = (sl >= 0) ? slot[sl&63] : 0; - // Default: unsupported (5 "C") - sprintf(out, "-"); - // stat#s: PID PPID PRI NI ADDR SZ RSS PGID VSZ MAJFL MINFL PR PSR RTPRIO SCH if (which <= PS_SCH) { char *fmt = "%lld"; @@ -490,16 +470,20 @@ static char *string_field(struct carveup *tb, struct strawberry *field) strftime(out, 260, "%F %T", localtime(&t)); out = out+strlen(out)-3-abs(field->len); if (out<buf) out = buf; - } else if (which==PS__CPU || which==PS__VSZ) { - if (which==PS__CPU) sl = (slot[11]*1000)/slot[44]; - else sl = (slot[23]*1000)/TT.si.totalram; - sprintf(out, "%d.%d", sl/10, sl%10); + } else if (strchr((char []){PS_C,PS__CPU,PS__VSZ,PS__MEM,0}, which)) { + ll = slot[sl&63]*1000; + if (which==PS__VSZ || which==PS__MEM) ll /= TT.si.totalram; + else if (slot[44]) ll /= slot[44]; + sl = ll; + if (which==PS_C) sl += 5; + sprintf(out, "%d", sl/10); + if (which!=PS_C && sl<1000) sprintf(out+strlen(out), ".%d", sl%10); } else if (which>=PS_VIRT && which <= PS_DIO) { ll = slot[typos[which].slot]; if (which <= PS_SHR) ll *= sysconf(_SC_PAGESIZE); if (TT.forcek) sprintf(out, "%lldk", ll/1024); else human_readable(out, ll, 0); - } + } else if (CFG_TOYBOX_DEBUG) error_exit("bad which %d", which); return out; } @@ -914,6 +898,7 @@ static int ksort(void *aa, void *bb) for (field = TT.kfields; field; field = field->next) { slot = typos[field->which].slot; + // Compare as strings? if (slot&64) { memccpy(toybuf, string_field(ta, field), 0, 2048); @@ -1067,16 +1052,7 @@ void ps_main(void) } #define CLEANUP_ps -#define FOR_ttop -#include "generated/flags.h" - -void ttop_main(void) -{ - printf("hello world\n"); -} - -#define CLEANUP_ttop -#define FOR_iotop +#define FOR_top #include "generated/flags.h" // select which of the -o fields to sort by @@ -1096,7 +1072,22 @@ static void setsort(int pos) } } -void iotop_main(void) +// If we have both, adjust slot[deltas[]] to be relative to previous +// measurement rather than process start. Stomping old.data is fine +// because we free it after displaying. +static int merge_deltas(long long *oslot, long long *nslot) +{ + char deltas[] = {11,28,29,44,50,51,52,53,54}; + int i; + + for (i = 0; i<ARRAY_LEN(deltas); i++) + oslot[deltas[i]] = nslot[deltas[i]] - oslot[deltas[i]]; + + return 1; +} + +static void top_common(char *header, + int (*filter)(long long *oslot, long long *nslot)) { struct timespec ts; long long timeout = 0, now; @@ -1104,15 +1095,12 @@ void iotop_main(void) struct carveup **tb; int count; } plist[2], *plold, *plnew, old, new, mix; - struct arg_list al; - char *d = "D"+!!(toys.optflags&FLAG_A), *header, scratch[16], - deltas[] = {11,28,29,44,50,51,52,53,54}; + char scratch[16]; unsigned tock = 0; int i, lines, done = 0; - if (!TT.iotop.d) TT.iotop.d = 3; - TT.iotop.d *= 1000; - if (toys.optflags&FLAG_k) TT.forcek++; + *scratch = 0; + TT.top.d *= 1000; if (toys.optflags&FLAG_b) TT.width = TT.height = 99999; else { xset_terminal(0, 1, 0); @@ -1120,24 +1108,8 @@ void iotop_main(void) } shared_main(); - // TODO: usage: iotop [-oq] - - comma_args(TT.iotop.u, &TT.uu, "bad -u", parse_rest); - comma_args(TT.iotop.p, &TT.pp, "bad -p", parse_rest); - - al.next = 0; - al.arg = xmprintf("PID,PR,USER,%sREAD,%sWRITE,SWAP,%sIO,COMM", d, d, d); - comma_args(&al, &TT.fields, 0, parse_ko); - free(al.arg); - dlist_terminate(TT.fields); - header = strdup(toybuf); - - // Fallback sorts. First (dummy) field gets overwritten by setsort() - al.arg = xmprintf("-S,-%sIO,-ETIME,-PID",d); - comma_args(&al, &TT.kfields, 0, parse_ko); - free(al.arg); - dlist_terminate(TT.kfields); - setsort(6); + comma_args(TT.top.u, &TT.uu, "bad -u", parse_rest); + comma_args(TT.top.p, &TT.pp, "bad -p", parse_rest); TT.match_process = shared_match_process; memset(plist, 0, sizeof(plist)); @@ -1175,14 +1147,8 @@ void iotop_main(void) // If we just have new, use it verbatim if (!old.count || *otb->slot > *ntb->slot) mix.tb[mix.count] = ntb; else { - - // If we have both, adjust slot[deltas[]] to be relative to previous - // measurement rather than process start. Stomping old.data is fine - // because we free it after displaying. - if (!(toys.optflags&FLAG_a)) - for (i = 0; i<ARRAY_LEN(deltas); i++) - otb->slot[deltas[i]] = ntb->slot[deltas[i]] - otb->slot[deltas[i]]; - if (!(toys.optflags&FLAG_o) || otb->slot[28+!(toys.optflags&FLAG_A)]) { + // Keep or discard + if (filter(otb->slot, ntb->slot)) { mix.tb[mix.count] = otb; mix.count++; } @@ -1202,13 +1168,16 @@ void iotop_main(void) if (!(toys.optflags&FLAG_q)) { i = 0; strcpy(pos = toybuf, header); + for (i=0, is = *pos; *pos; pos++) { was = is; is = *pos; if (isspace(was) && !isspace(is) && i++==TT.sortpos) pos[-1] = '['; if (!isspace(was) && isspace(is) && i==TT.sortpos+1) *pos = ']'; } - printf("\033[7m%s\033[0m\n\r", toybuf); + *pos = 0; + printf("\033[7m%*.*s\033[0m\n\r", + (toys.optflags&FLAG_b) ? 0 : -TT.width, TT.width, toybuf); if (!(toys.optflags&FLAG_b)) terminal_probesize(&TT.width, &TT.height); @@ -1220,7 +1189,7 @@ void iotop_main(void) xputc('\r'); } - if (TT.iotop.n && !--TT.iotop.n) { + if (TT.top.n && !--TT.top.n) { done++; break; } @@ -1228,8 +1197,8 @@ void iotop_main(void) // Get current time in miliseconds clock_gettime(CLOCK_MONOTONIC, &ts); now = ts.tv_sec*1000+ts.tv_nsec/1000000; - if (timeout<=now) timeout += TT.iotop.d; - if (timeout<=now) timeout = now+TT.iotop.d; + if (timeout<=now) timeout += TT.top.d; + if (timeout<=now) timeout = now+TT.top.d; i = scan_key_getsize(scratch, timeout-now, &TT.width, &TT.height); if (i==-1 || i==3 || toupper(i)=='Q') { @@ -1239,16 +1208,16 @@ void iotop_main(void) if (i==-2) break; // Flush unknown escape sequences. - if (i==27) { - while (0<scan_key_getsize(scratch, 0, &TT.width, &TT.height)); - continue; + if (i==27) while (0<scan_key_getsize(scratch, 0, &TT.width, &TT.height)); + else if (i==' ') { + timeout = now; + break; + } else { + i -= 256; + if (i == KEY_LEFT) setsort(TT.sortpos-1); + else if (i == KEY_RIGHT) setsort(TT.sortpos+1); } - - i -= 256; - if (i == KEY_LEFT) setsort(TT.sortpos-1); - else if (i == KEY_RIGHT) setsort(TT.sortpos+1); - else continue; - break; + continue; } free(mix.tb); @@ -1258,6 +1227,63 @@ void iotop_main(void) if (!(toys.optflags&FLAG_b)) tty_reset(); } +void top_main(void) +{ + struct arg_list al; + char *header; + + // Display fields + al.next = 0; + al.arg = xstrdup("PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,CMDLINE"); + comma_args(&al, &TT.fields, 0, parse_ko); + free(al.arg); + dlist_terminate(TT.fields); + header=xstrdup(toybuf); + + // Fallback sorts + al.arg = xstrdup("-S,-%CPU,-ETIME,-PID"); + comma_args(&al, &TT.kfields, "bang", parse_ko); + dlist_terminate(TT.kfields); + setsort(8); + + top_common(header, merge_deltas); +} + +#define CLEANUP_top +#define FOR_iotop +#include "generated/flags.h" + +static int iotop_filter(long long *oslot, long long *nslot) +{ + if (!(toys.optflags&FLAG_a)) merge_deltas(oslot, nslot); + + return !(toys.optflags&FLAG_o) || oslot[28+!(toys.optflags&FLAG_A)]; +} + +void iotop_main(void) +{ + struct arg_list al; + char *header, *d = "D"+!!(toys.optflags&FLAG_A); + + if (toys.optflags&FLAG_k) TT.forcek++; + + al.next = 0; + al.arg = xmprintf("PID,PR,USER,%sREAD,%sWRITE,SWAP,%sIO,COMM", d, d, d); + comma_args(&al, &TT.fields, 0, parse_ko); + free(al.arg); + dlist_terminate(TT.fields); + header = strdup(toybuf); + + // Fallback sorts. First (dummy) field gets overwritten by setsort() + al.arg = xmprintf("-S,-%sIO,-ETIME,-PID",d); + comma_args(&al, &TT.kfields, 0, parse_ko); + free(al.arg); + dlist_terminate(TT.kfields); + setsort(6); + + top_common(header, iotop_filter); +} + // pkill's plumbing wrap's pgrep's and thus mostly takes place in pgrep's flag // context, so force pgrep's flags on even when building pkill standalone. // (All the pgrep/pkill functions drop out when building ps standalone.) |