/*
   TraceVisualizer.c : Main file for linux trace visualizer.
   Copyright (C) 1999, 2000 Karim Yaghmour (karym@opersys.com).
 
   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; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

   History : 
     K.Y., 11/10/99, Modified to conform with complete kernel instrumentation
     K.Y., 26/06/1999, Initial typing.
*/

#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

#include <Tables.h>
#include <EventDB.h>
#include <RTAIDB.h>
#include <EventOptions.h>

#include "MainWindow.h"

/*******************************************************************
 * Function :
 *    ParseOptions()
 * Description :
 *    Parses the command line arguments and sets the options
 *    structure to the right values.
 * Parameters :
 *    pmArgc, The number of command line options given to the program
 *    pmArgv, The argument vector given to the program
 *    pmOptions, The options structure to be set
 * Return values :
 *    NONE
 * History :
 *    K. Yaghmour, 19/05/99, Initial typing.
 * Note :
 *    This function will "exit()" if something is wrong with the
 *    command line options. If this is to go in graphic mode then
 *    gtk_init() will be called.
 *******************************************************************/
void ParseOptions(int pmArgc, char** pmArgv, options* pmOptions)
{
  int     i, j;         /* Generic indexes */
  int     lOptLen = 0;  /* Length of option string */

#if GTK_ENV
  /* Are we going in graphic mode right away */
  if(pmArgc == 1)
    {
    pmOptions->Graphic = TRUE;
    gtk_init(&pmArgc, &pmArgv);
    return;
    }
#endif /* GTK_ENV */

  /* Read the command line arguments */
  for(i = 1; i < pmArgc; i++)
    {
    /* Get the option's length */
    lOptLen = strlen(pmArgv[i]);

#if 0
    /* DEBUG */
    printf("Analyzing option : %s \n", pmArgv[i]);
#endif

    /* Is this an option or a file name */
    if(pmArgv[i][0] == '-')
      {
      /* Is this long enough to really be an option */
      if(lOptLen < 2)
	{
        /* Hey Larry, Please give us something meaningfull! */
	printf("Incomplete option : %d \n", i);
	exit(1);
	}

      /* Depending on the option given */
      switch(pmArgv[i][1])
	{
	/* Graphic */
	case 'g':
	case 'G':
#if GTK_ENV
	  /* -g has to be the first argument */
	  if(i != 1)
	    {
	    printf("Option -g has to be the first option \n");
	    exit(1);
	    }

	  /* Set graphic option */
	  pmOptions->Graphic = TRUE;

	  /* Initialize the graphic environment, Note that the original argc doesn't change */
	  gtk_init(&pmArgc, &pmArgv);
#else
	  printf("This version wasn't compiled with GTK support ... Aborting \n");
	  exit(1);
#endif /* GTK_ENV */
	  break;

	/* Dump */
	case 'd':
	case 'D':
	  pmOptions->Dump = TRUE;
	  break;

	/* Omit */
	case 'o':
	case 'O':
	  /* Did he specify omit and trace :( */
	  if(pmOptions->Trace == TRUE)
	    {
	    printf("Cannot specify omit and trace, they are mutually exclusive options! \n");
	    exit(1);
	    }

	  /* Is the option given long enough */
	  if(lOptLen < 3)
	    {
	    printf("Omit without event : %d \n", i);
	    printf("What are we supposed to omit? \n");
	    exit(1);
	    }

	  /* We're on omit mode */
	  pmOptions->Omit = TRUE;

	  /* Set event to be omitted */
	  for(j = 0; j <= MaxEventID; j++)
	    {
            /* Is this the option he gave us */
	    if(!strcmp(EVENT_OT(j), &(pmArgv[i][2])))
	      {
	      ltt_set_bit(j, &(pmOptions->OmitMask));
	      break;
	      }
	    }

	  /* Did he specify an unknown option */
	  if(j > MaxEventID)
	    {
	    printf("Option -o or -O accompanied by unknown event %s \n", &(pmArgv[i][2]));
	    exit(1);
	    }
	  break;

	/* Trace */
	case 't':
	case 'T':
	  /* Did he specify omit and trace :( */
	  if(pmOptions->Omit == TRUE)
	    {
	    printf("Cannot specify omit and trace, they are mutually exclusive options \n");
	    exit(1);
	    }

	  /* Is the option given long enough */
	  if(lOptLen < 3)
	    {
	    printf("Trace without event : %d \n", i);
	    printf("What are we supposed to trace? \n");
	    exit(1);
	    }

	  /* We're on trace mode */
	  pmOptions->Trace = TRUE;

	  /* Set event to be traced */
	  for(j = 0; j <= MaxEventID; j++)
	    {
            /* Is this the option he gave us */
	    if(!strcmp(EVENT_OT(j), &(pmArgv[i][2])))
	      {
	      ltt_set_bit(j, &(pmOptions->TraceMask));
	      break;
	      }
	    }

	  /* Did he specify an unknown option */
	  if(j > MaxEventID)
	    {
	    printf("Option -t or -T accompanied by unknown event %s \n", &(pmArgv[i][2]));
	    exit(1);
	    }
	  break;

	/* Trace only one CPU */
	case 'c':
	case 'C':
	  /* Is there enough to specify a CPU-ID */
	  if(lOptLen < 3)
	    {
	    printf("Option -c or -C needs a CPU-ID \n");
	    exit(1);
	    }
	  /* Set the required information */
	  pmOptions->TraceCPUID = TRUE;
	  pmOptions->CPUID = strtol(&(pmArgv[i][2]), (char**) NULL, 10);
	  break;

	/* Trace only one PID */
	case 'p':
	case 'P':
	  /* Is there enough to specify a PID */
	  if(lOptLen < 3)
	    {
	    printf("Option -p or -P needs a PID \n");
	    exit(1);
	    }

	  /* Set the required information */
	  pmOptions->TracePID = TRUE;
	  pmOptions->PID = strtol(&(pmArgv[i][2]), (char**) NULL, 10);
	  /* printf("Tracing %d only \n", pmOptions->PID); */
	  break;

	/* Summarize */
	case 's':
	case 'S':
	  pmOptions->Summarize = TRUE;
	  break;

	/* Account the time passed in each system call */
	case 'a':
	case 'A':
	  pmOptions->AcctSysCall = TRUE;
	  break;

	/* Forget */
	case 'f':
	case 'F':
	  /* Is the option long enough to tell us what to forget */
	  if(lOptLen < 3)
	    {
	    printf("Option %d doesn't specify what to forget \n", i);
	    exit(1);
	    }

	  /* Depending on what to forget */
	  switch(pmArgv[i][2])
	    {
            /* CPUID */
	    case 'c':
	    case 'C':
	      pmOptions->ForgetCPUID = TRUE;
	      break;

            /* Time */
	    case 't':
	    case 'T':
	      pmOptions->ForgetTime = TRUE;
	      break;

            /* PID */
	    case 'p':
	    case 'P':
	      pmOptions->ForgetPID = TRUE;
	      break;

            /* Data entry length */
	    case 'l':
	    case 'L':
	      pmOptions->ForgetDataLen = TRUE;
	      break;

            /* String */
	    case 's':
	    case 'S':
	      pmOptions->ForgetString = TRUE;
	      break;

	    default:
	      /* I would really like to do this for you, but I wasn't taught how to! */
	      printf("Unknown option : -%c%c \n", pmArgv[i][1], pmArgv[i][2]);
	      exit(1);
	    }
	  break;

	/* Benchmark and print drawing times */
	case 'Z':
	  pmOptions->Benchmark = TRUE;
	  break;

	default :
	  /* I would really like to do this for you, but I wasn't taught how to! */
	  printf("Unknown option : -%c \n", pmArgv[i][1]);
	  exit(1);
	}
      }
    else /* Definitely a file name */
      {
      /* Do we already have the input file name */
      if(pmOptions->InputFileName == NULL)
	{
	/* Copy the file name */
	pmOptions->InputFileName = (char*) malloc(lOptLen + 1);
	strcpy(pmOptions->InputFileName, pmArgv[i]);
	}
      else
	{
	/* Do we already have /proc information */
	if(pmOptions->ProcFileName == NULL)
	  {
	  /* Copy the file name */
	  pmOptions->ProcFileName = (char*) malloc(lOptLen + 1);
	  strcpy(pmOptions->ProcFileName, pmArgv[i]);
	  }
	else
	  {
	  /* Do we already have the output file name and are we not in graphic mode */
	  if((pmOptions->OutputFileName == NULL) && (pmOptions->Graphic != TRUE))
	    {
	    /* Copy the file name */
	    pmOptions->OutputFileName = (char*) malloc(lOptLen + 1);
	    strcpy(pmOptions->OutputFileName, pmArgv[i]);
	    }
	  else if(pmOptions->Graphic != TRUE)
	    {
	    /* This guy doesn't know what he's doing! Tell him about it and get outa here ... */
	    printf("Wrong number of command line arguments \n");
	    printf("Unknown : %s \n", pmArgv[i]);
	    exit(1);
	    }
	  }
	}
      }
    }

  /* If no output options were given, then dump */
  if ((pmOptions->Graphic     == FALSE)
   && (pmOptions->Dump        == FALSE)
   && (pmOptions->Omit        == FALSE)
   && (pmOptions->Trace       == FALSE)
   && (pmOptions->TraceCPUID  == FALSE)
   && (pmOptions->TracePID    == FALSE)
   && (pmOptions->AcctSysCall == FALSE)
   && (pmOptions->InputFileName  != NULL)
   && (pmOptions->ProcFileName   != NULL)
   && (pmOptions->OutputFileName != NULL))
    pmOptions->Dump = TRUE;

  /* Do we have the correct arguments */
  if(((pmOptions->InputFileName  == NULL)
   || (pmOptions->ProcFileName   == NULL)
   || (pmOptions->OutputFileName == NULL))
   && (pmOptions->Graphic != TRUE))
    {
    /* Insufficient number of arguments */
    printf("Insufficient number of command line arguments \n");
    printf("Usage: tracevisualizer [[Options]] [InputFile] [ProcInfoFile] [OutputFile] \n");
    exit(1);
    }
}

/******************************************************************
 * Function : 
 *    main()
 * Description :
 *    Main entry point into data decoder.
 * Parameters :
 *    argc, The number of command line arguments
 *    argv, Array of strings containing the command line arguments
 * Return values :
 *    NONE
 * History :
 *    K. Yaghmour, 03/99, Initial typing.
 * Note :
 ******************************************************************/
int main(int argc, char** argv)
{
  db*               pTraceDB;             /* The trace database */
  int               lDecodedDataFile = 0; /* File containing the human readable data */
  int               lTraceDataFile = 0;   /* The file to which tracing data WAS dumped */
  systemInfo*       pSystem;              /* Full description of system during trace */
  options*          pOptions;             /* Parsed command line options */
  FILE*             lProcDataFile = NULL; /* File containing /proc information */
#if GTK_ENV
  systemView*       pSysView;             /* A windowed view of the system's evolution */
#endif

  /* Initialize variables used */
  pTraceDB = NULL;
  pSystem  = NULL;
  pOptions = NULL;
#if GTK_ENV
  pSysView = NULL;
#endif

  /* Allocate space for options */
  pOptions = CreateOptions();

  /* Parse the command line options */
  ParseOptions(argc, argv, pOptions);

  /* Are we in graphic mode but have an input filename  */
  if(!((pOptions->Graphic == TRUE) && (pOptions->InputFileName == NULL)))
    {
    /* Open the input file */
    if((lTraceDataFile = open(pOptions->InputFileName, O_RDONLY, 0)) < 0)
      {
      /* File ain't good */
      printf("Unable to open input data file \n");
      exit(1);
      }
    /* Open the /proc information file */
    if((lProcDataFile = fopen(pOptions->ProcFileName, "r")) == NULL)
      {
      /* File ain't good */
      printf("Unable to open /proc information file \n");
      close(lTraceDataFile);
      exit(1);
      }
    }

  /* Are we not in graphic mode */
  if(pOptions->Graphic != TRUE)
    {
    /* Open the output file */
    if((lDecodedDataFile = creat(pOptions->OutputFileName, S_IRUSR| S_IWUSR | S_IRGRP | S_IROTH)) < 0)
      {
      /* File ain't good */
      printf("Unable to open output file \n");
      close(lTraceDataFile);
      fclose(lProcDataFile);
      exit(1);
      }
    }

  /* Create data-base and system structures */
  pTraceDB = DBCreateDB();
  pSystem  = DBCreateSystem();

  /* Read raw trace data into data-base */
  if(pOptions->InputFileName != NULL)
    {
    if(DBReadTrace(lTraceDataFile, pTraceDB) != TRUE)
      {
      /* Something's wrong with the input file */
      printf("Input file isn't a valid trace file \n");
      exit(1);
      }

    /* Process the information in the /proc information file */
    DBProcessProcInfo(lProcDataFile, pSystem);

    /* Close the /proc info file */
    fclose(lProcDataFile);

    /* Process the database according to selected options */
    DBProcessTrace(pSystem, pTraceDB, pOptions);
    }

  /* Are we not in graphic mode */
  if(pOptions->Graphic != TRUE)
    {
    /* Depending on the type of system */
    switch(pTraceDB->SystemType)
      {
      /* Plain Linux */
      case TRACE_SYS_TYPE_VANILLA_LINUX:
	/* Print out the data-base and add details requested at the command-line */
	DBPrintTrace(lDecodedDataFile, pSystem, pTraceDB, pOptions);
	break;

#if SUPP_RTAI
      /* RTAI/Linux system */
      case TRACE_SYS_TYPE_RTAI_LINUX:
	/* Print out the data-base and add details requested at the command-line */
	RTAIDBPrintTrace(lDecodedDataFile, pSystem, pTraceDB, pOptions);
#endif /* SUPP_RTAI */
      }

    /* Close the output */
    close(lDecodedDataFile);
    printf("Output has been written to %s \n", pOptions->OutputFileName);
    }
  else
    {
#if GTK_ENV
    /* Create a main window */
    pSysView = WDCreateSystemView(pSystem, pTraceDB, pOptions);

    /* Did we have an input file */
    if(pOptions->InputFileName != NULL)
      WDDisplayTrace(pSysView);

    /* Show the main window  */
    WDShowMainWindow(pSysView);

    /* Go into main loop */
    gtk_main();
#endif /* GTK_ENV */
    }

  /* Close the input file */
  if(pOptions->InputFileName != NULL)
    close(lTraceDataFile);

  /* We're outa here */
  exit(0);
}

#if 0 /* This is for coding purposes only */
/******************************************************************
 * Function :
 * Description :
 * Parameters :
 * Return values :
 * History :
 * Note :
 ******************************************************************/
#endif
