1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#include "libbb.h"
21
22typedef unsigned long long ullong;
23
24enum {
25 PROC_MIN_FILE_SIZE = 256,
26 PROC_MAX_FILE_SIZE = 16 * 1024,
27};
28
29typedef struct proc_file {
30 char *file;
31 int file_sz;
32 smallint last_gen;
33} proc_file;
34
35static const char *const proc_name[] = {
36 "stat",
37 "loadavg",
38 "net/dev",
39 "meminfo",
40 "diskstats",
41 "sys/fs/file-nr"
42};
43
44struct globals {
45
46 smallint gen;
47
48 smallint is26;
49
50 smallint need_seconds;
51 char *cur_outbuf;
52 const char *final_str;
53 int delta;
54 int deltanz;
55 struct timeval tv;
56#define first_proc_file proc_stat
57 proc_file proc_stat;
58 proc_file proc_loadavg;
59 proc_file proc_net_dev;
60 proc_file proc_meminfo;
61 proc_file proc_diskstats;
62 proc_file proc_sys_fs_filenr;
63};
64#define G (*ptr_to_globals)
65#define gen (G.gen )
66#define is26 (G.is26 )
67#define need_seconds (G.need_seconds )
68#define cur_outbuf (G.cur_outbuf )
69#define final_str (G.final_str )
70#define delta (G.delta )
71#define deltanz (G.deltanz )
72#define tv (G.tv )
73#define proc_stat (G.proc_stat )
74#define proc_loadavg (G.proc_loadavg )
75#define proc_net_dev (G.proc_net_dev )
76#define proc_meminfo (G.proc_meminfo )
77#define proc_diskstats (G.proc_diskstats )
78#define proc_sys_fs_filenr (G.proc_sys_fs_filenr)
79#define INIT_G() do { \
80 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
81 cur_outbuf = outbuf; \
82 final_str = "\n"; \
83 deltanz = delta = 1000000; \
84} while (0)
85
86
87#define outbuf bb_common_bufsiz1
88
89static inline void reset_outbuf(void)
90{
91 cur_outbuf = outbuf;
92}
93
94static inline int outbuf_count(void)
95{
96 return cur_outbuf - outbuf;
97}
98
99static void print_outbuf(void)
100{
101 int sz = cur_outbuf - outbuf;
102 if (sz > 0) {
103 xwrite(STDOUT_FILENO, outbuf, sz);
104 cur_outbuf = outbuf;
105 }
106}
107
108static void put(const char *s)
109{
110 int sz = strlen(s);
111 if (sz > outbuf + sizeof(outbuf) - cur_outbuf)
112 sz = outbuf + sizeof(outbuf) - cur_outbuf;
113 memcpy(cur_outbuf, s, sz);
114 cur_outbuf += sz;
115}
116
117static void put_c(char c)
118{
119 if (cur_outbuf < outbuf + sizeof(outbuf))
120 *cur_outbuf++ = c;
121}
122
123static void put_question_marks(int count)
124{
125 while (count--)
126 put_c('?');
127}
128
129static void readfile_z(proc_file *pf, const char* fname)
130{
131
132
133 int fd;
134 int sz, rdsz;
135 char *buf;
136
137 sz = pf->file_sz;
138 buf = pf->file;
139 if (!buf) {
140 buf = xmalloc(PROC_MIN_FILE_SIZE);
141 sz = PROC_MIN_FILE_SIZE;
142 }
143 again:
144 fd = xopen(fname, O_RDONLY);
145 buf[0] = '\0';
146 rdsz = read(fd, buf, sz-1);
147 close(fd);
148 if (rdsz > 0) {
149 if (rdsz == sz-1 && sz < PROC_MAX_FILE_SIZE) {
150 sz *= 2;
151 buf = xrealloc(buf, sz);
152 goto again;
153 }
154 buf[rdsz] = '\0';
155 }
156 pf->file_sz = sz;
157 pf->file = buf;
158}
159
160static const char* get_file(proc_file *pf)
161{
162 if (pf->last_gen != gen) {
163 pf->last_gen = gen;
164 readfile_z(pf, proc_name[pf - &first_proc_file]);
165 }
166 return pf->file;
167}
168
169static ullong read_after_slash(const char *p)
170{
171 p = strchr(p, '/');
172 if (!p) return 0;
173 return strtoull(p+1, NULL, 10);
174}
175
176enum conv_type { conv_decimal, conv_slash };
177
178
179
180
181
182static int vrdval(const char* p, const char* key,
183 enum conv_type conv, ullong *vec, va_list arg_ptr)
184{
185 int indexline;
186 int indexnext;
187
188 p = strstr(p, key);
189 if (!p) return 1;
190
191 p += strlen(key);
192 indexline = 1;
193 indexnext = va_arg(arg_ptr, int);
194 while (1) {
195 while (*p == ' ' || *p == '\t') p++;
196 if (*p == '\n' || *p == '\0') break;
197
198 if (indexline == indexnext) {
199 *vec++ = conv==conv_decimal ?
200 strtoull(p, NULL, 10) :
201 read_after_slash(p);
202 indexnext = va_arg(arg_ptr, int);
203 }
204 while (*p > ' ') p++;
205 indexline++;
206 }
207 return 0;
208}
209
210
211
212
213static int rdval(const char* p, const char* key, ullong *vec, ...)
214{
215 va_list arg_ptr;
216 int result;
217
218 va_start(arg_ptr, vec);
219 result = vrdval(p, key, conv_decimal, vec, arg_ptr);
220 va_end(arg_ptr);
221
222 return result;
223}
224
225
226static int rdval_loadavg(const char* p, ullong *vec, ...)
227{
228 va_list arg_ptr;
229 int result;
230
231 va_start(arg_ptr, vec);
232 result = vrdval(p, "", conv_slash, vec, arg_ptr);
233 va_end(arg_ptr);
234
235 return result;
236}
237
238
239
240
241
242static int rdval_diskstats(const char* p, ullong *vec)
243{
244 ullong rd = rd;
245 int indexline = 0;
246 vec[0] = 0;
247 vec[1] = 0;
248 while (1) {
249 indexline++;
250 while (*p == ' ' || *p == '\t') p++;
251 if (*p == '\0') break;
252 if (*p == '\n') {
253 indexline = 0;
254 p++;
255 continue;
256 }
257 if (indexline == 6) {
258 rd = strtoull(p, NULL, 10);
259 } else if (indexline == 10) {
260 vec[0] += rd;
261 vec[1] += strtoull(p, NULL, 10);
262 while (*p != '\n' && *p != '\0') p++;
263 continue;
264 }
265 while (*p > ' ') p++;
266 }
267 return 0;
268}
269
270static void scale(ullong ul)
271{
272 char buf[5];
273
274
275 smart_ulltoa4(ul, buf, " kmgtpezy");
276 buf[4] = '\0';
277 put(buf);
278}
279
280
281#define S_STAT(a) \
282typedef struct a { \
283 struct s_stat *next; \
284 void (*collect)(struct a *s); \
285 const char *label;
286#define S_STAT_END(a) } a;
287
288S_STAT(s_stat)
289S_STAT_END(s_stat)
290
291static void collect_literal(s_stat *s UNUSED_PARAM)
292{
293}
294
295static s_stat* init_literal(void)
296{
297 s_stat *s = xzalloc(sizeof(*s));
298 s->collect = collect_literal;
299 return (s_stat*)s;
300}
301
302static s_stat* init_delay(const char *param)
303{
304 delta = strtoul(param, NULL, 0) * 1000;
305 deltanz = delta > 0 ? delta : 1;
306 need_seconds = (1000000%deltanz) != 0;
307 return NULL;
308}
309
310static s_stat* init_cr(const char *param UNUSED_PARAM)
311{
312 final_str = "\r";
313 return (s_stat*)0;
314}
315
316
317
318
319
320enum { CPU_FIELDCNT = 7 };
321S_STAT(cpu_stat)
322 ullong old[CPU_FIELDCNT];
323 int bar_sz;
324 char *bar;
325S_STAT_END(cpu_stat)
326
327
328static void collect_cpu(cpu_stat *s)
329{
330 ullong data[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
331 unsigned frac[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
332 ullong all = 0;
333 int norm_all = 0;
334 int bar_sz = s->bar_sz;
335 char *bar = s->bar;
336 int i;
337
338 if (rdval(get_file(&proc_stat), "cpu ", data, 1, 2, 3, 4, 5, 6, 7)) {
339 put_question_marks(bar_sz);
340 return;
341 }
342
343 for (i = 0; i < CPU_FIELDCNT; i++) {
344 ullong old = s->old[i];
345 if (data[i] < old) old = data[i];
346 s->old[i] = data[i];
347 all += (data[i] -= old);
348 }
349
350 if (all) {
351 for (i = 0; i < CPU_FIELDCNT; i++) {
352 ullong t = bar_sz * data[i];
353 norm_all += data[i] = t / all;
354 frac[i] = t % all;
355 }
356
357 while (norm_all < bar_sz) {
358 unsigned max = frac[0];
359 int pos = 0;
360 for (i = 1; i < CPU_FIELDCNT; i++) {
361 if (frac[i] > max) max = frac[i], pos = i;
362 }
363 frac[pos] = 0;
364 data[pos]++;
365 norm_all++;
366 }
367
368 memset(bar, '.', bar_sz);
369 memset(bar, 'S', data[2]); bar += data[2];
370 memset(bar, 'U', data[0]); bar += data[0];
371 memset(bar, 'N', data[1]); bar += data[1];
372 memset(bar, 'D', data[4]); bar += data[4];
373 memset(bar, 'I', data[5]); bar += data[5];
374 memset(bar, 'i', data[6]); bar += data[6];
375 } else {
376 memset(bar, '?', bar_sz);
377 }
378 put(s->bar);
379}
380
381
382static s_stat* init_cpu(const char *param)
383{
384 int sz;
385 cpu_stat *s = xzalloc(sizeof(*s));
386 s->collect = collect_cpu;
387 sz = strtoul(param, NULL, 0);
388 if (sz < 10) sz = 10;
389 if (sz > 1000) sz = 1000;
390 s->bar = xzalloc(sz+1);
391
392 s->bar_sz = sz;
393 return (s_stat*)s;
394}
395
396
397S_STAT(int_stat)
398 ullong old;
399 int no;
400S_STAT_END(int_stat)
401
402static void collect_int(int_stat *s)
403{
404 ullong data[1];
405 ullong old;
406
407 if (rdval(get_file(&proc_stat), "intr", data, s->no)) {
408 put_question_marks(4);
409 return;
410 }
411
412 old = s->old;
413 if (data[0] < old) old = data[0];
414 s->old = data[0];
415 scale(data[0] - old);
416}
417
418static s_stat* init_int(const char *param)
419{
420 int_stat *s = xzalloc(sizeof(*s));
421 s->collect = collect_int;
422 if (param[0] == '\0') {
423 s->no = 1;
424 } else {
425 int n = xatoi_u(param);
426 s->no = n + 2;
427 }
428 return (s_stat*)s;
429}
430
431
432S_STAT(ctx_stat)
433 ullong old;
434S_STAT_END(ctx_stat)
435
436static void collect_ctx(ctx_stat *s)
437{
438 ullong data[1];
439 ullong old;
440
441 if (rdval(get_file(&proc_stat), "ctxt", data, 1)) {
442 put_question_marks(4);
443 return;
444 }
445
446 old = s->old;
447 if (data[0] < old) old = data[0];
448 s->old = data[0];
449 scale(data[0] - old);
450}
451
452static s_stat* init_ctx(const char *param UNUSED_PARAM)
453{
454 ctx_stat *s = xzalloc(sizeof(*s));
455 s->collect = collect_ctx;
456 return (s_stat*)s;
457}
458
459
460S_STAT(blk_stat)
461 const char* lookfor;
462 ullong old[2];
463S_STAT_END(blk_stat)
464
465static void collect_blk(blk_stat *s)
466{
467 ullong data[2];
468 int i;
469
470 if (is26) {
471 i = rdval_diskstats(get_file(&proc_diskstats), data);
472 } else {
473 i = rdval(get_file(&proc_stat), s->lookfor, data, 1, 2);
474
475 data[0] *= 2;
476 data[1] *= 2;
477 }
478 if (i) {
479 put_question_marks(9);
480 return;
481 }
482
483 for (i=0; i<2; i++) {
484 ullong old = s->old[i];
485 if (data[i] < old) old = data[i];
486 s->old[i] = data[i];
487 data[i] -= old;
488 }
489 scale(data[0]*512);
490 put_c(' ');
491 scale(data[1]*512);
492}
493
494static s_stat* init_blk(const char *param UNUSED_PARAM)
495{
496 blk_stat *s = xzalloc(sizeof(*s));
497 s->collect = collect_blk;
498 s->lookfor = "page";
499 return (s_stat*)s;
500}
501
502
503S_STAT(fork_stat)
504 ullong old;
505S_STAT_END(fork_stat)
506
507static void collect_thread_nr(fork_stat *s UNUSED_PARAM)
508{
509 ullong data[1];
510
511 if (rdval_loadavg(get_file(&proc_loadavg), data, 4)) {
512 put_question_marks(4);
513 return;
514 }
515 scale(data[0]);
516}
517
518static void collect_fork(fork_stat *s)
519{
520 ullong data[1];
521 ullong old;
522
523 if (rdval(get_file(&proc_stat), "processes", data, 1)) {
524 put_question_marks(4);
525 return;
526 }
527
528 old = s->old;
529 if (data[0] < old) old = data[0];
530 s->old = data[0];
531 scale(data[0] - old);
532}
533
534static s_stat* init_fork(const char *param)
535{
536 fork_stat *s = xzalloc(sizeof(*s));
537 if (*param == 'n') {
538 s->collect = collect_thread_nr;
539 } else {
540 s->collect = collect_fork;
541 }
542 return (s_stat*)s;
543}
544
545
546S_STAT(if_stat)
547 ullong old[4];
548 const char *device;
549 char *device_colon;
550S_STAT_END(if_stat)
551
552static void collect_if(if_stat *s)
553{
554 ullong data[4];
555 int i;
556
557 if (rdval(get_file(&proc_net_dev), s->device_colon, data, 1, 3, 9, 11)) {
558 put_question_marks(10);
559 return;
560 }
561
562 for (i=0; i<4; i++) {
563 ullong old = s->old[i];
564 if (data[i] < old) old = data[i];
565 s->old[i] = data[i];
566 data[i] -= old;
567 }
568 put_c(data[1] ? '*' : ' ');
569 scale(data[0]);
570 put_c(data[3] ? '*' : ' ');
571 scale(data[2]);
572}
573
574static s_stat* init_if(const char *device)
575{
576 if_stat *s = xzalloc(sizeof(*s));
577
578 if (!device || !device[0])
579 bb_show_usage();
580 s->collect = collect_if;
581
582 s->device = device;
583 s->device_colon = xasprintf("%s:", device);
584 return (s_stat*)s;
585}
586
587
588S_STAT(mem_stat)
589 char opt;
590S_STAT_END(mem_stat)
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627static void collect_mem(mem_stat *s)
628{
629 ullong m_total = 0;
630 ullong m_free = 0;
631 ullong m_bufs = 0;
632 ullong m_cached = 0;
633 ullong m_slab = 0;
634
635 if (rdval(get_file(&proc_meminfo), "MemTotal:", &m_total, 1)) {
636 put_question_marks(4);
637 return;
638 }
639 if (s->opt == 't') {
640 scale(m_total << 10);
641 return;
642 }
643
644 if (rdval(proc_meminfo.file, "MemFree:", &m_free , 1)
645 || rdval(proc_meminfo.file, "Buffers:", &m_bufs , 1)
646 || rdval(proc_meminfo.file, "Cached:", &m_cached, 1)
647 || rdval(proc_meminfo.file, "Slab:", &m_slab , 1)
648 ) {
649 put_question_marks(4);
650 return;
651 }
652
653 m_free += m_bufs + m_cached + m_slab;
654 switch (s->opt) {
655 case 'f':
656 scale(m_free << 10); break;
657 default:
658 scale((m_total - m_free) << 10); break;
659 }
660}
661
662static s_stat* init_mem(const char *param)
663{
664 mem_stat *s = xzalloc(sizeof(*s));
665 s->collect = collect_mem;
666 s->opt = param[0];
667 return (s_stat*)s;
668}
669
670
671S_STAT(swp_stat)
672S_STAT_END(swp_stat)
673
674static void collect_swp(swp_stat *s UNUSED_PARAM)
675{
676 ullong s_total[1];
677 ullong s_free[1];
678 if (rdval(get_file(&proc_meminfo), "SwapTotal:", s_total, 1)
679 || rdval(proc_meminfo.file, "SwapFree:" , s_free, 1)
680 ) {
681 put_question_marks(4);
682 return;
683 }
684 scale((s_total[0]-s_free[0]) << 10);
685}
686
687static s_stat* init_swp(const char *param UNUSED_PARAM)
688{
689 swp_stat *s = xzalloc(sizeof(*s));
690 s->collect = collect_swp;
691 return (s_stat*)s;
692}
693
694
695S_STAT(fd_stat)
696S_STAT_END(fd_stat)
697
698static void collect_fd(fd_stat *s UNUSED_PARAM)
699{
700 ullong data[2];
701
702 if (rdval(get_file(&proc_sys_fs_filenr), "", data, 1, 2)) {
703 put_question_marks(4);
704 return;
705 }
706
707 scale(data[0] - data[1]);
708}
709
710static s_stat* init_fd(const char *param UNUSED_PARAM)
711{
712 fd_stat *s = xzalloc(sizeof(*s));
713 s->collect = collect_fd;
714 return (s_stat*)s;
715}
716
717
718S_STAT(time_stat)
719 int prec;
720 int scale;
721S_STAT_END(time_stat)
722
723static void collect_time(time_stat *s)
724{
725 char buf[sizeof("12:34:56.123456")];
726 struct tm* tm;
727 int us = tv.tv_usec + s->scale/2;
728 time_t t = tv.tv_sec;
729
730 if (us >= 1000000) {
731 t++;
732 us -= 1000000;
733 }
734 tm = localtime(&t);
735
736 sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
737 if (s->prec)
738 sprintf(buf+8, ".%0*d", s->prec, us / s->scale);
739 put(buf);
740}
741
742static s_stat* init_time(const char *param)
743{
744 int prec;
745 time_stat *s = xzalloc(sizeof(*s));
746
747 s->collect = collect_time;
748 prec = param[0] - '0';
749 if (prec < 0) prec = 0;
750 else if (prec > 6) prec = 6;
751 s->prec = prec;
752 s->scale = 1;
753 while (prec++ < 6)
754 s->scale *= 10;
755 return (s_stat*)s;
756}
757
758static void collect_info(s_stat *s)
759{
760 gen ^= 1;
761 while (s) {
762 put(s->label);
763 s->collect(s);
764 s = s->next;
765 }
766}
767
768
769typedef s_stat* init_func(const char *param);
770
771static const char options[] ALIGN1 = "ncmsfixptbdr";
772static init_func *const init_functions[] = {
773 init_if,
774 init_cpu,
775 init_mem,
776 init_swp,
777 init_fd,
778 init_int,
779 init_ctx,
780 init_fork,
781 init_time,
782 init_blk,
783 init_delay,
784 init_cr
785};
786
787int nmeter_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
788int nmeter_main(int argc, char **argv)
789{
790 char buf[32];
791 s_stat *first = NULL;
792 s_stat *last = NULL;
793 s_stat *s;
794 char *cur, *prev;
795
796 INIT_G();
797
798 xchdir("/proc");
799
800 if (argc != 2)
801 bb_show_usage();
802
803 if (open_read_close("version", buf, sizeof(buf)-1) > 0) {
804 buf[sizeof(buf)-1] = '\0';
805 is26 = (strstr(buf, " 2.4.") == NULL);
806 }
807
808
809
810 cur = xstrdup(argv[1]);
811 while (1) {
812 char *param, *p;
813 prev = cur;
814 again:
815 cur = strchr(cur, '%');
816 if (!cur)
817 break;
818 if (cur[1] == '%') {
819 overlapping_strcpy(cur, cur + 1);
820 cur++;
821 goto again;
822 }
823 *cur++ = '\0';
824 if (cur[0] == '[') {
825
826 cur++;
827 p = strchr(options, cur[0]);
828 param = cur+1;
829 while (cur[0] != ']') {
830 if (!cur[0])
831 bb_show_usage();
832 cur++;
833 }
834 *cur++ = '\0';
835 } else {
836
837 param = cur;
838 while (cur[0] >= '0' && cur[0] <= '9')
839 cur++;
840 if (!cur[0])
841 bb_show_usage();
842 p = strchr(options, cur[0]);
843 *cur++ = '\0';
844 }
845 if (!p)
846 bb_show_usage();
847 s = init_functions[p-options](param);
848 if (s) {
849 s->label = prev;
850
851 if (!first)
852 first = s;
853 else
854 last->next = s;
855 last = s;
856 } else {
857
858 strcpy(prev + strlen(prev), cur);
859 cur = prev;
860 }
861 }
862 if (prev[0]) {
863 s = init_literal();
864 s->label = prev;
865
866 if (!first)
867 first = s;
868 else
869 last->next = s;
870 last = s;
871 }
872
873
874 collect_info(first);
875 reset_outbuf();
876 if (delta >= 0) {
877 gettimeofday(&tv, NULL);
878 usleep(delta > 1000000 ? 1000000 : delta - tv.tv_usec%deltanz);
879 }
880
881 while (1) {
882 gettimeofday(&tv, NULL);
883 collect_info(first);
884 put(final_str);
885 print_outbuf();
886
887
888
889
890
891
892 if (delta >= 0) {
893 int rem;
894
895 gettimeofday(&tv, NULL);
896 if (need_seconds)
897 rem = delta - ((ullong)tv.tv_sec*1000000 + tv.tv_usec) % deltanz;
898 else
899 rem = delta - tv.tv_usec%deltanz;
900
901
902 if (rem < delta/128) {
903 rem += delta;
904 }
905 usleep(rem);
906 }
907 }
908
909
910}
911