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
  27/* Original code Copyrights */
  28/*
  29 * Copyright (c) 1992 Branko Lankester
  30 * Copyright (c) 1992 Roger Binns
  31 * Copyright (C) 1994-1996 Charles L. Blake.
  32 * Copyright (C) 1992-1998 Michael K. Johnson
  33 * May be distributed under the conditions of the
  34 * GNU Library General Public License
  35 */
  36
  37#include "libbb.h"
  38
  39
  40typedef struct top_status_t {
  41        unsigned long vsz;
  42#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
  43        unsigned long ticks;
  44        unsigned pcpu; /* delta of ticks */
  45#endif
  46        unsigned pid, ppid;
  47        unsigned uid;
  48        char state[4];
  49        char comm[COMM_LEN];
  50#if ENABLE_FEATURE_TOP_SMP_PROCESS
  51        int last_seen_on_cpu;
  52#endif
  53} top_status_t;
  54
  55typedef struct jiffy_counts_t {
  56        unsigned long long usr,nic,sys,idle,iowait,irq,softirq,steal;
  57        unsigned long long total;
  58        unsigned long long busy;
  59} jiffy_counts_t;
  60
  61/* This structure stores some critical information from one frame to
  62   the next. Used for finding deltas. */
  63typedef struct save_hist {
  64        unsigned long ticks;
  65        pid_t pid;
  66} save_hist;
  67
  68typedef int (*cmp_funcp)(top_status_t *P, top_status_t *Q);
  69
  70
  71enum { SORT_DEPTH = 3 };
  72
  73
  74struct globals {
  75        top_status_t *top;
  76        int ntop;
  77#if ENABLE_FEATURE_TOPMEM
  78        smallint sort_field;
  79        smallint inverted;
  80#endif
  81#if ENABLE_FEATURE_TOP_SMP_CPU
  82        smallint smp_cpu_info; /* one/many cpu info lines? */
  83#endif
  84#if ENABLE_FEATURE_USE_TERMIOS
  85        struct termios initial_settings;
  86#endif
  87#if !ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
  88        cmp_funcp sort_function[1];
  89#else
  90        cmp_funcp sort_function[SORT_DEPTH];
  91        struct save_hist *prev_hist;
  92        int prev_hist_count;
  93        jiffy_counts_t cur_jif, prev_jif;
  94        /* int hist_iterations; */
  95        unsigned total_pcpu;
  96        /* unsigned long total_vsz; */
  97#endif
  98#if ENABLE_FEATURE_TOP_SMP_CPU
  99        /* Per CPU samples: current and last */
 100        jiffy_counts_t *cpu_jif, *cpu_prev_jif;
 101        int num_cpus;
 102#endif
 103        char line_buf[80];
 104};
 105
 106enum { LINE_BUF_SIZE = COMMON_BUFSIZE - offsetof(struct globals, line_buf) };
 107
 108#define G (*(struct globals*)&bb_common_bufsiz1)
 109#define INIT_G() do { \
 110        struct G_sizecheck { \
 111                char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \
 112        }; \
 113} while (0)
 114#define top              (G.top               )
 115#define ntop             (G.ntop              )
 116#define sort_field       (G.sort_field        )
 117#define inverted         (G.inverted          )
 118#define smp_cpu_info     (G.smp_cpu_info      )
 119#define initial_settings (G.initial_settings  )
 120#define sort_function    (G.sort_function     )
 121#define prev_hist        (G.prev_hist         )
 122#define prev_hist_count  (G.prev_hist_count   )
 123#define cur_jif          (G.cur_jif           )
 124#define prev_jif         (G.prev_jif          )
 125#define cpu_jif          (G.cpu_jif           )
 126#define cpu_prev_jif     (G.cpu_prev_jif      )
 127#define num_cpus         (G.num_cpus          )
 128#define total_pcpu       (G.total_pcpu        )
 129#define line_buf         (G.line_buf          )
 130
 131enum {
 132        OPT_d = (1 << 0),
 133        OPT_n = (1 << 1),
 134        OPT_b = (1 << 2),
 135        OPT_EOF = (1 << 3), /* pseudo: "we saw EOF in stdin" */
 136};
 137#define OPT_BATCH_MODE (option_mask32 & OPT_b)
 138
 139
 140#if ENABLE_FEATURE_USE_TERMIOS
 141static int pid_sort(top_status_t *P, top_status_t *Q)
 142{
 143        /* Buggy wrt pids with high bit set */
 144        /* (linux pids are in [1..2^15-1]) */
 145        return (Q->pid - P->pid);
 146}
 147#endif
 148
 149static int mem_sort(top_status_t *P, top_status_t *Q)
 150{
 151        /* We want to avoid unsigned->signed and truncation errors */
 152        if (Q->vsz < P->vsz) return -1;
 153        return Q->vsz != P->vsz; /* 0 if ==, 1 if > */
 154}
 155
 156
 157#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 158
 159static int pcpu_sort(top_status_t *P, top_status_t *Q)
 160{
 161        /* Buggy wrt ticks with high bit set */
 162        /* Affects only processes for which ticks overflow */
 163        return (int)Q->pcpu - (int)P->pcpu;
 164}
 165
 166static int time_sort(top_status_t *P, top_status_t *Q)
 167{
 168        /* We want to avoid unsigned->signed and truncation errors */
 169        if (Q->ticks < P->ticks) return -1;
 170        return Q->ticks != P->ticks; /* 0 if ==, 1 if > */
 171}
 172
 173static int mult_lvl_cmp(void* a, void* b)
 174{
 175        int i, cmp_val;
 176
 177        for (i = 0; i < SORT_DEPTH; i++) {
 178                cmp_val = (*sort_function[i])(a, b);
 179                if (cmp_val != 0)
 180                        return cmp_val;
 181        }
 182        return 0;
 183}
 184
 185/* NOINLINE so that complier doesn't unfold the call
 186 * causing multiple copies of the arithmatic instrns
 187 */
 188static NOINLINE int read_cpu_jiffy(FILE *fp, jiffy_counts_t *p_jif)
 189{
 190#if !ENABLE_FEATURE_TOP_SMP_CPU
 191        static const char fmt[] = "cpu %lld %lld %lld %lld %lld %lld %lld %lld";
 192#else
 193        static const char fmt[] = "cp%*s %lld %lld %lld %lld %lld %lld %lld %lld";
 194#endif
 195        int ret;
 196
 197        if (!fgets(line_buf, LINE_BUF_SIZE, fp) || line_buf[0] != 'c' /* not "cpu" */)
 198                return 0;
 199        ret = sscanf(line_buf, fmt,
 200                        &p_jif->usr, &p_jif->nic, &p_jif->sys, &p_jif->idle,
 201                        &p_jif->iowait, &p_jif->irq, &p_jif->softirq,
 202                        &p_jif->steal);
 203        if (ret >= 4) {
 204                p_jif->total = p_jif->usr + p_jif->nic + p_jif->sys + p_jif->idle
 205                        + p_jif->iowait + p_jif->irq + p_jif->softirq + p_jif->steal;
 206                /* procps 2.x does not count iowait as busy time */
 207                p_jif->busy = p_jif->total - p_jif->idle - p_jif->iowait;
 208        }
 209
 210        return ret;
 211}
 212
 213static void get_jiffy_counts(void)
 214{
 215        FILE* fp = xfopen_for_read("stat");
 216
 217        /* We need to parse cumulative counts even if SMP CPU display is on,
 218         * they are used to calculate per process CPU% */
 219        prev_jif = cur_jif;
 220        if (read_cpu_jiffy(fp, &cur_jif) < 4)
 221                bb_error_msg_and_die("can't read /proc/stat");
 222
 223#if !ENABLE_FEATURE_TOP_SMP_CPU
 224        fclose(fp);
 225        return;
 226#else
 227        if (!smp_cpu_info) {
 228                fclose(fp);
 229                return;
 230        }
 231
 232        if (!num_cpus) {
 233                /* First time here. How many CPUs?
 234                 * There will be at least 1 /proc/stat line with cpu%d
 235                 */
 236                while (1) {
 237                        cpu_jif = xrealloc_vector(cpu_jif, 1, num_cpus);
 238                        if (read_cpu_jiffy(fp, &cpu_jif[num_cpus]) <= 4)
 239                                break;
 240                        num_cpus++;
 241                }
 242                if (num_cpus == 0) /* /proc/stat with only "cpu ..." line?! */
 243                        smp_cpu_info = 0;
 244
 245                cpu_prev_jif = xzalloc(sizeof(cpu_prev_jif[0]) * num_cpus);
 246
 247                /* Otherwise the first per cpu display shows all 100% idles */
 248                usleep(50000);
 249        } else { /* Non first time invocation */
 250                jiffy_counts_t *tmp;
 251                int i;
 252
 253                /* First switch the sample pointers: no need to copy */
 254                tmp = cpu_prev_jif;
 255                cpu_prev_jif = cpu_jif;
 256                cpu_jif = tmp;
 257
 258                /* Get the new samples */
 259                for (i = 0; i < num_cpus; i++)
 260                        read_cpu_jiffy(fp, &cpu_jif[i]);
 261        }
 262#endif
 263        fclose(fp);
 264}
 265
 266static void do_stats(void)
 267{
 268        top_status_t *cur;
 269        pid_t pid;
 270        int i, last_i, n;
 271        struct save_hist *new_hist;
 272
 273        get_jiffy_counts();
 274        total_pcpu = 0;
 275        /* total_vsz = 0; */
 276        new_hist = xmalloc(sizeof(new_hist[0]) * ntop);
 277        /*
 278         * Make a pass through the data to get stats.
 279         */
 280        /* hist_iterations = 0; */
 281        i = 0;
 282        for (n = 0; n < ntop; n++) {
 283                cur = top + n;
 284
 285                /*
 286                 * Calculate time in cur process.  Time is sum of user time
 287                 * and system time
 288                 */
 289                pid = cur->pid;
 290                new_hist[n].ticks = cur->ticks;
 291                new_hist[n].pid = pid;
 292
 293                /* find matching entry from previous pass */
 294                cur->pcpu = 0;
 295                /* do not start at index 0, continue at last used one
 296                 * (brought hist_iterations from ~14000 down to 172) */
 297                last_i = i;
 298                if (prev_hist_count) do {
 299                        if (prev_hist[i].pid == pid) {
 300                                cur->pcpu = cur->ticks - prev_hist[i].ticks;
 301                                total_pcpu += cur->pcpu;
 302                                break;
 303                        }
 304                        i = (i+1) % prev_hist_count;
 305                        /* hist_iterations++; */
 306                } while (i != last_i);
 307                /* total_vsz += cur->vsz; */
 308        }
 309
 310        /*
 311         * Save cur frame's information.
 312         */
 313        free(prev_hist);
 314        prev_hist = new_hist;
 315        prev_hist_count = ntop;
 316}
 317
 318#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */
 319
 320#if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS && ENABLE_FEATURE_TOP_DECIMALS
 321/* formats 7 char string (8 with terminating NUL) */
 322static char *fmt_100percent_8(char pbuf[8], unsigned value, unsigned total)
 323{
 324        unsigned t;
 325        if (value >= total) { /* 100% ? */
 326                strcpy(pbuf, "  100% ");
 327                return pbuf;
 328        }
 329        /* else generate " [N/space]N.N% " string */
 330        value = 1000 * value / total;
 331        t = value / 100;
 332        value = value % 100;
 333        pbuf[0] = ' ';
 334        pbuf[1] = t ? t + '0' : ' ';
 335        pbuf[2] = '0' + (value / 10);
 336        pbuf[3] = '.';
 337        pbuf[4] = '0' + (value % 10);
 338        pbuf[5] = '%';
 339        pbuf[6] = ' ';
 340        pbuf[7] = '\0';
 341        return pbuf;
 342}
 343#endif
 344
 345#if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS
 346static void display_cpus(int scr_width, char *scrbuf, int *lines_rem_p)
 347{
 348        /*
 349         * xxx% = (cur_jif.xxx - prev_jif.xxx) / (cur_jif.total - prev_jif.total) * 100%
 350         */
 351        unsigned total_diff;
 352        jiffy_counts_t *p_jif, *p_prev_jif;
 353        int i;
 354
 355#if ENABLE_FEATURE_TOP_SMP_CPU
 356        int n_cpu_lines;
 357#endif
 358
 359        /* using (unsigned) casts to make operations cheaper */
 360#define  CALC_TOT_DIFF  ((unsigned)(p_jif->total - p_prev_jif->total) ? : 1)
 361
 362#if ENABLE_FEATURE_TOP_DECIMALS
 363#define CALC_STAT(xxx) char xxx[8]
 364#define SHOW_STAT(xxx) fmt_100percent_8(xxx, (unsigned)(p_jif->xxx - p_prev_jif->xxx), total_diff)
 365#define FMT "%s"
 366#else
 367#define CALC_STAT(xxx) unsigned xxx = 100 * (unsigned)(p_jif->xxx - p_prev_jif->xxx) / total_diff
 368#define SHOW_STAT(xxx) xxx
 369#define FMT "%4u%% "
 370#endif
 371
 372#if !ENABLE_FEATURE_TOP_SMP_CPU
 373        {
 374                i = 1;
 375                p_jif = &cur_jif;
 376                p_prev_jif = &prev_jif;
 377#else
 378        /* Loop thru CPU(s) */
 379        n_cpu_lines = smp_cpu_info ? num_cpus : 1;
 380        if (n_cpu_lines > *lines_rem_p)
 381                n_cpu_lines = *lines_rem_p;
 382
 383        for (i = 0; i < n_cpu_lines; i++) {
 384                p_jif = &cpu_jif[i];
 385                p_prev_jif = &cpu_prev_jif[i];
 386#endif
 387                total_diff = CALC_TOT_DIFF;
 388
 389                { /* Need a block: CALC_STAT are declarations */
 390                        CALC_STAT(usr);
 391                        CALC_STAT(sys);
 392                        CALC_STAT(nic);
 393                        CALC_STAT(idle);
 394                        CALC_STAT(iowait);
 395                        CALC_STAT(irq);
 396                        CALC_STAT(softirq);
 397                        /*CALC_STAT(steal);*/
 398
 399                        snprintf(scrbuf, scr_width,
 400                                /* Barely fits in 79 chars when in "decimals" mode. */
 401#if ENABLE_FEATURE_TOP_SMP_CPU
 402                                "CPU%s:"FMT"usr"FMT"sys"FMT"nic"FMT"idle"FMT"io"FMT"irq"FMT"sirq",
 403                                (smp_cpu_info ? utoa(i) : ""),
 404#else
 405                                "CPU:"FMT"usr"FMT"sys"FMT"nic"FMT"idle"FMT"io"FMT"irq"FMT"sirq",
 406#endif
 407                                SHOW_STAT(usr), SHOW_STAT(sys), SHOW_STAT(nic), SHOW_STAT(idle),
 408                                SHOW_STAT(iowait), SHOW_STAT(irq), SHOW_STAT(softirq)
 409                                /*, SHOW_STAT(steal) - what is this 'steal' thing? */
 410                                /* I doubt anyone wants to know it */
 411                        );
 412                        puts(scrbuf);
 413                }
 414        }
 415#undef SHOW_STAT
 416#undef CALC_STAT
 417#undef FMT
 418        *lines_rem_p -= i;
 419}
 420#else  /* !ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS */
 421#define display_cpus(scr_width, scrbuf, lines_rem) ((void)0)
 422#endif
 423
 424static unsigned long display_header(int scr_width, int *lines_rem_p)
 425{
 426        FILE *fp;
 427        char buf[80];
 428        char scrbuf[80];
 429        unsigned long total, used, mfree, shared, buffers, cached;
 430
 431        /* read memory info */
 432        fp = xfopen_for_read("meminfo");
 433
 434        /*
 435         * Old kernels (such as 2.4.x) had a nice summary of memory info that
 436         * we could parse, however this is gone entirely in 2.6. Try parsing
 437         * the old way first, and if that fails, parse each field manually.
 438         *
 439         * First, we read in the first line. Old kernels will have bogus
 440         * strings we don't care about, whereas new kernels will start right
 441         * out with MemTotal:
 442         *                              -- PFM.
 443         */
 444        if (fscanf(fp, "MemTotal: %lu %s\n", &total, buf) != 2) {
 445                fgets(buf, sizeof(buf), fp);    /* skip first line */
 446
 447                fscanf(fp, "Mem: %lu %lu %lu %lu %lu %lu",
 448                        &total, &used, &mfree, &shared, &buffers, &cached);
 449                /* convert to kilobytes */
 450                used /= 1024;
 451                mfree /= 1024;
 452                shared /= 1024;
 453                buffers /= 1024;
 454                cached /= 1024;
 455                total /= 1024;
 456        } else {
 457                /*
 458                 * Revert to manual parsing, which incidentally already has the
 459                 * sizes in kilobytes. This should be safe for both 2.4 and
 460                 * 2.6.
 461                 */
 462                fscanf(fp, "MemFree: %lu %s\n", &mfree, buf);
 463
 464                /*
 465                 * MemShared: is no longer present in 2.6. Report this as 0,
 466                 * to maintain consistent behavior with normal procps.
 467                 */
 468                if (fscanf(fp, "MemShared: %lu %s\n", &shared, buf) != 2)
 469                        shared = 0;
 470
 471                fscanf(fp, "Buffers: %lu %s\n", &buffers, buf);
 472                fscanf(fp, "Cached: %lu %s\n", &cached, buf);
 473
 474                used = total - mfree;
 475        }
 476        fclose(fp);
 477
 478        /* output memory info */
 479        if (scr_width > (int)sizeof(scrbuf))
 480                scr_width = sizeof(scrbuf);
 481        snprintf(scrbuf, scr_width,
 482                "Mem: %luK used, %luK free, %luK shrd, %luK buff, %luK cached",
 483                used, mfree, shared, buffers, cached);
 484        /* clear screen & go to top */
 485        printf(OPT_BATCH_MODE ? "%s\n" : "\e[H\e[J%s\n", scrbuf);
 486        (*lines_rem_p)--;
 487
 488        /* Display CPU time split as percentage of total time
 489         * This displays either a cumulative line or one line per CPU
 490         */
 491        display_cpus(scr_width, scrbuf, lines_rem_p);
 492
 493        /* read load average as a string */
 494        buf[0] = '\0';
 495        open_read_close("loadavg", buf, sizeof(buf) - 1);
 496        buf[sizeof(buf) - 1] = '\n';
 497        *strchr(buf, '\n') = '\0';
 498        snprintf(scrbuf, scr_width, "Load average: %s", buf);
 499        puts(scrbuf);
 500        (*lines_rem_p)--;
 501
 502        return total;
 503}
 504
 505static NOINLINE void display_process_list(int lines_rem, int scr_width)
 506{
 507        enum {
 508                BITS_PER_INT = sizeof(int) * 8
 509        };
 510
 511        top_status_t *s;
 512        char vsz_str_buf[8];
 513        unsigned long total_memory = display_header(scr_width, &lines_rem); /* or use total_vsz? */
 514        /* xxx_shift and xxx_scale variables allow us to replace
 515         * expensive divides with multiply and shift */
 516        unsigned pmem_shift, pmem_scale, pmem_half;
 517#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 518        unsigned pcpu_shift, pcpu_scale, pcpu_half;
 519        unsigned busy_jifs;
 520#endif
 521
 522        /* what info of the processes is shown */
 523        printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width,
 524                "  PID  PPID USER     STAT   VSZ %MEM"
 525#if ENABLE_FEATURE_TOP_SMP_PROCESS
 526                " CPU"
 527#endif
 528#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 529                " %CPU"
 530#endif
 531                " COMMAND");
 532        lines_rem--;
 533
 534#if ENABLE_FEATURE_TOP_DECIMALS
 535#define UPSCALE 1000
 536#define CALC_STAT(name, val) div_t name = div((val), 10)
 537#define SHOW_STAT(name) name.quot, '0'+name.rem
 538#define FMT "%3u.%c"
 539#else
 540#define UPSCALE 100
 541#define CALC_STAT(name, val) unsigned name = (val)
 542#define SHOW_STAT(name) name
 543#define FMT "%4u%%"
 544#endif
 545        /*
 546         * MEM% = s->vsz/MemTotal
 547         */
 548        pmem_shift = BITS_PER_INT-11;
 549        pmem_scale = UPSCALE*(1U<<(BITS_PER_INT-11)) / total_memory;
 550        /* s->vsz is in kb. we want (s->vsz * pmem_scale) to never overflow */
 551        while (pmem_scale >= 512) {
 552                pmem_scale /= 4;
 553                pmem_shift -= 2;
 554        }
 555        pmem_half = (1U << pmem_shift) / (ENABLE_FEATURE_TOP_DECIMALS? 20 : 2);
 556#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 557        busy_jifs = cur_jif.busy - prev_jif.busy;
 558        /* This happens if there were lots of short-lived processes
 559         * between two top updates (e.g. compilation) */
 560        if (total_pcpu < busy_jifs) total_pcpu = busy_jifs;
 561
 562        /*
 563         * CPU% = s->pcpu/sum(s->pcpu) * busy_cpu_ticks/total_cpu_ticks
 564         * (pcpu is delta of sys+user time between samples)
 565         */
 566        /* (cur_jif.xxx - prev_jif.xxx) and s->pcpu are
 567         * in 0..~64000 range (HZ*update_interval).
 568         * we assume that unsigned is at least 32-bit.
 569         */
 570        pcpu_shift = 6;
 571        pcpu_scale = (UPSCALE*64 * (uint16_t)busy_jifs ? : 1);
 572        while (pcpu_scale < (1U << (BITS_PER_INT-2))) {
 573                pcpu_scale *= 4;
 574                pcpu_shift += 2;
 575        }
 576        pcpu_scale /= ( (uint16_t)(cur_jif.total - prev_jif.total) * total_pcpu ? : 1);
 577        /* we want (s->pcpu * pcpu_scale) to never overflow */
 578        while (pcpu_scale >= 1024) {
 579                pcpu_scale /= 4;
 580                pcpu_shift -= 2;
 581        }
 582        pcpu_half = (1U << pcpu_shift) / (ENABLE_FEATURE_TOP_DECIMALS? 20 : 2);
 583        /* printf(" pmem_scale=%u pcpu_scale=%u ", pmem_scale, pcpu_scale); */
 584#endif
 585
 586        /* Ok, all preliminary data is ready, go through the list */
 587        scr_width += 2; /* account for leading '\n' and trailing NUL */
 588        if (lines_rem > ntop)
 589                lines_rem = ntop;
 590        s = top;
 591        while (--lines_rem >= 0) {
 592                unsigned col;
 593                CALC_STAT(pmem, (s->vsz*pmem_scale + pmem_half) >> pmem_shift);
 594#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 595                CALC_STAT(pcpu, (s->pcpu*pcpu_scale + pcpu_half) >> pcpu_shift);
 596#endif
 597
 598                if (s->vsz >= 100000)
 599                        sprintf(vsz_str_buf, "%6ldm", s->vsz/1024);
 600                else
 601                        sprintf(vsz_str_buf, "%7ld", s->vsz);
 602                /* PID PPID USER STAT VSZ %MEM [%CPU] COMMAND */
 603                col = snprintf(line_buf, scr_width,
 604                                "\n" "%5u%6u %-8.8s %s%s" FMT
 605#if ENABLE_FEATURE_TOP_SMP_PROCESS
 606                                " %3d"
 607#endif
 608#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 609                                FMT
 610#endif
 611                                " ",
 612                                s->pid, s->ppid, get_cached_username(s->uid),
 613                                s->state, vsz_str_buf,
 614                                SHOW_STAT(pmem)
 615#if ENABLE_FEATURE_TOP_SMP_PROCESS
 616                                , s->last_seen_on_cpu
 617#endif
 618#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 619                                , SHOW_STAT(pcpu)
 620#endif
 621                );
 622                if ((int)(col + 1) < scr_width)
 623                        read_cmdline(line_buf + col, scr_width - col - 1, s->pid, s->comm);
 624                fputs(line_buf, stdout);
 625                /* printf(" %d/%d %lld/%lld", s->pcpu, total_pcpu,
 626                        cur_jif.busy - prev_jif.busy, cur_jif.total - prev_jif.total); */
 627                s++;
 628        }
 629        /* printf(" %d", hist_iterations); */
 630        bb_putchar(OPT_BATCH_MODE ? '\n' : '\r');
 631        fflush(stdout);
 632}
 633#undef UPSCALE
 634#undef SHOW_STAT
 635#undef CALC_STAT
 636#undef FMT
 637
 638static void clearmems(void)
 639{
 640        clear_username_cache();
 641        free(top);
 642        top = NULL;
 643        ntop = 0;
 644}
 645
 646#if ENABLE_FEATURE_USE_TERMIOS
 647#include <termios.h>
 648#include <signal.h>
 649
 650static void reset_term(void)
 651{
 652        tcsetattr_stdin_TCSANOW(&initial_settings);
 653        if (ENABLE_FEATURE_CLEAN_UP) {
 654                clearmems();
 655#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 656                free(prev_hist);
 657#endif
 658        }
 659}
 660
 661static void sig_catcher(int sig UNUSED_PARAM)
 662{
 663        reset_term();
 664        exit(EXIT_FAILURE);
 665}
 666#endif /* FEATURE_USE_TERMIOS */
 667
 668/*
 669 * TOPMEM support
 670 */
 671
 672typedef unsigned long mem_t;
 673
 674typedef struct topmem_status_t {
 675        unsigned pid;
 676        char comm[COMM_LEN];
 677        /* vsz doesn't count /dev/xxx mappings except /dev/zero */
 678        mem_t vsz     ;
 679        mem_t vszrw   ;
 680        mem_t rss     ;
 681        mem_t rss_sh  ;
 682        mem_t dirty   ;
 683        mem_t dirty_sh;
 684        mem_t stack   ;
 685} topmem_status_t;
 686
 687enum { NUM_SORT_FIELD = 7 };
 688
 689#define topmem ((topmem_status_t*)top)
 690
 691#if ENABLE_FEATURE_TOPMEM
 692
 693static int topmem_sort(char *a, char *b)
 694{
 695        int n;
 696        mem_t l, r;
 697
 698        n = offsetof(topmem_status_t, vsz) + (sort_field * sizeof(mem_t));
 699        l = *(mem_t*)(a + n);
 700        r = *(mem_t*)(b + n);
 701//      if (l == r) {
 702//              l = a->mapped_rw;
 703//              r = b->mapped_rw;
 704//      }
 705        /* We want to avoid unsigned->signed and truncation errors */
 706        /* l>r: -1, l=r: 0, l<r: 1 */
 707        n = (l > r) ? -1 : (l != r);
 708        return inverted ? -n : n;
 709}
 710
 711/* Cut "NNNN " out of "    NNNN kb" */
 712static char *grab_number(char *str, const char *match, unsigned sz)
 713{
 714        if (strncmp(str, match, sz) == 0) {
 715                str = skip_whitespace(str + sz);
 716                (skip_non_whitespace(str))[1] = '\0';
 717                return xstrdup(str);
 718        }
 719        return NULL;
 720}
 721
 722/* display header info (meminfo / loadavg) */
 723static void display_topmem_header(int scr_width, int *lines_rem_p)
 724{
 725        char linebuf[128];
 726        unsigned i;
 727        FILE *fp;
 728        union {
 729                struct {
 730                        /*  1 */ char *total;
 731                        /*  2 */ char *mfree;
 732                        /*  3 */ char *buf;
 733                        /*  4 */ char *cache;
 734                        /*  5 */ char *swaptotal;
 735                        /*  6 */ char *swapfree;
 736                        /*  7 */ char *dirty;
 737                        /*  8 */ char *mwrite;
 738                        /*  9 */ char *anon;
 739                        /* 10 */ char *map;
 740                        /* 11 */ char *slab;
 741                } u;
 742                char *str[11];
 743        } Z;
 744#define total     Z.u.total
 745#define mfree     Z.u.mfree
 746#define buf       Z.u.buf
 747#define cache     Z.u.cache
 748#define swaptotal Z.u.swaptotal
 749#define swapfree  Z.u.swapfree
 750#define dirty     Z.u.dirty
 751#define mwrite    Z.u.mwrite
 752#define anon      Z.u.anon
 753#define map       Z.u.map
 754#define slab      Z.u.slab
 755#define str       Z.str
 756
 757        memset(&Z, 0, sizeof(Z));
 758
 759        /* read memory info */
 760        fp = xfopen_for_read("meminfo");
 761        while (fgets(linebuf, sizeof(linebuf), fp)) {
 762                char *p;
 763
 764#define SCAN(match, name) \
 765                p = grab_number(linebuf, match, sizeof(match)-1); \
 766                if (p) { name = p; continue; }
 767
 768                SCAN("MemTotal:", total);
 769                SCAN("MemFree:", mfree);
 770                SCAN("Buffers:", buf);
 771                SCAN("Cached:", cache);
 772                SCAN("SwapTotal:", swaptotal);
 773                SCAN("SwapFree:", swapfree);
 774                SCAN("Dirty:", dirty);
 775                SCAN("Writeback:", mwrite);
 776                SCAN("AnonPages:", anon);
 777                SCAN("Mapped:", map);
 778                SCAN("Slab:", slab);
 779#undef SCAN
 780        }
 781        fclose(fp);
 782
 783#define S(s) (s ? s : "0 ")
 784        snprintf(linebuf, sizeof(linebuf),
 785                "Mem %stotal %sanon %smap %sfree",
 786                S(total), S(anon), S(map), S(mfree));
 787        printf(OPT_BATCH_MODE ? "%.*s\n" : "\e[H\e[J%.*s\n", scr_width, linebuf);
 788
 789        snprintf(linebuf, sizeof(linebuf),
 790                " %sslab %sbuf %scache %sdirty %swrite",
 791                S(slab), S(buf), S(cache), S(dirty), S(mwrite));
 792        printf("%.*s\n", scr_width, linebuf);
 793
 794        snprintf(linebuf, sizeof(linebuf),
 795                "Swap %stotal %sfree", // TODO: % used?
 796                S(swaptotal), S(swapfree));
 797        printf("%.*s\n", scr_width, linebuf);
 798
 799        (*lines_rem_p) -= 3;
 800#undef S
 801
 802        for (i = 0; i < ARRAY_SIZE(str); i++)
 803                free(str[i]);
 804#undef total
 805#undef free
 806#undef buf
 807#undef cache
 808#undef swaptotal
 809#undef swapfree
 810#undef dirty
 811#undef write
 812#undef anon
 813#undef map
 814#undef slab
 815#undef str
 816}
 817
 818static void ulltoa6_and_space(unsigned long long ul, char buf[6])
 819{
 820        /* see http://en.wikipedia.org/wiki/Tera */
 821        smart_ulltoa5(ul, buf, " mgtpezy");
 822        buf[5] = ' ';
 823}
 824
 825static NOINLINE void display_topmem_process_list(int lines_rem, int scr_width)
 826{
 827#define HDR_STR "  PID   VSZ VSZRW   RSS (SHR) DIRTY (SHR) STACK"
 828#define MIN_WIDTH sizeof(HDR_STR)
 829        const topmem_status_t *s = topmem;
 830
 831        display_topmem_header(scr_width, &lines_rem);
 832        strcpy(line_buf, HDR_STR " COMMAND");
 833        line_buf[5 + sort_field * 6] = '*';
 834        printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width, line_buf);
 835        lines_rem--;
 836
 837        if (lines_rem > ntop)
 838                lines_rem = ntop;
 839        while (--lines_rem >= 0) {
 840                /* PID VSZ VSZRW RSS (SHR) DIRTY (SHR) COMMAND */
 841                ulltoa6_and_space(s->pid     , &line_buf[0*6]);
 842                ulltoa6_and_space(s->vsz     , &line_buf[1*6]);
 843                ulltoa6_and_space(s->vszrw   , &line_buf[2*6]);
 844                ulltoa6_and_space(s->rss     , &line_buf[3*6]);
 845                ulltoa6_and_space(s->rss_sh  , &line_buf[4*6]);
 846                ulltoa6_and_space(s->dirty   , &line_buf[5*6]);
 847                ulltoa6_and_space(s->dirty_sh, &line_buf[6*6]);
 848                ulltoa6_and_space(s->stack   , &line_buf[7*6]);
 849                line_buf[8*6] = '\0';
 850                if (scr_width > (int)MIN_WIDTH) {
 851                        read_cmdline(&line_buf[8*6], scr_width - MIN_WIDTH, s->pid, s->comm);
 852                }
 853                printf("\n""%.*s", scr_width, line_buf);
 854                s++;
 855        }
 856        bb_putchar(OPT_BATCH_MODE ? '\n' : '\r');
 857        fflush(stdout);
 858#undef HDR_STR
 859#undef MIN_WIDTH
 860}
 861
 862#else
 863void display_topmem_process_list(int lines_rem, int scr_width);
 864int topmem_sort(char *a, char *b);
 865#endif /* TOPMEM */
 866
 867/*
 868 * end TOPMEM support
 869 */
 870
 871enum {
 872        TOP_MASK = 0
 873                | PSSCAN_PID
 874                | PSSCAN_PPID
 875                | PSSCAN_VSZ
 876                | PSSCAN_STIME
 877                | PSSCAN_UTIME
 878                | PSSCAN_STATE
 879                | PSSCAN_COMM
 880#if ENABLE_FEATURE_TOP_SMP_PROCESS
 881                | PSSCAN_CPU
 882#endif
 883                | PSSCAN_UIDGID,
 884        TOPMEM_MASK = 0
 885                | PSSCAN_PID
 886                | PSSCAN_SMAPS
 887                | PSSCAN_COMM,
 888};
 889
 890int top_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 891int top_main(int argc UNUSED_PARAM, char **argv)
 892{
 893        int iterations;
 894        unsigned lines, col;
 895        int lines_rem;
 896        unsigned interval;
 897        char *str_interval, *str_iterations;
 898        SKIP_FEATURE_TOPMEM(const) unsigned scan_mask = TOP_MASK;
 899#if ENABLE_FEATURE_USE_TERMIOS
 900        struct termios new_settings;
 901        struct pollfd pfd[1];
 902        unsigned char c;
 903
 904        pfd[0].fd = 0;
 905        pfd[0].events = POLLIN;
 906#endif /* FEATURE_USE_TERMIOS */
 907
 908        INIT_G();
 909
 910        interval = 5; /* default update interval is 5 seconds */
 911        iterations = 0; /* infinite */
 912#if ENABLE_FEATURE_TOP_SMP_CPU
 913        /*num_cpus = 0;*/
 914        /*smp_cpu_info = 0;*/  /* to start with show aggregate */
 915        cpu_jif = &cur_jif;
 916        cpu_prev_jif = &prev_jif;
 917#endif
 918
 919        /* all args are options; -n NUM */
 920        opt_complementary = "-";
 921        col = getopt32(argv, "d:n:b", &str_interval, &str_iterations);
 922        if (col & OPT_d) {
 923                /* work around for "-d 1" -> "-d -1" done by getopt32 */
 924                if (str_interval[0] == '-')
 925                        str_interval++;
 926                /* Need to limit it to not overflow poll timeout */
 927                interval = xatou16(str_interval);
 928        }
 929        if (col & OPT_n) {
 930                if (str_iterations[0] == '-')
 931                        str_iterations++;
 932                iterations = xatou(str_iterations);
 933        }
 934
 935        /* change to /proc */
 936        xchdir("/proc");
 937#if ENABLE_FEATURE_USE_TERMIOS
 938        tcgetattr(0, (void *) &initial_settings);
 939        memcpy(&new_settings, &initial_settings, sizeof(new_settings));
 940        /* unbuffered input, turn off echo */
 941        new_settings.c_lflag &= ~(ISIG | ICANON | ECHO | ECHONL);
 942
 943        bb_signals(BB_FATAL_SIGS, sig_catcher);
 944        tcsetattr_stdin_TCSANOW(&new_settings);
 945#endif /* FEATURE_USE_TERMIOS */
 946
 947#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 948        sort_function[0] = pcpu_sort;
 949        sort_function[1] = mem_sort;
 950        sort_function[2] = time_sort;
 951#else
 952        sort_function[0] = mem_sort;
 953#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */
 954
 955        while (1) {
 956                procps_status_t *p = NULL;
 957
 958                lines = 24; /* default */
 959                col = 79;
 960#if ENABLE_FEATURE_USE_TERMIOS
 961                /* We output to stdout, we need size of stdout (not stdin)! */
 962                get_terminal_width_height(STDOUT_FILENO, &col, &lines);
 963                if (lines < 5 || col < 10) {
 964                        sleep(interval);
 965                        continue;
 966                }
 967#endif /* FEATURE_USE_TERMIOS */
 968                if (col > LINE_BUF_SIZE-2) /* +2 bytes for '\n', NUL, */
 969                        col = LINE_BUF_SIZE-2;
 970
 971                /* read process IDs & status for all the processes */
 972                while ((p = procps_scan(p, scan_mask)) != NULL) {
 973                        int n;
 974                        if (scan_mask == TOP_MASK) {
 975                                n = ntop;
 976                                top = xrealloc_vector(top, 6, ntop++);
 977                                top[n].pid = p->pid;
 978                                top[n].ppid = p->ppid;
 979                                top[n].vsz = p->vsz;
 980#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
 981                                top[n].ticks = p->stime + p->utime;
 982#endif
 983                                top[n].uid = p->uid;
 984                                strcpy(top[n].state, p->state);
 985                                strcpy(top[n].comm, p->comm);
 986#if ENABLE_FEATURE_TOP_SMP_PROCESS
 987                                top[n].last_seen_on_cpu = p->last_seen_on_cpu;
 988#endif
 989                        } else { /* TOPMEM */
 990#if ENABLE_FEATURE_TOPMEM
 991                                if (!(p->mapped_ro | p->mapped_rw))
 992                                        continue; /* kernel threads are ignored */
 993                                n = ntop;
 994                                /* No bug here - top and topmem are the same */
 995                                top = xrealloc_vector(topmem, 6, ntop++);
 996                                strcpy(topmem[n].comm, p->comm);
 997                                topmem[n].pid      = p->pid;
 998                                topmem[n].vsz      = p->mapped_rw + p->mapped_ro;
 999                                topmem[n].vszrw    = p->mapped_rw;
1000                                topmem[n].rss_sh   = p->shared_clean + p->shared_dirty;
1001                                topmem[n].rss      = p->private_clean + p->private_dirty + topmem[n].rss_sh;
1002                                topmem[n].dirty    = p->private_dirty + p->shared_dirty;
1003                                topmem[n].dirty_sh = p->shared_dirty;
1004                                topmem[n].stack    = p->stack;
1005#endif
1006                        }
1007                } /* end of "while we read /proc" */
1008                if (ntop == 0) {
1009                        bb_error_msg("no process info in /proc");
1010                        break;
1011                }
1012
1013                if (scan_mask == TOP_MASK) {
1014#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
1015                        if (!prev_hist_count) {
1016                                do_stats();
1017                                usleep(100000);
1018                                clearmems();
1019                                continue;
1020                        }
1021                        do_stats();
1022                        /* TODO: we don't need to sort all 10000 processes, we need to find top 24! */
1023                        qsort(top, ntop, sizeof(top_status_t), (void*)mult_lvl_cmp);
1024#else
1025                        qsort(top, ntop, sizeof(top_status_t), (void*)(sort_function[0]));
1026#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */
1027                }
1028#if ENABLE_FEATURE_TOPMEM
1029                else { /* TOPMEM */
1030                        qsort(topmem, ntop, sizeof(topmem_status_t), (void*)topmem_sort);
1031                }
1032#endif
1033                lines_rem = lines;
1034                if (OPT_BATCH_MODE) {
1035                        lines_rem = INT_MAX;
1036                }
1037                if (scan_mask == TOP_MASK)
1038                        display_process_list(lines_rem, col);
1039#if ENABLE_FEATURE_TOPMEM
1040                else
1041                        display_topmem_process_list(lines_rem, col);
1042#endif
1043                clearmems();
1044                if (iterations >= 0 && !--iterations)
1045                        break;
1046#if !ENABLE_FEATURE_USE_TERMIOS
1047                sleep(interval);
1048#else
1049                if (option_mask32 & (OPT_b|OPT_EOF))
1050                         /* batch mode, or EOF on stdin ("top </dev/null") */
1051                        sleep(interval);
1052                else if (safe_poll(pfd, 1, interval * 1000) > 0) {
1053                        if (safe_read(STDIN_FILENO, &c, 1) != 1) { /* error/EOF? */
1054                                option_mask32 |= OPT_EOF;
1055                                continue;
1056                        }
1057                        if (c == initial_settings.c_cc[VINTR])
1058                                break;
1059                        c |= 0x20; /* lowercase */
1060                        if (c == 'q')
1061                                break;
1062                        if (c == 'n') {
1063                                USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
1064                                sort_function[0] = pid_sort;
1065                        }
1066                        if (c == 'm') {
1067                                USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
1068                                sort_function[0] = mem_sort;
1069#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
1070                                sort_function[1] = pcpu_sort;
1071                                sort_function[2] = time_sort;
1072#endif
1073                        }
1074#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
1075                        if (c == 'p') {
1076                                USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
1077                                sort_function[0] = pcpu_sort;
1078                                sort_function[1] = mem_sort;
1079                                sort_function[2] = time_sort;
1080                        }
1081                        if (c == 't') {
1082                                USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
1083                                sort_function[0] = time_sort;
1084                                sort_function[1] = mem_sort;
1085                                sort_function[2] = pcpu_sort;
1086                        }
1087#if ENABLE_FEATURE_TOPMEM
1088                        if (c == 's') {
1089                                scan_mask = TOPMEM_MASK;
1090                                free(prev_hist);
1091                                prev_hist = NULL;
1092                                prev_hist_count = 0;
1093                                sort_field = (sort_field + 1) % NUM_SORT_FIELD;
1094                        }
1095                        if (c == 'r')
1096                                inverted ^= 1;
1097#endif
1098#if ENABLE_FEATURE_TOP_SMP_CPU
1099                        /* procps-2.0.18 uses 'C', 3.2.7 uses '1' */
1100                        if (c == 'c' || c == '1') {
1101                                /* User wants to toggle per cpu <> aggregate */
1102                                if (smp_cpu_info) {
1103                                        free(cpu_prev_jif);
1104                                        free(cpu_jif);
1105                                        cpu_jif = &cur_jif;
1106                                        cpu_prev_jif = &prev_jif;
1107                                } else {
1108                                        /* Prepare for xrealloc() */
1109                                        cpu_jif = cpu_prev_jif = NULL;
1110                                }
1111                                num_cpus = 0;
1112                                smp_cpu_info = !smp_cpu_info;
1113                                get_jiffy_counts();
1114                        }
1115#endif
1116#endif
1117                }
1118#endif /* FEATURE_USE_TERMIOS */
1119        } /* end of "while (1)" */
1120
1121        bb_putchar('\n');
1122#if ENABLE_FEATURE_USE_TERMIOS
1123        reset_term();
1124#endif
1125        return EXIT_SUCCESS;
1126}
1127