#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 /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);
	}
}
