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