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