/* cytune.c -- Tune Cyclades driver
 *
 * Copyright 1995 Nick Simicich (njs@scifi.emi.net)
 *
 * Modifications by Rik Faith (faith@cs.unc.edu)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Nick Simicich
 * 4. Neither the name of the Nick Simicich nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY NICK SIMICICH AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL NICK SIMICICH OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/tty.h>
#include <termios.h>
#include <linux/tqueue.h>
#include <linux/cyclades.h>
#include <signal.h>

#if 0
#ifndef XMIT
# include <linux/version.h>
# if LINUX_VERSION_CODE > 66056
#  define XMIT
# endif
#endif
#endif
				/* Until it gets put in the kernel,
				   toggle by hand. */
#undef XMIT

struct cyclades_control {
  struct cyclades_monitor c;
  int cfile;
  int maxmax;
  double maxtran;
  double maxxmit;
  unsigned long threshold_value;
  unsigned long timeout_value;
};
struct cyclades_control *cmon;
int cmon_index;

#define mvtime(tvpto, tvpfrom)  (((tvpto)->tv_sec = (tvpfrom)->tv_sec),(tvpto)->tv_usec = (tvpfrom)->tv_usec)


inline double dtime(struct timeval * tvpnew, struct timeval * tvpold) {
  double diff;
  diff = (double)tvpnew->tv_sec - (double)tvpold->tv_sec;
  diff += ((double)tvpnew->tv_usec - (double)tvpold->tv_usec)/1000000;
  return diff;
}

static int global_argc, global_optind;
static char ***global_argv;

void summary(int signal) {
  struct cyclades_control *cc;

  int argc, optind;
  char **argv;

  int i,j;

  argc = global_argc;
  argv = *global_argv;
  optind = global_optind;

  if (signal > 0) {
    for(i = optind; i < argc; i ++) {
      j = i - optind;
      cc = &cmon[cmon_index];
      fprintf(stderr, "File %s, For threshold value %lu, Maximum characters "
	      "in fifo were %d,\nand the maximum transfer rate in "
	      "characters/second was %f\n", 
	      argv[i],
	      cc->threshold_value,
	      cc->maxmax,
	      cc->maxtran);
    }
    
    exit(0);
  }
  cc = &cmon[cmon_index];
  if (cc->threshold_value > 0 && signal != -1) {
    fprintf(stderr, "File %s, For threshold value %lu and timrout value %lu,"
	    " Maximum characters "
	    "in fifo were %d,\nand the maximum transfer rate in "
	    "characters/second was %f\n", 
	    argv[cmon_index+optind],
	    cc->threshold_value,
	    cc->timeout_value,
	    cc->maxmax,
	    cc->maxtran);
  }
  cc->maxmax = 0;
  cc->maxtran = 0.0;
  cc->threshold_value = 0;
  cc->timeout_value = 0;
}

static int query   = 0;
static int interval = 1;

static int set     = 0;
static int set_val = -1;
static int get     = 0;

static int set_def     = 0;
static int set_def_val = -1;
static int get_def     = 0;

static int set_time = 0;
static int set_time_val = -1;

static int set_def_time = 0;
static int set_def_time_val = -1;


int main(int argc, char *argv[]) {

  struct timeval lasttime, thistime;
  struct timezone tz = {0,0};
  double diff;
  int errflg = 0;
  int file;
  int numfiles;
  struct cyclades_monitor cywork;
  
  int i;
  extern char *optarg;
  extern int optind;
  unsigned long threshold_value;
  unsigned long timeout_value;
  double xfer_rate;
#ifdef XMIT
  double xmit_rate;
#endif
  
  global_argc = argc;		/* For signal routine. */
  global_argv = &argv;		/* For signal routine. */

  while (EOF != (i = getopt(argc, argv, 
			    "qs:S:t:T:gGi:"))) {
    switch (i) {
    case 'q':
      query = 1;
      break;
    case 'i':
      interval = atoi(optarg);
      if(interval <= 0) {
	fprintf(stderr, "Invalid interval value: %s\n",optarg);
	errflg ++;
      }
      break;
    case 's':
      ++set;
      set_val = atoi(optarg);
      if(set_val <= 0 || set_val > 12) {
	fprintf(stderr, "Invalid set value: %s\n",optarg);
	errflg ++;
      }
      break;
    case 'S':
       ++set_def;
      set_def_val = atoi(optarg);
      if(set_def_val < 0 || set_def_val > 12) {
	fprintf(stderr, "Invalid default value: %s\n",optarg);
	errflg ++;
      }
      break;
    case 't':
      ++set_time;
      set_time_val = atoi(optarg);
      if(set_time_val <= 0 || set_time_val > 255) {
	fprintf(stderr, "Invalid set time value: %s\n",optarg);
	errflg ++;
      }
      break;
    case 'T':
       ++set_def_time;
      set_def_time_val = atoi(optarg);
      if(set_def_time_val < 0 || set_def_time_val > 255) {
	fprintf(stderr, "Invalid default time value: %s\n",optarg);
	errflg ++;
      }
      break;
    case 'g': ++get;     break;
    case 'G': ++get_def; break;
    default:
      errflg ++;
    }
  }
  numfiles = argc - optind;
  if(errflg || (numfiles == 0)
     || (!query && !set && !set_def && 
	 !get && !get_def && !set_time && !set_def_time) || 
     (set && set_def) || (set_time && set_def_time) || 
     (get && get_def)) {
    fprintf(stderr, 
	    "Usage: %s [-q [-i interval]]"
	    " ([-s value]|[-S value]) ([-t value]|[-T value])"
	    " [-g|-G] file [file...]\n",
	    argv[0]);
    exit(1);
  }

  global_optind = optind;	/* For signal routine. */

  if (set || set_def) {
    for(i = optind;i < argc;i ++) {
      file = open(argv[i],O_RDONLY);
      if(file == -1) {
	fprintf(stderr, "Can't open %s: %s\n",argv[i],strerror(errno));
	exit(1);
      }
      if(ioctl(file,
	       set ? CYSETTHRESH : CYSETDEFTHRESH,
	       set ? set_val : set_def_val)) {
	fprintf(stderr, "Can't set %s to threshold %d: %s\n",
		argv[i],set?set_val:set_def_val,strerror(errno));
	exit(1);
      }
    }
  }
  if (set_time || set_def_time) {
    for(i = optind;i < argc;i ++) {
      file = open(argv[i],O_RDONLY);
      if(file == -1) {
	fprintf(stderr, "Can't open %s: %s\n",argv[i],strerror(errno));
	exit(1);
      }
      if(ioctl(file,
	       set_time ? CYSETTIMEOUT : CYSETDEFTIMEOUT,
	       set_time ? set_time_val : set_def_time_val)) {
	fprintf(stderr, "Can't set %s to time threshold %d: %s\n",
		argv[i],set_time?set_time_val:set_def_time_val,strerror(errno));
	exit(1);
      }
    }
  }

  if (get || get_def) {
    for(i = optind;i < argc;i ++) {
      file = open(argv[i],O_RDONLY);
      if(file == -1) {
	fprintf(stderr, "Can't open %s: %s\n",argv[i],strerror(errno));
	exit(1);
      }
      if(ioctl(file, get ? CYGETTHRESH : CYGETDEFTHRESH, &threshold_value)) {
	fprintf(stderr, "Can't get threshold for %s: %s\n",
		argv[i],strerror(errno));
	exit(1);
      }
      if(ioctl(file, get ? CYGETTIMEOUT : CYGETDEFTIMEOUT, &timeout_value)) {
	fprintf(stderr, "Can't get timeout for %s: %s\n",
		argv[i],strerror(errno));
	exit(1);
      }
      printf("%s: %ld %s threshold and %ld %s timeout\n",
	     argv[i], threshold_value, 
	     get?"current":"default",
	     timeout_value,
	     get?"current":"default");
    }
  }

  if(!query) return 0;		/* must have been something earlier */

  /* query stuff after this line */
  
  cmon = (struct cyclades_control *) malloc(sizeof (struct cyclades_control)
					    * numfiles);
  if(!cmon) {
    perror("malloc failed");
    exit(1);
  }
  if(signal(SIGINT, summary)||
     signal(SIGQUIT, summary)||
     signal(SIGTERM, summary)) {
    perror("Can't set signal handler");
    exit(1);
  }
  if(gettimeofday(&lasttime,&tz)) {
    perror("gettimeofday failed");
    exit(1);
  }
  for(i = optind; i < argc; i ++) {
    cmon_index = i - optind;
    cmon[cmon_index].cfile = open(argv[i], O_RDONLY);
    if(-1 == cmon[cmon_index].cfile) {
      fprintf(stderr, "Can't open %s: %s\n",argv[i],strerror(errno));
      exit(1);
    }
    if(ioctl(cmon[cmon_index].cfile, CYGETMON, &cmon[cmon_index].c)) {
      fprintf(stderr, "Can't issue CYGETMON on %s: %s\n",
	      argv[i],strerror(errno));
      exit(1);
    }
    summary(-1);
    if(ioctl(cmon[cmon_index].cfile, CYGETTHRESH, &threshold_value)) {
      fprintf(stderr, "Can't get threshold for %s: %s\n",
	      argv[i],strerror(errno));
      exit(1);
    }
    if(ioctl(cmon[cmon_index].cfile, CYGETTIMEOUT, &timeout_value)) {
      fprintf(stderr, "Can't get timeout for %s: %s\n",
	      argv[i],strerror(errno));
      exit(1);
    }
  }
  while(1) {
    sleep(interval);
    
    if(gettimeofday(&thistime,&tz)) {
      perror("gettimeofday failed");
      exit(1);
    }
    diff = dtime(&thistime, &lasttime);
    mvtime(&lasttime, &thistime);

    for(i = optind; i < argc; i ++) {
      cmon_index = i - optind;
      if(ioctl(cmon[cmon_index].cfile, CYGETMON, &cywork)) {
	fprintf(stderr, "Can't issue CYGETMON on %s: %s\n",
		argv[i],strerror(errno));
	exit(1);
      }
      if(ioctl(cmon[cmon_index].cfile, CYGETTHRESH, &threshold_value)) {
	fprintf(stderr, "Can't get threshold for %s: %s\n",
		argv[i],strerror(errno));
	exit(1);
      }
      if(ioctl(cmon[cmon_index].cfile, CYGETTIMEOUT, &timeout_value)) {
	fprintf(stderr, "Can't get timeout for %s: %s\n",
		argv[i],strerror(errno));
	exit(1);
      }

      xfer_rate = cywork.char_count/diff;
#ifdef XMIT
      xmit_rate = cywork.send_count/diff;
#endif

      if(threshold_value != cmon[cmon_index].threshold_value ||
	 timeout_value != cmon[cmon_index].timeout_value) {
	summary(-2);
	/* Note that the summary must come before the setting of */
	/* threshold_value */
	cmon[cmon_index].threshold_value = threshold_value;	 
	cmon[cmon_index].timeout_value = timeout_value;	 
      } else {
	/* Don't record this first cycle after change */
	if(xfer_rate > cmon[cmon_index].maxtran) 
	  cmon[cmon_index].maxtran = xfer_rate;
#ifdef XMIT
	if(xmit_rate > cmon[cmon_index].maxxmit)
          cmon[cmon_index].maxxmit = xmit_rate;
#endif
	if(cywork.char_max > cmon[cmon_index].maxmax) 
	  cmon[cmon_index].maxmax = cywork.char_max;
      }

#ifdef XMIT
      printf("%s: %lu ints, %lu/%lu chars; "
	     "fifo: %lu thresh, %lu tmout, "
	     "%lu max, %lu now\n",
	     argv[i],
	     cywork.int_count,cywork.char_count,cywork.send_count,
	     threshold_value,timeout_value,
	     cywork.char_max,cywork.char_last);
      printf("   %f int/sec; %f rec, %f send (char/sec)\n",
	     cywork.int_count/diff,
	     xfer_rate,
	     xmit_rate);
#else
      printf("%s: %lu ints, %lu chars; "
	     "fifo: %lu thresh, %lu tmout, "
	     "%lu max, %lu now\n",
	     argv[i],
	     cywork.int_count,cywork.char_count,
	     threshold_value,timeout_value,
	     cywork.char_max,cywork.char_last);
      printf("   %f int/sec; %f rec (char/sec)\n",
	     cywork.int_count/diff,
	     xfer_rate);
#endif
      memcpy(&cmon[cmon_index].c, &cywork, sizeof (struct cyclades_monitor));
    }
  }

  return 0;
}
