/* $Id: bwbar.c,v 1.7 2006/08/26 16:31:26 hpa Exp $ */
/* ----------------------------------------------------------------------- *
 *   
 *   Copyright 1999-2006 H. Peter Anvin - All Rights Reserved
 *
 *   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, Inc., 675 Mass Ave, Cambridge MA 02139,
 *   USA; either version 2 of the License, or (at your option) any later
 *   version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

/*
 * bwbar.c
 *
 * Bandwidth monitor using /proc/net/dev which produces output which can
 * be displayed on a web page.
 *
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <alloca.h>
#include <getopt.h>
#include <png.h>

void skipline(FILE *f)
{
  int ch;
  do {
    ch = getc(f);
  } while ( ch != '\n' && ch != EOF );
}

int write_bar_graph(FILE *f, double perc, int width, int height, int border, int spacers)
{
  static png_color palette[4] =
  {
    {  0,   0,   0},		/* RGB color for border */
    {255, 255, 255},		/* RGB color for separators */
    {214, 136, 131},		/* RGB color for unfilled part of graph */
    {204, 204, 227},		/* RGB color for filled part of graph */
  };
  int bwidth, bheight;		/* Width and height including border */
  double frac;
  int spacer, fspacer;
  int x, y;
  int color;
  png_structp png_ptr = NULL;
  png_infop info_ptr  = NULL;
  png_byte *border_row;
  png_byte *bar_row;
  png_byte *bp;
  int status = 1;		/* Assume failure */

  bwidth = width + border*2;
  bheight = height + border*2;

  border_row   = alloca(sizeof(png_byte) * bwidth);
  bar_row      = alloca(sizeof(png_byte) * bwidth);

  if ( !border_row || !bar_row )
    goto barf;

  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
				    NULL, NULL, NULL);

  if ( !png_ptr )
    goto barf;

  info_ptr = png_create_info_struct(png_ptr);
  if ( !info_ptr )
    goto barf;

  if ( setjmp(png_ptr->jmpbuf) ) {
    status = 1;
    goto barf;			/* libpng abort */
  }

  png_init_io(png_ptr, f);

  png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);

  png_set_IHDR(png_ptr, info_ptr, bwidth, bheight,
	       2, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
	       PNG_COMPRESSION_TYPE_DEFAULT,
	       PNG_FILTER_TYPE_DEFAULT);

  png_set_PLTE(png_ptr, info_ptr, palette, 4);

  png_write_info(png_ptr, info_ptr);

  png_set_packing(png_ptr);

  /* Now we actually produce the data */

  /* Generate the border row */
  memset(border_row, 0, bwidth);

  /* Generate the bar row */
  bp = bar_row;
  for ( x = 0 ; x < border ; x++ )
    *bp++ = 0;			/* Border color */

  spacer = 0;
  for ( x = 1 ; x <= width ; x++ ) {
    frac = (double)x/(double)width;
    fspacer = (int)(spacers*frac);
    if ( x < width && spacer < fspacer ) {
      color = 1;		/* Spacer color */
    } else if ( perc < frac ) {
      color = 3;		/* Bar color */
    } else {
      color = 2;		/* Background color */
    }
    *bp++ = color;
    spacer = fspacer;
  }
  
  for ( x = 0 ; x < border ; x++ )
    *bp++ = 0;			/* Border color */

  /* Write output */
  for ( y = 0 ; y < border ; y++ )
    png_write_row(png_ptr, border_row);
  for ( y = 0 ; y < height ; y++ )
    png_write_row(png_ptr, bar_row);
  for ( y = 0 ; y < border ; y++ )
    png_write_row(png_ptr, border_row);

  /* Done writing */
  png_write_end(png_ptr, info_ptr);
  status = 0;			/* Success! */

 barf:				/* Jump here on fatal error */
  png_destroy_write_struct(&png_ptr, &info_ptr);
  return status;
}

const struct option longopts[] = {
  { "input",        0, 0, 'i' },
  { "output",       0, 0, 'o' },
  { "text-file",    1, 0, 'f' },
  { "png-file",     1, 0, 'p' },
  { "interval",     1, 0, 't' },
  { "width",        1, 0, 'x' },
  { "height",       1, 0, 'y' },
  { "border",       1, 0, 'b' },
  { "kbps",         0, 0, 'k' },
  { "Mbps",         0, 0, 'M' },
  { "Gbps",         0, 0, 'G' },
  { "help",         0, 0, 'h' },
  { 0, 0, 0, 0 }
};

const char *program;

void usage(int err)
{
  fprintf(stderr,
	  "Usage: %s [options] interface max_mbps\n"
	  "Options: (defaults in parenthesis)\n"
	  "   --input               -i   Measure input bandwidth\n"
	  "   --output              -o   Measure output bandwidth (default)\n"
	  "   --text-file <file>    -f   The name of the text output file (ubar.txt)\n"
	  "   --png-file <file>     -p   The name of the graphical bar file (ubar.png)\n"
	  "   --interval <seconds>  -t   The poll interval in seconds (15)\n"
	  "   --width <pixels>      -x   Width of the graphical bar (600)\n"
	  "   --height <pixels>     -y   Height of the graphical bar (4)\n"
	  "   --border <pixels>     -b   Border width of the graphical bar (1)\n"
	  "   --kbps                -k   Bandwidth is measured in kbit/s\n"
	  "   --Mbps                -M   Bandwidth is measured in Mbit/s (default)\n"
	  "   --Gbps                -G   Bandwidth is measured in Gbit/s\n"
	  "   --help                -h   Display this text\n",
	  program);
  exit(err);
}

int main(int argc, char *argv[])
{
  FILE *pnd;
  char *interface;
  struct ifinfo {
    char name[8];
    unsigned int r_bytes, r_pkt, r_err, r_drop, r_fifo, r_frame;
    unsigned int r_compr, r_mcast;
    unsigned int x_bytes, x_pkt, x_err, x_drop, x_fifo, x_coll;
    unsigned int x_carrier, x_compr;
  } ifc;
  unsigned long long bin, bout, lbin, lbout;
  int first;
  struct timeval t_now, t_last;
  double maxbandwidth;
  double bwin, bwout, bwmeasure, timedelta;
  int opt;
  char *t_tmp, *g_tmp;
  /* Options */
  int measure_input = 0;	/* Input instead of output */
  char *text_file = "ubar.txt";	/* Text filename */
  char *graphics_file = "ubar.png"; /* Graphics filename */
  char *unit_name = "Mbit/s";	/* Unit name */
  double unit = 1.0e+6;		/* Unit multiplier */
  int interval = 15;		/* Interval between measurements (s) */
  int width = 600;		/* Bar width */
  int height = 4;		/* Bar height */
  int border = 1;		/* Bar border */

  program = argv[0];

  while ( (opt = getopt_long(argc, argv, "iof:p:t:x:y:b:kMGh", longopts, NULL)) != -1 ) {
    switch ( opt ) {
    case 'i':
      measure_input = 1;
      break;
    case 'o':
      measure_input = 0;
      break;
    case 'f':
      text_file = optarg;
      break;
    case 'p':
      graphics_file = optarg;
      break;
    case 't':
      interval = atoi(optarg);
      break;
    case 'x':
      width = atoi(optarg);
      break;
    case 'y':
      height = atoi(optarg);
      break;
    case 'b':
      border = atoi(optarg);
      break;
    case 'k':
      unit = 1.0e+3;
      unit_name = "kbit/s";
      break;
    case 'M':
      unit = 1.0e+6;
      unit_name = "Mbit/s";
      break;
    case 'G':
      unit = 1.0e+9;
      unit_name = "Gbit/s";
      break;
    case 'h':
      usage(0);
      break;
    default:
      usage(1);
      break;
    }
  }

  if ( argc-optind != 2 )
    usage(1);

  t_tmp = malloc(strlen(text_file) + 5);
  g_tmp = malloc(strlen(graphics_file) + 5);
  if ( !t_tmp || !g_tmp ) {
    perror(program);
    exit(1);
  }
  sprintf(t_tmp, "%s.tmp", text_file);
  sprintf(g_tmp, "%s.tmp", graphics_file);

  interface = argv[optind];
  maxbandwidth = atof(argv[optind+1]) * unit;

  first = 1;
  lbin = 0; lbout = 0;

  gettimeofday(&t_last, NULL);
  
  while ( 1 ) {

    /**** Begin code that obtains bandwidth data ****/

    gettimeofday(&t_now, NULL);
    pnd = fopen("/proc/net/dev", "r");
    if ( !pnd ) {
      fprintf(stderr, "%s: /proc/net/dev: %s", argv[0], strerror(errno));
      exit(1);
    }

    /* Skip header */
    skipline(pnd);
    skipline(pnd);

    /* Get interface info */
    do {
      if ( fscanf(pnd, " %6[^:]:%u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u",
		  ifc.name,
		  &ifc.r_bytes, &ifc.r_pkt, &ifc.r_err, &ifc.r_drop,
		  &ifc.r_fifo, &ifc.r_frame, &ifc.r_compr, &ifc.r_mcast,
		  &ifc.x_bytes, &ifc.x_pkt, &ifc.x_err, &ifc.x_drop,
		  &ifc.x_fifo, &ifc.x_coll, &ifc.x_carrier, &ifc.x_compr)
	   != 17 ) {
	exit(200);
      }
      skipline(pnd);
    } while ( strcmp(ifc.name, interface) );

    bin  = ifc.r_bytes + (lbin & ~0xffffffffULL);
    bout = ifc.x_bytes + (lbout & ~0xffffffffULL);

    if ( bin < lbin )
      bin += (1ULL << 32);
    if ( bout < lbout )
      bout += (1ULL << 32);

    /**** Begin code that generates output ****/

    if ( !first ) {
      FILE *ubar, *pngmaker;

      timedelta = (double)(t_now.tv_sec - t_last.tv_sec) + 
	(t_now.tv_usec - t_last.tv_usec)/1.0e+6;
      bwin = (bin-lbin)*8/timedelta;
      bwout = (bout-lbout)*8/timedelta;

      bwmeasure = measure_input ? bwin : bwout;
      
      ubar = fopen(t_tmp, "w");
      pngmaker = fopen(g_tmp, "w");
      if ( ubar ) {
	fprintf(ubar, "Current bandwidth utilization %6.2f %s\n", bwmeasure/unit, unit_name);
	fclose(ubar);
	rename(t_tmp, text_file);
      }
      if ( pngmaker ) {
	write_bar_graph(pngmaker, bwmeasure/maxbandwidth, width, height, border, 5);
	fclose(pngmaker);
	rename(g_tmp, graphics_file);
      }
    } else {
      first = 0;
    }

    lbin = bin;  lbout = bout;  t_last = t_now;

    fclose(pnd);

    sleep(interval);
  }
}
