aboutsummaryrefslogtreecommitdiff
path: root/procps
diff options
context:
space:
mode:
Diffstat (limited to 'procps')
-rw-r--r--procps/Config.in14
-rw-r--r--procps/Kbuild4
-rw-r--r--procps/nmeter.c889
-rw-r--r--procps/watch.c80
4 files changed, 986 insertions, 1 deletions
diff --git a/procps/Config.in b/procps/Config.in
index 91319103f..b834fbf96 100644
--- a/procps/Config.in
+++ b/procps/Config.in
@@ -43,6 +43,12 @@ config KILLALL5
default n
depends on KILL
+config NMETER
+ bool "nmeter"
+ default n
+ help
+ Prints selected system stats continuously, one line per update.
+
config PIDOF
bool "pidof"
default n
@@ -130,6 +136,14 @@ config UPTIME
the system has been running, how many users are currently logged
on, and the system load averages for the past 1, 5, and 15 minutes.
+config WATCH
+ bool "watch"
+ default n
+ #huh?? select DATE
+ help
+ watch is used to execute a program periodically, showing
+ output to the screen.
+
endmenu
diff --git a/procps/Kbuild b/procps/Kbuild
index 33f616fc2..c75be291b 100644
--- a/procps/Kbuild
+++ b/procps/Kbuild
@@ -6,12 +6,14 @@
lib-y:=
lib-$(CONFIG_FREE) += free.o
+lib-$(CONFIG_FUSER) += fuser.o
lib-$(CONFIG_KILL) += kill.o
lib-$(CONFIG_ASH) += kill.o # used for built-in kill by ash
+lib-$(CONFIG_NMETER) += nmeter.o
lib-$(CONFIG_PIDOF) += pidof.o
lib-$(CONFIG_PS) += ps.o
lib-$(CONFIG_RENICE) += renice.o
lib-$(CONFIG_BB_SYSCTL) += sysctl.o
lib-$(CONFIG_TOP) += top.o
lib-$(CONFIG_UPTIME) += uptime.o
-lib-$(CONFIG_FUSER) += fuser.o
+lib-$(CONFIG_WATCH) += watch.o
diff --git a/procps/nmeter.c b/procps/nmeter.c
new file mode 100644
index 000000000..1d58eb2c1
--- /dev/null
+++ b/procps/nmeter.c
@@ -0,0 +1,889 @@
+/*
+** 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 "libbb.h"
+#include <time.h>
+
+typedef unsigned long long ullong;
+
+enum { PROC_FILE_SIZE = 4096 };
+
+typedef struct proc_file {
+ char *file;
+ //const char *name;
+ smallint last_gen;
+} proc_file;
+
+static const char *const proc_name[] = {
+ "stat", // Must match the order of proc_file's!
+ "loadavg",
+ "net/dev",
+ "meminfo",
+ "diskstats",
+ "sys/fs/file-nr",
+};
+
+struct globals {
+ // Sample generation flip-flop
+ smallint gen;
+ // Linux 2.6? (otherwise assumes 2.4)
+ smallint is26;
+ // 1 if sample delay is not an integer fraction of a second
+ smallint need_seconds;
+ char *cur_outbuf;
+ const char *final_str;
+ int delta;
+ int deltanz;
+ struct timeval tv;
+#define first_proc_file proc_stat
+ proc_file proc_stat; // Must match the order of proc_name's!
+ proc_file proc_loadavg;
+ proc_file proc_net_dev;
+ proc_file proc_meminfo;
+ proc_file proc_diskstats;
+ proc_file proc_sys_fs_filenr;
+};
+#define G (*ptr_to_globals)
+#define gen (G.gen )
+#define is26 (G.is26 )
+#define need_seconds (G.need_seconds )
+#define cur_outbuf (G.cur_outbuf )
+#define final_str (G.final_str )
+#define delta (G.delta )
+#define deltanz (G.deltanz )
+#define tv (G.tv )
+#define proc_stat (G.proc_stat )
+#define proc_loadavg (G.proc_loadavg )
+#define proc_net_dev (G.proc_net_dev )
+#define proc_meminfo (G.proc_meminfo )
+#define proc_diskstats (G.proc_diskstats )
+#define proc_sys_fs_filenr (G.proc_sys_fs_filenr)
+#define INIT_G() do { \
+ PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
+ cur_outbuf = outbuf; \
+ final_str = "\n"; \
+ deltanz = delta = 1000000; \
+ } while (0)
+
+// We depend on this being a char[], not char* - we take sizeof() of it
+#define outbuf bb_common_bufsiz1
+
+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) {
+ xwrite(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 void readfile_z(char *buf, int sz, const char* fname)
+{
+// open_read_close() will do two reads in order to be sure we are at EOF,
+// and we don't need/want that.
+// sz = open_read_close(fname, buf, sz-1);
+
+ int fd = xopen(fname, O_RDONLY);
+ buf[0] = '\0';
+ if (fd >= 0) {
+ sz = read(fd, buf, sz-1);
+ if (sz > 0) buf[sz] = '\0';
+ close(fd);
+ }
+}
+
+static const char* get_file(proc_file *pf)
+{
+ if (pf->last_gen != gen) {
+ pf->last_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 = xmalloc(PROC_FILE_SIZE);
+ readfile_z(pf->file, PROC_FILE_SIZE, proc_name[pf - &first_proc_file]);
+ }
+ 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 buf[5];
+ smart_ulltoa5(ul, buf);
+ 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 = bb_strtoi(param, NULL, 0) * 1000;
+ deltanz = delta > 0 ? delta : 1;
+ need_seconds = (1000000%deltanz) != 0;
+ return NULL;
+}
+
+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)
+{
+ ullong data[2];
+
+ if (rdval(get_file(&proc_sys_fs_filenr), "", 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 ^= 1;
+ 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 *const 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);
+int nmeter_main(int argc, char **argv)
+{
+ char buf[32];
+ s_stat *first = NULL;
+ s_stat *last = NULL;
+ s_stat *s;
+ char *cur, *prev;
+
+ INIT_G();
+
+ xchdir("/proc");
+
+ if (argc != 2)
+ bb_show_usage();
+
+ if (open_read_close("version", buf, sizeof(buf)) > 0)
+ is26 = (strstr(buf, " 2.4.")==NULL);
+
+ // 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, NULL);
+ usleep(delta > 1000000 ? 1000000 : delta - tv.tv_usec%deltanz);
+ }
+
+ while (1) {
+ gettimeofday(&tv, NULL);
+ 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, NULL);
+ 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;*/
+}
diff --git a/procps/watch.c b/procps/watch.c
new file mode 100644
index 000000000..2ad0564cd
--- /dev/null
+++ b/procps/watch.c
@@ -0,0 +1,80 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini watch implementation for busybox
+ *
+ * Copyright (C) 2001 by Michael Habermann <mhabermann@gmx.de>
+ * Copyrigjt (C) Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org)
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* BB_AUDIT SUSv3 N/A */
+/* BB_AUDIT GNU defects -- only option -n is supported. */
+
+#include "libbb.h"
+
+// procps 2.0.18:
+// watch [-d] [-n seconds]
+// [--differences[=cumulative]] [--interval=seconds] command
+//
+// procps-3.2.3:
+// watch [-dt] [-n seconds]
+// [--differences[=cumulative]] [--interval=seconds] [--no-title] command
+//
+// (procps 3.x and procps 2.x are forks, not newer/older versions of the same)
+
+int watch_main(int argc, char **argv);
+int watch_main(int argc, char **argv)
+{
+ unsigned opt;
+ unsigned period = 2;
+ unsigned cmdlen = 1; // 1 for terminal NUL
+ char *header = NULL;
+ char *cmd;
+ char *tmp;
+ char **p;
+
+ opt_complementary = "-1"; // at least one param please
+ opt = getopt32(argc, argv, "+dtn:", &tmp);
+ //if (opt & 0x1) // -d (ignore)
+ //if (opt & 0x2) // -t
+ if (opt & 0x4) period = xatou(tmp);
+ argv += optind;
+
+ p = argv;
+ while (*p)
+ cmdlen += strlen(*p++) + 1;
+ tmp = cmd = xmalloc(cmdlen);
+ while (*argv) {
+ tmp += sprintf(tmp, " %s", *argv);
+ argv++;
+ }
+ cmd++; // skip initial space
+
+ while (1) {
+ printf("\033[H\033[J");
+ if (!(opt & 0x2)) { // no -t
+ int width, len;
+ char *thyme;
+ time_t t;
+
+ get_terminal_width_height(STDOUT_FILENO, &width, 0);
+ header = xrealloc(header, width--);
+ // '%-*s' pads header with spaces to the full width
+ snprintf(header, width, "Every %ds: %-*s", period, width, cmd);
+ time(&t);
+ thyme = ctime(&t);
+ len = strlen(thyme);
+ if (len < width)
+ strcpy(header + width - len, thyme);
+ puts(header);
+ }
+ fflush(stdout);
+ // TODO: 'real' watch pipes cmd's output to itself
+ // and does not allow it to overflow the screen
+ // (taking into account linewrap!)
+ system(cmd);
+ sleep(period);
+ }
+ return 0; // gcc thinks we can reach this :)
+}