/********************************************************************/
/*                                                                  */
/*   MQTIMES2 supports the following parameters                     */
/*                                                                  */
/*      -c message count (number to read before ending)             */
/*      -t maximum timeout value (end if no messages in this time)  */
/*      -q the name of the input queue (required)                   */
/*      -m queue manager name (optional)                            */
/*      -s buffer size to read messages into                        */
/*      -b batch size (number of messages in a unit of work)        */
/*      -r report interval (seconds between report lines)           */
/*      -p name of file containing PAN reply data                   */
/*      -n name of file containing NAN reply data                   */
/*      -d drain queue before starting measurements                 */
/*                                                                  */
/*    if no queue manager is specified, the default queue manager   */
/*    is used.                                                      */
/*                                                                  */
/********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "signal.h"

#ifdef WIN32
#include "windows.h"
#endif

/* includes for MQI */
#include <cmqc.h>
#include <cmqxc.h>

#ifdef SOLARIS
#include <ctype.h>
#include <unistd.h>
#endif

#include "int64defs.h"

#define MAX_MESSAGE_LENGTH	64 * 65536
#define TIME_OUT_DEF		120
#define DEF_SYNC			25
#define MAX_SYNC_ALLOW		1000

/************************************/
/* definition of argument structure */
/************************************/

typedef struct {
	int		maxmsgs;
	int		maxmsglen;
	int		maxtime;
	int		reportInterval;
	int		drainQ;
	int		batchSize;
	int		fileSizePAN;
	int		fileSizeNAN;
	int		calcLatency;
	char	*fileDataPAN;
	char	*fileDataNAN;
	char	qname[MQ_Q_NAME_LENGTH + 1];
	char	qmname[MQ_Q_MGR_NAME_LENGTH + 1];
	char	replyQname[MQ_Q_NAME_LENGTH + 1];
	char	replyQMname[MQ_Q_MGR_NAME_LENGTH + 1];
} PGM_ARGS;

/**********************************************************/
/* MY_TIME_T                                              */
/* The definition of this data type is platform specific. */
/**********************************************************/
#ifdef WIN32
typedef unsigned __int64 MY_TIME_T;
#else
typedef struct timeval MY_TIME_T;
#endif

/* global error switch */
	int		err=0;

/* global termination switch */
	volatile int	terminate=0;

static const unsigned char HEX_NUMBERS[] = "0123456789ABCDEF";

static char copyright[] = "(C) Copyright IBM Corp, 2001/2002/2004/2005";
static char Version[]=\
"@(#)MQTimes2 V2.0 - MQSI V2 Performance results tool  - Jim MacNair ";

#ifdef _DEBUG
static char Level[]="mqtimes2.c V2.0 Debug version ("__DATE__" "__TIME__")";
#else
#ifdef MQCLIENT
static char Level[]="mqtimes2c.c V2.0 Client version ("__DATE__" "__TIME__")";
#else
static char Level[]="mqtimes2.c V2.0 Release version ("__DATE__" "__TIME__")";
#endif
#endif

/**************************************************************/
/*                                                            */
/* Routine to reverse the bytes in an int variable            */
/*                                                            */
/**************************************************************/

int reverseBytes4(int var)

{
    return ((unsigned int) var >> 24) + (((unsigned int) var & 0x00ff0000) >> 8) +
           (((unsigned int) var & 0x0000ff00) << 8) + (((unsigned int) var & 255) <<24);
}

int getSecs(MQLONG time)

{
	int		hh;
	int		mm;
	int		ss;

	ss = time % 100;
	mm = ((time - ss) % 10000) / 100;
	hh = (time - mm - ss) / 10000;

	return (hh * 3600) + (mm * 60) + ss;
}

/**************************************************************/
/*                                                            */
/* Subroutine to format a time.                               */
/*                                                            */
/* The input time should be 6 numbers (hhmmss)                */
/*                                                            */
/**************************************************************/

void formatTimeSecs(char *timeOut, int timeIn)

{
	char	tempTime[16];

	sprintf(tempTime, "%6.6d", timeIn);
	timeOut[0] = tempTime[0];
	timeOut[1] = tempTime[1];
	timeOut[2] = ':';
	timeOut[3] = tempTime[2];
	timeOut[4] = tempTime[3];
	timeOut[5] = ':';
	timeOut[6] = tempTime[4];
	timeOut[7] = tempTime[5];
	timeOut[8] = 0;
}

void checkerror(const char *mqcalltype, MQLONG compcode, MQLONG reason, char *resource)

{
	if ((compcode != MQCC_OK) || (reason != MQRC_NONE))
	{
		printf("MQSeries error with %s on %s - compcode = %d, reason = %d\n",
				mqcalltype, resource, compcode, reason);
	}
}

void captureString(char * value, char * arg, const int maxlen)

{
	if ((int) strlen(arg) < maxlen)
	{
		strcpy(value, arg);
	}
	else
	{
		/* issue an error message */
		printf("*****Value of constant exceeds maximum length\n");

		/* Set the return code based on the message number */
		err = 94;
	}
}

/*********************************************************/
/* GetTime - get high precision time.                    */
/*********************************************************/

#ifdef _WIN32
MY_TIME_T GetTime()
{
	MY_TIME_T	count;
	if (!QueryPerformanceCounter((LARGE_INTEGER *)&count))
	{
		count = 0;
	}

	return(count);
}
#else
MY_TIME_T GetTime()
{
	MY_TIME_T	tv;
	gettimeofday(&tv, 0);
	return(tv);
}
#endif

/*********************************************************/
/* DiffTime - difference in microseconds between two     */
/*  high precision times.                                */
/*********************************************************/

#ifdef _WIN32
double DiffTime(MY_TIME_T start, MY_TIME_T end)
{
	unsigned long diffLong;
	__int64	freq;
	double			usecs;
	diffLong = (unsigned long)(end-start);
	QueryPerformanceFrequency((LARGE_INTEGER *)&freq);
	usecs = (double)diffLong*1000*1000/freq;
	return(usecs);
}
#else
double DiffTime(MY_TIME_T start, MY_TIME_T end)
{
  unsigned long val;
  if (end.tv_usec < start.tv_usec)
  {
      val = 1000000;
      val += start.tv_usec - end.tv_usec;
  }
  else
  {
      val = end.tv_usec - start.tv_usec;
  }
  val += (end.tv_sec - start.tv_sec)*1000000;
  return((double)val/1.0);
}
#endif

/*********************************************************/
/* formatTimeDiff - format a number of microseconds      */
/*  resulting from a difference between two times.       */
/*********************************************************/

void formatTimeDiff(char * result, double time)

{
	int secs=0;
	int usecs=0;

	result[0] = 0;

	/* calculate the average latency */
	secs = (int)(time / 1000000);
	usecs = (int)(time) % 1000000;

	/* display the results */
	sprintf(result, "%d.%6.6d", secs, usecs);
}

/*********************************************************/
/*                                                       */
/* Translate from ASCII to Hex                           */
/*                                                       */
/*********************************************************/

void AsciiToHex(unsigned char *dati, unsigned int pl, unsigned char *dato)

{
	unsigned int i;
	unsigned int buffer;
	char	ch;

	buffer = 0;
	i = 0;
	while (buffer < pl)
	{
		ch = (unsigned char) dati[buffer] >> 4;
		ch = HEX_NUMBERS[ch];
		dato[i++] = ch;

		ch = (unsigned char) dati[buffer] & 0x0F;
		ch = HEX_NUMBERS[ch];
		dato[i++] = ch;
		buffer++;
	}
}

int getArgPointer(int argc,
				  char *argv[],
				  int index,
				  char ** parmData,
				  const char * parmDesc)

{
	if (strlen(argv[index]) > 2)
	{
		/* point to the argument data */
		(*parmData) = argv[index] + 2;
	}
	else
	{
		index++;

		if (index < argc)
		{
			/* point to the argument data */
			(*parmData) = argv[index];
		}
		else
		{
			/* issue an error message */
			printf("*****%s parameter is missing value\n", parmDesc);
			
			/* Set the return code to indicate the error */
			err = 95;

			/*point to something valid */
			(*parmData) = argv[index - 1] + 2;
		}
	}


	return index;
}

int processFileArg(int argc,
				   char *argv[], 
				   int index, 
				   int * fileSize, 
				   char **fileData,
				   const char * parmDesc)

{
	FILE	*inputFile;
	char	*fileName;

	index = getArgPointer(argc, argv, index, &fileName, parmDesc);

	if (index < argc)
	{
		/* process the file */
		/* look for a file name that contains the PAN data */
		inputFile = fopen(fileName, "rb");
		if (NULL == inputFile)
		{
			/* error opening PAN file */
			printf("***** unable to open PAN file data for %s\n", argv[5]);
		}
		else
		{
			/* read the PAN file data */
			/* get the length of the data */
			fseek(inputFile, 0L, SEEK_END);
			(*fileSize) = ftell(inputFile);

			/* read the data from the beginning of the file */
			fseek(inputFile, 0L, SEEK_SET);
			(*fileData) = (char *)malloc((*fileSize) + 1);

			if ((*fileSize) > 0)
			{
				fread((*fileData), 1, (*fileSize), inputFile);
			}

			(*fileData)[(*fileSize)] = 0;

			/* close the file */
			fclose(inputFile);
		}
	}

	return index;
}

int processNumArg(int argc,
				  char *argv[], 
				  int index, 
				  int * value, 
				  const char * parmDesc)

{
	char *argData;

	index = getArgPointer(argc, argv, index, &argData, parmDesc);

	if (index < argc)
	{
		/* convert to an integer */
		(*value) = atoi(argData);
	}

	return index;
}

int processIndArg(int argc, 
				  char *argv[], 
				  int index, 
				  char * parm, 
				  int parmsize, 
				  const char * parmDesc)

{
	char *argData;

	index = getArgPointer(argc, argv, index, &argData, parmDesc);

	if (index < argc)
	{
		/* value is included with parameter */
		captureString(parm, argData, parmsize);
	}

	return index;
}

void processArgs(int argc, char **argv, PGM_ARGS * args)

{
	int		i;
	int		foundit;
	char	ch;

	i = 1;
	while ((i < argc) && (0 == err))
	{
		foundit = 0;

		if ('-' == argv[i][0])
		{
			ch = toupper(argv[i][1]);

			/* check for option */
			if ((0 == foundit) && ('Q' == ch))
			{
				foundit = 1;

				i = processIndArg(argc, 
								  argv, 
								  i,
								  args->qname, 
								  sizeof(args->qname), 
								  "queue name (-q)");
			}

			/* check for option */
			if ((0 == foundit) && ('M' == ch))
			{
				foundit = 1;

				i = processIndArg(argc, 
								  argv, 
								  i,
								  args->qmname, 
								  sizeof(args->qmname), 
								  "queue manager name (-m)");
			}

			/* check for option */
			if ((0 == foundit) && ('T' == ch))
			{
				foundit = 1;

				i = processNumArg(argc,
								  argv, 
								  i,
								  &(args->maxtime), 
								  "Timeout (-t)");

				if (args->maxtime < 30)
				{
					printf("Timeout parameter (%d) invalid or below minimum of 30 seconds\n", args->maxtime);
					args->maxtime = TIME_OUT_DEF;
					printf("Set to default value of %d\n", args->maxtime);
				}
			}

			/* check for option */
			if ((0 == foundit) && ('B' == ch))
			{
				foundit = 1;

				i = processNumArg(argc,
								  argv, 
								  i,
								  &(args->batchSize), 
								  "Messages within unit of work (-b)");

				if ((args->batchSize < 1) || (args->batchSize > MAX_SYNC_ALLOW))
				{
					printf("Invalid sync point interval %d\n", args->batchSize);
					args->batchSize = DEF_SYNC;
				}

				printf("Sync point interval set to %d\n", args->batchSize);
			}

			/* check for option */
			if ((0 == foundit) && ('C' == ch))
			{
				foundit = 1;

				i = processNumArg(argc,
								  argv, 
								  i,
								  &(args->maxmsgs), 
								  "Message count (-c)");
			}

			/* check for option */
			if ((0 == foundit) && ('R' == ch))
			{
				foundit = 1;

				i = processNumArg(argc,
								  argv, 
								  i,
								  &(args->reportInterval), 
								  "Report interval (-r)");

				if (args->reportInterval > 100)
				{
					printf("reportInterval cannot exceed 100 - reset to 100\n");
					args->reportInterval = 100;
				}
			}

			/* check for option */
			if ((0 == foundit) && ('S' == ch))
			{
				foundit = 1;

				i = processNumArg(argc,
								  argv, 
								  i,
								  &(args->maxmsglen), 
								  "Maximum message size (-S)");

				if ((args->maxmsglen > 104857600) || (args->maxmsglen < 4096))
				{
					printf("Maximum msg size cannot exceed 104857600 and must be at least 4096 - reset to %d\n", MAX_MESSAGE_LENGTH);
					args->maxmsglen = MAX_MESSAGE_LENGTH;
				}
			}

			/* check for option */
			if ((0 == foundit) && ('D' == ch))
			{
				foundit = 1;
				args->drainQ = 1;
			}

			/* check for option */
			if ((0 == foundit) && ('P' == ch))
			{
				foundit = 1;

				i = processFileArg(argc,
								   argv, 
								   i,
								   &(args->fileSizePAN), 
								   &(args->fileDataPAN),
								  "PAN Message data (-p)");

				printf("Positive acknowledgement report messages will be sent (data length %d)\n", 
					   args->fileSizePAN);
			}

			/* check for option */
			if ((0 == foundit) && ('N' == ch))
			{
				foundit = 1;

				i = processFileArg(argc,
								   argv, 
								   i,
								   &(args->fileSizeNAN), 
								   &(args->fileDataNAN),
								  "NAN Message data (-p)");

				printf("Negative acknowledgement report messages will be sent (data length %d)\n", 
					   args->fileSizeNAN);
			}

			/* check for option */
			if ((0 == foundit) && ('L' == ch))
			{
				foundit = 1;
				args->calcLatency = 1;
				printf("Latency calculations will be done and average/min/max latency will be calculated\n");
			}
		}

		/* did we recognize the parameter? */
		if (0 == foundit)
		{
			/* issue an error message */
			printf("*****Unrecognized command line parameter found %c\n", argv[i][0]);

			/* Set the return code based on the message number */
			err = 93;
		}

		i++;
	}

	if (args->maxmsgs <= 0)
	{
		printf("Message count parameter must be > 0");
		err = 1;
	}

	if (0 == args->qname[0])
	{
		printf("Queue name parameter is required\n");
		err = 1;
	}
}

char * checkRFH(char * msgdata, MQMD * msgdesc)

{
	int		rfhLen=0;
	int		encoding=msgdesc->Encoding;
	MQRFH	*tempRFH;

	/* N.B. this routine will only skip the first header - if there are more than one RFH header this will not work */
	if ((memcmp(msgdesc->Format, MQFMT_RF_HEADER, MQ_FORMAT_LENGTH) == 0) || (memcmp(msgdesc->Format, MQFMT_RF_HEADER_2, MQ_FORMAT_LENGTH) == 0))
	{
		tempRFH = (MQRFH *)msgdata;

		rfhLen = tempRFH->StrucLength;

		/* check what the native encoding is on this platform */
		if ((MQENC_NATIVE | MQENC_INTEGER_MASK) == MQENC_INTEGER_NORMAL)
		{
			/* host encoding */
			if ((encoding | MQENC_INTEGER_MASK) != MQENC_INTEGER_NORMAL)
			{
				rfhLen = reverseBytes4(rfhLen);
			}
		}
		else
		{
			/* pc encoding */
			if ((encoding | MQENC_INTEGER_MASK) == MQENC_INTEGER_NORMAL)
			{
				rfhLen = reverseBytes4(rfhLen);
			}
		}
	}

	return msgdata + rfhLen;
}

void issueReply(MQHCONN qm, MQLONG	report, int *uow, char * msgId, PGM_ARGS * args)

{
	MQHOBJ	replyq=0;
	MQOD	replyObjdesc = {MQOD_DEFAULT};
	MQMD	replyMsgdesc = {MQMD_DEFAULT};
	MQLONG	openopt = 0;
	MQLONG	compcode;
	MQLONG	reason;
	MQPMO	pmo = {MQPMO_DEFAULT};

	/* set the open options */
	strncpy(replyObjdesc.ObjectName, args->replyQname, MQ_Q_NAME_LENGTH);
	strncpy(replyObjdesc.ObjectQMgrName, args->replyQMname, MQ_Q_MGR_NAME_LENGTH);
	openopt = MQOO_OUTPUT | MQOO_FAIL_IF_QUIESCING;

	/* open the reply to queue to */
	MQOPEN(qm, &replyObjdesc, openopt, &replyq, &compcode, &reason);
	checkerror("MQOPEN(replyQ)", compcode, reason, args->replyQname);

	/* check if the open worked */
	if (MQCC_OK == compcode)
	{
		/* set the fields in the MQMD for the reply message */
		replyMsgdesc.MsgType = MQMT_REPORT;
		memcpy(replyMsgdesc.CorrelId, msgId, sizeof(replyMsgdesc.CorrelId));

		/* perform the put operation in a syncpoint */
		pmo.Options = MQPMO_SYNCPOINT | MQPMO_NEW_MSG_ID;

		/* write the report message */
		if (((report & MQRO_PAN) > 0) && (args->fileDataPAN != NULL))
		{
			/* check if the data has an RFH */
			if ((args->fileSizePAN > 32) && (memcmp(args->fileDataPAN, "RFH ", 4) == 0))
			{
				memcpy(replyMsgdesc.Format, "MQHRF2  ", 8);
			}

			MQPUT(qm, replyq, &replyMsgdesc, &pmo, args->fileSizePAN, args->fileDataPAN, &compcode, &reason);
		}
		else
		{
			/* check if the data has an RFH */
			if ((args->fileSizeNAN > 32) && (memcmp(args->fileDataNAN, "RFH ", 4) == 0))
			{
				memcpy(replyMsgdesc.Format, "MQHRF2  ", 8);
			}

			MQPUT(qm, replyq, &replyMsgdesc, &pmo, args->fileSizeNAN, args->fileDataNAN, &compcode, &reason);
		}

		checkerror("MQOPEN(reply)", compcode, reason, args->replyQname);

		/* force a syncpoint now */
		MQCMIT(qm, &compcode, &reason);
		checkerror("MQCMIT(reply)", compcode, reason, args->qmname);
		(*uow) = 0;

		/* close the queue */
		MQCLOSE(qm, &replyq, MQCO_NONE , &compcode, &reason);
		checkerror("MQCLOSE(reply)", compcode, reason, args->replyQname);

	}
}

void InterruptHandler (int sigVal) 
{ 
	terminate = 1;
}

void printHelp(char *pgmName)

{
	printf("\nformat is:\n");
#ifdef MQCLIENT
	printf("   mqtimes2c -c Count -q Queue <-m Queue manager> <-t Timeout> <-d> <-b nnn>\n");
#else
	printf("   mqtimes2 -c Count -q Queue <-m Queue manager> <-t Timeout> <-d> <-b nnn>\n");
#endif
	printf("    -s <nnn> <-r nnn> <-p PAN reply data file> <-n NAN reply data file> -l\n\n");
	printf("    Count is the number of messages to read before stopping.\n");
	printf("    Queue is the name of the queue to read messages from.\n");
#ifdef MQCLIENT
	printf("    Queue manager is the name of the queue manager that holds the input queue\n");
	printf("     or the format of an MQSERVER variable - channel name/TCP/hostname(port).\n");
#else
	printf("    Queue manager is the name of the queue manager that holds the input queue.\n");
#endif
	printf("    Timeout is the maximum time in seconds to wait for a message.  The program\n");
	printf("     ends when the maximum number of messages is read or a time out occurs.\n");
	printf("    The -d option will drain the queue before starting the measurement.\n");
	printf("     Any messages in the queue will be discarded.\n");
	printf("    The -b option specifies the number of messages in a single unit of work.\n");
	printf("    The -s option allows the message buffer size to be specified.\n");
	printf("     The default is 4MB.\n");
	printf("    The -r option allows the report interval to be specified.\n");
	printf("     It can be from 1 to 20.\n");
	printf("    The -l parameter is used for latency measurements.\n");
	printf("    If the program must respond to either PAN or NAN report options, a file\n");
	printf("     containing the data to be used for the reply message must be provided.\n");
}

int main(int argc, char **argv)
{
	int64_t		msgcount=0;
	int64_t		totcount=0;
	int64_t		totalbytes=0;
	int64_t		avgbytes;
	int64_t		maxrate=0;
	int64_t		firstsec=0;
	int			secondcount=0;
	int			reportCount=0;
	int			uow=0;
	int			drainCount=0;
	int64_t		recent10;
	int			recent10sec;
	int64_t		last10[10];			/* Average rate of last 10 intervals */
	int			last10secs[10];		/* time of last 10 intervals */
	int			firstTime=0;
	int			secondTime=0;
	int			lastTime=0;
	int			prevLastTime=0;
	int			remainingTime=0;
	int			firstInterval=1;	/* first interval indicator to not report recent average */
	int			minSize;
	int			secs;				/* work variable - number of seconds between first and last interval */
	int			i;
	MQLONG		report;				/* MQ report options */
	double		avgrate;
	MQHCONN		qm=0;
	MQHOBJ		q=0;
	MQLONG		compcode;
	MQLONG		reason;
	MQLONG		cc2;
	MQLONG		rc2;
	MQLONG		datalen=0;
	MQOD		objdesc = {MQOD_DEFAULT};
	MQMD		msgdesc = {MQMD_DEFAULT};
	MQLONG		openopt = 0;
	MQGMO		mqgmo = {MQGMO_DEFAULT};
	MQPMO		mqpmo = {MQPMO_DEFAULT};
	MQMD		replyMsg = {MQMD_DEFAULT};
	char		*msgdata;
	char		*userPtr;
	char		prevtime[9];
	char		currtime[9];
	MQLONG		prevtime_secs;
	MQLONG		currtime_secs;
	char		*msgPtr;
	char		timeFirst[16];
	char		timeLast[16];
	char		lastLatency[16];
	char		avgLatency[16];
	char		minLat[16];
	char		maxLat[16];
	char		msgArea[10240];
	PGM_ARGS	args;				/* command line arguments */

#ifdef WIN32
	MY_TIME_T	perfFreq;			/* frequence of the performance counter in counts per second */
#endif

	MY_TIME_T	endTime;			/* high performance counter to measure latency */
	MY_TIME_T	startTime;			/* high performance counter to measure latency */
	double		tempLatency=0.0;	/* latency in milliseconds */
	double		totalLatency=0.0;	/* latency in milliseconds */
	double		currLatency=0.0;	/* last observed latency in milliseconds */
	double		minLatency=0.0;		/* minimum latency observed */
	double		maxLatency=0.0;		/* maximum latency observed */
	int64_t		latencyCount=0;		/* number of results in totalLatency */
	int64_t		lastLatencyCount=0;
#ifdef MQCLIENT
	char	*ptr;
	char	*connName;
	MQLONG	opencode;
	MQLONG	rc;
	MQLONG	maxMsgLen=0;
	int		transType=MQXPT_TCP;
	char	mqserver[512];
	MQCHAR48	name;
	MQLONG	Select[1];			/* attribute selectors           */
	MQLONG	IAV[1];				/* integer attribute values      */
	MQHOBJ	Hobj;               /* object handle                 */
	MQCHAR	charAttrs[64];		/* character attribute area      */
	MQOD	od={MQOD_DEFAULT};  /* Object Descriptor             */
	static	MQCD	mqcd={MQCD_CLIENT_CONN_DEFAULT};
	static	MQCNO	mqcno={MQCNO_DEFAULT};
#endif

	/* display the program name and version information */
	printf("%s program start\n", Level);

	/* print the copyright statement */
	printf("%s\n", copyright);

	/* initialize the arguments area */
	memset(&args, 0, sizeof(PGM_ARGS));
	args.maxmsglen = MAX_MESSAGE_LENGTH;
	args.maxtime = TIME_OUT_DEF;
	args.reportInterval = 1;
	args.batchSize = DEF_SYNC;

	/* initialize the work areas */
	memset(prevtime, 0, sizeof(prevtime));
	memset(currtime, 0, sizeof(currtime));
	memset(msgArea, 0, sizeof(msgArea));

	/* allocate a buffer for the message */
	msgdata = (char *)malloc(args.maxmsglen);

	/* check if the allocate worked */
	if (NULL == msgdata)
	{
		/* allocate failed - exit with error */
		printf("Memory allocation failed\n");
		return 95;
	}

	/* initialize the message data buffer */
	memset(msgdata, 0, sizeof(msgdata));

	msgPtr = msgArea;

	for (i=0; i<10; i++)
	{
		last10[i] = 0;
		last10secs[i] = 0;
	}

	prevtime_secs = currtime_secs = 0;

	/* check for too few input parameters */
	if (argc < 4)
	{
		printHelp(argv[0]);
		exit(99);
	}

	/* check for help request */
	if ((argv[1][0] == '?') || (argv[1][1] == '?'))
	{
		printHelp(argv[0]);
		exit(0);
	}

	/* process any command line arguments */
	processArgs(argc, argv, &args);

	if (err != 0)
	{
		printHelp(argv[0]);
		exit(99);
	}

	/* set a termination handler */
	signal(SIGINT, InterruptHandler);

#ifdef WIN32
	if (1 == args.calcLatency)
	{
		/* get the counts per second as a LARGE_INTEGER if running under windows */
		if (!QueryPerformanceFrequency((LARGE_INTEGER *)&perfFreq))
		{
			/* didn't work - set to zero */
			perfFreq = 0;

			/* tell what we are doing about it */
			printf("WARNING! - No performance counter on hardware - ignoring latency parameter\n");
			args.calcLatency = 0;
		}
	}
#endif

#ifdef MQCLIENT
	/* make a local copy of the QM name */
	mqserver[0] = 0;

	/* make sure the source string is not too long */
	if (strlen(args.qmname) < sizeof(mqserver) - 1)
	{
		strcpy(mqserver, args.qmname);
	}

	ptr = strchr(mqserver, '/');
	if (NULL == ptr)
	{
		/* no forward slash in the name - must be a queue manager name */
		/* connect to the queue manager */
		/* tell what we are doing */
		printf("connecting to queue manager %s\n",args.qmname);

		MQCONN(args.qmname, &qm, &compcode, &reason);
	}
	else
	{
		ptr[0] = 0;
		ptr++;
		connName = strchr(ptr, '/');
		if (connName != NULL)
		{
			connName[0] = 0;
			connName++;
		}

		/* get the transport type */
		if (strcmp(ptr, "LOCAL") == 0)
		{
			transType = MQXPT_LOCAL;
		}

		if (strcmp(ptr, "LU62") == 0)
		{
			transType = MQXPT_LU62;
		}

		if (strcmp(ptr, "TCP") == 0)
		{
			transType = MQXPT_TCP;
		}

		if (strcmp(ptr, "NETBIOS") == 0)
		{
			transType = MQXPT_NETBIOS;
		}

		if (strcmp(ptr, "SPX") == 0)
		{
			transType = MQXPT_SPX;
		}

		if (strcmp(ptr, "DECNET") == 0)
		{
			transType = MQXPT_DECNET;
		}

		if (strcmp(ptr, "UDP") == 0)
		{
			transType = MQXPT_UDP;
		}

		/* now build a channel entry */
		memset(mqcd.ChannelName, ' ', sizeof(mqcd.ChannelName));
		if (strlen(mqserver) <= sizeof(mqcd.ChannelName))
		{
			memcpy(mqcd.ChannelName, mqserver, strlen(mqserver));
		}

		if ((connName != NULL) && (strlen(connName) <= sizeof(mqcd.ConnectionName)))
		{
			memcpy(mqcd.ConnectionName, connName, strlen(connName));
		}

		/* set the transport type */
		mqcd.TransportType = transType;

		/* clear the queue manager name */
		/*memset(mqcd.QMgrName, 0, sizeof(mqcd.QMgrName));*/

		/* set the channel reference */
		mqcno.ClientConnPtr = &mqcd;

		/* use a lower level for backwards compatibility (version 2 vs version 4) */
		mqcno.Version = MQCNO_VERSION_2;

		/* set version 4 instead of 7 for better backwards compatibility */
		mqcd.Version = MQCD_VERSION_4;

		/* try to connect to the queue manager */
		memset(name, ' ', sizeof(name));
		name[0] = 0;

		/* tell what we are doing */
		printf("connecting to qm using %s channel %s conn name %s\n",ptr, mqserver, connName);
		MQCONNX(name, &mqcno, &qm, &compcode, &reason);

		if (MQCC_OK == compcode)
		{
			/* try to get the queue manager name and display it */
			/* Open a qmgr object */
			od.ObjectType = MQOT_Q_MGR;		/* open the queue manager object*/
			MQOPEN(qm,                      /* connection handle            */
				   &od,                     /* object descriptor for queue  */
				   MQOO_INQUIRE +           /* open it for inquire          */
				   MQOO_FAIL_IF_QUIESCING,  /* but not if MQM stopping      */
				   &Hobj,                   /* object handle                */
				   &opencode,               /* MQOPEN completion code       */
				   &rc);					/* reason code                  */

			if (MQCC_OK == opencode)
			{
				memset(charAttrs, 0, sizeof(charAttrs));
				Select[0] = MQCA_Q_MGR_NAME;
				MQINQ(qm,
					  Hobj,
					  1L,
					  Select,
					  0L,
					  NULL,
					  sizeof(charAttrs),
					  charAttrs,
					  &opencode,
					  &rc);

				if (MQCC_OK == opencode)
				{
					printf("Connected to %s\n", charAttrs);
				}

				/* try to get the maximum message length allowed */
				/* to avoid 2010 reason codes on MQGETs          */
				Select[0] = MQIA_MAX_MSG_LENGTH;
				IAV[0]=0;
				MQINQ(qm,
					  Hobj,
					  1L,
					  Select,
					  1L,
					  IAV,
					  0L,
					  NULL,
					  &opencode,
					  &rc);

				if (MQCC_OK == opencode)
				{
					/* check if our maximum length is too large */
					if ((maxMsgLen > IAV[0]) && (IAV[0] > 0))
					{
						/* max message length is too large, so reduce it */
						maxMsgLen = IAV[0];
					}
				}

				MQCLOSE(qm,
						&Hobj,
						MQCO_NONE,
						&opencode,
						&rc);
			}
		}
	}
#else
	/* Connect to the queue manager */
	printf("connecting to queue manager %s\n",args.qmname);
	MQCONN(args.qmname, &qm, &compcode, &reason);
#endif

	/* check for errors */
	checkerror("MQCONN", compcode, reason, args.qmname);
	if (compcode != MQCC_OK)
	{
		return 98;
	}

	/* set the queue open options */
	strncpy(objdesc.ObjectName, args.qname, MQ_Q_NAME_LENGTH);
	openopt = MQOO_INPUT_SHARED | MQOO_FAIL_IF_QUIESCING;

	/* open the queue for input */
	printf("opening queue %s for input\n", args.qname);
	MQOPEN(qm, &objdesc, openopt, &q, &compcode, &reason);

	/* check for errors */
	checkerror("MQOPEN", compcode, reason, args.qname);
	if (compcode != MQCC_OK)
	{
		return 97;
	}

	/* calculate the minimum message size to check for latency calculations */
	minSize = strlen(args.qmname) + sizeof(MY_TIME_T) + 1;

	/* check to see if queue is to be drained before run starts */
	if (args.drainQ > 0)
	{
		printf("draining queue\n");
		uow = 0;
		compcode = MQCC_OK;
		while (compcode == MQCC_OK)
		{
			if (args.batchSize > 1)
			{
				mqgmo.Options = MQGMO_NO_WAIT | MQGMO_FAIL_IF_QUIESCING | MQGMO_SYNCPOINT | MQGMO_ACCEPT_TRUNCATED_MSG;
			}
			else
			{
				mqgmo.Options = MQGMO_NO_WAIT | MQGMO_FAIL_IF_QUIESCING | MQGMO_ACCEPT_TRUNCATED_MSG;
			}

			/* reset the msgid and correlid */
			memcpy(msgdesc.MsgId, MQMI_NONE, sizeof(msgdesc.MsgId));
			memcpy(msgdesc.CorrelId, MQCI_NONE, sizeof(msgdesc.CorrelId));

			/* perform the MQGET */
			MQGET(qm, q, &msgdesc, &mqgmo, args.maxmsglen, msgdata, &datalen, &compcode, &reason);
			
			if ((MQCC_WARNING == compcode) && (reason == 2079))
			{
				compcode = MQCC_OK;
				reason = 0;
			}

			/* check if we got a message */
			if (MQCC_OK == compcode)
			{
				uow++;
				drainCount++;

				if ((args.batchSize > 1) && (uow > args.batchSize))
				{
					MQCMIT(qm, &compcode, &reason);
					uow = 0;
				}
			}
		}

		if ((args.batchSize > 1) && (uow > args.batchSize))
		{
			MQCMIT(qm, &compcode, &reason);
			uow = 0;
		}

		if (drainCount > 0)
		{
			printf("%d messages drained from Q prior to measurement start\n", drainCount);
		}
	}

	/* tell what we are doing */
	printf("Reading %d messages from %s on %s with max wait time of %d secs\n\n",
		   args.maxmsgs, args.qname, args.qmname, args.maxtime);

	/* enter get message loop */
	compcode = MQCC_OK;
	uow = 0;
	while ((MQCC_OK == compcode) && (totcount < args.maxmsgs) && (0 == terminate))
	{
		/* set the get message options */
		if (args.batchSize > 1)
		{
			mqgmo.Options = MQGMO_WAIT | MQGMO_FAIL_IF_QUIESCING | MQGMO_SYNCPOINT | MQGMO_ACCEPT_TRUNCATED_MSG;
		}
		else
		{
			mqgmo.Options = MQGMO_WAIT | MQGMO_FAIL_IF_QUIESCING | MQGMO_ACCEPT_TRUNCATED_MSG;
		}

		mqgmo.WaitInterval = args.maxtime * 1000;
		mqgmo.MatchOptions = MQGMO_NONE;

		/* reset the msgid and correlid */
		memcpy(msgdesc.MsgId, MQMI_NONE, sizeof(msgdesc.MsgId));
		memcpy(msgdesc.CorrelId, MQCI_NONE, sizeof(msgdesc.CorrelId));

		remainingTime = args.maxtime;
		do
		{
			/* only wait for 1 second */
			mqgmo.WaitInterval = 1000;
			remainingTime--;

			/* since we have a signal handler installed, we do not want to be in an MQGET for a long time */
			/* perform the MQGET */
			MQGET(qm, q, &msgdesc, &mqgmo, args.maxmsglen, msgdata, &datalen, &compcode, &reason);

			/* check for time out with unit of work open */
			if ((MQCC_FAILED == compcode) && (2033 == reason) && (args.batchSize > 1) && (uow > 0))
			{
				/* not busy - avoid long-running unit of work */
				MQCMIT(qm, &cc2, &rc2);
				checkerror("MQCMIT2", cc2, rc2, args.qmname);
				if (MQCC_OK == cc2)
				{
					uow = 0;
				}
			}
		} while ((remainingTime > 0) && (MQCC_FAILED == compcode) && (2033 == reason) && (0 == terminate));

		/* check for truncated message */
		if ((MQCC_WARNING == compcode) && (reason == 2079))
		{
			/* accept truncated messages */
			compcode = MQCC_OK;
			reason = 0;
		}

		/* check if we got to the end of the queue */
		/* check for errors, except for no more messages in queue */
		if (2033 == reason)
		{
			printf("Program timed out before maximum number of messages were read\n");
		}
		else
		{
			checkerror("MQGET", compcode, reason, args.qname);
		}

		if (compcode == MQCC_OK)
		{
			/* increase the uow count */
			uow++;

			/* check if an acknowledgement is required */
			report = msgdesc.Report;
			if ((((report & MQRO_PAN) > 0) && (args.fileDataPAN != NULL)) ||
				(((report & MQRO_NAN) > 0) && (args.fileDataNAN != NULL)))
			{
				/* either NAN or PAN is set - therefore, we need to reply */
				/* get the reply to Q and QM and send the reply           */
				memcpy(args.replyQname, msgdesc.ReplyToQ, MQ_Q_NAME_LENGTH);
				memcpy(args.replyQMname, msgdesc.ReplyToQMgr, MQ_Q_MGR_NAME_LENGTH);
				issueReply(qm, report, &uow, (char *)msgdesc.MsgId, &args);
			}

			/* check if we are at the maximum batch size */
			if ((args.batchSize > 1) && (uow > args.batchSize))
			{
				MQCMIT(qm, &compcode, &reason);
				checkerror("MQCMIT", compcode, reason, args.qmname);
				uow = 0;
			}

			/* only do this step if the MQGET worked */
			/* get the message time from the MQMD */
			memcpy(currtime, msgdesc.PutTime, 8);

			/* check if the time is the same or not */
			/* only check down to the seconds position */
			/* if ((prevtime[0] == 0) || (memcmp(prevtime, currtime, 6) == 0))*/
			currtime_secs = ( atol(currtime) / 100 );

			if (0 == prevtime_secs)
			{
				/* capture the time of the first message */
				firstTime = currtime_secs;
			}
			else
			{
				if (0 == secondTime)
				{
					/* capture the second time */
					secondTime = currtime_secs;
				}

				prevLastTime = lastTime;
				lastTime = currtime_secs;
			}

			if ((0 == prevtime_secs) || (currtime_secs <= prevtime_secs ))
			{
				/* same second as last message */
				/* just increase the counters  */
				msgcount++;
				totcount++;
			}
			else
			{
				/* time has changed, so we must report the counts for the previous interval */

				/* is this the first interval? */
				if (0 == firstInterval)
				{
					/* get the totals of the last 10 intervals */
					recent10 = 0;
					recent10sec = 0;
					for (i=9; i>0; i--)
					{
						/* shift all the counts and times one position */
						last10secs[i] = last10secs[i - 1];
						last10[i] = last10[i - 1];

						/* get the total number of messages and seconds */
						recent10 += last10[i - 1];
						recent10sec += last10secs[i-1];
					}

					/* record the number of seconds in this interval */
					if (0 == prevtime_secs)
					{
						/* force the interval to 1 second */
						last10secs[0] = 1;
					}
					else
					{
						/* calculate the interval in seconds */
						last10secs[0] = getSecs(currtime_secs) - getSecs(prevtime_secs);
					}

					/* get the most recent count */
					last10[0] = msgcount;
					recent10 += msgcount;
					recent10sec += last10secs[0];

					avgrate = (double)recent10 / recent10sec;
				}
				else
				{
					/* start reporting the recent average the next time */
					firstInterval = 0;
					avgrate = 0.0;
				}

				/* write out the number of messages in this second */
				if (latencyCount > lastLatencyCount)
				{
					/* only report if it changes */
					lastLatencyCount = latencyCount;

					/* calculate the average latency */
					tempLatency = totalLatency / latencyCount;

					/* get the latencies into printable format */
					formatTimeDiff(lastLatency, currLatency);
					formatTimeDiff(avgLatency, tempLatency);
					formatTimeDiff(minLat, minLatency);
					formatTimeDiff(maxLat, maxLatency);

					/* save the last ten latencies */
					/* display the results */
#ifdef WIN32
					sprintf(msgPtr,"%6.6s %I64d msgs - rec avg = %7.2f Latency last %s avg %s min %s max %s latencyCount %I64d msgCount %I64d\n", 
							prevtime, msgcount, avgrate, lastLatency, avgLatency, minLat, maxLat, latencyCount, totcount);
#else
					sprintf(msgPtr,"%6.6s %lld msgs - rec avg = %7.2f Latency last %s avg %s min %s max %s latencyCount %lld msgCount %lld\n", 
							prevtime, msgcount, avgrate, lastLatency, avgLatency, minLat, maxLat, latencyCount, totcount);
#endif
				}
				else
				{
#ifdef WIN32
					sprintf(msgPtr,"%6.6s %I64d msgs - recent average = %7.2f\n", prevtime, msgcount, avgrate);
#else
					sprintf(msgPtr,"%6.6s %lld msgs - recent average = %7.2f\n", prevtime, msgcount, avgrate);
#endif
				}

				msgPtr += strlen(msgPtr);

				reportCount++;
				if (reportCount >= args.reportInterval)
				{
					printf("%s", msgArea);
					reportCount = 0;
					msgPtr = msgArea;
					msgArea[0] = 0;
				}

				/* is this the first time through? */
				if (0 == firstsec)
				{
					/* remember count in first second */
					firstsec = msgcount;
				}

				/* keep track of the maximum message rate */
				if (msgcount > maxrate)
				{
					maxrate = msgcount;
				}

				/* reset the messages in second counter, automatically counting the first message */
				msgcount = 1;

				/* count the total number of messages */
				totcount++;

				/* count the number of individual seconds with at least one message */
				secondcount++;
			}

			if (currtime_secs > prevtime_secs )
			{
				memcpy(prevtime, currtime, 8);
				prevtime_secs = currtime_secs;
			}

			/* calculate the total bytes in the message */
			totalbytes += datalen;

			/* check if we are calculating latencies */
			/* this assumes that the first 8 bytes of the message countains a performance counter */
			/* a queue manager name is placed after the counter, which must also match */
			/* this requires the setTimeStamp option be used on mqput2 */
			if ((1 == args.calcLatency) && (datalen > minSize))
			{
				/* get the end time and the frequency */
				endTime = GetTime();

				/* check if we have an RFH header */
				userPtr = checkRFH(msgdata, &msgdesc);

				/* check if the queue manager name matches */
				if (strcmp(userPtr + sizeof(MY_TIME_T), args.qmname) == 0)
				{
					/* get the start time */
					memcpy(&startTime, userPtr, sizeof(MY_TIME_T));

					/* make sure we have both counters */
#ifdef WIN32
					if ((endTime != 0) && (startTime != 0))
#else
					if (((endTime.tv_sec != 0) || (endTime.tv_usec != 0)) && ((startTime.tv_sec != 0) || (startTime.tv_usec != 0)))
#endif
					{
						double diff = DiffTime(startTime, endTime);
						if (diff <= 0)
						{
							printf("Invalid latency detected - less than zero - diff %e\n", diff);
						}
						else
						{
							/* add to the total latency */
							currLatency = diff;
							totalLatency += diff;
							latencyCount++;

							/* check if this is less than the minimum latency */
							if ((diff < minLatency) || (1 == latencyCount))
							{
								minLatency = diff;
							}

							/* check if this is more than the maximum latency */
							if (diff > maxLatency)
							{
								maxLatency = diff;
							}
						}
					}
				}
			}
		}
	}

	/* count the last interval */
	secondcount++;

	/* check if we have a uow open */
	if ((args.batchSize > 1) && (uow > 0))
	{
		MQCMIT(qm, &compcode, &reason);
		checkerror("MQCMIT", compcode, reason, args.qmname);
	}

	/* dump out the last time interval */
#ifdef WIN32
	sprintf(msgArea, "%6.6s %I64d msgs\n", prevtime, msgcount);
	printf("%s", msgArea);
#else
	sprintf(msgArea, "%6.6s %lld msgs\n", prevtime, msgcount);
	printf("%s", msgArea);
#endif

	/* keep track of the maximum message rate */
	if (msgcount > maxrate)
	{
		maxrate = msgcount;
	}

	/* close the input queue */
	printf("\nclosing the input queue (%s)\n", args.qname);
	MQCLOSE(qm, &q, MQCO_NONE, &compcode, &reason);

	checkerror("MQCLOSE", compcode, reason, args.qname);

	/* Disconnect from the queue manager */
	printf("disconnecting from the queue manager\n");
	MQDISC(&qm, &compcode, &reason);

	checkerror("MQDISC", compcode, reason, args.qmname);

	/* issue message if user cancelled the program */
	if (1 == terminate)
	{
		printf("Program cancelled by user\n");
	}

	/* dump out the total message count */
#ifdef WIN32
	printf("\nTotal messages %I64d\n", totcount);
#else
	printf("\nTotal messages %lld\n", totcount);
#endif

	/* give the total and average message size */
	if (totcount > 0)
	{
		/* calculate the average bytes per message */
		avgbytes = totalbytes / totcount;
#ifdef WIN32
		printf("total bytes in all messages %I64d\n", totalbytes);
		printf("average message size %I64d\n", avgbytes);
#else
		printf("total bytes in all messages %lld\n", totalbytes);
		printf("average message size %lld\n", avgbytes);
#endif
	}

	/* indicate the number of seconds with at least one message */
	printf("Total number of seconds with at least one message %d\n", secondcount);

	/* write out the average message rate, ignoring the first and last intervals */
	if (secondcount > 2)
	{
		formatTimeSecs(timeLast, lastTime);
		formatTimeSecs(timeFirst, firstTime);
		printf("First time %s Last time %s seconds %d\n", timeFirst, timeLast, getSecs(lastTime) - getSecs(firstTime) + 1);
		secs = getSecs(prevLastTime) - getSecs(secondTime) - 1;

		/* avoid any divide by zeros */
		if (secs > 0)
		{
			avgrate = (float)(totcount - firstsec - msgcount) / secs;
			printf("Average message rate except first and last intervals %7.2f\n", avgrate);
		}
	}

	/* print out the maximum rate */
#ifdef WIN32
	printf("Peak message rate %I64d\n", maxrate);
#else
	printf("Peak message rate %lld\n", maxrate);
#endif

	/* check if latency numbers were requested */
	if (1 == args.calcLatency)
	{
		if (0 == latencyCount)
		{
			printf("\nLatency was requested but the counter is 0\n");
		}
		else
		{
			/* calculate the average latency */
			totalLatency = totalLatency / latencyCount;

			/* get the latencies into printable format */
			formatTimeDiff(avgLatency, totalLatency);
			formatTimeDiff(minLat, minLatency);
			formatTimeDiff(maxLat, maxLatency);

			/* display the results */
#ifdef WIN32
			printf("\nAverage Latency %s - min %s max %s number  of msgs %I64d\n", avgLatency, minLat, maxLat, latencyCount);
#else
			printf("\nAverage Latency %s - min %s max %s number  of msgs %lld\n", avgLatency, minLat, maxLat, latencyCount);
#endif
		}
	}

	if (args.fileDataPAN != NULL)
	{
		free(args.fileDataPAN);
	}

	if (args.fileDataNAN != NULL)
	{
		free(args.fileDataNAN);
	}

	printf("\nMQTIMES2 program ended\n");

	/******************************************************************/
	/*                                                                */
	/* END OF PROGRAM                                                 */
	/*                                                                */
	/******************************************************************/

	return 0;
}
