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
277
278
279static int rdval_diskstats(const char* p, ullong *vec)
280{
281 char devname[32];
282 unsigned devname_len = 0;
283 int value_idx = 0;
284
285 vec[0] = 0;
286 vec[1] = 0;
287 while (1) {
288 value_idx++;
289 while (*p == ' ' || *p == '\t')
290 p++;
291 if (*p == '\0')
292 break;
293 if (*p == '\n') {
294 value_idx = 0;
295 p++;
296 continue;
297 }
298 if (value_idx == 3) {
299 char *end = strchrnul(p, ' ');
300
301 if (devname_len && strncmp(devname, p, devname_len) == 0 && isdigit(p[devname_len])) {
302 p = end;
303 goto skip_line;
304 }
305
306 devname_len = end - p;
307 if (devname_len > sizeof(devname)-1)
308 devname_len = sizeof(devname)-1;
309 strncpy(devname, p, devname_len);
310
311 p = end;
312 } else
313 if (value_idx == 6) {
314
315 vec[0] += strtoull(p, NULL, 10);
316 } else
317 if (value_idx == 10) {
318
319 vec[1] += strtoull(p, NULL, 10);
320 skip_line:
321 while (*p != '\n' && *p != '\0')
322 p++;
323 continue;
324 }
325 while ((unsigned char)(*p) > ' ')
326 p++;
327 }
328 return 0;
329}
330
331static void scale(ullong ul)
332{
333 char buf[5];
334
335
336 smart_ulltoa4(ul, buf, " kmgtpezy");
337 buf[4] = '\0';
338 put(buf);
339}
340
341
342#define S_STAT(a) \
343typedef struct a { \
344 struct s_stat *next; \
345 void (*collect)(struct a *s) FAST_FUNC; \
346 const char *label;
347#define S_STAT_END(a) } a;
348
349S_STAT(s_stat)
350S_STAT_END(s_stat)
351
352static void FAST_FUNC collect_literal(s_stat *s UNUSED_PARAM)
353{
354}
355
356static s_stat* init_literal(void)
357{
358 s_stat *s = xzalloc(sizeof(*s));
359 s->collect = collect_literal;
360 return (s_stat*)s;
361}
362
363static s_stat* init_delay(const char *param)
364{
365 delta = strtoul(param, NULL, 0) * 1000;
366 deltanz = delta > 0 ? delta : 1;
367 need_seconds = (1000000%deltanz) != 0;
368 return NULL;
369}
370
371static s_stat* init_cr(const char *param UNUSED_PARAM)
372{
373 final_str = "\r";
374 return (s_stat*)0;
375}
376
377
378
379
380
381enum { CPU_FIELDCNT = 7 };
382S_STAT(cpu_stat)
383 ullong old[CPU_FIELDCNT];
384 int bar_sz;
385 char *bar;
386S_STAT_END(cpu_stat)
387
388
389static void FAST_FUNC collect_cpu(cpu_stat *s)
390{
391 ullong data[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
392 unsigned frac[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
393 ullong all = 0;
394 int norm_all = 0;
395 int bar_sz = s->bar_sz;
396 char *bar = s->bar;
397 int i;
398
399 if (rdval(get_file(&proc_stat), "cpu ", data, 1, 2, 3, 4, 5, 6, 7)) {
400 put_question_marks(bar_sz);
401 return;
402 }
403
404 for (i = 0; i < CPU_FIELDCNT; i++) {
405 ullong old = s->old[i];
406 if (data[i] < old) old = data[i];
407 s->old[i] = data[i];
408 all += (data[i] -= old);
409 }
410
411 if (all) {
412 for (i = 0; i < CPU_FIELDCNT; i++) {
413 ullong t = bar_sz * data[i];
414 norm_all += data[i] = t / all;
415 frac[i] = t % all;
416 }
417
418 while (norm_all < bar_sz) {
419 unsigned max = frac[0];
420 int pos = 0;
421 for (i = 1; i < CPU_FIELDCNT; i++) {
422 if (frac[i] > max) max = frac[i], pos = i;
423 }
424 frac[pos] = 0;
425 data[pos]++;
426 norm_all++;
427 }
428
429 memset(bar, '.', bar_sz);
430 memset(bar, 'S', data[2]); bar += data[2];
431 memset(bar, 'U', data[0]); bar += data[0];
432 memset(bar, 'N', data[1]); bar += data[1];
433 memset(bar, 'D', data[4]); bar += data[4];
434 memset(bar, 'I', data[5]); bar += data[5];
435 memset(bar, 'i', data[6]); bar += data[6];
436 } else {
437 memset(bar, '?', bar_sz);
438 }
439 put(s->bar);
440}
441
442
443static s_stat* init_cpu(const char *param)
444{
445 int sz;
446 cpu_stat *s = xzalloc(sizeof(*s));
447 s->collect = collect_cpu;
448 sz = strtoul(param, NULL, 0);
449 if (sz < 10) sz = 10;
450 if (sz > 1000) sz = 1000;
451 s->bar = xzalloc(sz+1);
452
453 s->bar_sz = sz;
454 return (s_stat*)s;
455}
456
457
458S_STAT(int_stat)
459 ullong old;
460 int no;
461S_STAT_END(int_stat)
462
463static void FAST_FUNC collect_int(int_stat *s)
464{
465 ullong data[1];
466 ullong old;
467
468 if (rdval(get_file(&proc_stat), "intr", data, s->no)) {
469 put_question_marks(4);
470 return;
471 }
472
473 old = s->old;
474 if (data[0] < old) old = data[0];
475 s->old = data[0];
476 scale(data[0] - old);
477}
478
479static s_stat* init_int(const char *param)
480{
481 int_stat *s = xzalloc(sizeof(*s));
482 s->collect = collect_int;
483 if (param[0] == '\0') {
484 s->no = 1;
485 } else {
486 int n = xatoi_positive(param);
487 s->no = n + 2;
488 }
489 return (s_stat*)s;
490}
491
492
493S_STAT(ctx_stat)
494 ullong old;
495S_STAT_END(ctx_stat)
496
497static void FAST_FUNC collect_ctx(ctx_stat *s)
498{
499 ullong data[1];
500 ullong old;
501
502 if (rdval(get_file(&proc_stat), "ctxt", data, 1)) {
503 put_question_marks(4);
504 return;
505 }
506
507 old = s->old;
508 if (data[0] < old) old = data[0];
509 s->old = data[0];
510 scale(data[0] - old);
511}
512
513static s_stat* init_ctx(const char *param UNUSED_PARAM)
514{
515 ctx_stat *s = xzalloc(sizeof(*s));
516 s->collect = collect_ctx;
517 return (s_stat*)s;
518}
519
520
521S_STAT(blk_stat)
522 const char* lookfor;
523 ullong old[2];
524S_STAT_END(blk_stat)
525
526static void FAST_FUNC collect_blk(blk_stat *s)
527{
528 ullong data[2];
529 int i;
530
531 if (is26) {
532 i = rdval_diskstats(get_file(&proc_diskstats), data);
533 } else {
534 i = rdval(get_file(&proc_stat), s->lookfor, data, 1, 2);
535
536 data[0] *= 2;
537 data[1] *= 2;
538 }
539 if (i) {
540 put_question_marks(9);
541 return;
542 }
543
544 for (i=0; i<2; i++) {
545 ullong old = s->old[i];
546 if (data[i] < old) old = data[i];
547 s->old[i] = data[i];
548 data[i] -= old;
549 }
550 scale(data[0]*512);
551 put_c(' ');
552 scale(data[1]*512);
553}
554
555static s_stat* init_blk(const char *param UNUSED_PARAM)
556{
557 blk_stat *s = xzalloc(sizeof(*s));
558 s->collect = collect_blk;
559 s->lookfor = "page";
560 return (s_stat*)s;
561}
562
563
564S_STAT(fork_stat)
565 ullong old;
566S_STAT_END(fork_stat)
567
568static void FAST_FUNC collect_thread_nr(fork_stat *s UNUSED_PARAM)
569{
570 ullong data[1];
571
572 if (rdval_loadavg(get_file(&proc_loadavg), data, 4)) {
573 put_question_marks(4);
574 return;
575 }
576 scale(data[0]);
577}
578
579static void FAST_FUNC collect_fork(fork_stat *s)
580{
581 ullong data[1];
582 ullong old;
583
584 if (rdval(get_file(&proc_stat), "processes", data, 1)) {
585 put_question_marks(4);
586 return;
587 }
588
589 old = s->old;
590 if (data[0] < old) old = data[0];
591 s->old = data[0];
592 scale(data[0] - old);
593}
594
595static s_stat* init_fork(const char *param)
596{
597 fork_stat *s = xzalloc(sizeof(*s));
598 if (*param == 'n') {
599 s->collect = collect_thread_nr;
600 } else {
601 s->collect = collect_fork;
602 }
603 return (s_stat*)s;
604}
605
606
607S_STAT(if_stat)
608 ullong old[4];
609 const char *device;
610 char *device_colon;
611S_STAT_END(if_stat)
612
613static void FAST_FUNC collect_if(if_stat *s)
614{
615 ullong data[4];
616 int i;
617
618 if (rdval(get_file(&proc_net_dev), s->device_colon, data, 1, 3, 9, 11)) {
619 put_question_marks(10);
620 return;
621 }
622
623 for (i=0; i<4; i++) {
624 ullong old = s->old[i];
625 if (data[i] < old) old = data[i];
626 s->old[i] = data[i];
627 data[i] -= old;
628 }
629 put_c(data[1] ? '*' : ' ');
630 scale(data[0]);
631 put_c(data[3] ? '*' : ' ');
632 scale(data[2]);
633}
634
635static s_stat* init_if(const char *device)
636{
637 if_stat *s = xzalloc(sizeof(*s));
638
639 if (!device || !device[0])
640 bb_show_usage();
641 s->collect = collect_if;
642
643 s->device = device;
644 s->device_colon = xasprintf("%s:", device);
645 return (s_stat*)s;
646}
647
648
649S_STAT(mem_stat)
650 char opt;
651S_STAT_END(mem_stat)
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688static void FAST_FUNC collect_mem(mem_stat *s)
689{
690 ullong m_total = 0;
691 ullong m_free = 0;
692 ullong m_bufs = 0;
693 ullong m_cached = 0;
694 ullong m_slab = 0;
695
696 if (rdval(get_file(&proc_meminfo), "MemTotal:", &m_total, 1)) {
697 put_question_marks(4);
698 return;
699 }
700 if (s->opt == 't') {
701 scale(m_total << 10);
702 return;
703 }
704
705 if (rdval(proc_meminfo.file, "MemFree:", &m_free , 1)
706 || rdval(proc_meminfo.file, "Buffers:", &m_bufs , 1)
707 || rdval(proc_meminfo.file, "Cached:", &m_cached, 1)
708 || rdval(proc_meminfo.file, "Slab:", &m_slab , 1)
709 ) {
710 put_question_marks(4);
711 return;
712 }
713
714 m_free += m_bufs + m_cached + m_slab;
715 switch (s->opt) {
716 case 'f':
717 scale(m_free << 10); break;
718 default:
719 scale((m_total - m_free) << 10); break;
720 }
721}
722
723static s_stat* init_mem(const char *param)
724{
725 mem_stat *s = xzalloc(sizeof(*s));
726 s->collect = collect_mem;
727 s->opt = param[0];
728 return (s_stat*)s;
729}
730
731
732S_STAT(swp_stat)
733S_STAT_END(swp_stat)
734
735static void FAST_FUNC collect_swp(swp_stat *s UNUSED_PARAM)
736{
737 ullong s_total[1];
738 ullong s_free[1];
739 if (rdval(get_file(&proc_meminfo), "SwapTotal:", s_total, 1)
740 || rdval(proc_meminfo.file, "SwapFree:" , s_free, 1)
741 ) {
742 put_question_marks(4);
743 return;
744 }
745 scale((s_total[0]-s_free[0]) << 10);
746}
747
748static s_stat* init_swp(const char *param UNUSED_PARAM)
749{
750 swp_stat *s = xzalloc(sizeof(*s));
751 s->collect = collect_swp;
752 return (s_stat*)s;
753}
754
755
756S_STAT(fd_stat)
757S_STAT_END(fd_stat)
758
759static void FAST_FUNC collect_fd(fd_stat *s UNUSED_PARAM)
760{
761 ullong data[2];
762
763 if (rdval(get_file(&proc_sys_fs_filenr), "", data, 1, 2)) {
764 put_question_marks(4);
765 return;
766 }
767
768 scale(data[0] - data[1]);
769}
770
771static s_stat* init_fd(const char *param UNUSED_PARAM)
772{
773 fd_stat *s = xzalloc(sizeof(*s));
774 s->collect = collect_fd;
775 return (s_stat*)s;
776}
777
778
779S_STAT(time_stat)
780 int prec;
781 int scale;
782S_STAT_END(time_stat)
783
784static void FAST_FUNC collect_time(time_stat *s)
785{
786 char buf[sizeof("12:34:56.123456")];
787 struct tm* tm;
788 int us = tv.tv_usec + s->scale/2;
789 time_t t = tv.tv_sec;
790
791 if (us >= 1000000) {
792 t++;
793 us -= 1000000;
794 }
795 tm = localtime(&t);
796
797 sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
798 if (s->prec)
799 sprintf(buf+8, ".%0*d", s->prec, us / s->scale);
800 put(buf);
801}
802
803static s_stat* init_time(const char *param)
804{
805 int prec;
806 time_stat *s = xzalloc(sizeof(*s));
807
808 s->collect = collect_time;
809 prec = param[0] - '0';
810 if (prec < 0) prec = 0;
811 else if (prec > 6) prec = 6;
812 s->prec = prec;
813 s->scale = 1;
814 while (prec++ < 6)
815 s->scale *= 10;
816 return (s_stat*)s;
817}
818
819static void FAST_FUNC collect_info(s_stat *s)
820{
821 gen ^= 1;
822 while (s) {
823 put(s->label);
824 s->collect(s);
825 s = s->next;
826 }
827}
828
829
830typedef s_stat* init_func(const char *param);
831
832
833static const char options[] ALIGN1 = "ncmsfixptbdr";
834static init_func *const init_functions[] = {
835 init_if,
836 init_cpu,
837 init_mem,
838 init_swp,
839 init_fd,
840 init_int,
841 init_ctx,
842 init_fork,
843 init_time,
844 init_blk,
845 init_delay,
846 init_cr
847};
848
849int nmeter_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
850int nmeter_main(int argc UNUSED_PARAM, char **argv)
851{
852 char buf[32];
853 s_stat *first = NULL;
854 s_stat *last = NULL;
855 s_stat *s;
856 char *opt_d;
857 char *cur, *prev;
858
859 INIT_G();
860
861 xchdir("/proc");
862
863 if (open_read_close("version", buf, sizeof(buf)-1) > 0) {
864 buf[sizeof(buf)-1] = '\0';
865 is26 = (strstr(buf, " 2.4.") == NULL);
866 }
867
868 if (getopt32(argv, "d:", &opt_d))
869 init_delay(opt_d);
870 argv += optind;
871
872 if (!argv[0])
873 bb_show_usage();
874
875
876
877 cur = xstrdup(argv[0]);
878 while (1) {
879 char *param, *p;
880 prev = cur;
881 again:
882 cur = strchr(cur, '%');
883 if (!cur)
884 break;
885 if (cur[1] == '%') {
886 overlapping_strcpy(cur, cur + 1);
887 cur++;
888 goto again;
889 }
890 *cur++ = '\0';
891 if (cur[0] == '[') {
892
893 cur++;
894 p = strchr(options, cur[0]);
895 param = cur+1;
896 while (cur[0] != ']') {
897 if (!cur[0])
898 bb_show_usage();
899 cur++;
900 }
901 *cur++ = '\0';
902 } else {
903
904 param = cur;
905 while (cur[0] >= '0' && cur[0] <= '9')
906 cur++;
907 if (!cur[0])
908 bb_show_usage();
909 p = strchr(options, cur[0]);
910 *cur++ = '\0';
911 }
912 if (!p)
913 bb_show_usage();
914 s = init_functions[p-options](param);
915 if (s) {
916 s->label = prev;
917
918 if (!first)
919 first = s;
920 else
921 last->next = s;
922 last = s;
923 } else {
924
925 strcpy(prev + strlen(prev), cur);
926 cur = prev;
927 }
928 }
929 if (prev[0]) {
930 s = init_literal();
931 s->label = prev;
932
933 if (!first)
934 first = s;
935 else
936 last->next = s;
937 last = s;
938 }
939
940
941 collect_info(first);
942 reset_outbuf();
943 if (delta >= 0) {
944 gettimeofday(&tv, NULL);
945 usleep(delta > 1000000 ? 1000000 : delta - tv.tv_usec%deltanz);
946 }
947
948 while (1) {
949 gettimeofday(&tv, NULL);
950 collect_info(first);
951 put(final_str);
952 print_outbuf();
953
954
955
956
957
958
959 if (delta >= 0) {
960 int rem;
961
962 gettimeofday(&tv, NULL);
963 if (need_seconds)
964 rem = delta - ((ullong)tv.tv_sec*1000000 + tv.tv_usec) % deltanz;
965 else
966 rem = delta - tv.tv_usec%deltanz;
967
968
969 if (rem < delta/128) {
970 rem += delta;
971 }
972 usleep(rem);
973 }
974 }
975
976
977}
978