busybox/procps/top.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * A tiny 'top' utility.
   4 *
   5 * This is written specifically for the linux /proc/<PID>/stat(m)
   6 * files format.
   7 *
   8 * This reads the PIDs of all processes and their status and shows
   9 * the status of processes (first ones that fit to screen) at given
  10 * intervals.
  11 *
  12 * NOTES:
  13 * - At startup this changes to /proc, all the reads are then
  14 *   relative to that.
  15 *
  16 * (C) Eero Tamminen <oak at welho dot com>
  17 *
  18 * Rewritten by Vladimir Oleynik (C) 2002 <dzo@simtreas.ru>
  19 *
  20 * Sept 2008: Vineet Gupta <vineet.gupta@arc.com>
  21 * Added Support for reporting SMP Information
  22 * - CPU where process was last seen running
  23 *   (to see effect of sched_setaffinity() etc)
  24 * - CPU time split (idle/IO/wait etc) per CPU
  25 *
  26 * Copyright (c) 1992 Branko Lankester
  27 * Copyright (c) 1992 Roger Binns
  28 * Copyright (C) 1994-1996 Charles L. Blake.
  29 * Copyright (C) 1992-1998 Michael K. Johnson
  30 *
  31 * Licensed under GPLv2, see file LICENSE in this source tree.
  32 */
  33/* How to snapshot /proc for debugging top problems:
  34 * for f in /proc/[0-9]*""/stat; do
  35 *         n=${f#/proc/}
  36 *         n=${n%/stat}_stat
  37 *         cp $f $n
  38 * done
  39 * cp /proc/stat /proc/meminfo /proc/loadavg .
  40 * top -bn1 >top.out
  41 *
  42 * ...and how to run top on it on another machine:
  43 * rm -rf proc; mkdir proc
  44 * for f in [0-9]*_stat; do
  45 *         p=${f%_stat}
  46 *         mkdir -p proc/$p
  47 *         cp $f proc/$p/stat
  48 * done
  49 * cp stat meminfo loadavg proc
  50 * chroot . ./top -bn1 >top1.out
  51 */
  52//config:config TOP
  53//config:       bool "top (18 kb)"
  54//config:       default y
  55//config:       help
  56//config:       The top program provides a dynamic real-time view of a running
  57//config:       system.
  58//config:
  59//config:config FEATURE_TOP_INTERACTIVE
  60//config:       bool "Accept keyboard commands"
  61//config:       default y
  62//config:       depends on TOP
  63//config:       help
  64//config:       Without this, top will only refresh display every 5 seconds.
  65//config:       No keyboard commands will work, only ^C to terminate.
  66//config:
  67//config:config FEATURE_TOP_CPU_USAGE_PERCENTAGE
  68//config:       bool "Show CPU per-process usage percentage"
  69//config:       default y
  70//config:       depends on TOP
  71//config:       help
  72//config:       Make top display CPU usage for each process.
  73//config:       This adds about 2k.
  74//config:
  75//config:config FEATURE_TOP_CPU_GLOBAL_PERCENTS
  76//config:       bool "Show CPU global usage percentage"
  77//config:       default y
  78//config:       depends on FEATURE_TOP_CPU_USAGE_PERCENTAGE
  79//config:       help
  80//config:       Makes top display "CPU: NN% usr NN% sys..." line.
  81//config:       This adds about 0.5k.
  82//config:
  83//config:config FEATURE_TOP_SMP_CPU
  84//config:       bool "SMP CPU usage display ('c' key)"
  85//config:       default y
  86//config:       depends on FEATURE_TOP_CPU_GLOBAL_PERCENTS
  87//config:       help
  88//config:       Allow 'c' key to switch between individual/cumulative CPU stats
  89//config:       This adds about 0.5k.
  90//config:
  91//config:config FEATURE_TOP_DECIMALS
  92//config:       bool "Show 1/10th of a percent in CPU/mem statistics"
  93//config:       default y
  94//config:       depends on FEATURE_TOP_CPU_USAGE_PERCENTAGE
  95//config:       help
  96//config:       Show 1/10th of a percent in CPU/mem statistics.
  97//config:       This adds about 0.3k.
  98//config:
  99//config:config FEATURE_TOP_SMP_PROCESS
 100//config:       bool "Show CPU process runs on ('j' field)"
 101//config:       default y
 102//config:       depends on TOP
 103//config:       help
 104//config:       Show CPU where process was last found running on.
 105//config:       This is the 'j' field.
 106//config:
 107//config:config FEATURE_TOPMEM
 108//config:       bool "Topmem command ('s' key)"
 109//config:       default y
 110//config:       depends on TOP
 111//config:       help
 112//config:       Enable 's' in top (gives lots of memory info).
 113
 114//applet:IF_TOP(APPLET(top, BB_DIR_USR_BIN, BB_SUID_DROP))
 115
 116//kbuild:lib-$(CONFIG_TOP) += top.o
 117
 118#include "libbb.h"
 119
 120#define ESC "\033"
 121
 122typedef struct top_status_t {
 123        unsigned long vsz;
 124#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 125        unsigned long ticks;
 126        unsigned pcpu; /* delta of ticks */
 127#endif
 128        unsigned pid, ppid;
 129        unsigned uid;
 130        char state[4];
 131        char comm[COMM_LEN];
 132#if ENABLE_FEATURE_TOP_SMP_PROCESS
 133        int last_seen_on_cpu;
 134#endif
 135} top_status_t;
 136
 137typedef struct jiffy_counts_t {
 138        /* Linux 2.4.x has only first four */
 139        unsigned long long usr, nic, sys, idle;
 140        unsigned long long iowait, irq, softirq, steal;
 141        unsigned long long total;
 142        unsigned long long busy;
 143} jiffy_counts_t;
 144
 145/* This structure stores some critical information from one frame to
 146   the next. Used for finding deltas. */
 147typedef struct save_hist {
 148        unsigned long ticks;
 149        pid_t pid;
 150} save_hist;
 151
 152typedef int (*cmp_funcp)(top_status_t *P, top_status_t *Q);
 153
 154
 155enum { SORT_DEPTH = 3 };
 156
 157/* Screens wider than this are unlikely */
 158enum { LINE_BUF_SIZE = 512 - 64 };
 159
 160struct globals {
 161        top_status_t *top;
 162        int ntop;
 163        smallint inverted;
 164#if ENABLE_FEATURE_TOPMEM
 165        smallint sort_field;
 166#endif
 167#if ENABLE_FEATURE_TOP_SMP_CPU
 168        smallint smp_cpu_info; /* one/many cpu info lines? */
 169#endif
 170        unsigned lines;  /* screen height */
 171#if ENABLE_FEATURE_TOP_INTERACTIVE
 172        struct termios initial_settings;
 173        int scroll_ofs;
 174#define G_scroll_ofs G.scroll_ofs
 175#else
 176#define G_scroll_ofs 0
 177#endif
 178#if !ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 179        cmp_funcp sort_function[1];
 180#else
 181        cmp_funcp sort_function[SORT_DEPTH];
 182        struct save_hist *prev_hist;
 183        unsigned prev_hist_count;
 184        jiffy_counts_t cur_jif, prev_jif;
 185        /* int hist_iterations; */
 186        unsigned total_pcpu;
 187        /* unsigned long total_vsz; */
 188#endif
 189#if ENABLE_FEATURE_TOP_SMP_CPU
 190        /* Per CPU samples: current and last */
 191        jiffy_counts_t *cpu_jif, *cpu_prev_jif;
 192        unsigned num_cpus;
 193#endif
 194#if ENABLE_FEATURE_TOP_INTERACTIVE
 195        char kbd_input[KEYCODE_BUFFER_SIZE];
 196#endif
 197        char line_buf[LINE_BUF_SIZE];
 198};
 199#define G (*ptr_to_globals)
 200#define top              (G.top               )
 201#define ntop             (G.ntop              )
 202#define sort_field       (G.sort_field        )
 203#define inverted         (G.inverted          )
 204#define smp_cpu_info     (G.smp_cpu_info      )
 205#define initial_settings (G.initial_settings  )
 206#define sort_function    (G.sort_function     )
 207#define prev_hist        (G.prev_hist         )
 208#define prev_hist_count  (G.prev_hist_count   )
 209#define cur_jif          (G.cur_jif           )
 210#define prev_jif         (G.prev_jif          )
 211#define cpu_jif          (G.cpu_jif           )
 212#define cpu_prev_jif     (G.cpu_prev_jif      )
 213#define num_cpus         (G.num_cpus          )
 214#define total_pcpu       (G.total_pcpu        )
 215#define line_buf         (G.line_buf          )
 216#define INIT_G() do { \
 217        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
 218        BUILD_BUG_ON(LINE_BUF_SIZE <= 80); \
 219} while (0)
 220
 221enum {
 222        OPT_d = (1 << 0),
 223        OPT_n = (1 << 1),
 224        OPT_b = (1 << 2),
 225        OPT_H = (1 << 3),
 226        OPT_m = (1 << 4),
 227        OPT_EOF = (1 << 5), /* pseudo: "we saw EOF in stdin" */
 228};
 229#define OPT_BATCH_MODE (option_mask32 & OPT_b)
 230
 231
 232#if ENABLE_FEATURE_TOP_INTERACTIVE
 233static int pid_sort(top_status_t *P, top_status_t *Q)
 234{
 235        /* Buggy wrt pids with high bit set */
 236        /* (linux pids are in [1..2^15-1]) */
 237        return (Q->pid - P->pid);
 238}
 239#endif
 240
 241static int mem_sort(top_status_t *P, top_status_t *Q)
 242{
 243        /* We want to avoid unsigned->signed and truncation errors */
 244        if (Q->vsz < P->vsz) return -1;
 245        return Q->vsz != P->vsz; /* 0 if ==, 1 if > */
 246}
 247
 248
 249#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 250
 251static int pcpu_sort(top_status_t *P, top_status_t *Q)
 252{
 253        /* Buggy wrt ticks with high bit set */
 254        /* Affects only processes for which ticks overflow */
 255        return (int)Q->pcpu - (int)P->pcpu;
 256}
 257
 258static int time_sort(top_status_t *P, top_status_t *Q)
 259{
 260        /* We want to avoid unsigned->signed and truncation errors */
 261        if (Q->ticks < P->ticks) return -1;
 262        return Q->ticks != P->ticks; /* 0 if ==, 1 if > */
 263}
 264
 265static int mult_lvl_cmp(void* a, void* b)
 266{
 267        int i, cmp_val;
 268
 269        for (i = 0; i < SORT_DEPTH; i++) {
 270                cmp_val = (*sort_function[i])(a, b);
 271                if (cmp_val != 0)
 272                        break;
 273        }
 274        return inverted ? -cmp_val : cmp_val;
 275}
 276
 277static NOINLINE int read_cpu_jiffy(FILE *fp, jiffy_counts_t *p_jif)
 278{
 279#if !ENABLE_FEATURE_TOP_SMP_CPU
 280        static const char fmt[] ALIGN1 = "cpu %llu %llu %llu %llu %llu %llu %llu %llu";
 281#else
 282        static const char fmt[] ALIGN1 = "cp%*s %llu %llu %llu %llu %llu %llu %llu %llu";
 283#endif
 284        int ret;
 285
 286        if (!fgets(line_buf, LINE_BUF_SIZE, fp) || line_buf[0] != 'c' /* not "cpu" */)
 287                return 0;
 288        ret = sscanf(line_buf, fmt,
 289                        &p_jif->usr, &p_jif->nic, &p_jif->sys, &p_jif->idle,
 290                        &p_jif->iowait, &p_jif->irq, &p_jif->softirq,
 291                        &p_jif->steal);
 292        if (ret >= 4) {
 293                p_jif->total = p_jif->usr + p_jif->nic + p_jif->sys + p_jif->idle
 294                        + p_jif->iowait + p_jif->irq + p_jif->softirq + p_jif->steal;
 295                /* procps 2.x does not count iowait as busy time */
 296                p_jif->busy = p_jif->total - p_jif->idle - p_jif->iowait;
 297        }
 298
 299        return ret;
 300}
 301
 302static void get_jiffy_counts(void)
 303{
 304        FILE* fp = xfopen_for_read("stat");
 305
 306        /* We need to parse cumulative counts even if SMP CPU display is on,
 307         * they are used to calculate per process CPU% */
 308        prev_jif = cur_jif;
 309        if (read_cpu_jiffy(fp, &cur_jif) < 4)
 310                bb_error_msg_and_die("can't read '%s'", "/proc/stat");
 311
 312#if !ENABLE_FEATURE_TOP_SMP_CPU
 313        fclose(fp);
 314        return;
 315#else
 316        if (!smp_cpu_info) {
 317                fclose(fp);
 318                return;
 319        }
 320
 321        if (!num_cpus) {
 322                /* First time here. How many CPUs?
 323                 * There will be at least 1 /proc/stat line with cpu%d
 324                 */
 325                while (1) {
 326                        cpu_jif = xrealloc_vector(cpu_jif, 1, num_cpus);
 327                        if (read_cpu_jiffy(fp, &cpu_jif[num_cpus]) <= 4)
 328                                break;
 329                        num_cpus++;
 330                }
 331                if (num_cpus == 0) /* /proc/stat with only "cpu ..." line?! */
 332                        smp_cpu_info = 0;
 333
 334                cpu_prev_jif = xzalloc(sizeof(cpu_prev_jif[0]) * num_cpus);
 335
 336                /* Otherwise the first per cpu display shows all 100% idles */
 337                usleep(50000);
 338        } else { /* Non first time invocation */
 339                jiffy_counts_t *tmp;
 340                int i;
 341
 342                /* First switch the sample pointers: no need to copy */
 343                tmp = cpu_prev_jif;
 344                cpu_prev_jif = cpu_jif;
 345                cpu_jif = tmp;
 346
 347                /* Get the new samples */
 348                for (i = 0; i < num_cpus; i++)
 349                        read_cpu_jiffy(fp, &cpu_jif[i]);
 350        }
 351#endif
 352        fclose(fp);
 353}
 354
 355static void do_stats(void)
 356{
 357        top_status_t *cur;
 358        pid_t pid;
 359        int n;
 360        unsigned i, last_i;
 361        struct save_hist *new_hist;
 362
 363        get_jiffy_counts();
 364        total_pcpu = 0;
 365        /* total_vsz = 0; */
 366        new_hist = xmalloc(sizeof(new_hist[0]) * ntop);
 367        /*
 368         * Make a pass through the data to get stats.
 369         */
 370        /* hist_iterations = 0; */
 371        i = 0;
 372        for (n = 0; n < ntop; n++) {
 373                cur = top + n;
 374
 375                /*
 376                 * Calculate time in cur process.  Time is sum of user time
 377                 * and system time
 378                 */
 379                pid = cur->pid;
 380                new_hist[n].ticks = cur->ticks;
 381                new_hist[n].pid = pid;
 382
 383                /* find matching entry from previous pass */
 384                cur->pcpu = 0;
 385                /* do not start at index 0, continue at last used one
 386                 * (brought hist_iterations from ~14000 down to 172) */
 387                last_i = i;
 388                if (prev_hist_count) do {
 389                        if (prev_hist[i].pid == pid) {
 390                                cur->pcpu = cur->ticks - prev_hist[i].ticks;
 391                                total_pcpu += cur->pcpu;
 392                                break;
 393                        }
 394                        i = (i+1) % prev_hist_count;
 395                        /* hist_iterations++; */
 396                } while (i != last_i);
 397                /* total_vsz += cur->vsz; */
 398        }
 399
 400        /*
 401         * Save cur frame's information.
 402         */
 403        free(prev_hist);
 404        prev_hist = new_hist;
 405        prev_hist_count = ntop;
 406}
 407
 408#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */
 409
 410#if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS && ENABLE_FEATURE_TOP_DECIMALS
 411/* formats 7 char string (8 with terminating NUL) */
 412static char *fmt_100percent_8(char pbuf[8], unsigned value, unsigned total)
 413{
 414        unsigned t;
 415        if (value >= total) { /* 100% ? */
 416                strcpy(pbuf, "  100% ");
 417                return pbuf;
 418        }
 419        /* else generate " [N/space]N.N% " string */
 420        value = 1000 * value / total;
 421        t = value / 100;
 422        value = value % 100;
 423        pbuf[0] = ' ';
 424        pbuf[1] = t ? t + '0' : ' ';
 425        pbuf[2] = '0' + (value / 10);
 426        pbuf[3] = '.';
 427        pbuf[4] = '0' + (value % 10);
 428        pbuf[5] = '%';
 429        pbuf[6] = ' ';
 430        pbuf[7] = '\0';
 431        return pbuf;
 432}
 433#endif
 434
 435#if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS
 436static void display_cpus(int scr_width, char *scrbuf, int *lines_rem_p)
 437{
 438        /*
 439         * xxx% = (cur_jif.xxx - prev_jif.xxx) / (cur_jif.total - prev_jif.total) * 100%
 440         */
 441        unsigned total_diff;
 442        jiffy_counts_t *p_jif, *p_prev_jif;
 443        int i;
 444# if ENABLE_FEATURE_TOP_SMP_CPU
 445        int n_cpu_lines;
 446# endif
 447
 448        /* using (unsigned) casts to make operations cheaper */
 449# define  CALC_TOTAL_DIFF do { \
 450        total_diff = (unsigned)(p_jif->total - p_prev_jif->total); \
 451        if (total_diff == 0) total_diff = 1; \
 452} while (0)
 453
 454# if ENABLE_FEATURE_TOP_DECIMALS
 455#  define CALC_STAT(xxx) char xxx[8]
 456#  define SHOW_STAT(xxx) fmt_100percent_8(xxx, (unsigned)(p_jif->xxx - p_prev_jif->xxx), total_diff)
 457#  define FMT "%s"
 458# else
 459#  define CALC_STAT(xxx) unsigned xxx = 100 * (unsigned)(p_jif->xxx - p_prev_jif->xxx) / total_diff
 460#  define SHOW_STAT(xxx) xxx
 461#  define FMT "%4u%% "
 462# endif
 463
 464# if !ENABLE_FEATURE_TOP_SMP_CPU
 465        {
 466                i = 1;
 467                p_jif = &cur_jif;
 468                p_prev_jif = &prev_jif;
 469# else
 470        /* Loop thru CPU(s) */
 471        n_cpu_lines = smp_cpu_info ? num_cpus : 1;
 472        if (n_cpu_lines > *lines_rem_p)
 473                n_cpu_lines = *lines_rem_p;
 474
 475        for (i = 0; i < n_cpu_lines; i++) {
 476                p_jif = &cpu_jif[i];
 477                p_prev_jif = &cpu_prev_jif[i];
 478# endif
 479                CALC_TOTAL_DIFF;
 480
 481                { /* Need a block: CALC_STAT are declarations */
 482                        CALC_STAT(usr);
 483                        CALC_STAT(sys);
 484                        CALC_STAT(nic);
 485                        CALC_STAT(idle);
 486                        CALC_STAT(iowait);
 487                        CALC_STAT(irq);
 488                        CALC_STAT(softirq);
 489                        /*CALC_STAT(steal);*/
 490
 491                        snprintf(scrbuf, scr_width,
 492                                /* Barely fits in 79 chars when in "decimals" mode. */
 493# if ENABLE_FEATURE_TOP_SMP_CPU
 494                                "CPU%s:"FMT"usr"FMT"sys"FMT"nic"FMT"idle"FMT"io"FMT"irq"FMT"sirq",
 495                                (smp_cpu_info ? utoa(i) : ""),
 496# else
 497                                "CPU:"FMT"usr"FMT"sys"FMT"nic"FMT"idle"FMT"io"FMT"irq"FMT"sirq",
 498# endif
 499                                SHOW_STAT(usr), SHOW_STAT(sys), SHOW_STAT(nic), SHOW_STAT(idle),
 500                                SHOW_STAT(iowait), SHOW_STAT(irq), SHOW_STAT(softirq)
 501                                /*, SHOW_STAT(steal) - what is this 'steal' thing? */
 502                                /* I doubt anyone wants to know it */
 503                        );
 504                        puts(scrbuf);
 505                }
 506        }
 507# undef SHOW_STAT
 508# undef CALC_STAT
 509# undef FMT
 510        *lines_rem_p -= i;
 511}
 512#else  /* !ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS */
 513# define display_cpus(scr_width, scrbuf, lines_rem) ((void)0)
 514#endif
 515
 516enum {
 517        MI_MEMTOTAL,
 518        MI_MEMFREE,
 519        MI_MEMSHARED,
 520        MI_SHMEM,
 521        MI_BUFFERS,
 522        MI_CACHED,
 523        MI_SWAPTOTAL,
 524        MI_SWAPFREE,
 525        MI_DIRTY,
 526        MI_WRITEBACK,
 527        MI_ANONPAGES,
 528        MI_MAPPED,
 529        MI_SLAB,
 530        MI_MAX
 531};
 532
 533static void parse_meminfo(unsigned long meminfo[MI_MAX])
 534{
 535        static const char fields[] ALIGN1 =
 536                "MemTotal\0"
 537                "MemFree\0"
 538                "MemShared\0"
 539                "Shmem\0"
 540                "Buffers\0"
 541                "Cached\0"
 542                "SwapTotal\0"
 543                "SwapFree\0"
 544                "Dirty\0"
 545                "Writeback\0"
 546                "AnonPages\0"
 547                "Mapped\0"
 548                "Slab\0";
 549        char buf[60]; /* actual lines we expect are ~30 chars or less */
 550        FILE *f;
 551        int i;
 552
 553        memset(meminfo, 0, sizeof(meminfo[0]) * MI_MAX);
 554        f = xfopen_for_read("meminfo");
 555        while (fgets(buf, sizeof(buf), f) != NULL) {
 556                char *c = strchr(buf, ':');
 557                if (!c)
 558                        continue;
 559                *c = '\0';
 560                i = index_in_strings(fields, buf);
 561                if (i >= 0)
 562                        meminfo[i] = strtoul(c+1, NULL, 10);
 563        }
 564        fclose(f);
 565}
 566
 567static unsigned long display_header(int scr_width, int *lines_rem_p)
 568{
 569        char scrbuf[100]; /* [80] was a bit too low on 8Gb ram box */
 570        char *buf;
 571        unsigned long meminfo[MI_MAX];
 572
 573        parse_meminfo(meminfo);
 574
 575        /* Output memory info */
 576        if (scr_width > (int)sizeof(scrbuf))
 577                scr_width = sizeof(scrbuf);
 578        snprintf(scrbuf, scr_width,
 579                "Mem: %luK used, %luK free, %luK shrd, %luK buff, %luK cached",
 580                meminfo[MI_MEMTOTAL] - meminfo[MI_MEMFREE],
 581                meminfo[MI_MEMFREE],
 582                meminfo[MI_MEMSHARED] + meminfo[MI_SHMEM],
 583                meminfo[MI_BUFFERS],
 584                meminfo[MI_CACHED]);
 585        /* Go to top & clear to the end of screen */
 586        printf(OPT_BATCH_MODE ? "%s\n" : ESC"[H" ESC"[J" "%s\n", scrbuf);
 587        (*lines_rem_p)--;
 588
 589        /* Display CPU time split as percentage of total time.
 590         * This displays either a cumulative line or one line per CPU.
 591         */
 592        display_cpus(scr_width, scrbuf, lines_rem_p);
 593
 594        /* Read load average as a string */
 595        buf = stpcpy(scrbuf, "Load average: ");
 596        open_read_close("loadavg", buf, sizeof(scrbuf) - sizeof("Load average: "));
 597        scrbuf[scr_width - 1] = '\0';
 598        strchrnul(buf, '\n')[0] = '\0';
 599        puts(scrbuf);
 600        (*lines_rem_p)--;
 601
 602        return meminfo[MI_MEMTOTAL];
 603}
 604
 605static NOINLINE void display_process_list(int lines_rem, int scr_width)
 606{
 607        enum {
 608                BITS_PER_INT = sizeof(int) * 8
 609        };
 610
 611        top_status_t *s;
 612        unsigned long total_memory = display_header(scr_width, &lines_rem); /* or use total_vsz? */
 613        /* xxx_shift and xxx_scale variables allow us to replace
 614         * expensive divides with multiply and shift */
 615        unsigned pmem_shift, pmem_scale, pmem_half;
 616#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 617        unsigned tmp_unsigned;
 618        unsigned pcpu_shift, pcpu_scale, pcpu_half;
 619        unsigned busy_jifs;
 620#endif
 621
 622#if ENABLE_FEATURE_TOP_DECIMALS
 623# define UPSCALE 1000
 624typedef struct { unsigned quot, rem; } bb_div_t;
 625/* Used to have "div_t name = div((val), 10)" here
 626 * (IOW: intended to use libc-compatible way to divide and use
 627 * both result and remainder, but musl does not inline div()...)
 628 * Oh well. Modern compilers detect "N/d, N%d" idiom by themselves:
 629 */
 630# define CALC_STAT(name, val) bb_div_t name = { (val) / 10, (val) % 10 }
 631# define SHOW_STAT(name) name.quot, '0'+name.rem
 632# define FMT "%3u.%c"
 633#else
 634# define UPSCALE 100
 635# define CALC_STAT(name, val) unsigned name = (val)
 636# define SHOW_STAT(name) name
 637# define FMT "%4u%%"
 638#endif
 639
 640        /* what info of the processes is shown */
 641        printf(OPT_BATCH_MODE ? "%.*s" : ESC"[7m" "%.*s" ESC"[m", scr_width,
 642                "  PID  PPID USER     STAT   VSZ %VSZ"
 643                IF_FEATURE_TOP_SMP_PROCESS(" CPU")
 644                IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE(" %CPU")
 645                " COMMAND");
 646        lines_rem--;
 647
 648        /*
 649         * %VSZ = s->vsz/MemTotal
 650         */
 651        pmem_shift = BITS_PER_INT-11;
 652        pmem_scale = UPSCALE*(1U<<(BITS_PER_INT-11)) / total_memory;
 653        /* s->vsz is in kb. we want (s->vsz * pmem_scale) to never overflow */
 654        while (pmem_scale >= 512) {
 655                pmem_scale /= 4;
 656                pmem_shift -= 2;
 657        }
 658        pmem_half = (1U << pmem_shift) / (ENABLE_FEATURE_TOP_DECIMALS ? 20 : 2);
 659#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 660        busy_jifs = cur_jif.busy - prev_jif.busy;
 661        /* This happens if there were lots of short-lived processes
 662         * between two top updates (e.g. compilation) */
 663        if (total_pcpu < busy_jifs) total_pcpu = busy_jifs;
 664
 665        /*
 666         * CPU% = s->pcpu/sum(s->pcpu) * busy_cpu_ticks/total_cpu_ticks
 667         * (pcpu is delta of sys+user time between samples)
 668         */
 669        /* (cur_jif.xxx - prev_jif.xxx) and s->pcpu are
 670         * in 0..~64000 range (HZ*update_interval).
 671         * we assume that unsigned is at least 32-bit.
 672         */
 673        pcpu_shift = 6;
 674        pcpu_scale = UPSCALE*64 * (uint16_t)busy_jifs;
 675        if (pcpu_scale == 0)
 676                pcpu_scale = 1;
 677        while (pcpu_scale < (1U << (BITS_PER_INT-2))) {
 678                pcpu_scale *= 4;
 679                pcpu_shift += 2;
 680        }
 681        tmp_unsigned = (uint16_t)(cur_jif.total - prev_jif.total) * total_pcpu;
 682        if (tmp_unsigned != 0)
 683                pcpu_scale /= tmp_unsigned;
 684        /* we want (s->pcpu * pcpu_scale) to never overflow */
 685        while (pcpu_scale >= 1024) {
 686                pcpu_scale /= 4;
 687                pcpu_shift -= 2;
 688        }
 689        pcpu_half = (1U << pcpu_shift) / (ENABLE_FEATURE_TOP_DECIMALS ? 20 : 2);
 690        /* printf(" pmem_scale=%u pcpu_scale=%u ", pmem_scale, pcpu_scale); */
 691#endif
 692
 693        /* Ok, all preliminary data is ready, go through the list */
 694        scr_width += 2; /* account for leading '\n' and trailing NUL */
 695        if (lines_rem > ntop - G_scroll_ofs)
 696                lines_rem = ntop - G_scroll_ofs;
 697        s = top + G_scroll_ofs;
 698        while (--lines_rem >= 0) {
 699                int n;
 700                char *ppu;
 701                char ppubuf[sizeof(int)*3 * 2 + 12];
 702                char vsz_str_buf[8];
 703                unsigned col;
 704
 705                CALC_STAT(pmem, (s->vsz*pmem_scale + pmem_half) >> pmem_shift);
 706#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 707                CALC_STAT(pcpu, (s->pcpu*pcpu_scale + pcpu_half) >> pcpu_shift);
 708#endif
 709
 710                smart_ulltoa5(s->vsz, vsz_str_buf, " mgtpezy");
 711                /* PID PPID USER STAT VSZ %VSZ [%CPU] COMMAND */
 712                n = sprintf(ppubuf, "%5u %5u %-8.8s", s->pid, s->ppid, get_cached_username(s->uid));
 713                ppu = ppubuf;
 714                if (n != 6+6+8) {
 715                        /* Format PID PPID USER part into 6+6+8 chars:
 716                         * shrink PID/PPID if possible, then truncate USER.
 717                         * Tested on Linux 5.18.0:
 718                         * sysctl kernel.pid_max=4194304 is the maximum allowed,
 719                         * so PID and PPID are 7 chars wide at most.
 720                         */
 721                        char *p, *pp;
 722                        if (*ppu == ' ') {
 723                                do {
 724                                        ppu++, n--;
 725                                        if (n == 6+6+8)
 726                                                goto shortened;
 727                                } while (*ppu == ' ');
 728                        }
 729                        pp = p = skip_non_whitespace(ppu) + 1;
 730                        if (*p == ' ') {
 731                                do
 732                                        p++, n--;
 733                                while (n != 6+6+8 && *p == ' ');
 734                                overlapping_strcpy(pp, p); /* shrink PPID */
 735                        }
 736                        ppu[6+6+8] = '\0'; /* truncate USER */
 737                }
 738 shortened:
 739                col = snprintf(line_buf, scr_width,
 740                                "\n" "%s %s  %.5s" FMT
 741                                IF_FEATURE_TOP_SMP_PROCESS(" %3d")
 742                                IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE(FMT)
 743                                " ",
 744                                ppu,
 745                                s->state, vsz_str_buf,
 746                                SHOW_STAT(pmem)
 747                                IF_FEATURE_TOP_SMP_PROCESS(, s->last_seen_on_cpu)
 748                                IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE(, SHOW_STAT(pcpu))
 749                );
 750                if ((int)(scr_width - col) > 1)
 751                        read_cmdline(line_buf + col, scr_width - col, s->pid, s->comm);
 752                fputs_stdout(line_buf);
 753                /* printf(" %d/%d %lld/%lld", s->pcpu, total_pcpu,
 754                        cur_jif.busy - prev_jif.busy, cur_jif.total - prev_jif.total); */
 755                s++;
 756        }
 757        /* printf(" %d", hist_iterations); */
 758        bb_putchar(OPT_BATCH_MODE ? '\n' : '\r');
 759        fflush_all();
 760}
 761#undef UPSCALE
 762#undef SHOW_STAT
 763#undef CALC_STAT
 764#undef FMT
 765
 766static void clearmems(void)
 767{
 768        clear_username_cache();
 769        free(top);
 770        top = NULL;
 771}
 772
 773#if ENABLE_FEATURE_TOP_INTERACTIVE
 774static void reset_term(void)
 775{
 776        if (!OPT_BATCH_MODE)
 777                tcsetattr_stdin_TCSANOW(&initial_settings);
 778}
 779
 780static void sig_catcher(int sig)
 781{
 782        reset_term();
 783        kill_myself_with_sig(sig);
 784}
 785#endif /* FEATURE_TOP_INTERACTIVE */
 786
 787/*
 788 * TOPMEM support
 789 */
 790
 791typedef unsigned long mem_t;
 792
 793typedef struct topmem_status_t {
 794        unsigned pid;
 795        char comm[COMM_LEN];
 796        /* vsz doesn't count /dev/xxx mappings except /dev/zero */
 797        mem_t vsz     ;
 798        mem_t vszrw   ;
 799        mem_t rss     ;
 800        mem_t rss_sh  ;
 801        mem_t dirty   ;
 802        mem_t dirty_sh;
 803        mem_t stack   ;
 804} topmem_status_t;
 805
 806enum { NUM_SORT_FIELD = 7 };
 807
 808#define topmem ((topmem_status_t*)top)
 809
 810#if ENABLE_FEATURE_TOPMEM
 811
 812static int topmem_sort(char *a, char *b)
 813{
 814        int n;
 815        mem_t l, r;
 816
 817        n = offsetof(topmem_status_t, vsz) + (sort_field * sizeof(mem_t));
 818        l = *(mem_t*)(a + n);
 819        r = *(mem_t*)(b + n);
 820        if (l == r) {
 821                l = ((topmem_status_t*)a)->dirty;
 822                r = ((topmem_status_t*)b)->dirty;
 823        }
 824        /* We want to avoid unsigned->signed and truncation errors */
 825        /* l>r: -1, l=r: 0, l<r: 1 */
 826        n = (l > r) ? -1 : (l != r);
 827        return inverted ? -n : n;
 828}
 829
 830/* display header info (meminfo / loadavg) */
 831static void display_topmem_header(int scr_width, int *lines_rem_p)
 832{
 833        unsigned long meminfo[MI_MAX];
 834
 835        parse_meminfo(meminfo);
 836
 837        snprintf(line_buf, LINE_BUF_SIZE,
 838                "Mem total:%lu anon:%lu map:%lu free:%lu",
 839                meminfo[MI_MEMTOTAL],
 840                meminfo[MI_ANONPAGES],
 841                meminfo[MI_MAPPED],
 842                meminfo[MI_MEMFREE]);
 843        printf(OPT_BATCH_MODE ? "%.*s\n" : ESC"[H" ESC"[J" "%.*s\n", scr_width, line_buf);
 844
 845        snprintf(line_buf, LINE_BUF_SIZE,
 846                " slab:%lu buf:%lu cache:%lu dirty:%lu write:%lu",
 847                meminfo[MI_SLAB],
 848                meminfo[MI_BUFFERS],
 849                meminfo[MI_CACHED],
 850                meminfo[MI_DIRTY],
 851                meminfo[MI_WRITEBACK]);
 852        printf("%.*s\n", scr_width, line_buf);
 853
 854        snprintf(line_buf, LINE_BUF_SIZE,
 855                "Swap total:%lu free:%lu", // TODO: % used?
 856                meminfo[MI_SWAPTOTAL],
 857                meminfo[MI_SWAPFREE]);
 858        printf("%.*s\n", scr_width, line_buf);
 859
 860        (*lines_rem_p) -= 3;
 861}
 862
 863/* see http://en.wikipedia.org/wiki/Tera */
 864static void ulltoa5_and_space(unsigned long long ul, char buf[6])
 865{
 866        smart_ulltoa5(ul, buf, " mgtpezy")[0] = ' ';
 867}
 868static void ulltoa4_and_space(unsigned long long ul, char buf[5])
 869{
 870        smart_ulltoa4(ul, buf, " mgtpezy")[0] = ' ';
 871}
 872
 873static NOINLINE void display_topmem_process_list(int lines_rem, int scr_width)
 874{
 875#define HDR_STR "  PID   VSZ VSZRW   RSS (SHR) DIRTY (SHR) STACK"
 876#define MIN_WIDTH sizeof(HDR_STR)
 877        const topmem_status_t *s = topmem + G_scroll_ofs;
 878        char *cp, ch;
 879
 880        display_topmem_header(scr_width, &lines_rem);
 881
 882        strcpy(line_buf, HDR_STR " COMMAND");
 883        /* Mark the ^FIELD^ we sort by */
 884        cp = &line_buf[5 + sort_field * 6];
 885        ch = "^_"[inverted];
 886        cp[6] = ch;
 887        do *cp++ = ch; while (*cp == ' ');
 888
 889        printf(OPT_BATCH_MODE ? "%.*s" : ESC"[7m" "%.*s" ESC"[m", scr_width, line_buf);
 890        lines_rem--;
 891
 892        if (lines_rem > ntop - G_scroll_ofs)
 893                lines_rem = ntop - G_scroll_ofs;
 894        while (--lines_rem >= 0) {
 895                /* PID VSZ VSZRW RSS (SHR) DIRTY (SHR) COMMAND */
 896                int n = sprintf(line_buf, "%5u ", s->pid);
 897                if (n > 7) {
 898                        /* PID is 7 chars long (up to 4194304) */
 899                        ulltoa4_and_space(s->vsz  , &line_buf[8]);
 900                        ulltoa4_and_space(s->vszrw, &line_buf[8+5]);
 901                        /* the next field (RSS) starts at 8+10 = 3*6 */
 902                } else {
 903                        if (n == 7) /* PID is 6 chars long */
 904                                ulltoa4_and_space(s->vsz, &line_buf[7]);
 905                                /* the next field (VSZRW) starts at 7+5 = 2*6 */
 906                        else /* PID is 5 chars or less */
 907                                ulltoa5_and_space(s->vsz, &line_buf[6]);
 908                        ulltoa5_and_space(s->vszrw, &line_buf[2*6]);
 909                }
 910                ulltoa5_and_space(s->rss     , &line_buf[3*6]);
 911                ulltoa5_and_space(s->rss_sh  , &line_buf[4*6]);
 912                ulltoa5_and_space(s->dirty   , &line_buf[5*6]);
 913                ulltoa5_and_space(s->dirty_sh, &line_buf[6*6]);
 914                ulltoa5_and_space(s->stack   , &line_buf[7*6]);
 915                line_buf[8*6] = '\0';
 916                if (scr_width > (int)MIN_WIDTH) {
 917                        read_cmdline(&line_buf[8*6], scr_width - MIN_WIDTH, s->pid, s->comm);
 918                }
 919                printf("\n""%.*s", scr_width, line_buf);
 920                s++;
 921        }
 922        bb_putchar(OPT_BATCH_MODE ? '\n' : '\r');
 923        fflush_all();
 924#undef HDR_STR
 925#undef MIN_WIDTH
 926}
 927
 928#else
 929void display_topmem_process_list(int lines_rem, int scr_width);
 930int topmem_sort(char *a, char *b);
 931#endif /* TOPMEM */
 932
 933/*
 934 * end TOPMEM support
 935 */
 936
 937enum {
 938        TOP_MASK = 0
 939                | PSSCAN_PID
 940                | PSSCAN_PPID
 941                | PSSCAN_VSZ
 942                | PSSCAN_STIME
 943                | PSSCAN_UTIME
 944                | PSSCAN_STATE
 945                | PSSCAN_COMM
 946                | PSSCAN_CPU
 947                | PSSCAN_UIDGID,
 948        TOPMEM_MASK = 0
 949                | PSSCAN_PID
 950                | PSSCAN_SMAPS
 951                | PSSCAN_COMM,
 952        EXIT_MASK = 0,
 953        NO_RESCAN_MASK = (unsigned)-1,
 954};
 955
 956#if ENABLE_FEATURE_TOP_INTERACTIVE
 957static unsigned handle_input(unsigned scan_mask, duration_t interval)
 958{
 959        if (option_mask32 & OPT_EOF) {
 960                /* EOF on stdin ("top </dev/null") */
 961                sleep_for_duration(interval);
 962                return scan_mask;
 963        }
 964
 965        while (1) {
 966                int32_t c;
 967
 968                c = safe_read_key(STDIN_FILENO, G.kbd_input, interval * 1000);
 969                if (c == -1 && errno != EAGAIN) {
 970                        /* error/EOF */
 971                        option_mask32 |= OPT_EOF;
 972                        break;
 973                }
 974                interval = 0;
 975
 976                if (c == initial_settings.c_cc[VINTR])
 977                        return EXIT_MASK;
 978                if (c == initial_settings.c_cc[VEOF])
 979                        return EXIT_MASK;
 980
 981                if (c == KEYCODE_UP) {
 982                        G_scroll_ofs--;
 983                        goto normalize_ofs;
 984                }
 985                if (c == KEYCODE_DOWN) {
 986                        G_scroll_ofs++;
 987                        goto normalize_ofs;
 988                }
 989                if (c == KEYCODE_HOME) {
 990                        G_scroll_ofs = 0;
 991                        goto normalize_ofs;
 992                }
 993                if (c == KEYCODE_END) {
 994                        G_scroll_ofs = ntop - G.lines / 2;
 995                        goto normalize_ofs;
 996                }
 997                if (c == KEYCODE_PAGEUP) {
 998                        G_scroll_ofs -= G.lines / 2;
 999                        goto normalize_ofs;
1000                }
1001                if (c == KEYCODE_PAGEDOWN) {
1002                        G_scroll_ofs += G.lines / 2;
1003 normalize_ofs:
1004                        if (G_scroll_ofs >= ntop)
1005                                G_scroll_ofs = ntop - 1;
1006                        if (G_scroll_ofs < 0)
1007                                G_scroll_ofs = 0;
1008                        return NO_RESCAN_MASK;
1009                }
1010
1011                c |= 0x20; /* lowercase */
1012                if (c == 'q')
1013                        return EXIT_MASK;
1014
1015                if (c == 'n') {
1016                        IF_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
1017                        sort_function[0] = pid_sort;
1018                        continue;
1019                }
1020                if (c == 'm') {
1021                        IF_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
1022                        sort_function[0] = mem_sort;
1023# if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
1024                        sort_function[1] = pcpu_sort;
1025                        sort_function[2] = time_sort;
1026# endif
1027                        continue;
1028                }
1029# if ENABLE_FEATURE_SHOW_THREADS
1030                if (c == 'h'
1031                IF_FEATURE_TOPMEM(&& scan_mask != TOPMEM_MASK)
1032                ) {
1033                        scan_mask ^= PSSCAN_TASKS;
1034#  if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
1035                        free(prev_hist);
1036                        prev_hist = NULL;
1037                        prev_hist_count = 0;
1038#   endif
1039                        continue;
1040                }
1041# endif
1042# if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
1043                if (c == 'p') {
1044                        IF_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
1045                        sort_function[0] = pcpu_sort;
1046                        sort_function[1] = mem_sort;
1047                        sort_function[2] = time_sort;
1048                        continue;
1049                }
1050                if (c == 't') {
1051                        IF_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
1052                        sort_function[0] = time_sort;
1053                        sort_function[1] = mem_sort;
1054                        sort_function[2] = pcpu_sort;
1055                        continue;
1056                }
1057#  if ENABLE_FEATURE_TOPMEM
1058                if (c == 's') {
1059                        scan_mask = TOPMEM_MASK;
1060                        sort_field = (sort_field + 1) % NUM_SORT_FIELD;
1061                        free(prev_hist);
1062                        prev_hist = NULL;
1063                        prev_hist_count = 0;
1064                        continue;
1065                }
1066#  endif
1067                if (c == 'r') {
1068                        inverted ^= 1;
1069                        continue;
1070                }
1071#  if ENABLE_FEATURE_TOP_SMP_CPU
1072                /* procps-2.0.18 uses 'C', 3.2.7 uses '1' */
1073                if (c == 'c' || c == '1') {
1074                        /* User wants to toggle per cpu <> aggregate */
1075                        if (smp_cpu_info) {
1076                                free(cpu_prev_jif);
1077                                free(cpu_jif);
1078                                cpu_jif = &cur_jif;
1079                                cpu_prev_jif = &prev_jif;
1080                        } else {
1081                                /* Prepare for xrealloc() */
1082                                cpu_jif = cpu_prev_jif = NULL;
1083                        }
1084                        num_cpus = 0;
1085                        smp_cpu_info = !smp_cpu_info;
1086                        get_jiffy_counts();
1087                        continue;
1088                }
1089#  endif
1090# endif
1091                break; /* unknown key -> force refresh */
1092        }
1093
1094        return scan_mask;
1095}
1096#endif
1097
1098//usage:#if ENABLE_FEATURE_SHOW_THREADS || ENABLE_FEATURE_TOP_SMP_CPU
1099//usage:# define IF_SHOW_THREADS_OR_TOP_SMP(...) __VA_ARGS__
1100//usage:#else
1101//usage:# define IF_SHOW_THREADS_OR_TOP_SMP(...)
1102//usage:#endif
1103//usage:#define top_trivial_usage
1104//usage:       "[-b"IF_FEATURE_TOPMEM("m")IF_FEATURE_SHOW_THREADS("H")"]"
1105//usage:       " [-n COUNT] [-d SECONDS]"
1106//usage:#define top_full_usage "\n\n"
1107//usage:       "Show a view of process activity in real time."
1108//usage:   "\n""Read the status of all processes from /proc each SECONDS"
1109//usage:   "\n""and show a screenful of them."
1110//usage:   "\n"
1111//usage:        IF_FEATURE_TOP_INTERACTIVE(
1112//usage:       "Keys:"
1113//usage:   "\n""        N/M"
1114//usage:                IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE("/P")
1115//usage:                IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE("/T")
1116//usage:           ": " IF_FEATURE_TOPMEM("show CPU usage, ") "sort by pid/mem"
1117//usage:                IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE("/cpu")
1118//usage:                IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE("/time")
1119//usage:        IF_FEATURE_TOPMEM(
1120//usage:   "\n""        S: show memory"
1121//usage:        )
1122//usage:   "\n""        R: reverse sort"
1123//usage:        IF_SHOW_THREADS_OR_TOP_SMP(
1124//usage:   "\n""        "
1125//usage:                IF_FEATURE_SHOW_THREADS("H: toggle threads")
1126//usage:                IF_FEATURE_SHOW_THREADS(IF_FEATURE_TOP_SMP_CPU(", "))
1127//usage:                IF_FEATURE_TOP_SMP_CPU("1: toggle SMP")
1128//usage:        )
1129//usage:   "\n""        Q,^C: exit"
1130//usage:   "\n""Options:"
1131//usage:        )
1132//usage:   "\n""        -b      Batch mode"
1133//usage:   "\n""        -n N    Exit after N iterations"
1134//usage:   "\n""        -d SEC  Delay between updates"
1135//usage:        IF_FEATURE_TOPMEM(
1136//usage:   "\n""        -m      Same as 's' key"
1137//usage:        )
1138//usage:        IF_FEATURE_SHOW_THREADS(
1139//usage:   "\n""        -H      Show threads"
1140//usage:        )
1141
1142/* Interactive testing:
1143 * echo sss | ./busybox top
1144 * - shows memory screen
1145 * echo sss | ./busybox top -bn1 >mem
1146 * - saves memory screen - the *whole* list, not first NROWS processes!
1147 * echo .m.s.s.s.s.s.s.q | ./busybox top -b >z
1148 * - saves several different screens, and exits
1149 *
1150 * TODO: -i STRING param as a better alternative?
1151 */
1152
1153int top_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1154int top_main(int argc UNUSED_PARAM, char **argv)
1155{
1156        duration_t interval;
1157        int iterations;
1158        unsigned col;
1159        char *str_interval, *str_iterations;
1160        unsigned scan_mask = TOP_MASK;
1161
1162        INIT_G();
1163
1164        interval = 5; /* default update interval is 5 seconds */
1165        iterations = 0; /* infinite */
1166#if ENABLE_FEATURE_TOP_SMP_CPU
1167        /*num_cpus = 0;*/
1168        /*smp_cpu_info = 0;*/  /* to start with show aggregate */
1169        cpu_jif = &cur_jif;
1170        cpu_prev_jif = &prev_jif;
1171#endif
1172
1173        /* all args are options; -n NUM */
1174        make_all_argv_opts(argv); /* options can be specified w/o dash */
1175        col = getopt32(argv, "d:n:bHm", &str_interval, &str_iterations);
1176        /* NB: -m and -H are accepted even if not configured */
1177#if ENABLE_FEATURE_TOPMEM
1178        if (col & OPT_m) /* -m (busybox specific) */
1179                scan_mask = TOPMEM_MASK;
1180#endif
1181        if (col & OPT_d) {
1182                /* work around for "-d 1" -> "-d -1" done by make_all_argv_opts() */
1183                if (str_interval[0] == '-')
1184                        str_interval++;
1185                interval = parse_duration_str(str_interval);
1186                /* Need to limit it to not overflow poll timeout */
1187                if (interval > INT_MAX / 1000)
1188                        interval = INT_MAX / 1000;
1189        }
1190        if (col & OPT_n) {
1191                if (str_iterations[0] == '-')
1192                        str_iterations++;
1193                iterations = xatou(str_iterations);
1194        }
1195#if ENABLE_FEATURE_SHOW_THREADS
1196        if (col & OPT_H) {
1197                scan_mask |= PSSCAN_TASKS;
1198        }
1199#endif
1200
1201        /* change to /proc */
1202        xchdir("/proc");
1203
1204#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
1205        sort_function[0] = pcpu_sort;
1206        sort_function[1] = mem_sort;
1207        sort_function[2] = time_sort;
1208#else
1209        sort_function[0] = mem_sort;
1210#endif
1211
1212        if (OPT_BATCH_MODE) {
1213                option_mask32 |= OPT_EOF;
1214        }
1215#if ENABLE_FEATURE_TOP_INTERACTIVE
1216        else {
1217                /* Turn on unbuffered input; turn off echoing, ^C ^Z etc */
1218                set_termios_to_raw(STDIN_FILENO, &initial_settings, TERMIOS_CLEAR_ISIG);
1219                die_func = reset_term;
1220        }
1221
1222        bb_signals(BB_FATAL_SIGS, sig_catcher);
1223
1224        /* Eat initial input, if any */
1225        scan_mask = handle_input(scan_mask, 0);
1226#endif
1227
1228        while (scan_mask != EXIT_MASK) {
1229                IF_FEATURE_TOP_INTERACTIVE(unsigned new_mask;)
1230                procps_status_t *p = NULL;
1231
1232                if (OPT_BATCH_MODE) {
1233                        G.lines = INT_MAX;
1234                        col = LINE_BUF_SIZE - 2; /* +2 bytes for '\n', NUL */
1235                } else {
1236                        G.lines = 24; /* default */
1237                        col = 79;
1238                        /* We output to stdout, we need size of stdout (not stdin)! */
1239                        get_terminal_width_height(STDOUT_FILENO, &col, &G.lines);
1240                        if (G.lines < 5 || col < 10) {
1241                                sleep_for_duration(interval);
1242                                continue;
1243                        }
1244                        if (col > LINE_BUF_SIZE - 2)
1245                                col = LINE_BUF_SIZE - 2;
1246                }
1247
1248                /* read process IDs & status for all the processes */
1249                ntop = 0;
1250                while ((p = procps_scan(p, scan_mask)) != NULL) {
1251                        int n;
1252
1253                        IF_FEATURE_TOPMEM(if (scan_mask != TOPMEM_MASK)) {
1254                                n = ntop;
1255                                top = xrealloc_vector(top, 6, ntop++);
1256                                top[n].pid = p->pid;
1257                                top[n].ppid = p->ppid;
1258                                top[n].vsz = p->vsz;
1259#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
1260                                top[n].ticks = p->stime + p->utime;
1261#endif
1262                                top[n].uid = p->uid;
1263                                strcpy(top[n].state, p->state);
1264                                strcpy(top[n].comm, p->comm);
1265#if ENABLE_FEATURE_TOP_SMP_PROCESS
1266                                top[n].last_seen_on_cpu = p->last_seen_on_cpu;
1267#endif
1268                        }
1269#if ENABLE_FEATURE_TOPMEM
1270                        else { /* TOPMEM */
1271                                if (!(p->smaps.mapped_ro | p->smaps.mapped_rw))
1272                                        continue; /* kernel threads are ignored */
1273                                n = ntop;
1274                                /* No bug here - top and topmem are the same */
1275                                top = xrealloc_vector(topmem, 6, ntop++);
1276                                strcpy(topmem[n].comm, p->comm);
1277                                topmem[n].pid      = p->pid;
1278                                topmem[n].vsz      = p->smaps.mapped_rw + p->smaps.mapped_ro;
1279                                topmem[n].vszrw    = p->smaps.mapped_rw;
1280                                topmem[n].rss_sh   = p->smaps.shared_clean + p->smaps.shared_dirty;
1281                                topmem[n].rss      = p->smaps.private_clean + p->smaps.private_dirty + topmem[n].rss_sh;
1282                                topmem[n].dirty    = p->smaps.private_dirty + p->smaps.shared_dirty;
1283                                topmem[n].dirty_sh = p->smaps.shared_dirty;
1284                                topmem[n].stack    = p->smaps.stack;
1285                        }
1286#endif
1287                } /* end of "while we read /proc" */
1288                if (ntop == 0) {
1289                        bb_simple_error_msg("no process info in /proc");
1290                        break;
1291                }
1292
1293                IF_FEATURE_TOPMEM(if (scan_mask != TOPMEM_MASK)) {
1294#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
1295                        if (!prev_hist_count) {
1296                                do_stats();
1297                                usleep(100000);
1298                                clearmems();
1299                                continue;
1300                        }
1301                        do_stats();
1302                        /* TODO: we don't need to sort all 10000 processes, we need to find top 24! */
1303                        qsort(top, ntop, sizeof(top_status_t), (void*)mult_lvl_cmp);
1304#else
1305                        qsort(top, ntop, sizeof(top_status_t), (void*)(sort_function[0]));
1306#endif
1307                }
1308#if ENABLE_FEATURE_TOPMEM
1309                else { /* TOPMEM */
1310                        qsort(topmem, ntop, sizeof(topmem_status_t), (void*)topmem_sort);
1311                }
1312#endif
1313 IF_FEATURE_TOP_INTERACTIVE(display:)
1314                IF_FEATURE_TOPMEM(if (scan_mask != TOPMEM_MASK)) {
1315                        display_process_list(G.lines, col);
1316                }
1317#if ENABLE_FEATURE_TOPMEM
1318                else { /* TOPMEM */
1319                        display_topmem_process_list(G.lines, col);
1320                }
1321#endif
1322                if (iterations >= 0 && !--iterations)
1323                        break;
1324#if !ENABLE_FEATURE_TOP_INTERACTIVE
1325                clearmems();
1326                sleep_for_duration(interval);
1327#else
1328                new_mask = handle_input(scan_mask, interval);
1329                if (new_mask == NO_RESCAN_MASK)
1330                        goto display;
1331                scan_mask = new_mask;
1332                clearmems();
1333#endif
1334        } /* end of "while (not Q)" */
1335
1336        bb_putchar('\n');
1337#if ENABLE_FEATURE_TOP_INTERACTIVE
1338        reset_term();
1339#endif
1340        if (ENABLE_FEATURE_CLEAN_UP) {
1341                clearmems();
1342#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
1343                free(prev_hist);
1344#endif
1345        }
1346        return EXIT_SUCCESS;
1347}
1348