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