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