forgot about avn add... :(
[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 buf[5];
218         smart_ulltoa5(ul, buf);
219         put(buf);
220 }
221
222
223 #define S_STAT(a) \
224 typedef struct a { \
225         struct s_stat *next; \
226         void (*collect)(struct a *s); \
227         const char *label;
228 #define S_STAT_END(a) } a;
229
230 S_STAT(s_stat)
231 S_STAT_END(s_stat)
232
233 static void collect_literal(s_stat *s)
234 {
235 }
236
237 static s_stat* init_literal(void)
238 {
239         s_stat *s = xmalloc(sizeof(s_stat));
240         s->collect = collect_literal;
241         return (s_stat*)s;
242 }
243
244 static s_stat* init_delay(const char *param)
245 {
246         delta = strtol(param, NULL, 0)*1000;
247         deltanz = delta > 0 ? delta : 1;
248         need_seconds = (1000000%deltanz) != 0;
249         return (s_stat*)0;
250 }
251
252 static s_stat* init_cr(const char *param)
253 {
254         final_str = "\r";
255         return (s_stat*)0;
256 }
257
258
259 //     user nice system idle  iowait irq  softirq (last 3 only in 2.6)
260 //cpu  649369 0 341297 4336769 11640 7122 1183
261 //cpuN 649369 0 341297 4336769 11640 7122 1183
262 enum { CPU_FIELDCNT = 7 };
263 S_STAT(cpu_stat)
264         ullong old[CPU_FIELDCNT];
265         int bar_sz;
266         char *bar;
267 S_STAT_END(cpu_stat)
268
269
270 static void collect_cpu(cpu_stat *s)
271 {
272         ullong data[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
273         unsigned frac[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
274         ullong all = 0;
275         int norm_all = 0;
276         int bar_sz = s->bar_sz;
277         char *bar = s->bar;
278         int i;
279
280         if (rdval(get_file(&proc_stat), "cpu ", data, 1, 2, 3, 4, 5, 6, 7)) {
281                 put_question_marks(bar_sz);
282                 return;
283         }
284
285         for (i=0; i<CPU_FIELDCNT; i++) {
286                 ullong old = s->old[i];
287                 if (data[i] < old) old = data[i];               //sanitize
288                 s->old[i] = data[i];
289                 all += (data[i] -= old);
290         }
291
292         if (all) {
293                 for (i=0; i<CPU_FIELDCNT; i++) {
294                         ullong t = bar_sz * data[i];
295                         norm_all += data[i] = t / all;
296                         frac[i] = t % all;
297                 }
298
299                 while (norm_all < bar_sz) {
300                         unsigned max = frac[0];
301                         int pos = 0;
302                         for (i=1; i<CPU_FIELDCNT; i++) {
303                                 if (frac[i] > max) max = frac[i], pos = i;
304                         }
305                         frac[pos] = 0;  //avoid bumping up same value twice
306                         data[pos]++;
307                         norm_all++;
308                 }
309
310                 memset(bar, '.', bar_sz);
311                 memset(bar, 'S', data[2]); bar += data[2]; //sys
312                 memset(bar, 'U', data[0]); bar += data[0]; //usr
313                 memset(bar, 'N', data[1]); bar += data[1]; //nice
314                 memset(bar, 'D', data[4]); bar += data[4]; //iowait
315                 memset(bar, 'I', data[5]); bar += data[5]; //irq
316                 memset(bar, 'i', data[6]); bar += data[6]; //softirq
317         } else {
318                 memset(bar, '?', bar_sz);
319         }
320         put(s->bar);
321 }
322
323
324 static s_stat* init_cpu(const char *param)
325 {
326         int sz;
327         cpu_stat *s = xmalloc(sizeof(cpu_stat));
328         s->collect = collect_cpu;
329         sz = strtol(param, NULL, 0);
330         if (sz < 10) sz = 10;
331         if (sz > 1000) sz = 1000;
332         s->bar = xmalloc(sz+1);
333         s->bar[sz] = '\0';
334         s->bar_sz = sz;
335         return (s_stat*)s;
336 }
337
338
339 S_STAT(int_stat)
340         ullong old;
341         int no;
342 S_STAT_END(int_stat)
343
344 static void collect_int(int_stat *s)
345 {
346         ullong data[1];
347         ullong old;
348
349         if (rdval(get_file(&proc_stat), "intr", data, s->no)) {
350                 put_question_marks(4);
351                 return;
352         }
353
354         old = s->old;
355         if (data[0] < old) old = data[0];               //sanitize
356         s->old = data[0];
357         scale(data[0] - old);
358 }
359
360 static s_stat* init_int(const char *param)
361 {
362         int_stat *s = xmalloc(sizeof(int_stat));
363         s->collect = collect_int;
364         if (param[0]=='\0') {
365                 s->no = 1;
366         } else {
367                 int n = strtoul(param, NULL, 0);
368                 s->no = n+2;
369         }
370         return (s_stat*)s;
371 }
372
373
374 S_STAT(ctx_stat)
375         ullong old;
376 S_STAT_END(ctx_stat)
377
378 static void collect_ctx(ctx_stat *s)
379 {
380         ullong data[1];
381         ullong old;
382
383         if (rdval(get_file(&proc_stat), "ctxt", data, 1)) {
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_ctx(const char *param)
395 {
396         ctx_stat *s = xmalloc(sizeof(ctx_stat));
397         s->collect = collect_ctx;
398         return (s_stat*)s;
399 }
400
401
402 S_STAT(blk_stat)
403         const char* lookfor;
404         ullong old[2];
405 S_STAT_END(blk_stat)
406
407 static void collect_blk(blk_stat *s)
408 {
409         ullong data[2];
410         int i;
411
412         if (is26) {
413                 i = rdval_diskstats(get_file(&proc_diskstats), data);
414         } else {
415                 i = rdval(get_file(&proc_stat), s->lookfor, data, 1, 2);
416                 // Linux 2.4 reports bio in Kbytes, convert to sectors:
417                 data[0] *= 2;
418                 data[1] *= 2;
419         }
420         if (i) {
421                 put_question_marks(9);
422                 return;
423         }
424
425         for (i=0; i<2; i++) {
426                 ullong old = s->old[i];
427                 if (data[i] < old) old = data[i];               //sanitize
428                 s->old[i] = data[i];
429                 data[i] -= old;
430         }
431         scale(data[0]*512); // TODO: *sectorsize
432         put_c(' ');
433         scale(data[1]*512);
434 }
435
436 static s_stat* init_blk(const char *param)
437 {
438         blk_stat *s = xmalloc(sizeof(blk_stat));
439         s->collect = collect_blk;
440         s->lookfor = "page";
441         return (s_stat*)s;
442 }
443
444
445 S_STAT(fork_stat)
446         ullong old;
447 S_STAT_END(fork_stat)
448
449 static void collect_thread_nr(fork_stat *s)
450 {
451         ullong data[1];
452
453         if (rdval_loadavg(get_file(&proc_loadavg), data, 4)) {
454                 put_question_marks(4);
455                 return;
456         }
457         scale(data[0]);
458 }
459
460 static void collect_fork(fork_stat *s)
461 {
462         ullong data[1];
463         ullong old;
464
465         if (rdval(get_file(&proc_stat), "processes", data, 1)) {
466                 put_question_marks(4);
467                 return;
468         }
469
470         old = s->old;
471         if (data[0] < old) old = data[0];       //sanitize
472         s->old = data[0];
473         scale(data[0] - old);
474 }
475
476 static s_stat* init_fork(const char *param)
477 {
478         fork_stat *s = xmalloc(sizeof(fork_stat));
479         if (*param == 'n') {
480                 s->collect = collect_thread_nr;
481         } else {
482                 s->collect = collect_fork;
483         }
484         return (s_stat*)s;
485 }
486
487
488 S_STAT(if_stat)
489         ullong old[4];
490         const char *device;
491         char *device_colon;
492 S_STAT_END(if_stat)
493
494 static void collect_if(if_stat *s)
495 {
496         ullong data[4];
497         int i;
498
499         if (rdval(get_file(&proc_net_dev), s->device_colon, data, 1, 3, 9, 11)) {
500                 put_question_marks(10);
501                 return;
502         }
503
504         for (i=0; i<4; i++) {
505                 ullong old = s->old[i];
506                 if (data[i] < old) old = data[i];               //sanitize
507                 s->old[i] = data[i];
508                 data[i] -= old;
509         }
510         put_c(data[1] ? '*' : ' ');
511         scale(data[0]);
512         put_c(data[3] ? '*' : ' ');
513         scale(data[2]);
514 }
515
516 static s_stat* init_if(const char *device)
517 {
518         if_stat *s = xmalloc(sizeof(if_stat));
519
520         if (!device || !device[0])
521                 bb_show_usage();
522         s->collect = collect_if;
523
524         s->device = device;
525         s->device_colon = xmalloc(strlen(device)+2);
526         strcpy(s->device_colon, device);
527         strcat(s->device_colon, ":");
528         return (s_stat*)s;
529 }
530
531
532 S_STAT(mem_stat)
533         char opt;
534 S_STAT_END(mem_stat)
535
536 // "Memory" value should not include any caches.
537 // IOW: neither "ls -laR /" nor heavy read/write activity
538 //      should affect it. We'd like to also include any
539 //      long-term allocated kernel-side mem, but it is hard
540 //      to figure out. For now, bufs, cached & slab are
541 //      counted as "free" memory
542 //2.6.16:
543 //MemTotal:       773280 kB
544 //MemFree:         25912 kB - genuinely free
545 //Buffers:        320672 kB - cache
546 //Cached:         146396 kB - cache
547 //SwapCached:          0 kB
548 //Active:         183064 kB
549 //Inactive:       356892 kB
550 //HighTotal:           0 kB
551 //HighFree:            0 kB
552 //LowTotal:       773280 kB
553 //LowFree:         25912 kB
554 //SwapTotal:      131064 kB
555 //SwapFree:       131064 kB
556 //Dirty:              48 kB
557 //Writeback:           0 kB
558 //Mapped:          96620 kB
559 //Slab:           200668 kB - takes 7 Mb on my box fresh after boot,
560 //                            but includes dentries and inodes
561 //                            (== can take arbitrary amount of mem)
562 //CommitLimit:    517704 kB
563 //Committed_AS:   236776 kB
564 //PageTables:       1248 kB
565 //VmallocTotal:   516052 kB
566 //VmallocUsed:      3852 kB
567 //VmallocChunk:   512096 kB
568 //HugePages_Total:     0
569 //HugePages_Free:      0
570 //Hugepagesize:     4096 kB
571 static void collect_mem(mem_stat *s)
572 {
573         ullong m_total = 0;
574         ullong m_free = 0;
575         ullong m_bufs = 0;
576         ullong m_cached = 0;
577         ullong m_slab = 0;
578
579         if (rdval(get_file(&proc_meminfo), "MemTotal:", &m_total, 1)) {
580                 put_question_marks(4);
581                 return;
582         }
583         if (s->opt == 'f') {
584                 scale(m_total << 10);
585                 return;
586         }
587
588         if (rdval(proc_meminfo.file, "MemFree:", &m_free  , 1)
589          || rdval(proc_meminfo.file, "Buffers:", &m_bufs  , 1)
590          || rdval(proc_meminfo.file, "Cached:",  &m_cached, 1)
591          || rdval(proc_meminfo.file, "Slab:",    &m_slab  , 1)
592         ) {
593                 put_question_marks(4);
594                 return;
595         }
596
597         m_free += m_bufs + m_cached + m_slab;
598         switch(s->opt) {
599         case 'f':
600                 scale(m_free << 10); break;
601         default:
602                 scale((m_total - m_free) << 10); break;
603         }
604 }
605
606 static s_stat* init_mem(const char *param)
607 {
608         mem_stat *s = xmalloc(sizeof(mem_stat));
609         s->collect = collect_mem;
610         s->opt = param[0];
611         return (s_stat*)s;
612 }
613
614
615 S_STAT(swp_stat)
616 S_STAT_END(swp_stat)
617
618 static void collect_swp(swp_stat *s)
619 {
620         ullong s_total[1];
621         ullong s_free[1];
622         if (rdval(get_file(&proc_meminfo), "SwapTotal:", s_total, 1)
623          || rdval(proc_meminfo.file,       "SwapFree:" , s_free,  1)
624         ) {
625                 put_question_marks(4);
626                 return;
627         }
628         scale((s_total[0]-s_free[0]) << 10);
629 }
630
631 static s_stat* init_swp(const char *param)
632 {
633         swp_stat *s = xmalloc(sizeof(swp_stat));
634         s->collect = collect_swp;
635         return (s_stat*)s;
636 }
637
638
639 S_STAT(fd_stat)
640 S_STAT_END(fd_stat)
641
642 static void collect_fd(fd_stat *s)
643 {
644         char file[4096];
645         ullong data[2];
646
647         readfile_z(file, sizeof(file), "/proc/sys/fs/file-nr");
648         if (rdval(file, "", data, 1, 2)) {
649                 put_question_marks(4);
650                 return;
651         }
652
653         scale(data[0] - data[1]);
654 }
655
656 static s_stat* init_fd(const char *param)
657 {
658         fd_stat *s = xmalloc(sizeof(fd_stat));
659         s->collect = collect_fd;
660         return (s_stat*)s;
661 }
662
663
664 S_STAT(time_stat)
665         int prec;
666         int scale;
667 S_STAT_END(time_stat)
668
669 static void collect_time(time_stat *s)
670 {
671         char buf[sizeof("12:34:56.123456")];
672         struct tm* tm;
673         int us = tv.tv_usec + s->scale/2;
674         time_t t = tv.tv_sec;
675
676         if (us >= 1000000) {
677                 t++;
678                 us -= 1000000;
679         }
680         tm = localtime(&t);
681
682         sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
683         if (s->prec)
684                 sprintf(buf+8, ".%0*d", s->prec, us / s->scale);
685         put(buf);
686 }
687
688 static s_stat* init_time(const char *param)
689 {
690         int prec;
691         time_stat *s = xmalloc(sizeof(time_stat));
692
693         s->collect = collect_time;
694         prec = param[0]-'0';
695         if (prec < 0) prec = 0;
696         else if (prec > 6) prec = 6;
697         s->prec = prec;
698         s->scale = 1;
699         while (prec++ < 6)
700                 s->scale *= 10;
701         return (s_stat*)s;
702 }
703
704 static void collect_info(s_stat *s)
705 {
706         gen++;
707         while (s) {
708                 put(s->label);
709                 s->collect(s);
710                 s = s->next;
711         }
712 }
713
714
715 typedef s_stat* init_func(const char *param);
716
717 static const char options[] = "ncmsfixptbdr";
718 static init_func* init_functions[] = {
719         init_if,
720         init_cpu,
721         init_mem,
722         init_swp,
723         init_fd,
724         init_int,
725         init_ctx,
726         init_fork,
727         init_time,
728         init_blk,
729         init_delay,
730         init_cr,
731 };
732
733 int nmeter_main(int argc, char* argv[])
734 {
735         char buf[32];
736         s_stat *first = NULL;
737         s_stat *last = NULL;
738         s_stat *s;
739         char *cur, *prev;
740
741         if (argc != 2)
742                 bb_show_usage();
743
744         if (open_read_close("/proc/version", buf, sizeof(buf)) > 0)
745                 is26 = (strstr(buf, " 2.4.")==NULL);
746
747         // Can use argv[1] directly, but this will mess up
748         // parameters as seen by e.g. ps. Making a copy...
749         cur = xstrdup(argv[1]);
750         while (1) {
751                 char *param, *p;
752                 prev = cur;
753 again:
754                 cur = strchr(cur, '%');
755                 if (!cur)
756                         break;
757                 if (cur[1]=='%') {      // %%
758                         strcpy(cur, cur+1);
759                         cur++;
760                         goto again;
761                 }
762                 *cur++ = '\0';          // overwrite %
763                 if (cur[0] == '[') {
764                         // format: %[foptstring]
765                         cur++;
766                         p = strchr(options, cur[0]);
767                         param = cur+1;
768                         while (cur[0] != ']') {
769                                 if (!cur[0])
770                                         bb_show_usage();
771                                 cur++;
772                         }
773                         *cur++ = '\0';  // overwrite [
774                 } else {
775                         // format: %NNNNNNf
776                         param = cur;
777                         while (cur[0] >= '0' && cur[0] <= '9')
778                                 cur++;
779                         if (!cur[0])
780                                 bb_show_usage();
781                         p = strchr(options, cur[0]);
782                         *cur++ = '\0';  // overwrite format char
783                 }
784                 if (!p)
785                         bb_show_usage();
786                 s = init_functions[p-options](param);
787                 if (s) {
788                         s->label = prev;
789                         s->next = 0;
790                         if (!first)
791                                 first = s;
792                         else
793                                 last->next = s;
794                         last = s;
795                 } else {
796                         // %NNNNd or %r option. remove it from string
797                         strcpy(prev + strlen(prev), cur);
798                         cur = prev;
799                 }
800         }
801         if (prev[0]) {
802                 s = init_literal();
803                 s->label = prev;
804                 s->next = 0;
805                 if (!first)
806                         first = s;
807                 else
808                         last->next = s;
809                 last = s;
810         }
811
812         // Generate first samples but do not print them, they're bogus
813         collect_info(first);
814         reset_outbuf();
815         if (delta >= 0) {
816                 gettimeofday(&tv, 0);
817                 usleep(delta > 1000000 ? 1000000 : delta - tv.tv_usec%deltanz);
818         }
819
820         while (1) {
821                 gettimeofday(&tv, 0);
822                 collect_info(first);
823                 put(final_str);
824                 print_outbuf();
825
826                 // Negative delta -> no usleep at all
827                 // This will hog the CPU but you can have REALLY GOOD
828                 // time resolution ;)
829                 // TODO: detect and avoid useless updates
830                 // (like: nothing happens except time)
831                 if (delta >= 0) {
832                         int rem;
833                         // can be commented out, will sacrifice sleep time precision a bit
834                         gettimeofday(&tv, 0);
835                         if (need_seconds)
836                                 rem = delta - ((ullong)tv.tv_sec*1000000+tv.tv_usec)%deltanz;
837                         else
838                                 rem = delta - tv.tv_usec%deltanz;
839                         // Sometimes kernel wakes us up just a tiny bit earlier than asked
840                         // Do not go to very short sleep in this case
841                         if (rem < delta/128) {
842                                 rem += delta;
843                         }
844                         usleep(rem);
845                 }
846         }
847
848         return 0;
849 }