From ff6e8e2974433aeabaaefaf9d8b6a35e9641b9ac Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Sat, 8 Sep 2007 16:51:19 +0000 Subject: [PATCH] top: TOPMEM feature - 's(how sizes)' command. +2.5k when enabled, +80 bytes when disabled (mainly because of text wrapping fixes in display_process_list). --- include/libbb.h | 12 +- libbb/procps.c | 73 ++++++++ procps/Config.in | 7 + procps/top.c | 435 +++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 473 insertions(+), 54 deletions(-) diff --git a/include/libbb.h b/include/libbb.h index f1658945c..a4aa90da3 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -904,6 +904,15 @@ typedef struct procps_status_t { unsigned uid; unsigned gid; unsigned tty_major,tty_minor; +#if ENABLE_FEATURE_TOPMEM + unsigned long mapped_rw; + unsigned long mapped_ro; + unsigned long shared_clean; + unsigned long shared_dirty; + unsigned long private_clean; + unsigned long private_dirty; + unsigned long stack; +#endif char state[4]; /* basename of executable in exec(2), read from /proc/N/stat * (if executable is symlink or script, it is NOT replaced @@ -927,7 +936,8 @@ enum { PSSCAN_STIME = 1 << 12, PSSCAN_UTIME = 1 << 13, PSSCAN_TTY = 1 << 14, - USE_SELINUX(PSSCAN_CONTEXT = 1 << 15,) + PSSCAN_SMAPS = (1 << 15) * ENABLE_FEATURE_TOPMEM, + USE_SELINUX(PSSCAN_CONTEXT = 1 << 16,) /* These are all retrieved from proc/NN/stat in one go: */ PSSCAN_STAT = PSSCAN_PPID | PSSCAN_PGID | PSSCAN_SID | PSSCAN_COMM | PSSCAN_STATE diff --git a/libbb/procps.c b/libbb/procps.c index 8d3aea332..3a31eeff3 100644 --- a/libbb/procps.c +++ b/libbb/procps.c @@ -115,6 +115,28 @@ void free_procps_scan(procps_status_t* sp) free(sp); } +#if ENABLE_FEATURE_TOPMEM +static unsigned long fast_strtoul_16(char **endptr) +{ + unsigned char c; + char *str = *endptr; + unsigned long n = 0; + + while ((c = *str++) != ' ') { + c = ((c|0x20) - '0'); + if (c > 9) + // c = c + '0' - 'a' + 10: + c = c - ('a' - '0' - 10); + n = n*16 + c; + } + *endptr = str; /* We skip trailing space! */ + return n; +} +/* TOPMEM uses fast_strtoul_10, so... */ +#undef ENABLE_FEATURE_FAST_TOP +#define ENABLE_FEATURE_FAST_TOP 1 +#endif + #if ENABLE_FEATURE_FAST_TOP /* We cut a lot of corners here for speed */ static unsigned long fast_strtoul_10(char **endptr) @@ -278,6 +300,57 @@ procps_status_t *procps_scan(procps_status_t* sp, int flags) } +#if ENABLE_FEATURE_TOPMEM + if (flags & (PSSCAN_SMAPS)) { + FILE *file; + + strcpy(filename_tail, "/smaps"); + file = fopen(filename, "r"); + if (!file) + break; + while (fgets(buf, sizeof(buf), file)) { + unsigned long sz; + char *tp; + char w; +#define SCAN(str, name) \ + if (strncmp(buf, str, sizeof(str)-1) == 0) { \ + tp = skip_whitespace(buf + sizeof(str)-1); \ + sp->name += fast_strtoul_10(&tp); \ + continue; \ + } + SCAN("Shared_Clean:" , shared_clean ); + SCAN("Shared_Dirty:" , shared_dirty ); + SCAN("Private_Clean:", private_clean); + SCAN("Private_Dirty:", private_dirty); +#undef SCAN + // f7d29000-f7d39000 rw-s ADR M:m OFS FILE + tp = strchr(buf, '-'); + if (tp) { + *tp = ' '; + tp = buf; + sz = fast_strtoul_16(&tp); /* start */ + sz = (fast_strtoul_16(&tp) - sz) >> 10; /* end - start */ + // tp -> "rw-s" string + w = tp[1]; + // skipping "rw-s ADR M:m OFS " + tp = skip_whitespace(skip_fields(tp, 4)); + // filter out /dev/something (something != zero) + if (strncmp(tp, "/dev/", 5) != 0 || strcmp(tp, "/dev/zero\n") == 0) { + if (w == 'w') { + sp->mapped_rw += sz; + } else if (w == '-') { + sp->mapped_ro += sz; + } + } +//else printf("DROPPING %s (%s)\n", buf, tp); + if (strcmp(tp, "[stack]\n") == 0) + sp->stack += sz; + } + } + fclose(file); + } +#endif /* TOPMEM */ + #if 0 /* PSSCAN_CMD is not used */ if (flags & (PSSCAN_CMD|PSSCAN_ARGV0)) { if (sp->argv0) { diff --git a/procps/Config.in b/procps/Config.in index f041b5d72..2bd3cd245 100644 --- a/procps/Config.in +++ b/procps/Config.in @@ -128,6 +128,13 @@ config FEATURE_TOP_DECIMALS help Show 1/10th of a percent in CPU/mem statistics. +config FEATURE_TOPMEM + bool "topmem" + default n + depends on TOP + help + Enable 's' in top (gives lots of memory info) + config UPTIME bool "uptime" default n diff --git a/procps/top.c b/procps/top.c index 87e85003d..69f63d08b 100644 --- a/procps/top.c +++ b/procps/top.c @@ -58,11 +58,17 @@ typedef struct save_hist { typedef int (*cmp_funcp)(top_status_t *P, top_status_t *Q); + enum { SORT_DEPTH = 3 }; + struct globals { top_status_t *top; int ntop; +#if ENABLE_FEATURE_TOPMEM + smallint sort_field; + smallint inverted; +#endif #if ENABLE_FEATURE_USE_TERMIOS struct termios initial_settings; #endif @@ -81,7 +87,9 @@ struct globals { #define G (*(struct globals*)&bb_common_bufsiz1) #define top (G.top ) #define ntop (G.ntop ) -#define initial_settings (G. initial_settings ) +#define sort_field (G.sort_field ) +#define inverted (G.inverted ) +#define initial_settings (G.initial_settings ) #define sort_function (G.sort_function ) #define prev_hist (G.prev_hist ) #define prev_hist_count (G.prev_hist_count ) @@ -371,15 +379,11 @@ static void display_process_list(int count, int scr_width) /* what info of the processes is shown */ printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width, " PID PPID USER STAT VSZ %MEM %CPU COMMAND"); -#define MIN_WIDTH \ - sizeof( " PID PPID USER STAT VSZ %MEM %CPU C") #else /* !CPU_USAGE_PERCENTAGE */ printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width, " PID PPID USER STAT VSZ %MEM COMMAND"); -#define MIN_WIDTH \ - sizeof( " PID PPID USER STAT VSZ %MEM C") #endif #if ENABLE_FEATURE_TOP_DECIMALS @@ -434,20 +438,23 @@ static void display_process_list(int count, int scr_width) /* printf(" pmem_scale=%u pcpu_scale=%u ", pmem_scale, pcpu_scale); */ #endif - /* Ok, all prelim data is ready, go thru the list */ + scr_width += 2; /* account for leading '\n' and trailing NUL */ + /* Ok, all preliminary data is ready, go thru the list */ while (count-- > 0) { - int col = scr_width; + char buf[scr_width]; + unsigned col; CALC_STAT(pmem, (s->vsz*pmem_scale + pmem_half) >> pmem_shift); #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE CALC_STAT(pcpu, (s->pcpu*pcpu_scale + pcpu_half) >> pcpu_shift); #endif - if (s->vsz >= 100*1024) + if (s->vsz >= 100000) sprintf(vsz_str_buf, "%6ldm", s->vsz/1024); else sprintf(vsz_str_buf, "%7ld", s->vsz); // PID PPID USER STAT VSZ %MEM [%CPU] COMMAND - col -= printf("\n" "%5u%6u %-8.8s %s%s" FMT + col = snprintf(buf, scr_width, + "\n" "%5u%6u %-8.8s %s%s" FMT #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE FMT #endif @@ -459,11 +466,9 @@ static void display_process_list(int count, int scr_width) , SHOW_STAT(pcpu) #endif ); - if (col > 0) { - char buf[col + 1]; - read_cmdline(buf, col, s->pid, s->comm); - fputs(buf, stdout); - } + if (col < scr_width) + read_cmdline(buf + col, scr_width - col, s->pid, s->comm); + fputs(buf, stdout); /* printf(" %d/%d %lld/%lld", s->pcpu, total_pcpu, jif.busy - prev_jif.busy, jif.total - prev_jif.total); */ s++; @@ -481,7 +486,7 @@ static void clearmems(void) { clear_username_cache(); free(top); - top = 0; + top = NULL; ntop = 0; } @@ -508,13 +513,298 @@ static void sig_catcher(int sig ATTRIBUTE_UNUSED) #endif /* FEATURE_USE_TERMIOS */ + + + + + + + + + + + + + + +typedef unsigned long mem_t; + +typedef struct topmem_status_t { + unsigned pid; + char comm[COMM_LEN]; + /* vsz doesn't count /dev/xxx mappings except /dev/zero */ + mem_t vsz ; + mem_t vszrw ; + mem_t rss ; + mem_t rss_sh ; + mem_t dirty ; + mem_t dirty_sh; + mem_t stack ; +} topmem_status_t; + +enum { NUM_SORT_FIELD = 7 }; + +#define topmem ((topmem_status_t*)top) + +#if ENABLE_FEATURE_TOPMEM +static int topmem_sort(char *a, char *b) +{ + int n; + mem_t l, r; + + n = offsetof(topmem_status_t, vsz) + (sort_field * sizeof(mem_t)); + l = *(mem_t*)(a + n); + r = *(mem_t*)(b + n); +// if (l == r) { +// l = a->mapped_rw; +// r = b->mapped_rw; +// } + /* We want to avoid unsigned->signed and truncation errors */ + /* l>r: -1, l=r: 0, l r) ? -1 : (l != r); + return inverted ? -n : n; +} + +/* Cut "NNNN " out of " NNNN kb" */ +static char *grab_number(char *str, const char *match, unsigned sz) +{ + if (strncmp(str, match, sz) == 0) { + str = skip_whitespace(str + sz); + (skip_non_whitespace(str))[1] = '\0'; + return xstrdup(str); + } + return NULL; +} + +/* display header info (meminfo / loadavg) */ +static void display_topmem_header(int scr_width) +{ + char linebuf[128]; + int i; + FILE *fp; + union { + struct { + /* 1 */ char *total; + /* 2 */ char *mfree; + /* 3 */ char *buf; + /* 4 */ char *cache; + /* 5 */ char *swaptotal; + /* 6 */ char *swapfree; + /* 7 */ char *dirty; + /* 8 */ char *mwrite; + /* 9 */ char *anon; + /* 10 */ char *map; + /* 11 */ char *slab; + }; + char *str[11]; + } Z; +#define total Z.total +#define mfree Z.mfree +#define buf Z.buf +#define cache Z.cache +#define swaptotal Z.swaptotal +#define swapfree Z.swapfree +#define dirty Z.dirty +#define mwrite Z.mwrite +#define anon Z.anon +#define map Z.map +#define slab Z.slab +#define str Z.str + + memset(&Z, 0, sizeof(Z)); + + /* read memory info */ + fp = xfopen("meminfo", "r"); + while (fgets(linebuf, sizeof(linebuf), fp)) { + char *p; + +#define SCAN(match, name) \ + p = grab_number(linebuf, match, sizeof(match)-1); \ + if (p) { name = p; continue; } + + SCAN("MemTotal:", total); + SCAN("MemFree:", mfree); + SCAN("Buffers:", buf); + SCAN("Cached:", cache); + SCAN("SwapTotal:", swaptotal); + SCAN("SwapFree:", swapfree); + SCAN("Dirty:", dirty); + SCAN("Writeback:", mwrite); + SCAN("AnonPages:", anon); + SCAN("Mapped:", map); + SCAN("Slab:", slab); +#undef SCAN + } + fclose(fp); + +#define S(s) (s ? s : "0") + snprintf(linebuf, sizeof(linebuf), + "Mem %stotal %sanon %smap %sfree", + S(total), S(anon), S(map), S(mfree)); + printf(OPT_BATCH_MODE ? "%.*s\n" : "\e[H\e[J%.*s\n", scr_width, linebuf); + + snprintf(linebuf, sizeof(linebuf), + " %sslab %sbuf %scache %sdirty %swrite", + S(slab), S(buf), S(cache), S(dirty), S(mwrite)); + printf("%.*s\n", scr_width, linebuf); + + snprintf(linebuf, sizeof(linebuf), + "Swap %stotal %sfree", // TODO: % used? + S(swaptotal), S(swapfree)); + printf("%.*s\n", scr_width, linebuf); +#undef S + + for (i = 0; i < ARRAY_SIZE(str); i++) + free(str[i]); +#undef total +#undef free +#undef buf +#undef cache +#undef swaptotal +#undef swapfree +#undef dirty +#undef write +#undef anon +#undef map +#undef slab +#undef str +} + +// Converts unsigned long long value into compact 5-char +// representation. Sixth char is always ' ' +static void smart_ulltoa6(unsigned long long ul, char buf[6]) +{ + const char *fmt; + char c; + unsigned v, u, idx = 0; + + if (ul > 99999) { // do not scale if 99999 or less + ul *= 10; + do { + ul /= 1024; + idx++; + } while (ul >= 100000); + } + v = ul; // ullong divisions are expensive, avoid them + + fmt = " 123456789"; + u = v / 10; + v = v % 10; + if (!idx) { + // 99999 or less: use "12345" format + // u is value/10, v is last digit + c = buf[0] = " 123456789"[u/1000]; + if (c != ' ') fmt = "0123456789"; + c = buf[1] = fmt[u/100%10]; + if (c != ' ') fmt = "0123456789"; + c = buf[2] = fmt[u/10%10]; + if (c != ' ') fmt = "0123456789"; + buf[3] = fmt[u%10]; + buf[4] = "0123456789"[v]; + } else { + // value has been scaled into 0..9999.9 range + // u is value, v is 1/10ths (allows for 92.1M format) + if (u >= 100) { + // value is >= 100: use "1234M', " 123M" formats + c = buf[0] = " 123456789"[u/1000]; + if (c != ' ') fmt = "0123456789"; + c = buf[1] = fmt[u/100%10]; + if (c != ' ') fmt = "0123456789"; + v = u % 10; + u = u / 10; + buf[2] = fmt[u%10]; + } else { + // value is < 100: use "92.1M" format + c = buf[0] = " 123456789"[u/10]; + if (c != ' ') fmt = "0123456789"; + buf[1] = fmt[u%10]; + buf[2] = '.'; + } + buf[3] = "0123456789"[v]; + // see http://en.wikipedia.org/wiki/Tera + buf[4] = " mgtpezy"[idx]; + } + buf[5] = ' '; +} + +static void display_topmem_process_list(int count, int scr_width) +{ +#define HDR_STR " PID VSZ VSZRW RSS (SHR) DIRTY (SHR) STACK" +#define MIN_WIDTH sizeof(HDR_STR) + const topmem_status_t *s = topmem; + char buf[scr_width | MIN_WIDTH]; /* a|b is a cheap max(a,b) */ + + display_topmem_header(scr_width); + strcpy(buf, HDR_STR " COMMAND"); + buf[5 + sort_field * 6] = '*'; + printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width, buf); + + while (--count >= 0) { + // PID VSZ VSZRW RSS (SHR) DIRTY (SHR) COMMAND + smart_ulltoa6(s->pid , &buf[0*6]); + smart_ulltoa6(s->vsz , &buf[1*6]); + smart_ulltoa6(s->vszrw , &buf[2*6]); + smart_ulltoa6(s->rss , &buf[3*6]); + smart_ulltoa6(s->rss_sh , &buf[4*6]); + smart_ulltoa6(s->dirty , &buf[5*6]); + smart_ulltoa6(s->dirty_sh, &buf[6*6]); + smart_ulltoa6(s->stack , &buf[7*6]); + buf[8*6] = '\0'; + if (scr_width > MIN_WIDTH) { + read_cmdline(&buf[8*6], scr_width - MIN_WIDTH, s->pid, s->comm); + } + printf("\n""%.*s", scr_width, buf); + s++; + } + putchar(OPT_BATCH_MODE ? '\n' : '\r'); + fflush(stdout); +#undef HDR_STR +#undef MIN_WIDTH +} +#else +void display_topmem_process_list(int count, int scr_width); +int topmem_sort(char *a, char *b); +#endif /* TOPMEM */ + + + + + + + + + + + + + + + + +enum { + TOP_MASK = 0 + | PSSCAN_PID + | PSSCAN_PPID + | PSSCAN_VSZ + | PSSCAN_STIME + | PSSCAN_UTIME + | PSSCAN_STATE + | PSSCAN_COMM + | PSSCAN_UIDGID, + TOPMEM_MASK = 0 + | PSSCAN_PID + | PSSCAN_SMAPS + | PSSCAN_COMM, +}; + int top_main(int argc, char **argv); int top_main(int argc, char **argv) { int count, lines, col; unsigned interval; - int iterations = -1; /* infinite */ + int iterations = 0; /* infinite */ char *sinterval, *siterations; + SKIP_FEATURE_TOPMEM(const) unsigned scan_mask = TOP_MASK; #if ENABLE_FEATURE_USE_TERMIOS struct termios new_settings; struct pollfd pfd[1]; @@ -563,62 +853,82 @@ int top_main(int argc, char **argv) procps_status_t *p = NULL; /* Default */ - lines = 24 - 3 USE_FEATURE_TOP_CPU_GLOBAL_PERCENTS( - 1); + lines = 24; col = 79; #if ENABLE_FEATURE_USE_TERMIOS get_terminal_width_height(0, &col, &lines); - /* We wrap horribly if width is too narrow (TODO) */ - if (lines < 5 || col < MIN_WIDTH) { + if (lines < 5 || col < 10) { sleep(interval); continue; } - lines -= 3 USE_FEATURE_TOP_CPU_GLOBAL_PERCENTS( + 1); #endif /* FEATURE_USE_TERMIOS */ + if (!ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS && scan_mask == TOP_MASK) + lines -= 3; + else + lines -= 4; /* read process IDs & status for all the processes */ - while ((p = procps_scan(p, 0 - | PSSCAN_PID - | PSSCAN_PPID - | PSSCAN_VSZ - | PSSCAN_STIME - | PSSCAN_UTIME - | PSSCAN_STATE - | PSSCAN_COMM - | PSSCAN_UIDGID - )) != NULL) { - int n = ntop; - top = xrealloc(top, (++ntop) * sizeof(*top)); - top[n].pid = p->pid; - top[n].ppid = p->ppid; - top[n].vsz = p->vsz; + while ((p = procps_scan(p, scan_mask)) != NULL) { + int n; + if (scan_mask == TOP_MASK) { + n = ntop; + top = xrealloc(top, (++ntop) * sizeof(*top)); + top[n].pid = p->pid; + top[n].ppid = p->ppid; + top[n].vsz = p->vsz; #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE - top[n].ticks = p->stime + p->utime; + top[n].ticks = p->stime + p->utime; #endif - top[n].uid = p->uid; - strcpy(top[n].state, p->state); - strcpy(top[n].comm, p->comm); + top[n].uid = p->uid; + strcpy(top[n].state, p->state); + strcpy(top[n].comm, p->comm); + } else { /* TOPMEM */ +#if ENABLE_FEATURE_TOPMEM + if (!(p->mapped_ro | p->mapped_rw)) + continue; /* kernel threads are ignored */ + n = ntop; + top = xrealloc(topmem, (++ntop) * sizeof(*topmem)); + strcpy(topmem[n].comm, p->comm); + topmem[n].pid = p->pid; + topmem[n].vsz = p->mapped_rw + p->mapped_ro; + topmem[n].vszrw = p->mapped_rw; + topmem[n].rss_sh = p->shared_clean + p->shared_dirty; + topmem[n].rss = p->private_clean + p->private_dirty + topmem[n].rss_sh; + topmem[n].dirty = p->private_dirty + p->shared_dirty; + topmem[n].dirty_sh = p->shared_dirty; + topmem[n].stack = p->stack; +#endif + } } if (ntop == 0) { bb_error_msg_and_die("no process info in /proc"); } + + if (scan_mask == TOP_MASK) { #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE - if (!prev_hist_count) { + if (!prev_hist_count) { + do_stats(); + usleep(100000); + clearmems(); + continue; + } do_stats(); - sleep(1); - clearmems(); - continue; - } - do_stats(); /* TODO: we don't need to sort all 10000 processes, we need to find top 24! */ - qsort(top, ntop, sizeof(top_status_t), (void*)mult_lvl_cmp); + qsort(top, ntop, sizeof(top_status_t), (void*)mult_lvl_cmp); #else - qsort(top, ntop, sizeof(top_status_t), (void*)(sort_function[0])); + qsort(top, ntop, sizeof(top_status_t), (void*)(sort_function[0])); #endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */ + } else { /* TOPMEM */ + qsort(topmem, ntop, sizeof(topmem_status_t), (void*)topmem_sort); + } count = lines; if (OPT_BATCH_MODE || count > ntop) { count = ntop; } - display_process_list(count, col); + if (scan_mask == TOP_MASK) + display_process_list(count, col); + else + display_topmem_process_list(count, col); clearmems(); if (iterations >= 0 && !--iterations) break; @@ -628,11 +938,17 @@ int top_main(int argc, char **argv) if (poll(pfd, 1, interval * 1000) != 0) { if (read(0, &c, 1) != 1) /* signal */ break; - if (c == 'q' || c == initial_settings.c_cc[VINTR]) + if (c == initial_settings.c_cc[VINTR]) break; - if (c == 'N') + c |= 0x20; /* lowercase */ + if (c == 'q') + break; + if (c == 'n') { + USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;) sort_function[0] = pid_sort; - if (c == 'M') { + } + if (c == 'm') { + USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;) sort_function[0] = mem_sort; #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE sort_function[1] = pcpu_sort; @@ -640,16 +956,29 @@ int top_main(int argc, char **argv) #endif } #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE - if (c == 'P') { + if (c == 'p') { + USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;) sort_function[0] = pcpu_sort; sort_function[1] = mem_sort; sort_function[2] = time_sort; } - if (c == 'T') { + if (c == 't') { + USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;) sort_function[0] = time_sort; sort_function[1] = mem_sort; sort_function[2] = pcpu_sort; } +#if ENABLE_FEATURE_TOPMEM + if (c == 's') { + scan_mask = TOPMEM_MASK; + free(prev_hist); + prev_hist = NULL; + prev_hist_count = 0; + sort_field = (sort_field + 1) % NUM_SORT_FIELD; + } + if (c == 'r') + inverted ^= 1; +#endif #endif } #endif /* FEATURE_USE_TERMIOS */