#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <sys/sysinfo.h>
#include <sys/time.h>
#include <string.h>
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 /www/bw-%s.png ; cp -f /tmp/bw.pdf /www/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("/www/bw", "r")) == NULL) {
perror("fopen(/www/bw)");
return;
}
/* read the last 64KB of the bwlog for data */
if (fseek(f, -65536, SEEK_END) < 0) {
perror("fseek(/www/bw)");
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, "eth0");
}
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);
}
}