aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Denis Vlasenko <vda.linux@googlemail.com>2006-10-11 20:59:02 +0000
committerGravatar Denis Vlasenko <vda.linux@googlemail.com>2006-10-11 20:59:02 +0000
commit1db39b2f51c2877ad4b5c0094f2e1f8d6449ca91 (patch)
treec3e9787a51628fbb2314ade4785d7a6d887686e1
parent1114de7219f8e42d6d883c476a9458f637806365 (diff)
downloadbusybox-1db39b2f51c2877ad4b5c0094f2e1f8d6449ca91.tar.gz
busybox-1db39b2f51c2877ad4b5c0094f2e1f8d6449ca91.tar.bz2
nmeter: new applet. ~4k
Fix build proble on x86_64
-rw-r--r--Makefile.flags3
-rw-r--r--arch/i386/Makefile2
-rw-r--r--include/applets.h1
-rw-r--r--include/usage.h24
-rw-r--r--libbb/xfuncs.c18
-rw-r--r--miscutils/Config.in6
-rw-r--r--miscutils/Kbuild1
-rw-r--r--miscutils/nmeter.c891
8 files changed, 933 insertions, 13 deletions
diff --git a/Makefile.flags b/Makefile.flags
index a6ad10bf6..200cf70c7 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -13,7 +13,6 @@ CPPFLAGS += \
-D"BB_VER=KBUILD_STR($(BB_VER))" -DBB_BT=AUTOCONF_TIMESTAMP \
-Wall -Wstrict-prototypes -Wshadow -Werror \
-funsigned-char -fno-builtin-strlen -finline-limit=0 -static-libgcc \
- -Os -mpreferred-stack-boundary=2 \
- -falign-functions=1 -falign-jumps=1 -falign-loops=1 \
+ -Os -falign-functions=1 -falign-jumps=1 -falign-loops=1 \
-fomit-frame-pointer -ffunction-sections -fdata-sections \
-funsigned-char -fno-builtin-strlen \
diff --git a/arch/i386/Makefile b/arch/i386/Makefile
index b9af293a0..0a235921c 100644
--- a/arch/i386/Makefile
+++ b/arch/i386/Makefile
@@ -2,4 +2,4 @@
# Build system
# ==========================================================================
-CPPFLAGS += -march=i386
+CPPFLAGS += -march=i386 -mpreferred-stack-boundary=2
diff --git a/include/applets.h b/include/applets.h
index 328848478..3b759de20 100644
--- a/include/applets.h
+++ b/include/applets.h
@@ -207,6 +207,7 @@ USE_NAMEIF(APPLET(nameif, _BB_DIR_SBIN, _BB_SUID_NEVER))
USE_NC(APPLET(nc, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
USE_NETSTAT(APPLET(netstat, _BB_DIR_BIN, _BB_SUID_NEVER))
USE_NICE(APPLET(nice, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_NMETER(APPLET(nmeter, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
USE_NOHUP(APPLET(nohup, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
USE_NSLOOKUP(APPLET(nslookup, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
USE_OD(APPLET(od, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
diff --git a/include/usage.h b/include/usage.h
index ef348a2a9..b119c55c1 100644
--- a/include/usage.h
+++ b/include/usage.h
@@ -2172,6 +2172,30 @@ USE_FEATURE_MDEV_CONFIG( \
"Options:\n" \
"\t-n ADJUST\tAdjust the scheduling priority by ADJUST"
+#define nmeter_trivial_usage \
+ "format_string"
+#define nmeter_full_usage \
+ "Nmeter monitors your system in real time.\n\n" \
+ "Format specifiers:\n" \
+ "%Nc or %[cN] monitor CPU. N - bar size, default 10\n" \
+ " (displays: S:system U:user N:niced D:iowait I:irq i:softirq)\n" \
+ "%[niface] monitor network interface 'iface'\n" \
+ "%m monitor allocated memory\n" \
+ "%[mf] monitor free memory\n" \
+ "%[mt] monitor total memory\n" \
+ "%s monitor allocated swap\n" \
+ "%f monitor number of used file descriptors\n" \
+ "%Ni monitor total/specific IRQ rate\n" \
+ "%x monitor context switch rate\n" \
+ "%p monitor forks\n" \
+ "%[pn] monitor # of processes\n" \
+ "%b monitor block io\n" \
+ "%Nt show time (with N decimal points)\n" \
+ "%Nd milliseconds between updates (default=1000)\n" \
+ "%r print <cr> instead of <lf> at EOL"
+#define nmeter_example_usage \
+ "nmeter '%250d%t %20c int %i bio %b mem %m forks%p'"
+
#define nohup_trivial_usage \
"COMMAND [ARGS]"
#define nohup_full_usage \
diff --git a/libbb/xfuncs.c b/libbb/xfuncs.c
index de765a32d..84281105c 100644
--- a/libbb/xfuncs.c
+++ b/libbb/xfuncs.c
@@ -114,10 +114,9 @@ int xopen3(const char *pathname, int flags, int mode)
void xread(int fd, void *buf, size_t count)
{
while (count) {
- ssize_t size;
-
- if ((size = safe_read(fd, buf, count)) < 1)
- bb_error_msg_and_die("Short read");
+ ssize_t size = safe_read(fd, buf, count);
+ if (size < 1)
+ bb_error_msg_and_die("short read");
count -= size;
buf = ((char *) buf) + size;
}
@@ -127,10 +126,9 @@ void xread(int fd, void *buf, size_t count)
void xwrite(int fd, void *buf, size_t count)
{
while (count) {
- ssize_t size;
-
- if ((size = safe_write(fd, buf, count)) < 1)
- bb_error_msg_and_die("Short write");
+ ssize_t size = safe_write(fd, buf, count);
+ if (size < 1)
+ bb_error_msg_and_die("short write");
count -= size;
buf = ((char *) buf) + size;
}
@@ -149,7 +147,7 @@ unsigned char xread_char(int fd)
xread(fd, &tmp, 1);
- return(tmp);
+ return tmp;
}
// Die with supplied error message if this FILE * has ferror set.
@@ -220,7 +218,7 @@ int wait4pid(int pid)
void xsetenv(const char *key, const char *value)
{
- if(setenv(key, value, 1))
+ if (setenv(key, value, 1))
bb_error_msg_and_die(bb_msg_memory_exhausted);
}
diff --git a/miscutils/Config.in b/miscutils/Config.in
index 311c7135c..5d0ec8a42 100644
--- a/miscutils/Config.in
+++ b/miscutils/Config.in
@@ -278,6 +278,12 @@ config MT
to advance or rewind a tape past a specified number of archive
files on the tape.
+config NMETER
+ bool "nmeter"
+ default n
+ help
+ nmeter prints various system parameters continuously.
+
config READAHEAD
bool "readahead"
default n
diff --git a/miscutils/Kbuild b/miscutils/Kbuild
index 1c9e9fded..3173e1dab 100644
--- a/miscutils/Kbuild
+++ b/miscutils/Kbuild
@@ -18,6 +18,7 @@ lib-$(CONFIG_LESS) += less.o
lib-$(CONFIG_MAKEDEVS) += makedevs.o
lib-$(CONFIG_MOUNTPOINT) += mountpoint.o
lib-$(CONFIG_MT) += mt.o
+lib-$(CONFIG_NMETER) += nmeter.o
lib-$(CONFIG_READAHEAD) += readahead.o
lib-$(CONFIG_RUNLEVEL) += runlevel.o
lib-$(CONFIG_RX) += rx.o
diff --git a/miscutils/nmeter.c b/miscutils/nmeter.c
new file mode 100644
index 000000000..e83de38ec
--- /dev/null
+++ b/miscutils/nmeter.c
@@ -0,0 +1,891 @@
+/*
+** Licensed under the GPL v2, see the file LICENSE in this tarball
+**
+** Based on nanotop.c from floppyfw project
+**
+** Contact me: vda.linux@googlemail.com */
+
+//TODO:
+// simplify code
+// /proc/locks
+// /proc/stat:
+// disk_io: (3,0):(22272,17897,410702,4375,54750)
+// btime 1059401962
+
+#include "busybox.h"
+#include <time.h>
+
+typedef unsigned long long ullong;
+typedef unsigned long ulong;
+
+enum { proc_file_size = 4096 };
+
+typedef struct proc_file {
+ char *name;
+ int gen;
+ char *file;
+} proc_file;
+
+static proc_file proc_stat = { "/proc/stat", -1 };
+static proc_file proc_loadavg = { "/proc/loadavg", -1 };
+static proc_file proc_net_dev = { "/proc/net/dev", -1 };
+static proc_file proc_meminfo = { "/proc/meminfo", -1 };
+static proc_file proc_diskstats = { "/proc/diskstats", -1 };
+// Sample #
+static int gen = -1;
+// Linux 2.6? (otherwise assumes 2.4)
+static int is26 = 0;
+static struct timeval tv;
+static int delta = 1000000;
+static int deltanz = 1000000;
+static int need_seconds = 0;
+static char *final_str = "\n";
+
+// We depend on this being a char[], not char* - we take sizeof() of it
+#define outbuf bb_common_bufsiz1
+static char *cur_outbuf = outbuf;
+
+
+static inline void reset_outbuf(void)
+{
+ cur_outbuf = outbuf;
+}
+
+static inline int outbuf_count(void)
+{
+ return cur_outbuf - outbuf;
+}
+
+static void print_outbuf(void)
+{
+ int sz = cur_outbuf - outbuf;
+ if (sz > 0) {
+ write(1, outbuf, sz);
+ cur_outbuf = outbuf;
+ }
+}
+
+static void put(const char *s)
+{
+ int sz = strlen(s);
+ if (sz > outbuf + sizeof(outbuf) - cur_outbuf)
+ sz = outbuf + sizeof(outbuf) - cur_outbuf;
+ memcpy(cur_outbuf, s, sz);
+ cur_outbuf += sz;
+}
+
+static void put_c(char c)
+{
+ if (cur_outbuf < outbuf + sizeof(outbuf))
+ *cur_outbuf++ = c;
+}
+
+static void put_question_marks(int count)
+{
+ while (count--)
+ put_c('?');
+}
+
+static int readfile_z(char *buf, int sz, const char* fname)
+{
+ int fd;
+ fd = xopen(fname, O_RDONLY);
+ // We are not checking for short reads (valid only because
+ // we are reading /proc files)
+ sz = read(fd, buf, sz-1);
+ close(fd);
+ if (sz < 0) {
+ buf[0] = '\0';
+ return 1;
+ }
+ buf[sz] = '\0';
+ return 0;
+}
+
+static const char* get_file(proc_file *pf)
+{
+ if (pf->gen != gen) {
+ pf->gen = gen;
+ // We allocate proc_file_size bytes. This wastes memory,
+ // but allows us to allocate only once (at first sample)
+ // per proc file, and reuse buffer for each sample
+ if (!pf->file)
+ pf->file = (char*)xmalloc(proc_file_size);
+ readfile_z(pf->file, proc_file_size, pf->name);
+ }
+ return pf->file;
+}
+
+static inline ullong read_after_slash(const char *p)
+{
+ p = strchr(p, '/');
+ if (!p) return 0;
+ return strtoull(p+1, NULL, 10);
+}
+
+enum conv_type { conv_decimal, conv_slash };
+
+// Reads decimal values from line. Values start after key, for example:
+// "cpu 649369 0 341297 4336769..." - key is "cpu" here.
+// Values are stored in vec[]. arg_ptr has list of positions
+// we are interested in: for example: 1,2,5 - we want 1st, 2nd and 5th value.
+static int vrdval(const char* p, const char* key,
+ enum conv_type conv, ullong *vec, va_list arg_ptr)
+{
+ int indexline;
+ int indexnext;
+
+ p = strstr(p, key);
+ if (!p) return 1;
+
+ p += strlen(key);
+ indexline = 1;
+ indexnext = va_arg(arg_ptr, int);
+ while (1) {
+ while (*p == ' ' || *p == '\t') p++;
+ if (*p == '\n' || *p == '\0') break;
+
+ if (indexline == indexnext) { // read this value
+ *vec++ = conv==conv_decimal ?
+ strtoull(p, NULL, 10) :
+ read_after_slash(p);
+ indexnext = va_arg(arg_ptr, int);
+ }
+ while (*p > ' ') p++; // skip over value
+ indexline++;
+ }
+ return 0;
+}
+
+// Parses files with lines like "cpu0 21727 0 15718 1813856 9461 10485 0 0":
+// rdval(file_contents, "string_to_find", result_vector, value#, value#...)
+// value# start with 1
+static int rdval(const char* p, const char* key, ullong *vec, ...)
+{
+ va_list arg_ptr;
+ int result;
+
+ va_start(arg_ptr, vec);
+ result = vrdval(p, key, conv_decimal, vec, arg_ptr);
+ va_end(arg_ptr);
+
+ return result;
+}
+
+// Parses files with lines like "... ... ... 3/148 ...."
+static int rdval_loadavg(const char* p, ullong *vec, ...)
+{
+ va_list arg_ptr;
+ int result;
+
+ va_start(arg_ptr, vec);
+ result = vrdval(p, "", conv_slash, vec, arg_ptr);
+ va_end(arg_ptr);
+
+ return result;
+}
+
+// Parses /proc/diskstats
+// 1 2 3 4 5 6(rd) 7 8 9 10(wr) 11 12 13 14
+// 3 0 hda 51292 14441 841783 926052 25717 79650 843256 3029804 0 148459 3956933
+// 3 1 hda1 0 0 0 0 <- ignore if only 4 fields
+static int rdval_diskstats(const char* p, ullong *vec)
+{
+ ullong rd = 0; // to avoid "warning: 'rd' might be used uninitialized"
+ int indexline = 0;
+ vec[0] = 0;
+ vec[1] = 0;
+ while (1) {
+ indexline++;
+ while (*p == ' ' || *p == '\t') p++;
+ if (*p == '\0') break;
+ if (*p == '\n') {
+ indexline = 0;
+ p++;
+ continue;
+ }
+ if (indexline == 6) {
+ rd = strtoull(p, NULL, 10);
+ } else if (indexline == 10) {
+ vec[0] += rd; // TODO: *sectorsize (don't know how to find out sectorsize)
+ vec[1] += strtoull(p, NULL, 10);
+ while (*p != '\n' && *p != '\0') p++;
+ continue;
+ }
+ while (*p > ' ') p++; // skip over value
+ }
+ return 0;
+}
+
+static void scale(ullong ul)
+{
+ char *fmt;
+ char buf[5];
+ char c;
+ unsigned v,idx = 0;
+ ul *= 10;
+ if (ul > 9999*10) { // do not scale if 9999 or less
+ while (ul >= 10000) {
+ ul /= 1024;
+ idx++;
+ }
+ }
+ v = ul; // ullong divisions are expensive, avoid them
+
+ fmt = " 123456789";
+ if (!idx) { // 9999 or less: use 1234 format
+ c = buf[0] = " 123456789"[v/10000];
+ if (c!=' ') fmt = "0123456789";
+ c = buf[1] = fmt[v/1000%10];
+ if (c!=' ') fmt = "0123456789";
+ buf[2] = fmt[v/100%10];
+ buf[3] = "0123456789"[v/10%10];
+ } else {
+ if (v>=10*10) { // scaled value is >=10: use 123M format
+ c = buf[0] = " 123456789"[v/1000];
+ if (c!=' ') fmt = "0123456789";
+ buf[1] = fmt[v/100%10];
+ buf[2] = "0123456789"[v/10%10];
+ } else { // scaled value is <10: use 1.2M format
+ buf[0] = "0123456789"[v/10];
+ buf[1] = '.';
+ buf[2] = "0123456789"[v%10];
+ }
+ // see http://en.wikipedia.org/wiki/Tera
+ buf[3] = " kMGTPEZY"[idx];
+ }
+ buf[4] = '\0';
+ put(buf);
+}
+
+
+#define S_STAT(a) \
+typedef struct a { \
+ struct s_stat *next; \
+ void (*collect)(struct a *s); \
+ const char *label;
+#define S_STAT_END(a) } a;
+
+S_STAT(s_stat)
+S_STAT_END(s_stat)
+
+static void collect_literal(s_stat *s)
+{
+}
+
+static s_stat* init_literal(void)
+{
+ s_stat *s = xmalloc(sizeof(s_stat));
+ s->collect = collect_literal;
+ return (s_stat*)s;
+}
+
+static s_stat* init_delay(const char *param)
+{
+ delta = strtol(param, NULL, 0)*1000;
+ deltanz = delta > 0 ? delta : 1;
+ need_seconds = (1000000%deltanz) != 0;
+ return (s_stat*)0;
+}
+
+static s_stat* init_cr(const char *param)
+{
+ final_str = "\r";
+ return (s_stat*)0;
+}
+
+
+// user nice system idle iowait irq softirq (last 3 only in 2.6)
+//cpu 649369 0 341297 4336769 11640 7122 1183
+//cpuN 649369 0 341297 4336769 11640 7122 1183
+enum { CPU_FIELDCNT = 7 };
+S_STAT(cpu_stat)
+ ullong old[CPU_FIELDCNT];
+ int bar_sz;
+ char *bar;
+S_STAT_END(cpu_stat)
+
+
+static void collect_cpu(cpu_stat *s)
+{
+ ullong data[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
+ unsigned frac[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
+ ullong all = 0;
+ int norm_all = 0;
+ int bar_sz = s->bar_sz;
+ char *bar = s->bar;
+ int i;
+
+ if (rdval(get_file(&proc_stat), "cpu ", data, 1, 2, 3, 4, 5, 6, 7)) {
+ put_question_marks(bar_sz);
+ return;
+ }
+
+ for (i=0; i<CPU_FIELDCNT; i++) {
+ ullong old = s->old[i];
+ if (data[i] < old) old = data[i]; //sanitize
+ s->old[i] = data[i];
+ all += (data[i] -= old);
+ }
+
+ if (all) {
+ for (i=0; i<CPU_FIELDCNT; i++) {
+ ullong t = bar_sz * data[i];
+ norm_all += data[i] = t / all;
+ frac[i] = t % all;
+ }
+
+ while (norm_all < bar_sz) {
+ unsigned max = frac[0];
+ int pos = 0;
+ for (i=1; i<CPU_FIELDCNT; i++) {
+ if (frac[i] > max) max = frac[i], pos = i;
+ }
+ frac[pos] = 0; //avoid bumping up same value twice
+ data[pos]++;
+ norm_all++;
+ }
+
+ memset(bar, '.', bar_sz);
+ memset(bar, 'S', data[2]); bar += data[2]; //sys
+ memset(bar, 'U', data[0]); bar += data[0]; //usr
+ memset(bar, 'N', data[1]); bar += data[1]; //nice
+ memset(bar, 'D', data[4]); bar += data[4]; //iowait
+ memset(bar, 'I', data[5]); bar += data[5]; //irq
+ memset(bar, 'i', data[6]); bar += data[6]; //softirq
+ } else {
+ memset(bar, '?', bar_sz);
+ }
+ put(s->bar);
+}
+
+
+static s_stat* init_cpu(const char *param)
+{
+ int sz;
+ cpu_stat *s = xmalloc(sizeof(cpu_stat));
+ s->collect = collect_cpu;
+ sz = strtol(param, NULL, 0);
+ if (sz < 10) sz = 10;
+ if (sz > 1000) sz = 1000;
+ s->bar = xmalloc(sz+1);
+ s->bar[sz] = '\0';
+ s->bar_sz = sz;
+ return (s_stat*)s;
+}
+
+
+S_STAT(int_stat)
+ ullong old;
+ int no;
+S_STAT_END(int_stat)
+
+static void collect_int(int_stat *s)
+{
+ ullong data[1];
+ ullong old;
+
+ if (rdval(get_file(&proc_stat), "intr", data, s->no)) {
+ put_question_marks(4);
+ return;
+ }
+
+ old = s->old;
+ if (data[0] < old) old = data[0]; //sanitize
+ s->old = data[0];
+ scale(data[0] - old);
+}
+
+static s_stat* init_int(const char *param)
+{
+ int_stat *s = xmalloc(sizeof(int_stat));
+ s->collect = collect_int;
+ if (param[0]=='\0') {
+ s->no = 1;
+ } else {
+ int n = strtoul(param, NULL, 0);
+ s->no = n+2;
+ }
+ return (s_stat*)s;
+}
+
+
+S_STAT(ctx_stat)
+ ullong old;
+S_STAT_END(ctx_stat)
+
+static void collect_ctx(ctx_stat *s)
+{
+ ullong data[1];
+ ullong old;
+
+ if (rdval(get_file(&proc_stat), "ctxt", data, 1)) {
+ put_question_marks(4);
+ return;
+ }
+
+ old = s->old;
+ if (data[0] < old) old = data[0]; //sanitize
+ s->old = data[0];
+ scale(data[0] - old);
+}
+
+static s_stat* init_ctx(const char *param)
+{
+ ctx_stat *s = xmalloc(sizeof(ctx_stat));
+ s->collect = collect_ctx;
+ return (s_stat*)s;
+}
+
+
+S_STAT(blk_stat)
+ const char* lookfor;
+ ullong old[2];
+S_STAT_END(blk_stat)
+
+static void collect_blk(blk_stat *s)
+{
+ ullong data[2];
+ int i;
+
+ if (is26) {
+ i = rdval_diskstats(get_file(&proc_diskstats), data);
+ } else {
+ i = rdval(get_file(&proc_stat), s->lookfor, data, 1, 2);
+ // Linux 2.4 reports bio in Kbytes, convert to sectors:
+ data[0] *= 2;
+ data[1] *= 2;
+ }
+ if (i) {
+ put_question_marks(9);
+ return;
+ }
+
+ for (i=0; i<2; i++) {
+ ullong old = s->old[i];
+ if (data[i] < old) old = data[i]; //sanitize
+ s->old[i] = data[i];
+ data[i] -= old;
+ }
+ scale(data[0]*512); // TODO: *sectorsize
+ put_c(' ');
+ scale(data[1]*512);
+}
+
+static s_stat* init_blk(const char *param)
+{
+ blk_stat *s = xmalloc(sizeof(blk_stat));
+ s->collect = collect_blk;
+ s->lookfor = "page";
+ return (s_stat*)s;
+}
+
+
+S_STAT(fork_stat)
+ ullong old;
+S_STAT_END(fork_stat)
+
+static void collect_thread_nr(fork_stat *s)
+{
+ ullong data[1];
+
+ if (rdval_loadavg(get_file(&proc_loadavg), data, 4)) {
+ put_question_marks(4);
+ return;
+ }
+ scale(data[0]);
+}
+
+static void collect_fork(fork_stat *s)
+{
+ ullong data[1];
+ ullong old;
+
+ if (rdval(get_file(&proc_stat), "processes", data, 1)) {
+ put_question_marks(4);
+ return;
+ }
+
+ old = s->old;
+ if (data[0] < old) old = data[0]; //sanitize
+ s->old = data[0];
+ scale(data[0] - old);
+}
+
+static s_stat* init_fork(const char *param)
+{
+ fork_stat *s = xmalloc(sizeof(fork_stat));
+ if (*param == 'n') {
+ s->collect = collect_thread_nr;
+ } else {
+ s->collect = collect_fork;
+ }
+ return (s_stat*)s;
+}
+
+
+S_STAT(if_stat)
+ ullong old[4];
+ const char *device;
+ char *device_colon;
+S_STAT_END(if_stat)
+
+static void collect_if(if_stat *s)
+{
+ ullong data[4];
+ int i;
+
+ if (rdval(get_file(&proc_net_dev), s->device_colon, data, 1, 3, 9, 11)) {
+ put_question_marks(10);
+ return;
+ }
+
+ for (i=0; i<4; i++) {
+ ullong old = s->old[i];
+ if (data[i] < old) old = data[i]; //sanitize
+ s->old[i] = data[i];
+ data[i] -= old;
+ }
+ put_c(data[1] ? '*' : ' ');
+ scale(data[0]);
+ put_c(data[3] ? '*' : ' ');
+ scale(data[2]);
+}
+
+static s_stat* init_if(const char *device)
+{
+ if_stat *s = xmalloc(sizeof(if_stat));
+
+ if (!device || !device[0])
+ bb_show_usage();
+ s->collect = collect_if;
+
+ s->device = device;
+ s->device_colon = xmalloc(strlen(device)+2);
+ strcpy(s->device_colon, device);
+ strcat(s->device_colon, ":");
+ return (s_stat*)s;
+}
+
+
+S_STAT(mem_stat)
+ char opt;
+S_STAT_END(mem_stat)
+
+// "Memory" value should not include any caches.
+// IOW: neither "ls -laR /" nor heavy read/write activity
+// should affect it. We'd like to also include any
+// long-term allocated kernel-side mem, but it is hard
+// to figure out. For now, bufs, cached & slab are
+// counted as "free" memory
+//2.6.16:
+//MemTotal: 773280 kB
+//MemFree: 25912 kB - genuinely free
+//Buffers: 320672 kB - cache
+//Cached: 146396 kB - cache
+//SwapCached: 0 kB
+//Active: 183064 kB
+//Inactive: 356892 kB
+//HighTotal: 0 kB
+//HighFree: 0 kB
+//LowTotal: 773280 kB
+//LowFree: 25912 kB
+//SwapTotal: 131064 kB
+//SwapFree: 131064 kB
+//Dirty: 48 kB
+//Writeback: 0 kB
+//Mapped: 96620 kB
+//Slab: 200668 kB - takes 7 Mb on my box fresh after boot,
+// but includes dentries and inodes
+// (== can take arbitrary amount of mem)
+//CommitLimit: 517704 kB
+//Committed_AS: 236776 kB
+//PageTables: 1248 kB
+//VmallocTotal: 516052 kB
+//VmallocUsed: 3852 kB
+//VmallocChunk: 512096 kB
+//HugePages_Total: 0
+//HugePages_Free: 0
+//Hugepagesize: 4096 kB
+static void collect_mem(mem_stat *s)
+{
+ ullong m_total = 0;
+ ullong m_free = 0;
+ ullong m_bufs = 0;
+ ullong m_cached = 0;
+ ullong m_slab = 0;
+
+ if (rdval(get_file(&proc_meminfo), "MemTotal:", &m_total, 1)) {
+ put_question_marks(4);
+ return;
+ }
+ if (s->opt == 'f') {
+ scale(m_total << 10);
+ return;
+ }
+
+ if (rdval(proc_meminfo.file, "MemFree:", &m_free , 1)
+ || rdval(proc_meminfo.file, "Buffers:", &m_bufs , 1)
+ || rdval(proc_meminfo.file, "Cached:", &m_cached, 1)
+ || rdval(proc_meminfo.file, "Slab:", &m_slab , 1)
+ ) {
+ put_question_marks(4);
+ return;
+ }
+
+ m_free += m_bufs + m_cached + m_slab;
+ switch(s->opt) {
+ case 'f':
+ scale(m_free << 10); break;
+ default:
+ scale((m_total - m_free) << 10); break;
+ }
+}
+
+static s_stat* init_mem(const char *param)
+{
+ mem_stat *s = xmalloc(sizeof(mem_stat));
+ s->collect = collect_mem;
+ s->opt = param[0];
+ return (s_stat*)s;
+}
+
+
+S_STAT(swp_stat)
+S_STAT_END(swp_stat)
+
+static void collect_swp(swp_stat *s)
+{
+ ullong s_total[1];
+ ullong s_free[1];
+ if (rdval(get_file(&proc_meminfo), "SwapTotal:", s_total, 1)
+ || rdval(proc_meminfo.file, "SwapFree:" , s_free, 1)
+ ) {
+ put_question_marks(4);
+ return;
+ }
+ scale((s_total[0]-s_free[0]) << 10);
+}
+
+static s_stat* init_swp(const char *param)
+{
+ swp_stat *s = xmalloc(sizeof(swp_stat));
+ s->collect = collect_swp;
+ return (s_stat*)s;
+}
+
+
+S_STAT(fd_stat)
+S_STAT_END(fd_stat)
+
+static void collect_fd(fd_stat *s)
+{
+ char file[4096];
+ ullong data[2];
+
+ readfile_z(file, sizeof(file), "/proc/sys/fs/file-nr");
+ if (rdval(file, "", data, 1, 2)) {
+ put_question_marks(4);
+ return;
+ }
+
+ scale(data[0] - data[1]);
+}
+
+static s_stat* init_fd(const char *param)
+{
+ fd_stat *s = xmalloc(sizeof(fd_stat));
+ s->collect = collect_fd;
+ return (s_stat*)s;
+}
+
+
+S_STAT(time_stat)
+ int prec;
+ int scale;
+S_STAT_END(time_stat)
+
+static void collect_time(time_stat *s)
+{
+ char buf[sizeof("12:34:56.123456")];
+ struct tm* tm;
+ int us = tv.tv_usec + s->scale/2;
+ time_t t = tv.tv_sec;
+
+ if (us >= 1000000) {
+ t++;
+ us -= 1000000;
+ }
+ tm = localtime(&t);
+
+ sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
+ if (s->prec)
+ sprintf(buf+8, ".%0*d", s->prec, us / s->scale);
+ put(buf);
+}
+
+static s_stat* init_time(const char *param)
+{
+ int prec;
+ time_stat *s = xmalloc(sizeof(time_stat));
+
+ s->collect = collect_time;
+ prec = param[0]-'0';
+ if (prec < 0) prec = 0;
+ else if (prec > 6) prec = 6;
+ s->prec = prec;
+ s->scale = 1;
+ while (prec++ < 6)
+ s->scale *= 10;
+ return (s_stat*)s;
+}
+
+static void collect_info(s_stat *s)
+{
+ gen++;
+ while (s) {
+ put(s->label);
+ s->collect(s);
+ s = s->next;
+ }
+}
+
+
+typedef s_stat* init_func(const char *param);
+
+static const char options[] = "ncmsfixptbdr";
+static init_func* init_functions[] = {
+ init_if,
+ init_cpu,
+ init_mem,
+ init_swp,
+ init_fd,
+ init_int,
+ init_ctx,
+ init_fork,
+ init_time,
+ init_blk,
+ init_delay,
+ init_cr,
+};
+
+int nmeter_main(int argc, char* argv[])
+{
+ char buf[32];
+ s_stat *first = NULL;
+ s_stat *last = NULL;
+ s_stat *s;
+ char *cur, *prev;
+ int fd;
+
+ if (argc != 2)
+ bb_show_usage();
+
+ fd = xopen("/proc/version", O_RDONLY);
+ if (read(fd, buf, sizeof(buf)) > 0)
+ is26 = (strstr(buf, "Linux version 2.4.")==NULL);
+ close(fd);
+
+ // Can use argv[1] directly, but this will mess up
+ // parameters as seen by e.g. ps. Making a copy...
+ cur = xstrdup(argv[1]);
+ while (1) {
+ char *param, *p;
+ prev = cur;
+again:
+ cur = strchr(cur, '%');
+ if (!cur)
+ break;
+ if (cur[1]=='%') { // %%
+ strcpy(cur, cur+1);
+ cur++;
+ goto again;
+ }
+ *cur++ = '\0'; // overwrite %
+ if (cur[0] == '[') {
+ // format: %[foptstring]
+ cur++;
+ p = strchr(options, cur[0]);
+ param = cur+1;
+ while (cur[0] != ']') {
+ if (!cur[0])
+ bb_show_usage();
+ cur++;
+ }
+ *cur++ = '\0'; // overwrite [
+ } else {
+ // format: %NNNNNNf
+ param = cur;
+ while (cur[0] >= '0' && cur[0] <= '9')
+ cur++;
+ if (!cur[0])
+ bb_show_usage();
+ p = strchr(options, cur[0]);
+ *cur++ = '\0'; // overwrite format char
+ }
+ if (!p)
+ bb_show_usage();
+ s = init_functions[p-options](param);
+ if (s) {
+ s->label = prev;
+ s->next = 0;
+ if (!first)
+ first = s;
+ else
+ last->next = s;
+ last = s;
+ } else {
+ // %NNNNd or %r option. remove it from string
+ strcpy(prev + strlen(prev), cur);
+ cur = prev;
+ }
+ }
+ if (prev[0]) {
+ s = init_literal();
+ s->label = prev;
+ s->next = 0;
+ if (!first)
+ first = s;
+ else
+ last->next = s;
+ last = s;
+ }
+
+ // Generate first samples but do not print them, they're bogus
+ collect_info(first);
+ reset_outbuf();
+ if (delta >= 0) {
+ gettimeofday(&tv, 0);
+ usleep(delta > 1000000 ? 1000000 : delta - tv.tv_usec%deltanz);
+ }
+
+ while (1) {
+ gettimeofday(&tv, 0);
+ collect_info(first);
+ put(final_str);
+ print_outbuf();
+
+ // Negative delta -> no usleep at all
+ // This will hog the CPU but you can have REALLY GOOD
+ // time resolution ;)
+ // TODO: detect and avoid useless updates
+ // (like: nothing happens except time)
+ if (delta >= 0) {
+ int rem;
+ // can be commented out, will sacrifice sleep time precision a bit
+ gettimeofday(&tv, 0);
+ if (need_seconds)
+ rem = delta - ((ullong)tv.tv_sec*1000000+tv.tv_usec)%deltanz;
+ else
+ rem = delta - tv.tv_usec%deltanz;
+ // Sometimes kernel wakes us up just a tiny bit earlier than asked
+ // Do not go to very short sleep in this case
+ if (rem < delta/128) {
+ rem += delta;
+ }
+ usleep(rem);
+ }
+ }
+
+ return 0;
+}