busybox/procps/nmeter.c
<<
>>
Prefs
   1/*
   2 * Licensed under GPLv2, see file LICENSE in this source tree.
   3 *
   4 * Based on nanotop.c from floppyfw project
   5 *
   6 * Contact me: vda.linux@googlemail.com
   7 */
   8//config:config NMETER
   9//config:       bool "nmeter (11 kb)"
  10//config:       default y
  11//config:       help
  12//config:       Prints selected system stats continuously, one line per update.
  13
  14//applet:IF_NMETER(APPLET(nmeter, BB_DIR_USR_BIN, BB_SUID_DROP))
  15
  16//kbuild:lib-$(CONFIG_NMETER) += nmeter.o
  17
  18//usage:#define nmeter_trivial_usage
  19//usage:       "[-d MSEC] FORMAT_STRING"
  20//usage:#define nmeter_full_usage "\n\n"
  21//usage:       "Monitor system in real time"
  22//usage:     "\n"
  23//usage:     "\n -d MSEC        Milliseconds between updates, default:1000, none:-1"
  24//usage:     "\n"
  25//usage:     "\nFormat specifiers:"
  26//usage:     "\n %Nc or %[cN]   CPU. N - bar size (default 10)"
  27//usage:     "\n                (displays: S:system U:user N:niced D:iowait I:irq i:softirq)"
  28//usage:     "\n %[nINTERFACE]  Network INTERFACE"
  29//usage:     "\n %m             Allocated memory"
  30//usage:     "\n %[mf]          Free memory"
  31//usage:     "\n %[mt]          Total memory"
  32//usage:     "\n %s             Allocated swap"
  33//usage:     "\n %f             Number of used file descriptors"
  34//usage:     "\n %Ni            Total/specific IRQ rate"
  35//usage:     "\n %x             Context switch rate"
  36//usage:     "\n %p             Forks"
  37//usage:     "\n %[pn]          # of processes"
  38//usage:     "\n %b             Block io"
  39//usage:     "\n %Nt            Time (with N decimal points)"
  40//usage:     "\n %NT            Zero-based timestamp (with N decimal points)"
  41//usage:     "\n %r             Print <cr> instead of <lf> at EOL"
  42
  43//TODO:
  44// simplify code
  45// /proc/locks
  46// /proc/stat:
  47// disk_io: (3,0):(22272,17897,410702,4375,54750)
  48// btime 1059401962
  49//TODO: use sysinfo libc call/syscall, if appropriate
  50// (faster than open/read/close):
  51// sysinfo({uptime=15017, loads=[5728, 15040, 16480]
  52//  totalram=2107416576, freeram=211525632, sharedram=0, bufferram=157204480}
  53//  totalswap=134209536, freeswap=134209536, procs=157})
  54
  55#include "libbb.h"
  56#include "common_bufsiz.h"
  57
  58typedef unsigned long long ullong;
  59
  60enum {  /* Preferably use powers of 2 */
  61        PROC_MIN_FILE_SIZE = 256,
  62        PROC_MAX_FILE_SIZE = 16 * 1024,
  63};
  64
  65typedef struct proc_file {
  66        char *file;
  67        int file_sz;
  68        smallint last_gen;
  69} proc_file;
  70
  71static const char *const proc_name[] = {
  72        "stat",         // Must match the order of proc_file's!
  73        "loadavg",
  74        "net/dev",
  75        "meminfo",
  76        "diskstats",
  77        "sys/fs/file-nr"
  78};
  79
  80struct globals {
  81        // Sample generation flip-flop
  82        smallint gen;
  83        // Linux 2.6? (otherwise assumes 2.4)
  84        smallint is26;
  85        // 1 if sample delay is not an integer fraction of a second
  86        smallint need_seconds;
  87        char final_char;
  88        char *cur_outbuf;
  89        int delta;
  90        unsigned deltanz;
  91        struct timeval tv;
  92        struct timeval start;
  93#define first_proc_file proc_stat
  94        proc_file proc_stat;    // Must match the order of proc_name's!
  95        proc_file proc_loadavg;
  96        proc_file proc_net_dev;
  97        proc_file proc_meminfo;
  98        proc_file proc_diskstats;
  99        proc_file proc_sys_fs_filenr;
 100};
 101#define G (*ptr_to_globals)
 102#define gen                (G.gen               )
 103#define is26               (G.is26              )
 104#define need_seconds       (G.need_seconds      )
 105#define cur_outbuf         (G.cur_outbuf        )
 106#define proc_stat          (G.proc_stat         )
 107#define proc_loadavg       (G.proc_loadavg      )
 108#define proc_net_dev       (G.proc_net_dev      )
 109#define proc_meminfo       (G.proc_meminfo      )
 110#define proc_diskstats     (G.proc_diskstats    )
 111#define proc_sys_fs_filenr (G.proc_sys_fs_filenr)
 112#define outbuf bb_common_bufsiz1
 113#define INIT_G() do { \
 114        setup_common_bufsiz(); \
 115        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
 116        cur_outbuf = outbuf; \
 117        G.final_char = '\n'; \
 118        G.deltanz = G.delta = 1000000; \
 119} while (0)
 120
 121static inline void reset_outbuf(void)
 122{
 123        cur_outbuf = outbuf;
 124}
 125
 126static void print_outbuf(void)
 127{
 128        int sz = cur_outbuf - outbuf;
 129        if (sz > 0) {
 130                xwrite(STDOUT_FILENO, outbuf, sz);
 131                cur_outbuf = outbuf;
 132        }
 133}
 134
 135static void put(const char *s)
 136{
 137        char *p = cur_outbuf;
 138        int sz = outbuf + COMMON_BUFSIZE - p;
 139        while (*s && --sz >= 0)
 140                *p++ = *s++;
 141        cur_outbuf = p;
 142}
 143
 144static void put_c(char c)
 145{
 146        if (cur_outbuf < outbuf + COMMON_BUFSIZE)
 147                *cur_outbuf++ = c;
 148}
 149
 150static void put_question_marks(int count)
 151{
 152        while (count--)
 153                put_c('?');
 154}
 155
 156static void readfile_z(proc_file *pf, const char* fname)
 157{
 158// open_read_close() will do two reads in order to be sure we are at EOF,
 159// and we don't need/want that.
 160        int fd;
 161        int sz, rdsz;
 162        char *buf;
 163
 164        sz = pf->file_sz;
 165        buf = pf->file;
 166        if (!buf) {
 167                buf = xmalloc(PROC_MIN_FILE_SIZE);
 168                sz = PROC_MIN_FILE_SIZE;
 169        }
 170 again:
 171        fd = xopen(fname, O_RDONLY);
 172        buf[0] = '\0';
 173        rdsz = read(fd, buf, sz-1);
 174        close(fd);
 175        if (rdsz > 0) {
 176                if (rdsz == sz-1 && sz < PROC_MAX_FILE_SIZE) {
 177                        sz *= 2;
 178                        buf = xrealloc(buf, sz);
 179                        goto again;
 180                }
 181                buf[rdsz] = '\0';
 182        }
 183        pf->file_sz = sz;
 184        pf->file = buf;
 185}
 186
 187static const char* get_file(proc_file *pf)
 188{
 189        if (pf->last_gen != gen) {
 190                pf->last_gen = gen;
 191                readfile_z(pf, proc_name[pf - &first_proc_file]);
 192        }
 193        return pf->file;
 194}
 195
 196static ullong read_after_slash(const char *p)
 197{
 198        p = strchr(p, '/');
 199        if (!p) return 0;
 200        return strtoull(p+1, NULL, 10);
 201}
 202
 203enum conv_type {
 204        conv_decimal = 0,
 205        conv_slash = 1
 206};
 207
 208// Reads decimal values from line. Values start after key, for example:
 209// "cpu  649369 0 341297 4336769..." - key is "cpu" here.
 210// Values are stored in vec[].
 211// posbits is a bit lit of positions we are interested in.
 212// for example: 00100110 - we want 1st, 2nd and 5th value.
 213// posbits.bit0 encodes conversion type.
 214static int rdval(const char* p, const char* key, ullong *vec, long posbits)
 215{
 216        unsigned curpos;
 217
 218        p = strstr(p, key);
 219        if (!p) return 1;
 220
 221        p += strlen(key);
 222        curpos = 1 << 1;
 223        while (1) {
 224                while (*p == ' ' || *p == '\t') p++;
 225                if (*p == '\n' || *p == '\0') break;
 226
 227                if (curpos & posbits) { // read this value
 228                        *vec++ = (posbits & 1) == conv_decimal ?
 229                                strtoull(p, NULL, 10) :
 230                                read_after_slash(p);
 231                        posbits -= curpos;
 232                        if (posbits <= 1)
 233                                return 0;
 234                }
 235                while (*p > ' ') // skip over the value
 236                        p++;
 237                curpos <<= 1;
 238        }
 239        return 0;
 240}
 241
 242// Parses files with lines like "... ... ... 3/148 ...."
 243static int rdval_loadavg(const char* p, ullong *vec, long posbits)
 244{
 245        int result;
 246        result = rdval(p, "", vec, posbits | conv_slash);
 247        return result;
 248}
 249
 250// Parses /proc/diskstats
 251//   1  2 3   4     5     6(rd)  7      8     9     10(wr) 11     12 13     14
 252//   3  0 hda 51292 14441 841783 926052 25717 79650 843256 3029804 0 148459 3956933
 253//   3  1 hda1 0 0 0 0 <- ignore if only 4 fields
 254// Linux 3.0 (maybe earlier) started printing full stats for hda1 too.
 255// Had to add code which skips such devices.
 256static int rdval_diskstats(const char* p, ullong *vec)
 257{
 258        char devname[32];
 259        unsigned devname_len = 0;
 260        int value_idx = 0;
 261
 262        vec[0] = 0;
 263        vec[1] = 0;
 264        while (1) {
 265                value_idx++;
 266                while (*p == ' ' || *p == '\t')
 267                        p++;
 268                if (*p == '\0')
 269                        break;
 270                if (*p == '\n') {
 271                        value_idx = 0;
 272                        p++;
 273                        continue;
 274                }
 275                if (value_idx == 3) {
 276                        char *end = strchrnul(p, ' ');
 277                        /* If this a hda1-like device (same prefix as last one + digit)? */
 278                        if (devname_len && strncmp(devname, p, devname_len) == 0 && isdigit(p[devname_len])) {
 279                                p = end;
 280                                goto skip_line; /* skip entire line */
 281                        }
 282                        /* It is not. Remember the name for future checks */
 283                        devname_len = end - p;
 284                        if (devname_len > sizeof(devname)-1)
 285                                devname_len = sizeof(devname)-1;
 286                        strncpy(devname, p, devname_len);
 287                        /* devname[devname_len] = '\0'; - not really needed */
 288                        p = end;
 289                } else
 290                if (value_idx == 6) {
 291                        // TODO: *sectorsize (don't know how to find out sectorsize)
 292                        vec[0] += strtoull(p, NULL, 10);
 293                } else
 294                if (value_idx == 10) {
 295                        // TODO: *sectorsize (don't know how to find out sectorsize)
 296                        vec[1] += strtoull(p, NULL, 10);
 297 skip_line:
 298                        while (*p != '\n' && *p != '\0')
 299                                p++;
 300                        continue;
 301                }
 302                while ((unsigned char)(*p) > ' ') // skip over value
 303                        p++;
 304        }
 305        return 0;
 306}
 307
 308static void scale(ullong ul)
 309{
 310        char buf[5];
 311
 312        /* see http://en.wikipedia.org/wiki/Tera */
 313        smart_ulltoa4(ul, buf, " kmgtpezy")[0] = '\0';
 314        put(buf);
 315}
 316
 317#define S_STAT(a) \
 318typedef struct a { \
 319        struct s_stat *next; \
 320        void (*collect)(struct a *s) FAST_FUNC; \
 321        const char *label;
 322#define S_STAT_END(a) } a;
 323
 324S_STAT(s_stat)
 325S_STAT_END(s_stat)
 326
 327static void FAST_FUNC collect_literal(s_stat *s UNUSED_PARAM)
 328{
 329}
 330
 331static s_stat* init_literal(void)
 332{
 333        s_stat *s = xzalloc(sizeof(*s));
 334        s->collect = collect_literal;
 335        return (s_stat*)s;
 336}
 337
 338static s_stat* init_cr(const char *param UNUSED_PARAM)
 339{
 340        G.final_char = '\r';
 341        return NULL;
 342}
 343
 344//     user nice system idle  iowait irq  softirq (last 3 only in 2.6)
 345//cpu  649369 0 341297 4336769 11640 7122 1183
 346//cpuN 649369 0 341297 4336769 11640 7122 1183
 347enum { CPU_FIELDCNT = 7 };
 348S_STAT(cpu_stat)
 349        ullong old[CPU_FIELDCNT];
 350        unsigned bar_sz;
 351        char bar[1];
 352S_STAT_END(cpu_stat)
 353
 354static void FAST_FUNC collect_cpu(cpu_stat *s)
 355{
 356        ullong data[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
 357        unsigned frac[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
 358        ullong all = 0;
 359        unsigned norm_all = 0;
 360        unsigned bar_sz = s->bar_sz;
 361        char *bar = s->bar;
 362        int i;
 363
 364        if (rdval(get_file(&proc_stat), "cpu ", data, 0
 365            | (1 << 1)
 366            | (1 << 2)
 367            | (1 << 3)
 368            | (1 << 4)
 369            | (1 << 5)
 370            | (1 << 6)
 371            | (1 << 7))
 372        ) {
 373                put_question_marks(bar_sz);
 374                return;
 375        }
 376
 377        for (i = 0; i < CPU_FIELDCNT; i++) {
 378                ullong old = s->old[i];
 379                if (data[i] < old) old = data[i];               //sanitize
 380                s->old[i] = data[i];
 381                all += (data[i] -= old);
 382        }
 383
 384        if (all) {
 385                for (i = 0; i < CPU_FIELDCNT; i++) {
 386                        ullong t = bar_sz * data[i];
 387                        norm_all += data[i] = t / all;
 388                        frac[i] = t % all;
 389                }
 390
 391                while (norm_all < bar_sz) {
 392                        unsigned max = frac[0];
 393                        int pos = 0;
 394                        for (i = 1; i < CPU_FIELDCNT; i++) {
 395                                if (frac[i] > max) max = frac[i], pos = i;
 396                        }
 397                        frac[pos] = 0;  //avoid bumping up same value twice
 398                        data[pos]++;
 399                        norm_all++;
 400                }
 401
 402                memset(bar, '.', bar_sz);
 403                memset(bar, 'S', data[2]); bar += data[2]; //sys
 404                memset(bar, 'U', data[0]); bar += data[0]; //usr
 405                memset(bar, 'N', data[1]); bar += data[1]; //nice
 406                memset(bar, 'D', data[4]); bar += data[4]; //iowait
 407                memset(bar, 'I', data[5]); bar += data[5]; //irq
 408                memset(bar, 'i', data[6]); bar += data[6]; //softirq
 409        } else {
 410                memset(bar, '?', bar_sz);
 411        }
 412        put(s->bar);
 413}
 414
 415static s_stat* init_cpu(const char *param)
 416{
 417        int sz;
 418        cpu_stat *s;
 419        sz = param[0] ? strtoul(param, NULL, 0) : 10;
 420        if (sz <= 0) sz = 1;
 421        if (sz > 1000) sz = 1000;
 422        s = xzalloc(sizeof(*s) + sz);
 423        /*s->bar[sz] = '\0'; - xzalloc did it */
 424        s->bar_sz = sz;
 425        s->collect = collect_cpu;
 426        return (s_stat*)s;
 427}
 428
 429S_STAT(int_stat)
 430        ullong old;
 431        int no;
 432S_STAT_END(int_stat)
 433
 434static void FAST_FUNC collect_int(int_stat *s)
 435{
 436        ullong data[1];
 437        ullong old;
 438
 439        if (rdval(get_file(&proc_stat), "intr", data, 1 << s->no)) {
 440                put_question_marks(4);
 441                return;
 442        }
 443
 444        old = s->old;
 445        if (data[0] < old) old = data[0];               //sanitize
 446        s->old = data[0];
 447        scale(data[0] - old);
 448}
 449
 450static s_stat* init_int(const char *param)
 451{
 452        int_stat *s = xzalloc(sizeof(*s));
 453        s->collect = collect_int;
 454        if (param[0] == '\0') {
 455                s->no = 1;
 456        } else {
 457                int n = xatoi_positive(param);
 458                s->no = n + 2;
 459        }
 460        return (s_stat*)s;
 461}
 462
 463S_STAT(ctx_stat)
 464        ullong old;
 465S_STAT_END(ctx_stat)
 466
 467static void FAST_FUNC collect_ctx(ctx_stat *s)
 468{
 469        ullong data[1];
 470        ullong old;
 471
 472        if (rdval(get_file(&proc_stat), "ctxt", data, 1 << 1)) {
 473                put_question_marks(4);
 474                return;
 475        }
 476
 477        old = s->old;
 478        if (data[0] < old) old = data[0];               //sanitize
 479        s->old = data[0];
 480        scale(data[0] - old);
 481}
 482
 483static s_stat* init_ctx(const char *param UNUSED_PARAM)
 484{
 485        ctx_stat *s = xzalloc(sizeof(*s));
 486        s->collect = collect_ctx;
 487        return (s_stat*)s;
 488}
 489
 490S_STAT(blk_stat)
 491        const char* lookfor;
 492        ullong old[2];
 493S_STAT_END(blk_stat)
 494
 495static void FAST_FUNC collect_blk(blk_stat *s)
 496{
 497        ullong data[2];
 498        int i;
 499
 500        if (is26) {
 501                i = rdval_diskstats(get_file(&proc_diskstats), data);
 502        } else {
 503                i = rdval(get_file(&proc_stat), s->lookfor, data, 0
 504                                | (1 << 1)
 505                                | (1 << 2)
 506                );
 507                // Linux 2.4 reports bio in Kbytes, convert to sectors:
 508                data[0] *= 2;
 509                data[1] *= 2;
 510        }
 511        if (i) {
 512                put_question_marks(9);
 513                return;
 514        }
 515
 516        for (i=0; i<2; i++) {
 517                ullong old = s->old[i];
 518                if (data[i] < old) old = data[i];               //sanitize
 519                s->old[i] = data[i];
 520                data[i] -= old;
 521        }
 522        scale(data[0]*512); // TODO: *sectorsize
 523        put_c(' ');
 524        scale(data[1]*512);
 525}
 526
 527static s_stat* init_blk(const char *param UNUSED_PARAM)
 528{
 529        blk_stat *s = xzalloc(sizeof(*s));
 530        s->collect = collect_blk;
 531        s->lookfor = "page";
 532        return (s_stat*)s;
 533}
 534
 535S_STAT(fork_stat)
 536        ullong old;
 537S_STAT_END(fork_stat)
 538
 539static void FAST_FUNC collect_thread_nr(fork_stat *s UNUSED_PARAM)
 540{
 541        ullong data[1];
 542
 543        if (rdval_loadavg(get_file(&proc_loadavg), data, 1 << 4)) {
 544                put_question_marks(4);
 545                return;
 546        }
 547        scale(data[0]);
 548}
 549
 550static void FAST_FUNC collect_fork(fork_stat *s)
 551{
 552        ullong data[1];
 553        ullong old;
 554
 555        if (rdval(get_file(&proc_stat), "processes", data, 1 << 1)) {
 556                put_question_marks(4);
 557                return;
 558        }
 559
 560        old = s->old;
 561        if (data[0] < old) old = data[0];       //sanitize
 562        s->old = data[0];
 563        scale(data[0] - old);
 564}
 565
 566static s_stat* init_fork(const char *param)
 567{
 568        fork_stat *s = xzalloc(sizeof(*s));
 569        if (*param == 'n') {
 570                s->collect = collect_thread_nr;
 571        } else {
 572                s->collect = collect_fork;
 573        }
 574        return (s_stat*)s;
 575}
 576
 577S_STAT(if_stat)
 578        ullong old[4];
 579        const char *device;
 580        char *device_colon;
 581S_STAT_END(if_stat)
 582
 583static void FAST_FUNC collect_if(if_stat *s)
 584{
 585        ullong data[4];
 586        int i;
 587
 588        if (rdval(get_file(&proc_net_dev), s->device_colon, data, 0
 589            | (1 << 1)
 590            | (1 << 3)
 591            | (1 << 9)
 592            | (1 << 11))
 593        ) {
 594                put_question_marks(10);
 595                return;
 596        }
 597
 598        for (i=0; i<4; i++) {
 599                ullong old = s->old[i];
 600                if (data[i] < old) old = data[i];               //sanitize
 601                s->old[i] = data[i];
 602                data[i] -= old;
 603        }
 604        put_c(data[1] ? '*' : ' ');
 605        scale(data[0]);
 606        put_c(data[3] ? '*' : ' ');
 607        scale(data[2]);
 608}
 609
 610static s_stat* init_if(const char *device)
 611{
 612        if_stat *s = xzalloc(sizeof(*s));
 613
 614        if (!device || !device[0])
 615                bb_show_usage();
 616        s->collect = collect_if;
 617
 618        s->device = device;
 619        s->device_colon = xasprintf("%s:", device);
 620        return (s_stat*)s;
 621}
 622
 623S_STAT(mem_stat)
 624        char opt;
 625S_STAT_END(mem_stat)
 626
 627// "Memory" value should not include any caches.
 628// IOW: neither "ls -laR /" nor heavy read/write activity
 629//      should affect it. We'd like to also include any
 630//      long-term allocated kernel-side mem, but it is hard
 631//      to figure out. For now, bufs, cached & slab are
 632//      counted as "free" memory
 633//2.6.16:
 634//MemTotal:       773280 kB
 635//MemFree:         25912 kB - genuinely free
 636//Buffers:        320672 kB - cache
 637//Cached:         146396 kB - cache
 638//SwapCached:          0 kB
 639//Active:         183064 kB
 640//Inactive:       356892 kB
 641//HighTotal:           0 kB
 642//HighFree:            0 kB
 643//LowTotal:       773280 kB
 644//LowFree:         25912 kB
 645//SwapTotal:      131064 kB
 646//SwapFree:       131064 kB
 647//Dirty:              48 kB
 648//Writeback:           0 kB
 649//Mapped:          96620 kB
 650//Slab:           200668 kB - takes 7 Mb on my box fresh after boot,
 651//                            but includes dentries and inodes
 652//                            (== can take arbitrary amount of mem)
 653//CommitLimit:    517704 kB
 654//Committed_AS:   236776 kB
 655//PageTables:       1248 kB
 656//VmallocTotal:   516052 kB
 657//VmallocUsed:      3852 kB
 658//VmallocChunk:   512096 kB
 659//HugePages_Total:     0
 660//HugePages_Free:      0
 661//Hugepagesize:     4096 kB
 662static void FAST_FUNC collect_mem(mem_stat *s)
 663{
 664        ullong m_total = 0;
 665        ullong m_free = 0;
 666        ullong m_bufs = 0;
 667        ullong m_cached = 0;
 668        ullong m_slab = 0;
 669
 670        if (rdval(get_file(&proc_meminfo), "MemTotal:", &m_total, 1 << 1)) {
 671                put_question_marks(4);
 672                return;
 673        }
 674        if (s->opt == 't') {
 675                scale(m_total << 10);
 676                return;
 677        }
 678
 679        if (rdval(proc_meminfo.file, "MemFree:", &m_free  , 1 << 1)
 680         || rdval(proc_meminfo.file, "Buffers:", &m_bufs  , 1 << 1)
 681         || rdval(proc_meminfo.file, "Cached:",  &m_cached, 1 << 1)
 682         || rdval(proc_meminfo.file, "Slab:",    &m_slab  , 1 << 1)
 683        ) {
 684                put_question_marks(4);
 685                return;
 686        }
 687
 688        m_free += m_bufs + m_cached + m_slab;
 689        switch (s->opt) {
 690        case 'f':
 691                scale(m_free << 10); break;
 692        default:
 693                scale((m_total - m_free) << 10); break;
 694        }
 695}
 696
 697static s_stat* init_mem(const char *param)
 698{
 699        mem_stat *s = xzalloc(sizeof(*s));
 700        s->collect = collect_mem;
 701        s->opt = param[0];
 702        return (s_stat*)s;
 703}
 704
 705S_STAT(swp_stat)
 706S_STAT_END(swp_stat)
 707
 708static void FAST_FUNC collect_swp(swp_stat *s UNUSED_PARAM)
 709{
 710        ullong s_total[1];
 711        ullong s_free[1];
 712        if (rdval(get_file(&proc_meminfo), "SwapTotal:", s_total, 1 << 1)
 713         || rdval(proc_meminfo.file,       "SwapFree:" , s_free,  1 << 1)
 714        ) {
 715                put_question_marks(4);
 716                return;
 717        }
 718        scale((s_total[0]-s_free[0]) << 10);
 719}
 720
 721static s_stat* init_swp(const char *param UNUSED_PARAM)
 722{
 723        swp_stat *s = xzalloc(sizeof(*s));
 724        s->collect = collect_swp;
 725        return (s_stat*)s;
 726}
 727
 728S_STAT(fd_stat)
 729S_STAT_END(fd_stat)
 730
 731static void FAST_FUNC collect_fd(fd_stat *s UNUSED_PARAM)
 732{
 733        ullong data[2];
 734
 735        if (rdval(get_file(&proc_sys_fs_filenr), "", data, 0
 736            | (1 << 1)
 737            | (1 << 2))
 738        ) {
 739                put_question_marks(4);
 740                return;
 741        }
 742
 743        scale(data[0] - data[1]);
 744}
 745
 746static s_stat* init_fd(const char *param UNUSED_PARAM)
 747{
 748        fd_stat *s = xzalloc(sizeof(*s));
 749        s->collect = collect_fd;
 750        return (s_stat*)s;
 751}
 752
 753S_STAT(time_stat)
 754        unsigned prec;
 755        unsigned scale;
 756S_STAT_END(time_stat)
 757
 758static void FAST_FUNC collect_tv(time_stat *s, struct timeval *tv, int local)
 759{
 760        char buf[sizeof("12:34:56.123456")];
 761        struct tm* tm;
 762        unsigned us = tv->tv_usec + s->scale/2;
 763        time_t t = tv->tv_sec;
 764
 765        if (us >= 1000000) {
 766                t++;
 767                us -= 1000000;
 768        }
 769        if (local)
 770                tm = localtime(&t);
 771        else
 772                tm = gmtime(&t);
 773
 774        sprintf(buf, "%02u:%02u:%02u", tm->tm_hour, tm->tm_min, tm->tm_sec);
 775        if (s->prec)
 776                sprintf(buf+8, ".%0*u", s->prec, us / s->scale);
 777        put(buf);
 778}
 779
 780static void FAST_FUNC collect_time(time_stat *s)
 781{
 782        collect_tv(s, &G.tv, /*local:*/ 1);
 783}
 784
 785static void FAST_FUNC collect_monotonic(time_stat *s)
 786{
 787        struct timeval tv_mono;
 788
 789        tv_mono.tv_sec = G.tv.tv_sec - G.start.tv_sec;
 790#if 0 /* Do we want this? */
 791        if (tv_mono.tv_sec < 0) {
 792                /* Time went backwards, reset start time to "now" */
 793                tv_mono.tv_sec = 0;
 794                G.start = G.tv;
 795        }
 796#endif
 797        tv_mono.tv_usec = G.tv.tv_usec - G.start.tv_usec;
 798        if ((int32_t)tv_mono.tv_usec < 0) {
 799                tv_mono.tv_usec += 1000000;
 800                tv_mono.tv_sec--;
 801        }
 802        collect_tv(s, &tv_mono, /*local:*/ 0);
 803}
 804
 805static s_stat* init_time(const char *param)
 806{
 807        int prec;
 808        time_stat *s = xzalloc(sizeof(*s));
 809
 810        s->collect = collect_time;
 811        prec = param[0] - '0';
 812        if (prec < 0) prec = 0;
 813        else if (prec > 6) prec = 6;
 814        s->prec = prec;
 815        s->scale = 1;
 816        while (prec++ < 6)
 817                s->scale *= 10;
 818        return (s_stat*)s;
 819}
 820
 821static s_stat* init_monotonic(const char *param)
 822{
 823        time_stat *s = (void*)init_time(param);
 824        s->collect = collect_monotonic;
 825        return (s_stat*)s;
 826}
 827
 828static void FAST_FUNC collect_info(s_stat *s)
 829{
 830        gen ^= 1;
 831        while (s) {
 832                put(s->label);
 833                s->collect(s);
 834                s = s->next;
 835        }
 836}
 837
 838typedef s_stat* init_func(const char *param);
 839
 840static const char options[] ALIGN1 = "ncmsfixptTbr";
 841static init_func *const init_functions[] ALIGN_PTR = {
 842        init_if,
 843        init_cpu,
 844        init_mem,
 845        init_swp,
 846        init_fd,
 847        init_int,
 848        init_ctx,
 849        init_fork,
 850        init_time,
 851        init_monotonic,
 852        init_blk,
 853        init_cr
 854};
 855
 856int nmeter_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 857int nmeter_main(int argc UNUSED_PARAM, char **argv)
 858{
 859        char buf[32];
 860        s_stat *first = NULL;
 861        s_stat *last = NULL;
 862        s_stat *s;
 863        char *opt_d;
 864        char *cur, *prev;
 865
 866        INIT_G();
 867
 868        xchdir("/proc");
 869
 870        if (open_read_close("version", buf, sizeof(buf)-1) > 0) {
 871                buf[sizeof(buf)-1] = '\0';
 872                is26 = (strstr(buf, " 2.4.") == NULL);
 873        }
 874
 875        if (getopt32(argv, "d:", &opt_d)) {
 876                G.delta = xatoi(opt_d) * 1000;
 877                G.deltanz = G.delta > 0 ? G.delta : 1;
 878                need_seconds = (1000000 % G.deltanz) != 0;
 879        }
 880        argv += optind;
 881
 882        if (!argv[0])
 883                bb_show_usage();
 884
 885        // Can use argv[0] directly, but this will mess up
 886        // parameters as seen by e.g. ps. Making a copy...
 887        cur = xstrdup(argv[0]);
 888        while (1) {
 889                char *param, *p;
 890                prev = cur;
 891 again:
 892                cur = strchr(cur, '%');
 893                if (!cur)
 894                        break;
 895                if (cur[1] == '%') {    // %%
 896                        overlapping_strcpy(cur, cur + 1);
 897                        cur++;
 898                        goto again;
 899                }
 900                *cur++ = '\0';          // overwrite %
 901                if (cur[0] == '[') {
 902                        // format: %[foptstring]
 903                        cur++;
 904                        p = strchr(options, cur[0]);
 905                        param = cur+1;
 906                        while (cur[0] != ']') {
 907                                if (!cur[0])
 908                                        bb_show_usage();
 909                                cur++;
 910                        }
 911                        *cur++ = '\0';  // overwrite [
 912                } else {
 913                        // format: %NNNNNNf
 914                        param = cur;
 915                        while (cur[0] >= '0' && cur[0] <= '9')
 916                                cur++;
 917                        if (!cur[0])
 918                                bb_show_usage();
 919                        p = strchr(options, cur[0]);
 920                        *cur++ = '\0';  // overwrite format char
 921                }
 922                if (!p)
 923                        bb_show_usage();
 924                s = init_functions[p-options](param);
 925                if (s) {
 926                        s->label = prev;
 927                        /*s->next = NULL; - all initXXX funcs use xzalloc */
 928                        if (!first)
 929                                first = s;
 930                        else
 931                                last->next = s;
 932                        last = s;
 933                } else {
 934                        // %r option. remove it from string
 935                        overlapping_strcpy(prev + strlen(prev), cur);
 936                        cur = prev;
 937                }
 938        }
 939        if (prev[0]) {
 940                s = init_literal();
 941                s->label = prev;
 942                /*s->next = NULL; - all initXXX funcs use xzalloc */
 943                if (!first)
 944                        first = s;
 945                else
 946                        last->next = s;
 947                last = s;
 948        }
 949
 950        // Generate first samples but do not print them, they're bogus
 951        collect_info(first);
 952        reset_outbuf();
 953
 954        if (G.delta >= 0) {
 955                xgettimeofday(&G.tv);
 956                usleep(G.delta > 1000000 ? 1000000 : G.delta - G.tv.tv_usec % G.deltanz);
 957        }
 958
 959        xgettimeofday(&G.start);
 960        G.tv = G.start;
 961        while (1) {
 962                collect_info(first);
 963                put_c(G.final_char);
 964                print_outbuf();
 965
 966                // Negative delta -> no usleep at all
 967                // This will hog the CPU but you can have REALLY GOOD
 968                // time resolution ;)
 969                // TODO: detect and avoid useless updates
 970                // (like: nothing happens except time)
 971                if (G.delta >= 0) {
 972                        int rem;
 973                        // can be commented out, will sacrifice sleep time precision a bit
 974                        xgettimeofday(&G.tv);
 975                        if (need_seconds)
 976                                rem = G.delta - ((ullong)G.tv.tv_sec*1000000 + G.tv.tv_usec) % G.deltanz;
 977                        else
 978                                rem = G.delta - (unsigned)G.tv.tv_usec % G.deltanz;
 979                        // Sometimes kernel wakes us up just a tiny bit earlier than asked
 980                        // Do not go to very short sleep in this case
 981                        if (rem < (unsigned)G.delta / 128) {
 982                                rem += G.delta;
 983                        }
 984                        usleep(rem);
 985                }
 986                xgettimeofday(&G.tv);
 987        }
 988
 989        /*return 0;*/
 990}
 991