#include #include #include #include #include #include #include #include #include static char *postscript_header = "%!PS\n" "<< /PageSize [ 240 128 ] >> setpagedevice\n" "0 0 240 128 rectclip 1 setlinejoin\n" "/xc { dup stringwidth pop 2 div 120 exch sub } bind def\n" "/xr { dup stringwidth pop 237 exch sub } bind def\n" "/xrh { dup stringwidth pop 145 exch sub } bind def\n" "/fitshow { gsave dup stringwidth pop 237 exch div 1 scale show grestore } bind def\n" "/c_bgh { .96 setgray } bind def\n" "/c_889 { .53 .53 .6 setrgbcolor } bind def\n" "/c_mrk { 0 0 1 setrgbcolor } bind def\n" "/c_kin { .5 .8 .5 setrgbcolor } bind def\n" "/c_kout { .8 .5 .5 setrgbcolor } bind def\n" "/Helvetica-Bold findfont 130 scalefont setfont\n" "c_bgh (%s) dup 3 28 moveto fitshow\n" /* hostname */ "/Helvetica findfont 12 scalefont setfont\n" "c_889 3 3 moveto show\n" "(%s) xr 3 moveto show\n" /* uptime */ "c_889 (%s) xc 20 moveto show\n" /* loadaverages */ "(%02d:%02d) 3 20 moveto show\n" /* first data time */ "(%02d:%02d) xr 20 moveto show\n" /* last data time */ "/NewCenturySchlbk-Italic findfont 12 scalefont setfont\n" "/valshow {\n" " /ypos exch def\n" " /xpos exch def\n" " xpos 180 gt { dup stringwidth pop 1.5 add xpos exch sub }\n" " { xpos } ifelse\n" " ypos moveto\n" " gsave xpos ypos moveto\n" " ypos 64 gt {\n" " 0 5 rlineto 0 -5 rmoveto\n" " 2 2 rlineto -2 -2 rmoveto -2 2 rlineto\n" " } {\n" " 0 7 rmoveto 0 5 rlineto -2 -2 rlineto\n" " 2 2 rmoveto 2 -2 rlineto\n" " } ifelse c_mrk stroke grestore\n" " show\n" "} bind def\n" "/plotvalues {\n" " /yplot exch def\n" " /gkey exch def\n" " counttomark 1 sub /lim exch def\n" " /minidx 0 def /maxidx lim 1 sub def\n" " lim 1 add copy\n" " lim -1 0 {\n" " /idx exch def\n" " /val exch def\n" " idx lim eq { /now val def /max 0 def /min 99999999 def } if\n" " val max gt { /max val def /maxidx idx def } if\n" " val min le { /min val def /minidx idx def } if\n" " } for\n" " max (12345678) cvs maxidx yplot 64 add valshow\n" " 3 0 rmoveto gkey show\n" " min (12345678) cvs minidx yplot 12 sub valshow\n" " 3 0 rmoveto gkey show\n" " /yscale max min sub 64 div def\n" " yscale 0 eq { /yscale 12345678 def } if\n" " lim -1 0 {\n" " /idx exch def\n" " min sub yscale div yplot add /val exch def\n" " idx lim eq { idx val moveto } { idx val lineto } ifelse\n" " } for stroke\n" "} bind def\n" "newpath 238 35 moveto -3 -6 rlineto 6 0 rlineto closepath fill\n"; /* mark data . . . data c_kin (kbps in) 43 plotvalues */ /* mark data . . . data c_kout (kbps out) 54 plotvalues */ /* showpage */ /* note that file is converted to pnm 2x the desired size so pnmscale can antialias */ static char *convert_ps2pnm = "gs -q -r144 -sOutputFile=/tmp/bw.pnm -sDEVICE=pnm -dBATCH -dNOPAUSE /tmp/bw.ps"; static char *convert_pnm2png = "pnmscale 0.5 -quiet /tmp/bw.pnm | pnmtopng -quiet >/tmp/bw.png"; static char *convert_ps2pdf = "ps2pdf14 /tmp/bw.ps /tmp/bw.pdf"; static char *movecmd = "cp -f /tmp/bw.png /home/httpd/html/bw-%s.png ; cp -f /tmp/bw.pdf /home/httpd/html/bw-%s.pdf"; static char move_into_place[256]; static char myhostname[64]; static char myuptime[64]; static char myloadavg[64]; static time_t sample_time[256], last_psfile = 0; static uint64_t kbps_in[256], kbps_out[256]; static unsigned int first_new = 0, first_old = 0; static void write_postscript(FILE *f) { struct tm old, new; int i; gmtime_r(&sample_time[first_old & 0xff], &old); gmtime_r(&sample_time[first_new & 0xff], &new); fprintf(f, postscript_header, myhostname, myuptime, myloadavg, old.tm_hour, old.tm_min, new.tm_hour, new.tm_min); fprintf(f, "mark\n"); for (i = first_old; i < first_new; i++) { fprintf(f, "%qu\n", kbps_in[i & 0xff]); } fprintf(f, "c_kin (kbps in) 43 plotvalues\nmark\n"); for (i = first_old; i < first_new; i++) { fprintf(f, "%qu\n", kbps_out[i & 0xff]); } fprintf(f, "c_kout (kbps out) 54 plotvalues\nshowpage\n"); } /* this is used to read the last 32KB of hpa's bandwidth log */ /* this will give the graph initial history to show on startup */ static void read_bwlog(void) { FILE *f; double log_time; uint64_t log_out, log_in; if ((f = fopen("/home/hpa/bwlog", "r")) == NULL) { perror("fopen(/home/hpa/bwlog)"); return; } /* read the last 64KB of the bwlog for data */ if (fseek(f, -65536, SEEK_END) < 0) { perror("fseek(/home/hpa/bwlog)"); return; } /* synchronize on the first newline */ fscanf(f, "%*[^\n] "); while (!feof(f)) { fscanf(f, "%lf %qu %qu\n", &log_time, &log_out, &log_in); if ((time_t)log_time <= sample_time[first_new & 0xff]) continue; sample_time[first_new & 0xff] = (time_t)log_time; kbps_out[first_new & 0xff] = log_out >> 10; kbps_in[first_new & 0xff] = log_in >> 10; first_new++; if (first_new - first_old >= 240) { first_old++; } } fclose(f); } int main(int argc, char *argv[]) { FILE *f; char ifname[8], ethdev[8]; struct timeval tv_now, tv_then; uint64_t in_now, in_then = ~0, out_now, out_then = ~0; uint64_t bw_out, bw_in; int delta_sec = 4, i; if (argc > 1) { strncpy(ethdev, argv[1], 7); } else { strcpy(ethdev, "eth2"); } if (argc > 2) { delta_sec = atoi(argv[2]); } if (delta_sec <= 0 || ethdev[0] == '-' || ethdev[0] == '?') { fprintf(stderr, "usage: %s [ethdev [interval]]\n", argv[0]); exit(1); } if (gethostname(myhostname, 63) < 0) { perror("gethostname"); exit(1); } sprintf(move_into_place, movecmd, myhostname, myhostname); read_bwlog(); /* prime bandwidth history values from hpa's log */ while (1) { if ((f = fopen("/proc/net/dev", "r")) == NULL) { fprintf(stderr, "%s: /proc/net/dev: %s\n", argv[0], strerror(errno)); exit(1); } fscanf(f, "%*[^\n] %*[^\n] "); i = 0; do { fscanf(f, "%7[^:]:%llu %*u %*u %*u %*u %*u %*u %*u" "%llu %*[^\n] ", &ifname, &in_now, &out_now); if (strncmp(ifname, ethdev, 7) == 0) { i++; break; } } while (!feof(f)); fclose(f); if (!i) { fprintf(stderr, "device %s not found\n", ethdev); exit(1); } gettimeofday(&tv_now, NULL); /* record time of above sample */ sample_time[first_new & 0xff] = tv_now.tv_sec; if (out_then != ~0) { double sec = (double)tv_now.tv_sec + (double)tv_now.tv_usec / 1000000.0 - (double)tv_then.tv_sec - (double)tv_then.tv_usec / 1000000.0; if (sec <= 0) { fprintf(stderr, "Time warp detected\n"); exit(1); } /* handle 32-bit wrap of counters if necessary */ if (in_now < in_then) { bw_in = (uint64_t) ((double)(in_now + (1ULL << 32) - in_then) / sec); } else { bw_in = (uint64_t) ((double)(in_now - in_then) / sec); } if (out_now < out_then) { bw_out = (uint64_t) ((double)(out_now + (1ULL << 32) - out_then) / sec); } else { bw_out = (uint64_t) ((double)(out_now - out_then) / sec); } bw_in >>= 7; bw_out >>= 7; kbps_in[first_new & 0xff] = bw_in; kbps_out[first_new & 0xff] = bw_out; /* flag errors in math on the generated graphs */ if (kbps_in[i & 0xff] > 123456789) kbps_in[i & 0xff] = 123456789; if (kbps_out[i & 0xff] > 123456789) kbps_out[i & 0xff] = 123456789; } if (sample_time[first_new & 0xff] - last_psfile >= 20) { struct sysinfo si; last_psfile = sample_time[first_new & 0xff]; /* create new postscript file every 20 seconds */ /* get load averages */ if (sysinfo(&si) < 0) { perror("sysinfo"); bzero(&si, sizeof(si)); } sprintf(myloadavg, "%.2f %.2f %.2f", si.loads[0] / 65536.0, si.loads[1] / 65536.0, si.loads[2] / 65536.0); sprintf(myuptime, "up %ldd %ldh %ldm", si.uptime / 86400, (si.uptime % 86400) / 3600, (si.uptime % 3600) / 60); if ((f = fopen("/tmp/bw.ps", "w")) == NULL) { fprintf(stderr, "%s: /tmp/bw.ps: %s", argv[0], strerror(errno)); exit(1); } write_postscript(f); fclose(f); /* convert postscript file to png */ if (system(convert_ps2pnm) < 0) { perror("ps2pnm"); } if (system(convert_pnm2png) < 0) { perror("pnm2png"); } /* convert postscript file to pdf */ if (system(convert_ps2pdf) < 0) { perror("ps2pdf"); } /* copy the created files in place */ if (system(move_into_place) < 0) { perror("pnm2png"); } } in_then = in_now; out_then = out_now; tv_then = tv_now; first_new++; if (first_new - first_old >= 240) { first_old++; } if ((first_new & 0xff) > (first_old & 0xff)) { first_new &= 0xff; first_old &= 0xff; } sleep(delta_sec); } }