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