Initial public busybox upstream commit
[busybox4maemo] / 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(1, 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 = xmalloc(sizeof(s_stat));
285         s->collect = collect_literal;
286         return (s_stat*)s;
287 }
288
289 static s_stat* init_delay(const char *param)
290 {
291         delta = bb_strtoi(param, NULL, 0) * 1000;
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 = xmalloc(sizeof(cpu_stat));
373         s->collect = collect_cpu;
374         sz = strtol(param, NULL, 0);
375         if (sz < 10) sz = 10;
376         if (sz > 1000) sz = 1000;
377         s->bar = xmalloc(sz+1);
378         s->bar[sz] = '\0';
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 = xmalloc(sizeof(int_stat));
408         s->collect = collect_int;
409         if (param[0]=='\0') {
410                 s->no = 1;
411         } else {
412                 int n = strtoul(param, NULL, 0);
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 = xmalloc(sizeof(ctx_stat));
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 = xmalloc(sizeof(blk_stat));
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 = xmalloc(sizeof(fork_stat));
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 = xmalloc(sizeof(if_stat));
564
565         if (!device || !device[0])
566                 bb_show_usage();
567         s->collect = collect_if;
568
569         s->device = device;
570         s->device_colon = xmalloc(strlen(device)+2);
571         strcpy(s->device_colon, device);
572         strcat(s->device_colon, ":");
573         return (s_stat*)s;
574 }
575
576
577 S_STAT(mem_stat)
578         char opt;
579 S_STAT_END(mem_stat)
580
581 // "Memory" value should not include any caches.
582 // IOW: neither "ls -laR /" nor heavy read/write activity
583 //      should affect it. We'd like to also include any
584 //      long-term allocated kernel-side mem, but it is hard
585 //      to figure out. For now, bufs, cached & slab are
586 //      counted as "free" memory
587 //2.6.16:
588 //MemTotal:       773280 kB
589 //MemFree:         25912 kB - genuinely free
590 //Buffers:        320672 kB - cache
591 //Cached:         146396 kB - cache
592 //SwapCached:          0 kB
593 //Active:         183064 kB
594 //Inactive:       356892 kB
595 //HighTotal:           0 kB
596 //HighFree:            0 kB
597 //LowTotal:       773280 kB
598 //LowFree:         25912 kB
599 //SwapTotal:      131064 kB
600 //SwapFree:       131064 kB
601 //Dirty:              48 kB
602 //Writeback:           0 kB
603 //Mapped:          96620 kB
604 //Slab:           200668 kB - takes 7 Mb on my box fresh after boot,
605 //                            but includes dentries and inodes
606 //                            (== can take arbitrary amount of mem)
607 //CommitLimit:    517704 kB
608 //Committed_AS:   236776 kB
609 //PageTables:       1248 kB
610 //VmallocTotal:   516052 kB
611 //VmallocUsed:      3852 kB
612 //VmallocChunk:   512096 kB
613 //HugePages_Total:     0
614 //HugePages_Free:      0
615 //Hugepagesize:     4096 kB
616 static void collect_mem(mem_stat *s)
617 {
618         ullong m_total = 0;
619         ullong m_free = 0;
620         ullong m_bufs = 0;
621         ullong m_cached = 0;
622         ullong m_slab = 0;
623
624         if (rdval(get_file(&proc_meminfo), "MemTotal:", &m_total, 1)) {
625                 put_question_marks(4);
626                 return;
627         }
628         if (s->opt == 't') {
629                 scale(m_total << 10);
630                 return;
631         }
632
633         if (rdval(proc_meminfo.file, "MemFree:", &m_free  , 1)
634          || rdval(proc_meminfo.file, "Buffers:", &m_bufs  , 1)
635          || rdval(proc_meminfo.file, "Cached:",  &m_cached, 1)
636          || rdval(proc_meminfo.file, "Slab:",    &m_slab  , 1)
637         ) {
638                 put_question_marks(4);
639                 return;
640         }
641
642         m_free += m_bufs + m_cached + m_slab;
643         switch (s->opt) {
644         case 'f':
645                 scale(m_free << 10); break;
646         default:
647                 scale((m_total - m_free) << 10); break;
648         }
649 }
650
651 static s_stat* init_mem(const char *param)
652 {
653         mem_stat *s = xmalloc(sizeof(mem_stat));
654         s->collect = collect_mem;
655         s->opt = param[0];
656         return (s_stat*)s;
657 }
658
659
660 S_STAT(swp_stat)
661 S_STAT_END(swp_stat)
662
663 static void collect_swp(swp_stat *s ATTRIBUTE_UNUSED)
664 {
665         ullong s_total[1];
666         ullong s_free[1];
667         if (rdval(get_file(&proc_meminfo), "SwapTotal:", s_total, 1)
668          || rdval(proc_meminfo.file,       "SwapFree:" , s_free,  1)
669         ) {
670                 put_question_marks(4);
671                 return;
672         }
673         scale((s_total[0]-s_free[0]) << 10);
674 }
675
676 static s_stat* init_swp(const char *param ATTRIBUTE_UNUSED)
677 {
678         swp_stat *s = xmalloc(sizeof(swp_stat));
679         s->collect = collect_swp;
680         return (s_stat*)s;
681 }
682
683
684 S_STAT(fd_stat)
685 S_STAT_END(fd_stat)
686
687 static void collect_fd(fd_stat *s ATTRIBUTE_UNUSED)
688 {
689         ullong data[2];
690
691         if (rdval(get_file(&proc_sys_fs_filenr), "", data, 1, 2)) {
692                 put_question_marks(4);
693                 return;
694         }
695
696         scale(data[0] - data[1]);
697 }
698
699 static s_stat* init_fd(const char *param ATTRIBUTE_UNUSED)
700 {
701         fd_stat *s = xmalloc(sizeof(fd_stat));
702         s->collect = collect_fd;
703         return (s_stat*)s;
704 }
705
706
707 S_STAT(time_stat)
708         int prec;
709         int scale;
710 S_STAT_END(time_stat)
711
712 static void collect_time(time_stat *s)
713 {
714         char buf[sizeof("12:34:56.123456")];
715         struct tm* tm;
716         int us = tv.tv_usec + s->scale/2;
717         time_t t = tv.tv_sec;
718
719         if (us >= 1000000) {
720                 t++;
721                 us -= 1000000;
722         }
723         tm = localtime(&t);
724
725         sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
726         if (s->prec)
727                 sprintf(buf+8, ".%0*d", s->prec, us / s->scale);
728         put(buf);
729 }
730
731 static s_stat* init_time(const char *param)
732 {
733         int prec;
734         time_stat *s = xmalloc(sizeof(time_stat));
735
736         s->collect = collect_time;
737         prec = param[0]-'0';
738         if (prec < 0) prec = 0;
739         else if (prec > 6) prec = 6;
740         s->prec = prec;
741         s->scale = 1;
742         while (prec++ < 6)
743                 s->scale *= 10;
744         return (s_stat*)s;
745 }
746
747 static void collect_info(s_stat *s)
748 {
749         gen ^= 1;
750         while (s) {
751                 put(s->label);
752                 s->collect(s);
753                 s = s->next;
754         }
755 }
756
757
758 typedef s_stat* init_func(const char *param);
759
760 static const char options[] ALIGN1 = "ncmsfixptbdr";
761 static init_func *const init_functions[] = {
762         init_if,
763         init_cpu,
764         init_mem,
765         init_swp,
766         init_fd,
767         init_int,
768         init_ctx,
769         init_fork,
770         init_time,
771         init_blk,
772         init_delay,
773         init_cr
774 };
775
776 int nmeter_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
777 int nmeter_main(int argc, char **argv)
778 {
779         char buf[32];
780         s_stat *first = NULL;
781         s_stat *last = NULL;
782         s_stat *s;
783         char *cur, *prev;
784
785         INIT_G();
786
787         xchdir("/proc");
788
789         if (argc != 2)
790                 bb_show_usage();
791
792         if (open_read_close("version", buf, sizeof(buf)) > 0)
793                 is26 = (strstr(buf, " 2.4.")==NULL);
794
795         // Can use argv[1] directly, but this will mess up
796         // parameters as seen by e.g. ps. Making a copy...
797         cur = xstrdup(argv[1]);
798         while (1) {
799                 char *param, *p;
800                 prev = cur;
801  again:
802                 cur = strchr(cur, '%');
803                 if (!cur)
804                         break;
805                 if (cur[1] == '%') {    // %%
806                         strcpy(cur, cur+1);
807                         cur++;
808                         goto again;
809                 }
810                 *cur++ = '\0';          // overwrite %
811                 if (cur[0] == '[') {
812                         // format: %[foptstring]
813                         cur++;
814                         p = strchr(options, cur[0]);
815                         param = cur+1;
816                         while (cur[0] != ']') {
817                                 if (!cur[0])
818                                         bb_show_usage();
819                                 cur++;
820                         }
821                         *cur++ = '\0';  // overwrite [
822                 } else {
823                         // format: %NNNNNNf
824                         param = cur;
825                         while (cur[0] >= '0' && cur[0] <= '9')
826                                 cur++;
827                         if (!cur[0])
828                                 bb_show_usage();
829                         p = strchr(options, cur[0]);
830                         *cur++ = '\0';  // overwrite format char
831                 }
832                 if (!p)
833                         bb_show_usage();
834                 s = init_functions[p-options](param);
835                 if (s) {
836                         s->label = prev;
837                         s->next = 0;
838                         if (!first)
839                                 first = s;
840                         else
841                                 last->next = s;
842                         last = s;
843                 } else {
844                         // %NNNNd or %r option. remove it from string
845                         strcpy(prev + strlen(prev), cur);
846                         cur = prev;
847                 }
848         }
849         if (prev[0]) {
850                 s = init_literal();
851                 s->label = prev;
852                 s->next = 0;
853                 if (!first)
854                         first = s;
855                 else
856                         last->next = s;
857                 last = s;
858         }
859
860         // Generate first samples but do not print them, they're bogus
861         collect_info(first);
862         reset_outbuf();
863         if (delta >= 0) {
864                 gettimeofday(&tv, NULL);
865                 usleep(delta > 1000000 ? 1000000 : delta - tv.tv_usec%deltanz);
866         }
867
868         while (1) {
869                 gettimeofday(&tv, NULL);
870                 collect_info(first);
871                 put(final_str);
872                 print_outbuf();
873
874                 // Negative delta -> no usleep at all
875                 // This will hog the CPU but you can have REALLY GOOD
876                 // time resolution ;)
877                 // TODO: detect and avoid useless updates
878                 // (like: nothing happens except time)
879                 if (delta >= 0) {
880                         int rem;
881                         // can be commented out, will sacrifice sleep time precision a bit
882                         gettimeofday(&tv, NULL);
883                         if (need_seconds)
884                                 rem = delta - ((ullong)tv.tv_sec*1000000 + tv.tv_usec) % deltanz;
885                         else
886                                 rem = delta - tv.tv_usec%deltanz;
887                         // Sometimes kernel wakes us up just a tiny bit earlier than asked
888                         // Do not go to very short sleep in this case
889                         if (rem < delta/128) {
890                                 rem += delta;
891                         }
892                         usleep(rem);
893                 }
894         }
895
896         /*return 0;*/
897 }