top: TOPMEM feature - 's(how sizes)' command. +2.5k when enabled,
authorDenis Vlasenko <vda.linux@googlemail.com>
Sat, 8 Sep 2007 16:51:19 +0000 (16:51 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Sat, 8 Sep 2007 16:51:19 +0000 (16:51 -0000)
+80 bytes when disabled (mainly because of text wrapping fixes
in display_process_list).

include/libbb.h
libbb/procps.c
procps/Config.in
procps/top.c

index f1658945cc15e75ead4419b0af58377b9a045e8c..a4aa90da3114a65493c11d8f930e43fa0a4988d1 100644 (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
index 8d3aea3325c84a3dd8fb42ea6ad7429a196f18d9..3a31eeff3a3145add5bbfaada2b2b37fd69ccf09 100644 (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) {
index f041b5d721b8821cb9b1083b98bb02ce3e6b47d0..2bd3cd245b69311a1a313f2b820aac95dc79ac66 100644 (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
index 87e85003d9d20adfe8c7407686152b789eedad11..69f63d08b6da757e818499f9122f786de61d7721 100644 (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;
+                       c |= 0x20; /* lowercase */
+                       if (c == 'q')
                                break;
-                       if (c == 'N')
+                       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 */