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