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