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