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