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