/* $Header: /CVSROOT/mon/mon.c,v 1.3 2004/09/14 23:39:50 tino Exp $ * * mon: A lightweight system monitor * * This snapshots system monitoring information at a 5 seconds * interval to stdout in an auto-adapting format suitable for later * evaluation. The output is not very human readable. * * Shall be independent of everything, therefor no tinolib. * * Copyright (C)2004 Valentin Hilbig, webmaster@scylla-charybdis.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Log: mon.c,v $ * Revision 1.3 2004/09/14 23:39:50 tino * /proc/net/dev * * Revision 1.2 2004/09/13 02:39:51 tino * bugfix for wrong sizeof parameter in memcpy() of index_new * * Revision 1.1 2004/09/13 01:14:43 tino * first version without network monitoring */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "mon_version.h" #if 1 #define DP(X) do { ; } while (0) #else void debugprintf(const char *s, ...) { va_list list; fprintf(stderr, "["); va_start(list, s); vfprintf(stderr, s, list); va_end(list); fprintf(stderr, "]\n"); fflush(stderr); } #define DP(X) do { debugprintf X; } while (0) #endif #define xDP(X) do { ; } while (0) #define INTERNAL1(X,A) do { fprintf(stderr, "internal error at %s:%d: " X "\n", __FILE__, __LINE__, A); exit(-1); } while (1) static void * my_realloc(void *old, size_t len) { void *p; p = (old ? realloc(old, len) : malloc(len)); if (!p) INTERNAL1("out of memory: %d", errno); return p; } static const char * my_strdup(const char *s) { char *p; p = strdup(s); if (!p) INTERNAL1("out of memory: %d", errno); return p; } static const char * glue(const char *s, ...) { static char *buf; static size_t len; DP(("glue(%s)", s)); for (;;) { if (len) { va_list list; int n; va_start(list, s); n = vsnprintf(buf, len, s, list); va_end(list); if (n>(A*8)); \ } static void send_num_prefix(int c, unsigned long long u) { P(0, P(1, P(2, P(3, P(4, P(5, P(6, putchar(c+7); putchar(u>>(7*8)); ))))))); } static void send_num_abs(unsigned long long u) { send_num_prefix(0xf0, u); } static void send_num_rel(unsigned long long u) { if (u<0xe0) putchar((unsigned char)u); else send_num_prefix(0xe0, u-0xe0); } static void send_num_neg(unsigned long long u) { send_num_prefix(0xe8, u); } static void send_num(unsigned long long u, unsigned long long prev) { unsigned long long d; if (u=MAX_INDEXES) INTERNAL1("index %d", idx); index_new[idx] = val; } static void send_index(int from_idx, int to_idx) { int i; if (from_idx>to_idx) from_idx = to_idx; /* Find the lowest valid index */ for (i=from_idx; i2) INTERNAL1("index jump of %d", k); putchar(0xfa+k); send_num(index_new[i], index_is[i]); index_is[i] = index_new[i]; index_depth = i+1; } /* remember where the indexes are */ index_depth = to_idx; } /**********************************************************************/ static struct token { struct token *next; const char *tok; unsigned n; char type; unsigned long long last; int indexes; unsigned long long index_val[MAX_INDEXES]; } **token_list; static int tokens, token_count; static struct token * const *token_insert; static void send_token(unsigned n) { static unsigned long long last; send_num((unsigned long long)n, last); last = n; } static int token_compare(const void *key, const void *t) { int n; n = strcmp(key, (*(struct token **)t)->tok); if (n<0) token_insert = t; return n; } static struct token * token_get(const char *toknam, char type, int *fresh, int idx) { struct token **tok, *had; token_insert = 0; tok = bsearch(toknam, token_list, tokens, sizeof *token_list, token_compare); /* create a free slot if it is missing */ if (!tok) { size_t i=token_insert-token_list; xDP(("token '%s' insert %p:%d", toknam, token_insert, i)); token_list = my_realloc(token_list, (tokens+1)*sizeof *token_list); if (token_insert && tokens) memmove(token_list+i+1, token_list+i, (tokens-i)*sizeof *token_list); else i = tokens; tokens++; tok = token_list+i; *tok = 0; } /* look for the token in the list with a correct index */ had = *tok; for (; *tok; tok= &(*tok)->next) if ((*tok)->indexes==idx) { int i, ok; ok = 1; for (i=idx; --i>=0; ) if (index_new[i]!=(*tok)->index_val[i]) { ok = 0; break; } if (ok) break; } /* Create a new token if there is none yet. */ if (!*tok) { *tok = my_realloc(NULL, sizeof **tok); (*tok)->next = 0; (*tok)->tok = (had ? had->tok : my_strdup(toknam)); (*tok)->n = ++token_count; (*tok)->type = type; (*tok)->last = 0; (*tok)->indexes = idx; memcpy((*tok)->index_val, index_new, sizeof (*tok)->index_val); if (fresh) *fresh = 1; /* If we already had this token with some other index * or we define one new with an index * send out index information */ if (idx || had) { send_token(0); /* Reset indexes. * This is the magic to "learn" the indexes. * Note that this is a real index reset, too. */ putchar(0xfd); index_depth = 0; send_index(0, idx); } if (had) send_token(had->n); else { int i; /* tell that the new token name follows */ send_token(0); /* Send the token name * This is a string which is always delta 0. * It could be delta 5, as names are minimum 4 byte long, * but this is not "robust" and would be surprisingly. */ i = strlen(toknam); send_num_rel((unsigned long long)(i+1)); putchar(type); fwrite(toknam, i, 1, stdout); } } else if ((*tok)->type!=type) INTERNAL1("type mismatch for token '%s'", toknam); return *tok; } /**********************************************************************/ static void send_val(const char *token, unsigned long long u, int idx) { struct token *t; int fresh=0; DP(("%s=%Lu", token, u)); t = token_get(token, 'u', &fresh, idx); if (t->last==u && !fresh) return; send_index(idx, idx); send_token(t->n); send_num(u, t->last); t->last = u; } /* Send a data block (string) * * If append is set, mark the length as beeing an append to previous data. * (Note that you can append to position 0, this is no error.) */ static void send_data(const char *token, const void *s, size_t len, int append, int idx) { struct token *t; int fresh=0; t = token_get(token, 's', &fresh, idx); if (!len && append && fresh) return; send_index(idx, idx); send_token(t->n); if (append || !t->last) { send_num_rel((unsigned long long)len); t->last += len; } else { send_num_abs((unsigned long long)len); t->last = len; } fwrite(s, len, 1, stdout); } static void send_str(const char *token, const char *s, int idx) { DP(("%s='%s'", token, s)); send_data(token, s, strlen(s), 0, idx); } /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ static char *buf; static int buflen, buffill, bufpos; static int readin(int fd, const char *what) { xDP(("readin(%d,'%s')", fd, what)); bufpos = 0; for (buffill=0;; ) { int n; if (buflen<=buffill) { buflen += BUFSIZ*8; buf = my_realloc(buf, buflen); } n = read(fd, buf+buffill, buflen-buffill); if (n>0) { buffill += n; continue; } if (!n) { buf[buffill] = 0; return 1; } if (errno!=EINTR || errno!=EAGAIN) { perror(what); return 0; } } } /**********************************************************************/ static int nextline(void) { while (bufposbuffill || strncmp(buf+bufpos, s, n)) return 0; bufpos += n; return 1; } static int huntlinewith(const char *s) { int pos, lastline; size_t len; DP(("huntlinewith(%s)", s)); lastline = bufpos-1; len = strlen(s); if (len<1) INTERNAL1("string too short for hunt: %d", len); len--; for (pos=bufpos; pos=buffill) break; if (!len || !strncmp(s+1, buf+pos+1, len)) { bufpos=lastline+1; DP(("huntlinewith '%.10s'", buf+bufpos)); return 1; } } if (buf[pos]=='\n') lastline = pos; } DP(("huntlinewith fail", buf+bufpos)); return 0; } /**********************************************************************/ static int get_ul(unsigned long long *u) { char *end; trim(); if (bufpos>=buffill) return 0; *u = strtoull(buf+bufpos, &end, 10); if (!end || end==buf+bufpos) return 0; bufpos = end-buf; return 1; } static int getunsigned(unsigned *i) { unsigned long long u; if (!get_ul(&u)) return 0; *i = u; return *i==u; } static const char * getstr(const char *delimiter) { int pos; size_t len; len = strlen(delimiter); if (len<1) INTERNAL1("deliminter too short: %d", len); len--; pos = bufpos; for (; bufpos=buffill) break; if (!len || !strncmp(delimiter+1, buf+bufpos+1, len)) { buf[bufpos] = 0; bufpos += len+1; return buf+pos; } } return 0; } /**********************************************************************/ /* For some tradition, the kernel sometimes tries hard to "obfuscate" * the information for direct use within software. It looks like this * was done in the wrong assumption that these information should be * meaningful for humans directly using `cat` where a simple line of * `awk` would be sufficient as well .. SIGH. */ static int getfrac(const char *token, int factor) { unsigned long long u, f; if (!get_ul(&u) || !match(".") || !get_ul(&f)) return 0; send_val(token, u*(unsigned)factor+f, 0); return 1; } static int getval(const char *token) { unsigned long long u; if (!get_ul(&u)) return 0; send_val(token, u, 0); return 1; } static int getval1(const char *token, int i1) { unsigned long long u; if (!get_ul(&u)) return 0; set_index(0, i1); send_val(token, u, 1); return 1; } static int getval2(const char *token, int i1, int i2) { unsigned long long u; if (!get_ul(&u)) return 0; set_index(0, i1); set_index(1, i2); send_val(token, u, 2); return 1; } /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ /* total: used: free: shared: buffers: cached: Mem: 658542592 651612160 6930432 0 117604352 455626752 Swap: 4005699584 39223296 3966476288 MemTotal: 643108 kB MemFree: 6768 kB MemShared: 0 kB Buffers: 114848 kB Cached: 442032 kB SwapCached: 2916 kB Active: 400384 kB ActiveAnon: 7076 kB ActiveCache: 393308 kB Inact_dirty: 47972 kB Inact_laundry: 70148 kB Inact_clean: 46316 kB Inact_target: 112964 kB HighTotal: 0 kB HighFree: 0 kB LowTotal: 643108 kB LowFree: 6768 kB SwapTotal: 3911816 kB SwapFree: 3873512 kB */ static int mem(void) { static int fd=-1; xDP(("mem")); if (fd<0) fd = open("/proc/meminfo", O_RDONLY); return ( fd>=0 && !lseek(fd, (off_t)0, SEEK_SET) && readin(fd, "/proc/meminfo") && nextline() && match("Mem:") && getval("linux.mem.total") && getval("linux.mem.used") && getval("linux.mem.free") && getval("linux.mem.shared") && getval("linux.mem.buffers") && getval("linux.mem.cached") && nextline() && match("Swap:") && getval("linux.swap.all.total") && getval("linux.swap.all.used") && getval("linux.swap.all.free") && /* rest ignored for now */ 1); } /* cpu 686632 1016500 620460 100602985 cpu0 686632 1016500 620460 100602985 page 49276058 19671156 swap 134833 368412 intr 130169858 102926577 412177 0 3 3 16801949 4 0 1 0 0 4927150 82785 0 5019208 1 disk_io: (3,0):(5037418,3983229,88116832,1054189,29564796) (8,0):(1642466,1261501,10431874,380965,9777488) ctxt 32330237 btime 1093903922 processes 116420 */ static int cpu(void) { static int fd=-1; int i; xDP(("cpu")); if (fd<0) fd = open("/proc/stat", O_RDONLY); if (!( fd>=0 && !lseek(fd, (off_t)0, SEEK_SET) && readin(fd, "/proc/stat") && match("cpu ") && getval("linux.cpu.user") && getval("linux.cpu.nice") && getval("linux.cpu.sys") && getval("linux.cpu.idle") && /* ignore detailed cpu for now */ 1)) return 0; for (i=1; nextline() && match("cpu"); i++) if (!( getval1("linux.cpu.nr", i) && getval1("linux.cpu.user", i) && getval1("linux.cpu.nice", i) && getval1("linux.cpu.sys", i) && getval1("linux.cpu.idle", i) && 1)) return 0; if (!( match("page") && getval("linux.proc.pagein") && getval("linux.proc.pageout") && nextline() && match("swap") && getval("linux.proc.swapin") && getval("linux.proc.swapout") && nextline() && match("intr") && 1)) return 0; for (i=0; getval1("linux.intr", i); i++); if (!( nextline() && match("disk_io:") && 1)) return 0; while (match("(")) { unsigned maj, min; if (!( getunsigned(&maj) && match(",") && getunsigned(&min) && match("):(") && getval2("linux.disk.transactions", maj, min) && match(",") && getval2("linux.disk.read.nr", maj, min) && match(",") && getval2("linux.disk.read.blocks", maj, min) && match(",") && getval2("linux.disk.write.nr", maj, min) && match(",") && getval2("linux.disk.write.blocks", maj, min) && match(")") && 1)) return 0; } return ( nextline() && match("ctxt") && getval("linux.proc.ctxswitch") && nextline() && match("btime") && getval("linux.sys.boottime") && nextline() && match("processes") && getval("linux.proc.forks") && 1); } /* 0.18 0.16 0.06 1/84 19699 */ static int load(void) { static int fd=-1; xDP(("loadavg")); if (fd<0) fd = open("/proc/loadavg", O_RDONLY); return ( fd>=0 && !lseek(fd, (off_t)0, SEEK_SET) && readin(fd, "/proc/loadavg") && getfrac("linux.proc.loadavg.1", 100) && getfrac("linux.proc.loadavg.5", 100) && getfrac("linux.proc.loadavg.15", 100) && getval("linux.proc.run") && match("/") && getval("linux.proc.nr") && /* last pid ignored */ 1); } static int net(void) { static int fd=-1; xDP(("net")); if (fd<0) fd = open("/proc/net/dev", O_RDONLY); if (!( fd>=0 && !lseek(fd, (off_t)0, SEEK_SET) && readin(fd, "/proc/net/dev") && huntlinewith(":") && 1)) return 0; do { const char *ptr; trim(); ptr = getstr(":"); if (!ptr) { DP(("no")); return 0; } DP(("ptr=%s", ptr)); if (!( getval(glue("linux.net.%s.rx.bytes", ptr)) && getval(glue("linux.net.%s.rx.packets", ptr)) && getval(glue("linux.net.%s.rx.errs", ptr)) && getval(glue("linux.net.%s.rx.drops", ptr)) && getval(glue("linux.net.%s.rx.fifo", ptr)) && getval(glue("linux.net.%s.rx.frame", ptr)) && getval(glue("linux.net.%s.rx.compressed", ptr)) && getval(glue("linux.net.%s.rx.multicast", ptr)) && getval(glue("linux.net.%s.tx.bytes", ptr)) && getval(glue("linux.net.%s.tx.packets", ptr)) && getval(glue("linux.net.%s.tx.errs", ptr)) && getval(glue("linux.net.%s.tx.drops", ptr)) && getval(glue("linux.net.%s.tx.fifo", ptr)) && getval(glue("linux.net.%s.tx.colls", ptr)) && getval(glue("linux.net.%s.tx.carrier", ptr)) && getval(glue("linux.net.%s.tx.compressed", ptr)) && 1)) return 0; } while (nextline()); return 1; } /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ /**********************************************************************/ static int (*fn[])(void) = { mem, cpu, load, net, }; static void scan(void) { time_t t; int i; time(&t); send_val("time", (unsigned long long)t, 0); for (i=0; i1) sleeptime = atoi(argv[1]); if (sleeptime<1) sleeptime = 5; send_str("mon.version", MON_VERSION, 0); send_str("mon.compiled", __DATE__, 0); send_val("mon.interval", (unsigned long long)sleeptime, 0); while (!ferror(stdout)) { scan(); sleep(sleeptime); } return 0; }