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).
This commit is contained in:
Denis Vlasenko 2007-09-08 16:51:19 +00:00
parent 9382b3809b
commit ff6e8e2974
4 changed files with 473 additions and 54 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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 */
n = (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 */