2
* Licensed under GPLv2, see file LICENSE in this source tree.
4
* Based on nanotop.c from floppyfw project
6
* Contact me: vda.linux@googlemail.com
9
//config: bool "nmeter (12 kb)"
12
//config: Prints selected system stats continuously, one line per update.
14
//applet:IF_NMETER(APPLET(nmeter, BB_DIR_USR_BIN, BB_SUID_DROP))
16
//kbuild:lib-$(CONFIG_NMETER) += nmeter.o
18
//usage:#define nmeter_trivial_usage
19
//usage: "[-d MSEC] FORMAT_STRING"
20
//usage:#define nmeter_full_usage "\n\n"
21
//usage: "Monitor system in real time"
23
//usage: "\n -d MSEC Milliseconds between updates, default:1000, none:-1"
25
//usage: "\nFormat specifiers:"
26
//usage: "\n %Nc or %[cN] CPU. N - bar size (default 10)"
27
//usage: "\n (displays: S:system U:user N:niced D:iowait I:irq i:softirq)"
28
//usage: "\n %[nINTERFACE] Network INTERFACE"
29
//usage: "\n %m Allocated memory"
30
//usage: "\n %[md] Dirty file-backed memory"
31
//usage: "\n %[mw] Memory being written to storage"
32
//usage: "\n %[mf] Free memory"
33
//usage: "\n %[mt] Total memory"
34
//usage: "\n %s Allocated swap"
35
//usage: "\n %f Number of used file descriptors"
36
//usage: "\n %Ni Total/specific IRQ rate"
37
//usage: "\n %x Context switch rate"
38
//usage: "\n %p Forks"
39
//usage: "\n %[pn] # of processes"
40
//usage: "\n %b Block io"
41
//usage: "\n %Nt Time (with N decimal points)"
42
//usage: "\n %NT Zero-based timestamp (with N decimal points)"
43
//usage: "\n %r Print <cr> instead of <lf> at EOL"
49
// disk_io: (3,0):(22272,17897,410702,4375,54750)
51
//TODO: use sysinfo libc call/syscall, if appropriate
52
// (faster than open/read/close):
53
// sysinfo({uptime=15017, loads=[5728, 15040, 16480]
54
// totalram=2107416576, freeram=211525632, sharedram=0, bufferram=157204480}
55
// totalswap=134209536, freeswap=134209536, procs=157})
58
#include "common_bufsiz.h"
60
typedef unsigned long long ullong;
63
PROC_MIN_FILE_SIZE = 256,
64
PROC_MAX_FILE_SIZE = 64 * 1024, /* 16k was a bit too small for a 128-CPU machine */
67
typedef struct proc_file {
73
static const char *const proc_name[] ALIGN_PTR = {
74
"stat", // Must match the order of proc_file's!
83
// Sample generation flip-flop
85
// Linux 2.6? (otherwise assumes 2.4)
87
// 1 if sample delay is not an integer fraction of a second
88
smallint need_seconds;
95
#define first_proc_file proc_stat
96
proc_file proc_stat; // Must match the order of proc_name's!
97
proc_file proc_loadavg;
98
proc_file proc_net_dev;
99
proc_file proc_meminfo;
100
proc_file proc_diskstats;
101
proc_file proc_sys_fs_filenr;
103
#define G (*ptr_to_globals)
105
#define is26 (G.is26 )
106
#define need_seconds (G.need_seconds )
107
#define cur_outbuf (G.cur_outbuf )
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 outbuf bb_common_bufsiz1
115
#define INIT_G() do { \
116
setup_common_bufsiz(); \
117
SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
118
cur_outbuf = outbuf; \
119
G.final_char = '\n'; \
120
G.deltanz = G.delta = 1000000; \
123
static inline void reset_outbuf(void)
128
static void print_outbuf(void)
130
int sz = cur_outbuf - outbuf;
132
xwrite(STDOUT_FILENO, outbuf, sz);
137
static void put(const char *s)
139
char *p = cur_outbuf;
140
int sz = outbuf + COMMON_BUFSIZE - p;
141
while (*s && --sz >= 0)
146
static void put_c(char c)
148
if (cur_outbuf < outbuf + COMMON_BUFSIZE)
152
static void put_question_marks(int count)
158
static void readfile_z(proc_file *pf, const char* fname)
160
// open_read_close() will do two reads in order to be sure we are at EOF,
161
// and we don't need/want that.
169
buf = xmalloc(PROC_MIN_FILE_SIZE);
170
sz = PROC_MIN_FILE_SIZE;
173
fd = xopen(fname, O_RDONLY);
175
rdsz = read(fd, buf, sz-1);
178
if (rdsz == sz-1 && sz < PROC_MAX_FILE_SIZE) {
183
buf = xrealloc(buf, sz);
192
static const char* get_file(proc_file *pf)
194
if (pf->last_gen != gen) {
196
readfile_z(pf, proc_name[pf - &first_proc_file]);
201
static ullong read_after_slash(const char *p)
205
return strtoull(p+1, NULL, 10);
213
// Reads decimal values from line. Values start after key, for example:
214
// "cpu 649369 0 341297 4336769..." - key is "cpu" here.
215
// Values are stored in vec[].
216
// posbits is a bit list of positions we are interested in.
217
// for example: 00100110 - we want 1st, 2nd and 5th value.
218
// posbits.bit0 encodes conversion type.
219
static int rdval(const char* p, const char* key, ullong *vec, long posbits)
229
while (*p == ' ' || *p == '\t') p++;
230
if (*p == '\n' || *p == '\0') break;
232
if (curpos & posbits) { // read this value
233
*vec++ = (posbits & 1) == conv_decimal ?
234
strtoull(p, NULL, 10) :
240
while (*p > ' ') // skip over the value
247
// Parses files with lines like "... ... ... 3/148 ...."
248
static int rdval_loadavg(const char* p, ullong *vec, long posbits)
251
result = rdval(p, "", vec, posbits | conv_slash);
255
// Parses /proc/diskstats
256
// 1 2 3 4 5 6(rd) 7 8 9 10(wr) 11 12 13 14
257
// 3 0 hda 51292 14441 841783 926052 25717 79650 843256 3029804 0 148459 3956933
258
// 3 1 hda1 0 0 0 0 <- ignore if only 4 fields
259
// Linux 3.0 (maybe earlier) started printing full stats for hda1 too.
260
// Had to add code which skips such devices.
261
static int rdval_diskstats(const char* p, ullong *vec)
264
unsigned devname_len = 0;
271
while (*p == ' ' || *p == '\t')
280
if (value_idx == 3) {
281
char *end = strchrnul(p, ' ');
282
/* If this a hda1-like device (same prefix as last one + digit)? */
283
if (devname_len && strncmp(devname, p, devname_len) == 0 && isdigit(p[devname_len])) {
285
goto skip_line; /* skip entire line */
287
/* It is not. Remember the name for future checks */
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
/* devname[devname_len] = '\0'; - not really needed */
295
if (value_idx == 6) {
296
// TODO: *sectorsize (don't know how to find out sectorsize)
297
vec[0] += strtoull(p, NULL, 10);
299
if (value_idx == 10) {
300
// TODO: *sectorsize (don't know how to find out sectorsize)
301
vec[1] += strtoull(p, NULL, 10);
303
while (*p != '\n' && *p != '\0')
307
while ((unsigned char)(*p) > ' ') // skip over value
313
static void scale(ullong ul)
317
/* see http://en.wikipedia.org/wiki/Tera */
318
smart_ulltoa4(ul, buf, " kmgtpezy")[0] = '\0';
324
struct s_stat *next; \
325
void (*collect)(struct a *s) FAST_FUNC; \
327
#define S_STAT_END(a) } a;
332
static void FAST_FUNC collect_literal(s_stat *s UNUSED_PARAM)
336
static s_stat* init_literal(void)
338
s_stat *s = xzalloc(sizeof(*s));
339
s->collect = collect_literal;
343
static s_stat* init_cr(const char *param UNUSED_PARAM)
349
// user nice system idle iowait irq softirq (last 3 only in 2.6)
350
//cpu 649369 0 341297 4336769 11640 7122 1183
351
//cpuN 649369 0 341297 4336769 11640 7122 1183
352
enum { CPU_FIELDCNT = 7 };
354
ullong old[CPU_FIELDCNT];
359
static void FAST_FUNC collect_cpu(cpu_stat *s)
361
ullong data[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
362
unsigned frac[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 };
364
unsigned norm_all = 0;
365
unsigned bar_sz = s->bar_sz;
369
if (rdval(get_file(&proc_stat), "cpu ", data, 0
378
put_question_marks(bar_sz);
382
for (i = 0; i < CPU_FIELDCNT; i++) {
383
ullong old = s->old[i];
384
if (data[i] < old) old = data[i]; //sanitize
386
all += (data[i] -= old);
390
for (i = 0; i < CPU_FIELDCNT; i++) {
391
ullong t = bar_sz * data[i];
392
norm_all += data[i] = t / all;
396
while (norm_all < bar_sz) {
397
unsigned max = frac[0];
399
for (i = 1; i < CPU_FIELDCNT; i++) {
400
if (frac[i] > max) max = frac[i], pos = i;
402
frac[pos] = 0; //avoid bumping up same value twice
407
memset(bar, '.', bar_sz);
408
memset(bar, 'S', data[2]); bar += data[2]; //sys
409
memset(bar, 'U', data[0]); bar += data[0]; //usr
410
memset(bar, 'N', data[1]); bar += data[1]; //nice
411
memset(bar, 'D', data[4]); bar += data[4]; //iowait
412
memset(bar, 'I', data[5]); bar += data[5]; //irq
413
memset(bar, 'i', data[6]); bar += data[6]; //softirq
415
memset(bar, '?', bar_sz);
420
static s_stat* init_cpu(const char *param)
424
sz = param[0] ? strtoul(param, NULL, 0) : 10;
426
if (sz > 1000) sz = 1000;
427
s = xzalloc(sizeof(*s) + sz);
428
/*s->bar[sz] = '\0'; - xzalloc did it */
430
s->collect = collect_cpu;
439
static void FAST_FUNC collect_int(int_stat *s)
444
if (rdval(get_file(&proc_stat), "intr", data, 1 << s->no)) {
445
put_question_marks(4);
450
if (data[0] < old) old = data[0]; //sanitize
452
scale(data[0] - old);
455
static s_stat* init_int(const char *param)
457
int_stat *s = xzalloc(sizeof(*s));
458
s->collect = collect_int;
459
if (param[0] == '\0') {
462
int n = xatoi_positive(param);
472
static void FAST_FUNC collect_ctx(ctx_stat *s)
477
if (rdval(get_file(&proc_stat), "ctxt", data, 1 << 1)) {
478
put_question_marks(4);
483
if (data[0] < old) old = data[0]; //sanitize
485
scale(data[0] - old);
488
static s_stat* init_ctx(const char *param UNUSED_PARAM)
490
ctx_stat *s = xzalloc(sizeof(*s));
491
s->collect = collect_ctx;
500
static void FAST_FUNC collect_blk(blk_stat *s)
506
i = rdval_diskstats(get_file(&proc_diskstats), data);
508
i = rdval(get_file(&proc_stat), s->lookfor, data, 0
512
// Linux 2.4 reports bio in Kbytes, convert to sectors:
517
put_question_marks(9);
521
for (i = 0; i < 2; i++) {
522
ullong old = s->old[i];
523
if (data[i] < old) old = data[i]; //sanitize
527
scale(data[0]*512); // TODO: *sectorsize
532
static s_stat* init_blk(const char *param UNUSED_PARAM)
534
blk_stat *s = xzalloc(sizeof(*s));
535
s->collect = collect_blk;
542
S_STAT_END(fork_stat)
544
static void FAST_FUNC collect_thread_nr(fork_stat *s UNUSED_PARAM)
548
if (rdval_loadavg(get_file(&proc_loadavg), data, 1 << 4)) {
549
put_question_marks(4);
555
static void FAST_FUNC collect_fork(fork_stat *s)
560
if (rdval(get_file(&proc_stat), "processes", data, 1 << 1)) {
561
put_question_marks(4);
566
if (data[0] < old) old = data[0]; //sanitize
568
scale(data[0] - old);
571
static s_stat* init_fork(const char *param)
573
fork_stat *s = xzalloc(sizeof(*s));
575
s->collect = collect_thread_nr;
577
s->collect = collect_fork;
588
static void FAST_FUNC collect_if(if_stat *s)
593
if (rdval(get_file(&proc_net_dev), s->device_colon, data, 0
599
put_question_marks(10);
603
for (i = 0; i < 4; i++) {
604
ullong old = s->old[i];
605
if (data[i] < old) old = data[i]; //sanitize
609
put_c(data[1] ? '*' : ' ');
611
put_c(data[3] ? '*' : ' ');
615
static s_stat* init_if(const char *device)
617
if_stat *s = xzalloc(sizeof(*s));
619
if (!device || !device[0])
621
s->collect = collect_if;
624
s->device_colon = xasprintf("%s:", device);
632
// "Memory" value should not include any caches.
633
// IOW: neither "ls -laR /" nor heavy read/write activity
634
// should affect it. We'd like to also include any
635
// long-term allocated kernel-side mem, but it is hard
636
// to figure out. For now, bufs, cached & slab are
637
// counted as "free" memory
639
//MemTotal: 773280 kB
640
//MemFree: 25912 kB - genuinely free
641
//Buffers: 320672 kB - cache
642
//Cached: 146396 kB - cache
645
//Inactive: 356892 kB
648
//LowTotal: 773280 kB
650
//SwapTotal: 131064 kB
651
//SwapFree: 131064 kB
655
//Slab: 200668 kB - takes 7 Mb on my box fresh after boot,
656
// but includes dentries and inodes
657
// (== can take arbitrary amount of mem)
658
//CommitLimit: 517704 kB
659
//Committed_AS: 236776 kB
660
//PageTables: 1248 kB
661
//VmallocTotal: 516052 kB
662
//VmallocUsed: 3852 kB
663
//VmallocChunk: 512096 kB
666
//Hugepagesize: 4096 kB
667
static void FAST_FUNC collect_mem(mem_stat *s)
675
const char *meminfo = get_file(&proc_meminfo);
677
if (s->opt == 'd' /* dirty page cache */
678
|| s->opt == 'w' /* under writeback */
680
m_total = 0; /* temporary reuse m_total */
682
(s->opt == 'd' ? "Dirty:" : "Writeback:"),
685
put_question_marks(4);
688
scale(m_total << 10);
693
if (rdval(meminfo, "MemTotal:", &m_total, 1 << 1)) {
694
put_question_marks(4);
698
scale(m_total << 10);
706
if (rdval(meminfo, "MemFree:", &m_free , 1 << 1)
707
|| rdval(meminfo, "Buffers:", &m_bufs , 1 << 1)
708
|| rdval(meminfo, "Cached:", &m_cached, 1 << 1)
709
|| rdval(meminfo, "Slab:", &m_slab , 1 << 1)
711
put_question_marks(4);
715
m_free += m_bufs + m_cached + m_slab;
718
scale(m_free << 10); break;
720
scale((m_total - m_free) << 10); break;
724
static s_stat* init_mem(const char *param)
726
mem_stat *s = xzalloc(sizeof(*s));
727
s->collect = collect_mem;
735
static void FAST_FUNC collect_swp(swp_stat *s UNUSED_PARAM)
739
if (rdval(get_file(&proc_meminfo), "SwapTotal:", s_total, 1 << 1)
740
|| rdval(proc_meminfo.file, "SwapFree:" , s_free, 1 << 1)
742
put_question_marks(4);
745
scale((s_total[0]-s_free[0]) << 10);
748
static s_stat* init_swp(const char *param UNUSED_PARAM)
750
swp_stat *s = xzalloc(sizeof(*s));
751
s->collect = collect_swp;
758
static void FAST_FUNC collect_fd(fd_stat *s UNUSED_PARAM)
762
if (rdval(get_file(&proc_sys_fs_filenr), "", data, 0
766
put_question_marks(4);
770
scale(data[0] - data[1]);
773
static s_stat* init_fd(const char *param UNUSED_PARAM)
775
fd_stat *s = xzalloc(sizeof(*s));
776
s->collect = collect_fd;
783
S_STAT_END(time_stat)
785
static void FAST_FUNC collect_tv(time_stat *s, struct timeval *tv, int local)
787
char buf[sizeof("12:34:56.123456")];
789
unsigned us = tv->tv_usec + s->scale/2;
790
time_t t = tv->tv_sec;
801
sprintf(buf, "%02u:%02u:%02u", tm->tm_hour, tm->tm_min, tm->tm_sec);
803
sprintf(buf+8, ".%0*u", s->prec, us / s->scale);
807
static void FAST_FUNC collect_time(time_stat *s)
809
collect_tv(s, &G.tv, /*local:*/ 1);
812
static void FAST_FUNC collect_monotonic(time_stat *s)
814
struct timeval tv_mono;
816
tv_mono.tv_sec = G.tv.tv_sec - G.start.tv_sec;
817
#if 0 /* Do we want this? */
818
if (tv_mono.tv_sec < 0) {
819
/* Time went backwards, reset start time to "now" */
824
tv_mono.tv_usec = G.tv.tv_usec - G.start.tv_usec;
825
if ((int32_t)tv_mono.tv_usec < 0) {
826
tv_mono.tv_usec += 1000000;
829
collect_tv(s, &tv_mono, /*local:*/ 0);
832
static s_stat* init_time(const char *param)
835
time_stat *s = xzalloc(sizeof(*s));
837
s->collect = collect_time;
838
prec = param[0] - '0';
839
if (prec < 0) prec = 0;
840
else if (prec > 6) prec = 6;
848
static s_stat* init_monotonic(const char *param)
850
time_stat *s = (void*)init_time(param);
851
s->collect = collect_monotonic;
855
static void FAST_FUNC collect_info(s_stat *s)
865
typedef s_stat* init_func(const char *param);
867
static const char options[] ALIGN1 = "ncmsfixptTbr";
868
static init_func *const init_functions[] ALIGN_PTR = {
883
int nmeter_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
884
int nmeter_main(int argc UNUSED_PARAM, char **argv)
887
s_stat *first = NULL;
897
if (open_read_close("version", buf, sizeof(buf)-1) > 0) {
898
buf[sizeof(buf)-1] = '\0';
899
is26 = (strstr(buf, " 2.4.") == NULL);
902
if (getopt32(argv, "d:", &opt_d)) {
903
G.delta = xatoi(opt_d) * 1000;
904
G.deltanz = G.delta > 0 ? G.delta : 1;
905
need_seconds = (1000000 % G.deltanz) != 0;
912
// Can use argv[0] directly, but this will mess up
913
// parameters as seen by e.g. ps. Making a copy...
914
cur = xstrdup(argv[0]);
919
cur = strchr(cur, '%');
922
if (cur[1] == '%') { // %%
923
overlapping_strcpy(cur, cur + 1);
927
*cur++ = '\0'; // overwrite %
929
// format: %[foptstring]
931
p = strchr(options, cur[0]);
933
while (cur[0] != ']') {
938
*cur++ = '\0'; // overwrite [
942
while (cur[0] >= '0' && cur[0] <= '9')
946
p = strchr(options, cur[0]);
947
*cur++ = '\0'; // overwrite format char
951
s = init_functions[p-options](param);
954
/*s->next = NULL; - all initXXX funcs use xzalloc */
961
// %r option. remove it from string
962
overlapping_strcpy(prev + strlen(prev), cur);
969
/*s->next = NULL; - all initXXX funcs use xzalloc */
977
// Generate first samples but do not print them, they're bogus
982
xgettimeofday(&G.tv);
983
usleep(G.delta > 1000000 ? 1000000 : G.delta - G.tv.tv_usec % G.deltanz);
986
xgettimeofday(&G.start);
989
// Move back start of monotonic time a bit, to syncronize fractionals of %T and %t:
990
// nmeter -d500 '%6T %6t'
991
// 00:00:00.000161 12:32:07.500161
992
// 00:00:00.500282 12:32:08.000282
993
// 00:00:01.000286 12:32:08.500286
995
G.start.tv_usec -= (G.start.tv_usec % (unsigned)G.delta);
1002
// Negative delta -> no usleep at all
1003
// This will hog the CPU but you can have REALLY GOOD
1004
// time resolution ;)
1005
// TODO: detect and avoid useless updates
1006
// (like: nothing happens except time)
1009
// can be commented out, will sacrifice sleep time precision a bit
1010
xgettimeofday(&G.tv);
1012
// TODO: nmeter -d10000 '%6T %6t'
1013
// 00:00:00.770333 12:34:44.770333
1014
// 00:00:06.000088 12:34:50.000088
1015
// 00:00:16.000094 12:35:00.000094
1016
// 00:00:26.000275 12:35:10.000275
1017
// we can't syncronize interval to start close to 10 seconds for both
1018
// %T and %t (as shown above), but what if there is only %T
1019
// in format string? Maybe sync _it_ instead of %t in this case?
1021
rem = G.delta - ((ullong)G.tv.tv_sec*1000000 + G.tv.tv_usec) % G.deltanz;
1023
rem = G.delta - (unsigned)G.tv.tv_usec % G.deltanz;
1024
// Sometimes kernel wakes us up just a tiny bit earlier than asked
1025
// Do not go to very short sleep in this case
1026
if (rem < (unsigned)G.delta / 128) {
1031
xgettimeofday(&G.tv);