/********************************************************************/
/*                                                                  */
/*   MQLatency has 1 required input parameter                       */
/*                                                                  */
/*      -f name of the parameter file                               */
/*                                                                  */
/*   This program will write messages to a queue.  It will then     */
/*   read the reply messages from a different queue.  It will       */
/*   calculate the time from when the request message is written    */
/*   to when the reply message arrives on the reply queue.          */
/*                                                                  */
/*   Additional parameters can be used to override certain of the   */
/*   parameters in the parameter file, including the message        */
/*   count, queue manager name and queue names.  The think time     */
/*   parameter can also be overriden.                               */
/*                                                                  */
/*   The  parameter file contains all values, including the name    */
/*   of the queues and queue manager to write data to, the total    */
/*   number of messages to write, any MQMD parameters and a list    */
/*   of files which contain the message data.                       */
/*                                                                  */
/********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <time.h>

#ifdef WIN32
#include <windows.h>
#include "signal.h"
#endif

#ifdef SOLARIS
#include <ctype.h>
#include <unistd.h>
#include <sys/time.h>
#endif

/* definitions of 64-bit values for platform independence */
#include "int64defs.h"

#ifndef WIN32
void Sleep(int amount)
{
	usleep(amount*1000);
}
#endif

/*************************************************************/
/*                                                           */
/* 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

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

/* include for common subroutines */
#include "int64defs.h"
#include "comsubs.h"

/* parameter file processing routines */
#include "putparms.h"
#include "parmline.h"

/* include for MQ subroutines */
#include "qsubs.h"
#include "rfhsubs.h"

#define MAX_BATCH_ALLOW		5000

static char copyright[] = "(C) Copyright IBM Corp, 2008-2009";
static char Version[]=\
"@(#)MQLatency V2.0 - Message Broker Latency measurement tool  - Jim MacNair ";

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

	MQHCONN			qm=0;			/* queue manager connection handle */
	MQHOBJ			qReply=0;		/* queue handle used for mqget     */
	MQHOBJ			q=0;			/* queue handle used for mqput     */
	MQHOBJ			Hinq;			/* inquire object handle           */

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

/*************************************************************/
/*                                                           */
/* GetTime routine to 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

/*********************************************************/
/* formatTimeDiffSecs - format a number of seconds       */
/*  resulting from a difference between two times.       */
/*********************************************************/

void formatTimeDiffSecs(char * result, double time)

{
	int		secs=0;
	int		usecs=0;
	int		i;
	char	tempStr[8];

	result[0] = 0;

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

	/* get the microseconds as a string */
	sprintf(tempStr, "%6.2d", usecs);

	/* replace any leading blanks with zeros */
	i = 0;
	while ((i < (int)strlen(tempStr)) && (' ' == tempStr[i]))
	{
		tempStr[i] = '0';
		i++;
	}

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

/**************************************************************/
/*                                                            */
/* This routine puts a message on the queue.                  */
/*                                                            */
/**************************************************************/

int getMessage(FILEPTR* fptr, int maxWait, putParms * parms)

{
	MQLONG	compcode=0;
	MQLONG	reason=0;
	MQLONG	msgSize=0;
	MQMD	mqmd = {MQMD_DEFAULT};
	MQGMO	gmo = {MQGMO_DEFAULT};
 
	/* set the get message options */
	gmo.Options = MQGMO_WAIT | MQGMO_FAIL_IF_QUIESCING | MQGMO_NO_SYNCPOINT | MQGMO_ACCEPT_TRUNCATED_MSG;
	gmo.Version = MQGMO_VERSION_2;
	gmo.MatchOptions = MQMO_NONE;

	/* check if correlation ids are to be used to read the reply messages */
	if (1 == parms->GetByCorrelId)
	{
		/* set the correlation id match option in the gmo */
		gmo.MatchOptions = MQMO_MATCH_CORREL_ID;

		/* set the correlation id to look for */
		memcpy(mqmd.CorrelId, parms->savedMsgId, MQ_CORREL_ID_LENGTH);
	}

	/* set the maximum wait time to 5 seconds */
	gmo.WaitInterval = maxWait;

	MQGET(qm, qReply, &mqmd, &gmo, 0, NULL, &msgSize, &compcode, &reason);

	/* check for truncated message */
	if (MQRC_TRUNCATED_MSG_ACCEPTED == reason)
	{
		/* accept the truncated message since there is no need for the data */
		reason = MQRC_NONE;
		compcode = MQCC_OK;
	}

	/* if no wait is specified this is a request to drain the queue */
	/* therefore suppress a 2033 (no message found) return code */
	if ((0 == maxWait) && (MQRC_NO_MSG_AVAILABLE == reason))
	{
		/* do not treat this case as an error */
		/* just return the completion code so the caller knows there are no more messages in the queue */
		return compcode;
	}

	/* check for errors */
	checkerror("MQGET", compcode, reason, parms->replyQ);

	/* calculate the total number of bytes in the reply, even though it wasn't read */
	parms->totMsgLen += msgSize;

	/* count the number of messages that were read */
	parms->msgsRead++;

	return reason;
}

/**************************************************************/
/*                                                            */
/* This routine puts a message on the queue.                  */
/*                                                            */
/**************************************************************/

int putMessage(FILEPTR* fptr, MQCHAR8 *puttime, putParms * parms)

{
	MQLONG	compcode=0;
	MQLONG	reason=0;
	MQMD	msgdesc = {MQMD_DEFAULT};
	MQPMO	mqpmo = {MQPMO_DEFAULT};

	/* check if we are using an mqmd from the file */
	if (fptr->mqmdptr != NULL)
	{
		/* set the get message options */
		/* no synchpoint, each message as a separate UOW */
		mqpmo.Options = MQPMO_NO_SYNCPOINT | MQPMO_FAIL_IF_QUIESCING | MQPMO_SET_ALL_CONTEXT;

		if (1 == fptr->newMsgId)
		{
			mqpmo.Options |= MQPMO_NEW_MSG_ID;
		}

		/* set the MQMD */
		memcpy(&msgdesc, fptr->mqmdptr, sizeof(MQMD));
	}
	else
	{
		/* set the get message options */
		/* no synchpoint, each message as a separate UOW */
		mqpmo.Options = MQPMO_NO_SYNCPOINT | MQPMO_FAIL_IF_QUIESCING | MQPMO_NEW_MSG_ID;

		/* Indicate V2 of MQMD */
		msgdesc.Version = MQMD_VERSION_2;

		/* set the persistence, etc if specified */
		msgdesc.Persistence = fptr->Persist;
		msgdesc.Encoding = fptr->Encoding;
		msgdesc.CodedCharSetId = fptr->Codepage;

		/* check if message expiry was specified */
		if (fptr->Expiry > 0)
		{
			msgdesc.Expiry = fptr->Expiry;
		}

		/* check if message type was specified */
		if (fptr->Msgtype > 0)
		{
			msgdesc.MsgType = fptr->Msgtype;
		}

		/* check if message priority was specified */
		if (fptr->Priority != MQPRI_PRIORITY_AS_Q_DEF)
		{
			msgdesc.Priority = fptr->Priority;
		}

		/* check if report options were specified */
		if (fptr->Report > 0)
		{
			msgdesc.Report = fptr->Report;
		}

		/* set the message format in the MQMD was specified */
		switch (fptr->hasRFH)
		{
		case RFH_NO:
			{
				if (1 == fptr->FormatSet)
				{
					memcpy(msgdesc.Format, fptr->Format, MQ_FORMAT_LENGTH);
				}

				break;
			}
		case RFH_V1:
			{
				memcpy(msgdesc.Format, MQFMT_RF_HEADER, sizeof(msgdesc.Format));
				break;
			}
		case RFH_V2:
			{
				memcpy(msgdesc.Format, MQFMT_RF_HEADER_2, sizeof(msgdesc.Format));
				break;
			}
		case RFH_XML:
			{
				memcpy(msgdesc.Format, MQFMT_RF_HEADER_2, sizeof(msgdesc.Format));
				break;
			}
		}

		/* check if a reply to queue manager was specified */
		memset(msgdesc.ReplyToQMgr, 0, sizeof(msgdesc.ReplyToQMgr));
		if (fptr->ReplyQM[0] != 0)
		{
			memcpy(msgdesc.ReplyToQMgr, fptr->ReplyQM, strlen(fptr->ReplyQM));
		}

		/* check if a reply to queue was specified */
		memset(msgdesc.ReplyToQ, 0, sizeof(msgdesc.ReplyToQ));
		if (fptr->ReplyQ[0] != 0)
		{
			memcpy(msgdesc.ReplyToQ, fptr->ReplyQ, strlen(fptr->ReplyQ));
		}

		/* check if a correl id was specified */
		if (parms->correlidSet == 1)
		{
			memcpy(msgdesc.CorrelId, fptr->CorrelId, MQ_CORREL_ID_LENGTH);
		}
		else
		{
			memset(msgdesc.CorrelId, 0, MQ_CORREL_ID_LENGTH);
		}

		/* check if an accounting token was specified */
		if (1 == fptr->AcctTokenSet)
		{
			/* set the accounting token value */
			memcpy(msgdesc.AccountingToken, fptr->AccountingToken, MQ_ACCOUNTING_TOKEN_LENGTH);
		}
	}

	/* perform the MQPUT */
	MQPUT(qm, q, &msgdesc, &mqpmo, fptr->length, fptr->dataptr, &compcode, &reason);

	/* check for errors */
	checkerror("MQPUT", compcode, reason, parms->qname);

	/* check if correlation ids are to be used to read the reply messages */
	if (1 == parms->GetByCorrelId)
	{
		/* save the message id to match with the expected reply id */
		/* if this option is used the application must set the correlation id in the reply message */
		memcpy(parms->savedMsgId, msgdesc.MsgId, MQ_MSG_ID_LENGTH);
	}

	if (0 == compcode)
	{
		/* keep track of the number of bytes we have written */
		parms->byteswritten += fptr->length;
	}
	else
	{
		/* set the global error switch */
		parms->err = 1;
	}

	memcpy(puttime, msgdesc.PutTime, sizeof(msgdesc.PutTime));

	return compcode;
}

/**************************************************************/
/*                                                            */
/* Subroutine to format a time.                               */
/*                                                            */
/* The input time should be 8 numbers (hhmmsshh)              */
/*                                                            */
/**************************************************************/

void formatTime(char *timeOut, char *timeIn)

{
	timeOut[0] = timeIn[0];
	timeOut[1] = timeIn[1];
	timeOut[2] = ':';
	timeOut[3] = timeIn[2];
	timeOut[4] = timeIn[3];
	timeOut[5] = ':';
	timeOut[6] = timeIn[4];
	timeOut[7] = timeIn[5];
	timeOut[8] = ':';
	timeOut[9] = timeIn[6];
	timeOut[10] = timeIn[7];
	timeOut[11] = 0;
}

void InterruptHandler (int sigVal) 

{ 
	terminate = 1;
}

/**************************************************************/
/*                                                            */
/* Display command format.                                    */
/*                                                            */
/**************************************************************/

void printHelp(char *pgmName)

{
	printf("%s\n", Level);
	printf("format is:\n");
	printf("  %s -f parm_file {-v} {-m QMgr} {-q queue} {-c count} {-r replyQ} {-t thinktime}\n", pgmName);
	printf("   parm_file is the fully qualified name of the parameters file\n");
	printf("   -v verbose\n");
	printf("   -p purge reply queue before starting\n");
	printf("   -i use correlation id to read reply messages\n");
	printf("   Overrides\n");
	printf("   -m name of queue manager\n");
	printf("   -q name of queue\n");
	printf("   -r name of reply queue\n");
	printf("   -c message count\n");
	printf("   -t think time\n");
}

int main(int argc, char **argv)

{
	MQLONG		compcode=MQCC_OK;
	MQLONG		reason;
	MQOD		objdesc = {MQOD_DEFAULT};
	MQLONG		openopt = 0;
	MQLONG		datalen=0;
	MQLONG		maxMsgLen=0;
	int			numWrittenMin=-1;
	int			numWrittenMax=0;
	int64_t		MsgsAtLastInterval=0;
	int			iElapsed=0;
	int			saveGetByCorrelId;
	MQCHAR8		puttime;
	char		formTime[16];
	char		*msgdata;
	char		filename[512];
	MY_TIME_T	startTime;
	MY_TIME_T	endTime;
	MY_TIME_T	prevTime;
	MY_TIME_T	afterGet;
	MY_TIME_T	afterPut;
	time_t		startTOD;
	time_t		endTOD;
	double		avgrate;
	double		avglatency;				/* average latency */
	double		elapsed=0.0;
	double		latency=0.0;
	double		totalLatency=0.0;		/* total of all latency observed during test - used to calculate average latency */
	double		minLatency=0.0;			/* minimum latency observed during test */
	double		maxLatency=0.0;			/* maximum latency observed during test */
	FILEPTR		*fptr=NULL;
	FILEPTR		*fileptr;
	putParms	parms;

	printf(Level);

	/* print the copyright statement */
	printf("\nCopyright (c) IBM Corp., 2008\n");

	/* initialize the work areas */
	memset(filename, 0, sizeof(filename));
	memset(&parms, 0, sizeof(parms));

	/* initialize the work areas */
	initializeParms(&parms);

	/* check for too few input parameters */
	if (argc < 2)
	{
		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, &parms);

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

	/* initialize the queue name and queue manager name */
	memset(parms.qname, ' ', sizeof(parms.qname) - 1);
	memset(parms.replyQ, ' ', sizeof(parms.replyQ) - 1);
	memset(parms.qmname, ' ', sizeof(parms.qmname) - 1);

	/* process the parameters file data */
	fptr = processParmFile(parms.parmFilename, &parms);

	/* start with the first message */
	fileptr = fptr;

	/* check for overrides */
	if (parms.saveQMname[0] != 0)
	{
		strcpy(parms.qmname, parms.saveQMname);
	}

	/* check for overrides */
	if (parms.saveQname[0] != 0)
	{
		strcpy(parms.qname, parms.saveQname);
	}

	/* check for overrides */
	if (parms.saveQReply[0] != 0)
	{
		strcpy(parms.replyQ, parms.saveQReply);
	}

	/* check for overrides */
	if (parms.saveTotcount > 0)
	{
		parms.totcount = parms.saveTotcount;
	}

	/* check for overrides */
	if (parms.saveBatchSize > 0)
	{
		parms.batchsize = parms.saveBatchSize;
	}

	/* check if the write once parameter was found */
	if (1 == parms.writeOnce)
	{
		printf("Write once switch set - count set to %d\n", parms.mesgCount);
		parms.totcount = parms.mesgCount;
	}

	/* check if we found any message data files */
	if (NULL == fptr)
	{
		printf("***** No message data files found - program terminating\n");
		return 94;
	}

	/* check that the reply to queue name has been specified */
	if ((0 == parms.replyQ[0]) || (' ' == parms.replyQ[0]))
	{
		printf("***** Reply to queue name must be specified (REPLYQ parameter)\n");
		return 93;
	}

	/* check that the queue name has been specified */
	if ((0 == parms.qname[0]) || (' ' == parms.qname[0]))
	{
		printf("***** queue name must be specified (QNAME parameter or -q on command line)\n");
		return 93;
	}

	if (parms.err != 0)
	{
		printf("***** Error detected (err=%d) - program terminating\n", parms.err);
		return parms.err;
	}

	/* tell how many files and messages we found */
	printf("Total files read %d\n", parms.fileCount);
	printf("Total messages found %d\n", parms.mesgCount);

	/* explain what parameters are being used */
#ifdef WIN32
	printf("\n%I64d messages to be written to queue %s on queue manager %s\n", parms.totcount, parms.qname, parms.qmname);
#else
	printf("\n%lld messages to be written to queue %s on queue manager %s\n", parms.totcount, parms.qname, parms.qmname);
#endif

	/* was a delay time between messages specified? */
	if (parms.thinkTime > 0)
	{
		/* display the wait time between messages */
		printf("wait time between messages = %d\n", parms.thinkTime);
	}

#ifdef WIN32
	/* set a termination handler */
	signal(SIGINT, InterruptHandler);
#endif

	/* Connect to the queue manager */
#ifdef MQCLIENT
	clientConnect2QM(parms.qmname, &qm, &maxMsgLen, &compcode, &reason);
#else
	connect2QM(parms.qmname, &qm, &compcode, &reason);
#endif

	/* check for errors */
	if (compcode != MQCC_OK)
	{
		return 98;
	}

	/* set the queue open options */
	strcpy(objdesc.ObjectName, parms.qname);
	strcpy(objdesc.ObjectQMgrName, parms.remoteQM);
	openopt = MQOO_OUTPUT + MQOO_FAIL_IF_QUIESCING;

	/* check if we need to set all context */
	if (1 == parms.foundMQMD)
	{
		openopt |= MQOO_SET_ALL_CONTEXT;
	}

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

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

	/* set the queue open options */
	strcpy(objdesc.ObjectName, parms.replyQ);
	openopt = MQOO_INPUT_SHARED + MQOO_FAIL_IF_QUIESCING;

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

	/* check for errors */
	checkerror("MQOPEN2", compcode, reason, parms.replyQ);
	if (compcode != MQCC_OK)
	{
		/* close the output queue */
		MQCLOSE(qm, &q, MQCO_NONE, &compcode, &reason);
		return 96;
	}

	/* should the reply queue be purged? */
	if (1 == parms.purgeQ)
	{
		/* save the get by correlation id option */
		saveGetByCorrelId = parms.GetByCorrelId;

		/* do not use a get by correlation id */
		parms.GetByCorrelId = 0;

		/* purge the reply queue to make sure the times actually reflect the correct messages */
		compcode = MQCC_OK;
		while (MQCC_OK == compcode)
		{
			/* read any spurious reply messages with no wait time */
			/* the reply queue must be empty before the latency measurements begin */
			compcode = getMessage(fptr, 0, &parms);
		}

		/* restore the get by correlation id */
		parms.GetByCorrelId = saveGetByCorrelId;
	}

	/* remember the starting time */
	startTime = GetTime();
	prevTime = GetTime();

	/* reset the completion code */
	compcode = MQCC_OK;

	/* loop until all the messages have been written or an error occurs */
	while ((compcode == MQCC_OK) && (0 == terminate) && (0 == parms.err) && (parms.msgwritten < parms.totcount))
	{
		if (0 == parms.msgwritten)
		{
			/* write out the time the first messsage was sent */
			time(&startTOD);
			printf("First message written at %s\n", ctime(&startTOD));
		}

		/* get the data pointer and length from the next message */
		datalen = fileptr->length;
		msgdata = fileptr->dataptr;

		/* perform the MQPUT */
		compcode = putMessage(fileptr, &puttime, &parms);

		/* get a high precision timer value when the message was written */
		afterPut = GetTime();

		/* check for errors */
		if (compcode == MQCC_OK)
		{
			/* get the time after the put */
			afterPut = GetTime();

			/* increment the message count */
			parms.msgwritten++;

			/* read the reply message with a maximum wait - default is 5 seconds */
			/* this can be overridden on the command line using the -w parameter */
			compcode = getMessage(fileptr, parms.maxWaitTime * 1000, &parms);

			if (compcode != MQCC_OK)
			{
				/* let the user know about the error */
				printf("***** Error reading reply message %d\n", reason);

				/* error reading message - time to exit */
				parms.err = 1;
			}
			else
			{
				/* get the time after the MQGET */
				afterGet = GetTime();

				/* calculate the latency */
				latency = DiffTime(afterPut, afterGet);
				totalLatency += latency;

				/* check if this is the lowest latency so far */
				if ((0.0 == minLatency) || (minLatency > latency))
				{
					/* update the minimum latency */
					minLatency = latency;
				}

				/* check if this is the highest latency so far */
				if (latency > maxLatency)
				{
					/* update the minimum latency */
					maxLatency = latency;
				}

				/* check if we are supposed to report progress */
				if ((parms.reportEvery > 0) && ((parms.msgwritten % parms.reportEvery) == 0))
				{
					/* report the time to put a given number of messages */
					/* calculate the amount of time it took */
					elapsed = DiffTime(prevTime, GetTime());

					/* update the time for the next interval */
					prevTime = GetTime();

					/* format the difference as a string (seconds and 6 decimal places) */
					formatTimeDiffSecs(formTime, elapsed);

					if (elapsed > 0.0)
					{
						/* get the message rate */
						avgrate = (float)(((parms.msgwritten - MsgsAtLastInterval) * 1000000) / elapsed);

						/* write out the time it took to write the messages */
#ifdef WIN32
						printf("%I64d messages written in %s seconds rate %.2f\n", parms.msgwritten - MsgsAtLastInterval, formTime, avgrate);
#else
						printf("%lld messages written in %s seconds rate %.2f\n", parms.msgwritten - MsgsAtLastInterval, formTime, avgrate);
#endif
					}
					else
					{
						/* write out the time it took to write the messages without the rate */
#ifdef WIN32
						printf("%I64d messages written in %s seconds minimum latency=%.6f maximum latency=%.6f\n", parms.msgwritten - MsgsAtLastInterval, formTime, minLatency, maxLatency);
#else
						printf("%lld messages written in %s seconds minimum latency=%.6f maximum latency=%.6f\n", parms.msgwritten - MsgsAtLastInterval, formTime, minLatency, maxLatency);
#endif
					}

					/* remember the count at the beginning of the next interval */
					MsgsAtLastInterval = parms.msgwritten;
				}

				/* was a think time specified? */
				if (fileptr->thinkTime > 0)
				{
					/* delay the amount specified by the think time parameter */
					Sleep(fileptr->thinkTime);
				}

				/* move on to the next message file */
				fileptr = (FILEPTR *)fileptr->nextfile;
				if (NULL == fileptr)
				{
					/* go back to the first message data file */
					fileptr = fptr;
				}
			}
		}
	} /* while */

	/* remember the ending time */
	endTime = GetTime();

	/* issue message if user cancelled the program */
	if (1 == terminate)
	{
		printf("Program cancelled by user after %d messages written\n", parms.msgwritten);
	}

	/* write out the time the first messsage was sent */
	time(&endTOD);
	printf("Last message written at %s\n", ctime(&endTOD));

	/* dump out the total message count */
	printf("Total messages read %d\n", parms.msgsRead);
	printf("Total bytes read      %d\n", parms.totMsgLen);
	printf("Total memory used %d\n", parms.memUsed);
#ifdef WIN32
	printf("\nTotal messages written %I64d out of %I64d\n", parms.msgwritten, parms.totcount);
	printf("Total bytes written   %I64d\n", parms.byteswritten);
#else
	printf("\nTotal messages written %lld out of %lld\n", parms.msgwritten, parms.totcount);
	printf("Total bytes written   %lld\n", parms.byteswritten);
#endif

	if (parms.msgwritten > 0)
	{
		/* calculate the total elapsed time */
		elapsed = DiffTime(startTime, endTime);
		formatTimeDiffSecs(formTime, elapsed);
		printf("Total elapsed time in seconds %s\n", formTime);

		/* calculate the average latency */
		avglatency = totalLatency / parms.msgwritten;

		/* display the minimum, maximum and average latencies */
		printf("Min latency = %.6f  Max latency = %.6f Average latency = %.6f\n", minLatency / 1000000, maxLatency/1000000, avglatency/1000000);
	}

	/* close the output queue */
	printf("\nclosing the output queue\n");
	MQCLOSE(qm, &q, MQCO_NONE, &compcode, &reason);

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

	/* close the input queue */
	printf("\nclosing the reply queue\n");
	MQCLOSE(qm, &qReply, MQCO_NONE, &compcode, &reason);

	checkerror("MQCLOSE", compcode, reason, parms.replyQ);

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

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

	/* release any storage used for RFH areas */
	releaseRFH(&parms);

	/* release any storage we acquired for files */
	fileptr = fptr;
	while (fileptr != NULL)
	{
		/* do we have any acquired storage associated with this control block */
		if (fileptr->acqStorAddr != NULL)
		{
			/* release the acquired storage */
			free(fileptr->acqStorAddr);
		}

		/* remember the address of the current control block */
		fptr = fileptr;

		/* move on to the next control block */
		fileptr = (FILEPTR *)fileptr->nextfile;

		/* release the FILEPTR control block */
		free(fptr);
	}

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

	printf("MQLatency program ended\n");

	return(0);
}
