//-------------------------------------------------------------------------
// Desc:	Routines for updating statistics - for monitoring.
// Tabs:	3
//
//		Copyright (c) 1997-2003,2005-2006 Novell, Inc. All Rights Reserved.
//
//		This program is free software; you can redistribute it and/or
//		modify it under the terms of version 2 of the GNU General Public
//		License as published by the Free Software Foundation.
//
//		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, contact Novell, Inc.
//
//		To contact Novell about this file by physical or electronic mail,
//		you may find current contact information at www.novell.com
//
// $Id: flmstat.cpp 12334 2006-01-23 12:45:35 -0700 (Mon, 23 Jan 2006) dsanders $
//-------------------------------------------------------------------------

#include "flaimsys.h"

#define INIT_DB_STAT_ARRAY_SIZE			5
#define DB_STAT_ARRAY_INCR_SIZE			5
#define INIT_LFILE_STAT_ARRAY_SIZE		5
#define LFILE_STAT_ARRAY_INCR_SIZE		5

FSTATIC RCODE flmStatGetDbByName(
	FLM_STATS *				pFlmStats,
	const char *			pszDbName,
	FLMUINT					uiLowStart,
	DB_STATS **				ppDbStatsRV,
	FLMUINT *				puiDBAllocSeqRV,
	FLMUINT *				puiDbTblPosRV);

FSTATIC void flmUpdateLFileStats(
	LFILE_STATS *	pDest,
	LFILE_STATS *	pSrc);

FSTATIC RCODE flmUpdateDbStats(
	DB_STATS *		pDest,
	DB_STATS *		pSrc);

FSTATIC RCODE flmStatCopy(
	FLM_STATS *	pDestStats,
	FLM_STATS *	pSrcStats);

FSTATIC FLMUINT flmDaysInMonth(
	FLMUINT	uiYear,
	FLMUINT	uiMonth);

FSTATIC void flmAdjustTime(
	F_TMSTAMP *	pTime,
	FLMINT		iStartPoint);

/****************************************************************************
Desc:	This routine returns a pointer to a particular database's
		statistics block.
****************************************************************************/
FSTATIC RCODE flmStatGetDbByName(
	FLM_STATS *				pFlmStats,
	const char *			pszDbName,
	FLMUINT					uiLowStart,
	DB_STATS **				ppDbStatsRV,
	FLMUINT *				puiDBAllocSeqRV,
	FLMUINT *				puiDbTblPosRV)
{
	RCODE				rc = FERR_OK;
	FLMUINT			uiTblSize;
	DB_STATS *		pDbStatTbl;
	FLMUINT			uiLow;
	FLMUINT			uiMid = 0;
	FLMUINT			uiHigh;
	FLMINT			iCmp = 0;
	FLMUINT			uiNewSize;
	FLMUINT			uiElement;
	char *			pszTmpDbName = NULL;

	// If there is a database array, search it first.

	if (((pDbStatTbl = pFlmStats->pDbStats) != NULL) &&
		 ((uiTblSize = pFlmStats->uiNumDbStats) != 0))
	{
		for (uiHigh = --uiTblSize, uiLow = uiLowStart ; ; )		// Optimize: reduce uiTblSize
		{
			uiMid = (uiLow + uiHigh) >> 1;		// (uiLow + uiHigh) / 2

			// See if we found a match.

#ifdef FLM_UNIX
			if ((iCmp = f_strcmp( pszDbName,
								pDbStatTbl [uiMid].pszDbName)) == 0)
#else
			if ((iCmp = f_stricmp( pszDbName,
								pDbStatTbl [uiMid].pszDbName)) == 0)
#endif
			{

				// Found match.

				*ppDbStatsRV = &pDbStatTbl [uiMid];
				if (puiDBAllocSeqRV)
				{
					*puiDBAllocSeqRV = pFlmStats->uiDBAllocSeq;
				}
				if (puiDbTblPosRV)
				{
					*puiDbTblPosRV = uiMid;
				}
				goto Exit;
			}

			// Check if we are done - where uiLow equals uiHigh.

			if (uiLow >= uiHigh)
			{

				// Item not found.

				break;
			}

			if (iCmp < 0)
			{
				if (uiMid == uiLowStart)
				{
					break;					// Way too high?
				}
				uiHigh = uiMid - 1;		// Too high
			}
			else
			{
				if (uiMid == uiTblSize)
				{
					break;				// Done - Hit the top
				}
				uiLow = uiMid + 1;		// Too low
			}
		}
	}

	// If the array is full, or was never allocated, allocate a new one.

	if (pFlmStats->uiDbStatArraySize <= pFlmStats->uiNumDbStats)
	{
		if (!pFlmStats->pDbStats)
		{
			uiNewSize = INIT_DB_STAT_ARRAY_SIZE;
		}
		else
		{
			uiNewSize = pFlmStats->uiDbStatArraySize +
							DB_STAT_ARRAY_INCR_SIZE;
		}
		if (RC_BAD( rc = f_calloc(
						(FLMUINT)(sizeof( DB_STATS) * uiNewSize),
							&pDbStatTbl)))
		{
			goto Exit;
		}

		// Save whatever was in the old table, if any.

		if (pFlmStats->pDbStats && pFlmStats->uiNumDbStats)
		{
			f_memcpy( pDbStatTbl, pFlmStats->pDbStats,
					(FLMINT)(sizeof( DB_STATS) * pFlmStats->uiNumDbStats));
		}
		if (pFlmStats->pDbStats)
		{
			f_free( &pFlmStats->pDbStats);
		}

		pFlmStats->uiDBAllocSeq++;
		pFlmStats->pDbStats = pDbStatTbl;
		pFlmStats->uiDbStatArraySize = uiNewSize;
	}

	// Allocate space for the database name

	if (RC_BAD( rc = f_alloc( f_strlen( pszDbName) + 1, &pszTmpDbName)))
	{
		goto Exit;
	}

	// Insert the item into the array.

	if (iCmp != 0)
	{
		uiElement = pFlmStats->uiNumDbStats;

		// If our new database number is greater than database number of the
		// element pointed to by uiMid, increment uiMid so that the new
		// database number will be inserted after it instead of before it.

		if (iCmp > 0)
		{
			uiMid++;
		}

		// Move everything up in the array, including the slot pointed to
		// by uiMid.

		while (uiElement > uiMid)
		{
			f_memcpy( &pDbStatTbl [uiElement], &pDbStatTbl [uiElement - 1],
						sizeof( DB_STATS));
			uiElement--;
		}
		f_memset( &pDbStatTbl [uiMid], 0, sizeof( DB_STATS));
	}
	
	f_strcpy( pszTmpDbName, pszDbName);
	pDbStatTbl[ uiMid].pszDbName = pszTmpDbName;
	pszTmpDbName = NULL;
	
	pFlmStats->uiNumDbStats++;
	*ppDbStatsRV = &pDbStatTbl [uiMid];
	
	if (puiDBAllocSeqRV)
	{
		*puiDBAllocSeqRV = pFlmStats->uiDBAllocSeq;
	}
	
	if (puiDbTblPosRV)
	{
		*puiDbTblPosRV = uiMid;
	}
	
Exit:

	if (pszTmpDbName)
	{
		f_free( &pszTmpDbName);
	}
	
	return( rc);
}

/****************************************************************************
Desc:	This routine returns a pointer to a particular database's
		statistics block.
****************************************************************************/
RCODE	flmStatGetDb(
	FLM_STATS *		pFlmStats,
	void *			pFile,
	FLMUINT			uiLowStart,
	DB_STATS **		ppDbStatsRV,
	FLMUINT *		puiDBAllocSeqRV,
	FLMUINT *		puiDbTblPosRV)
{
	if( !pFlmStats)
	{
		*ppDbStatsRV = NULL;
		
		if (puiDBAllocSeqRV)
		{
			*puiDBAllocSeqRV = 0;
		}
		
		if (puiDbTblPosRV)
		{
			*puiDbTblPosRV = 0;
		}
		
		return( FERR_OK);
	}

	return( flmStatGetDbByName( pFlmStats, ((FFILE *)pFile)->pszDbPath,
					uiLowStart, ppDbStatsRV, puiDBAllocSeqRV,
					puiDbTblPosRV));
}

/****************************************************************************
Desc:	This routine returns a pointer to a particular logical file in a
		particular database's statistics block.
****************************************************************************/
RCODE	flmStatGetLFile(
	DB_STATS *			pDbStats,
	FLMUINT				uiLFileNum,
	FLMUINT				uiLfType,
	FLMUINT				uiLowStart,
	LFILE_STATS **		ppLFileStatsRV,
	FLMUINT *			puiLFileAllocSeqRV,
	FLMUINT *			puiLFileTblPosRV
	)
{
	RCODE				rc = FERR_OK;
	FLMUINT			uiTblSize;
	LFILE_STATS *	pLFileStatTbl;
	LFILE_STATS *	pLFileCurrStat;
	FLMUINT			uiLow;
	FLMUINT			uiMid = 0;
	FLMUINT			uiHigh;
	FLMINT			iCmp = 0;
	FLMUINT			uiNewSize;
	FLMUINT			uiElement;

	if (!pDbStats)
	{
		*ppLFileStatsRV = NULL;
		if (puiLFileAllocSeqRV)
		{
			*puiLFileAllocSeqRV = 0;
		}
		if (puiLFileTblPosRV)
		{
			*puiLFileTblPosRV = 0;
		}
		goto Exit;
	}

	// If there is a database array, search it first.

	if (((pLFileStatTbl = pDbStats->pLFileStats) != NULL) &&
		 ((uiTblSize = pDbStats->uiNumLFileStats) != 0))
	{
		for (uiHigh = --uiTblSize, uiLow = uiLowStart ; ; )		// Optimize: reduce uiTblSize
		{
			uiMid = (uiLow + uiHigh) >> 1;		// (uiLow + uiHigh) / 2

			// See if we found a match.

			pLFileCurrStat = &pLFileStatTbl [uiMid];
			if (uiLFileNum < pLFileCurrStat->uiLFileNum)
			{
				iCmp = -1;
			}
			else if (uiLFileNum > pLFileCurrStat->uiLFileNum)
			{
				iCmp = 1;
			}
			else
			{

				// Found match.

				*ppLFileStatsRV = pLFileCurrStat;
				if (uiLfType != 0xFF)
				{
					pLFileCurrStat->uiFlags &= (~(LFILE_TYPE_UNKNOWN));
					if (uiLfType == LF_INDEX)
					{
						pLFileCurrStat->uiFlags |= LFILE_IS_INDEX;
					}
					else
					{
						pLFileCurrStat->uiFlags &= (~(LFILE_IS_INDEX));
					}
				}
				if (puiLFileAllocSeqRV)
				{
					*puiLFileAllocSeqRV = pDbStats->uiLFileAllocSeq;
				}
				if (puiLFileTblPosRV)
				{
					*puiLFileTblPosRV = uiMid;
				}
				goto Exit;
			}

			// Check if we are done - where uiLow equals uiHigh.

			if (uiLow >= uiHigh)
			{

				// Item not found.

				break;
			}

			if (iCmp < 0)
			{
				if (uiMid == uiLowStart)
				{
					break;				// Way too high?
				}
				uiHigh = uiMid - 1;		// Too high
			}
			else
			{
				if (uiMid == uiTblSize)
				{
					break;				// Done - Hit the top
				}
				uiLow = uiMid + 1;		// Too low
			}
		}
	}

	// If the array is full, or was never allocated, allocate a new one.

	if (pDbStats->uiLFileStatArraySize <= pDbStats->uiNumLFileStats)
	{
		if (!pDbStats->pLFileStats)
		{
			uiNewSize = INIT_LFILE_STAT_ARRAY_SIZE;
		}
		else
		{
			uiNewSize = pDbStats->uiLFileStatArraySize +
							LFILE_STAT_ARRAY_INCR_SIZE;
		}
		if (RC_BAD( rc = f_calloc( 
			(FLMUINT)(sizeof( LFILE_STATS) * uiNewSize), &pLFileStatTbl)))
		{
			goto Exit;
		}

		// Save whatever was in the old table, if any.

		if ((pDbStats->pLFileStats) &&
			 (pDbStats->uiNumLFileStats))
		{
			f_memcpy( pLFileStatTbl, pDbStats->pLFileStats,
					(FLMUINT)(sizeof( LFILE_STATS) *
							 pDbStats->uiNumLFileStats));
		}
		if (pDbStats->pLFileStats)
		{
			f_free( &pDbStats->pLFileStats);
		}

		pDbStats->uiLFileAllocSeq++;
		pDbStats->pLFileStats = pLFileStatTbl;
		pDbStats->uiLFileStatArraySize = uiNewSize;
	}

	// Insert the item into the array.

	if (iCmp != 0)
	{
		uiElement = pDbStats->uiNumLFileStats;

		// If our new database number is greater than database number of the
		// element pointed to by uiMid, increment uiMid so that the new
		// database number will be inserted after it instead of before it.

		if (iCmp > 0)
		{
			uiMid++;
		}

		// Move everything up in the array, including the slot pointed to
		// by uiMid.

		while (uiElement > uiMid)
		{
			f_memcpy( &pLFileStatTbl [uiElement], &pLFileStatTbl [uiElement - 1],
						sizeof( LFILE_STATS));
			uiElement--;
		}
		f_memset( &pLFileStatTbl [uiMid], 0, sizeof( LFILE_STATS));
	}
	pLFileStatTbl [uiMid].uiLFileNum = uiLFileNum;
	if (uiLfType == LF_INDEX)
	{
		pLFileStatTbl [uiMid].uiFlags |= LFILE_IS_INDEX;
		pLFileStatTbl [uiMid].uiFlags &= (~(LFILE_TYPE_UNKNOWN));
	}
	else if (uiLfType == 0xFF)
	{
		pLFileStatTbl [uiMid].uiFlags |= LFILE_TYPE_UNKNOWN;
	}
	else
	{
		pLFileStatTbl [uiMid].uiFlags &=
			(~(LFILE_IS_INDEX | LFILE_TYPE_UNKNOWN));
	}
	pDbStats->uiNumLFileStats++;
	*ppLFileStatsRV = &pLFileStatTbl [uiMid];
	if (puiLFileAllocSeqRV)
	{
		*puiLFileAllocSeqRV = pDbStats->uiLFileAllocSeq;
	}
	if (puiLFileTblPosRV)
	{
		*puiLFileTblPosRV = uiMid;
	}
Exit:

	return( rc);
}

/****************************************************************************
Desc:	This routine returns a pointer to a particular LFILE's
		statistics block.  It uses the pointer in the OPC if it
		is up-to-date.
****************************************************************************/
LFILE_STATS * fdbGetLFileStatPtr(
	FDB *		pDb,
	LFILE *	pLFile
	)
{
	if (!pLFile)
	{
		return( (LFILE_STATS *)NULL);
	}

	if ((!pDb->pLFileStats) ||
		 (pDb->uiLFileAllocSeq !=
			 pDb->pDbStats->uiLFileAllocSeq) ||
		 (pDb->pLFileStats->uiLFileNum != pLFile->uiLfNum))
	{
		if (RC_BAD( flmStatGetLFile( pDb->pDbStats, pLFile->uiLfNum,
									pLFile->uiLfType, 0, &pDb->pLFileStats,
									&pDb->uiLFileAllocSeq, NULL)))
		{
			pDb->pLFileStats = NULL;
			pDb->uiLFileAllocSeq = 0;
		}
	}
	return( pDb->pLFileStats);
}

/****************************************************************************
Desc:	This routine resets the statistics in a FLM_STAT structure.
****************************************************************************/
void flmStatReset(
	FLM_STATS *	pStats,
	FLMBOOL		bMutexAlreadyLocked,
	FLMBOOL		bFree
	)
{
	FLMUINT			uiDb;
	DB_STATS	*		pDbStats;
	FLMUINT			uiLFile;
	LFILE_STATS *	pLFile;

	// If the structure has a mutex, lock it, if not already locked.

	if (!bMutexAlreadyLocked && pStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexLock( pStats->hMutex);
	}

	if ((pDbStats = pStats->pDbStats) != NULL)
	{
		for (uiDb = 0; uiDb < pStats->uiNumDbStats; uiDb++, pDbStats++)
		{
			if ((pLFile = pDbStats->pLFileStats) != NULL)
			{
				if (bFree)
				{
					f_free( &pDbStats->pLFileStats);
				}
				else
				{
					for (uiLFile = 0;
						  uiLFile < pDbStats->uiNumLFileStats;
						  uiLFile++, pLFile++)
					{
						FLMUINT	uiSaveLFileNum = pLFile->uiLFileNum;
						FLMUINT	uiSaveFlags = pLFile->uiFlags;

						f_memset( pLFile, 0, sizeof( LFILE_STATS));
						pLFile->uiLFileNum = uiSaveLFileNum;
						pLFile->uiFlags = uiSaveFlags;
					}
				}
			}
			if (!bFree)
			{
				const char *	pszSaveDbName;
				FLMUINT			uiSaveLFileAllocSeq = pDbStats->uiLFileAllocSeq;
				LFILE_STATS *	pSaveLFileStats = pDbStats->pLFileStats;
				FLMUINT			uiSaveLFileStatArraySize =
										pDbStats->uiLFileStatArraySize;
				FLMUINT			uiSaveNumLFileStats = pDbStats->uiNumLFileStats;

				pszSaveDbName = pDbStats->pszDbName;
				f_memset( pDbStats, 0, sizeof( DB_STATS));
				pDbStats->pszDbName = pszSaveDbName;
				pDbStats->uiLFileAllocSeq = uiSaveLFileAllocSeq;
				pDbStats->pLFileStats = pSaveLFileStats;
				pDbStats->uiLFileStatArraySize = uiSaveLFileStatArraySize;
				pDbStats->uiNumLFileStats = uiSaveNumLFileStats;
			}
			else
			{
				f_free( &pDbStats->pszDbName);
			}
		}
		if (bFree)
		{
			f_free( &pStats->pDbStats);
		}
	}
	if ((bFree) || (!pDbStats))
	{
		pStats->pDbStats = NULL;
		pStats->uiDbStatArraySize = 0;
		pStats->uiNumDbStats = 0;
	}
	pStats->uiStartTime = 0;
	pStats->uiStopTime = 0;

	if (pStats->bCollectingStats)
	{
		f_timeGetSeconds( &pStats->uiStartTime);
	}

	// Unlock the mutex, if we locked it in this routine.

	if (!bMutexAlreadyLocked && pStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexUnlock( pStats->hMutex);
	}
}

/****************************************************************************
Desc:	This routine starts collecting statistics in a FLM_STAT structure.
****************************************************************************/
void flmStatStart(
	FLM_STATS *	pStats
	)
{
	// If the structure has a mutex, lock it.

	if( pStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexLock( pStats->hMutex);
	}

	pStats->bCollectingStats = TRUE;
	flmStatReset( pStats, TRUE, TRUE);

	// If the structure has a mutex, unlock it.

	if( pStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexUnlock( pStats->hMutex);
	}
}

/****************************************************************************
Desc:	This routine stops collecting statistics in a FLM_STAT structure.
****************************************************************************/
void flmStatStop(
	FLM_STATS *	pStats
	)
{
	// If the structure has a mutex, lock it.

	if (pStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexLock( pStats->hMutex);
	}

	if (pStats->bCollectingStats)
	{
		pStats->bCollectingStats = FALSE;
		f_timeGetSeconds( &pStats->uiStopTime);
	}

	// If the structure has a mutex, unlock it.

	if (pStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexUnlock( pStats->hMutex);
	}
}

/****************************************************************************
Desc:	This routine initializes a FLM_STAT structure.
****************************************************************************/
RCODE flmStatInit(
	FLM_STATS *	pStats,
	FLMBOOL		bEnableSharing
	)
{
	RCODE	rc = FERR_OK;

	f_memset( pStats, 0, sizeof( FLM_STATS));
	if (!bEnableSharing)
	{
		pStats->hMutex = F_MUTEX_NULL;
	}
	else
	{
		if( RC_BAD( rc = f_mutexCreate( &pStats->hMutex)))
		{
			goto Exit;
		}
	}

Exit:

	return( rc);
}

/****************************************************************************
Desc:	This routine frees the memory associated with a FLM_STAT structure.
****************************************************************************/
FLMEXP void FLMAPI FlmFreeStats(
	FLM_STATS *	pStats
	)
{
	// If the structure has a mutex, lock it.

	if (pStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexLock( pStats->hMutex);
	}
	pStats->bCollectingStats = FALSE;
	flmStatReset( pStats, TRUE, TRUE);

	// Unlock and free the mutex

	if (pStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexUnlock( pStats->hMutex);
		f_mutexDestroy( &pStats->hMutex);
	}
}

/****************************************************************************
Desc:	This routine updates statistics from one RTRANS_STATS structure into
		another.
****************************************************************************/
FINLINE void flmUpdateRTransStats(
	RTRANS_STATS *	pDest,
	RTRANS_STATS *	pSrc)
{
	flmUpdateCountTimeStats( &pDest->CommittedTrans, &pSrc->CommittedTrans);
	flmUpdateCountTimeStats( &pDest->AbortedTrans, &pSrc->AbortedTrans);
	flmUpdateCountTimeStats( &pDest->InvisibleTrans, &pSrc->InvisibleTrans);
}

/****************************************************************************
Desc:	This routine updates statistics from one UTRANS_STATS structure into
		another.
****************************************************************************/
FINLINE void flmUpdateUTransStats(
	UTRANS_STATS *	pDest,
	UTRANS_STATS *	pSrc)
{
	flmUpdateCountTimeStats( &pDest->CommittedTrans, &pSrc->CommittedTrans);
	flmUpdateCountTimeStats( &pDest->GroupCompletes, &pSrc->GroupCompletes);
	pDest->ui64GroupFinished += pSrc->ui64GroupFinished;
	flmUpdateCountTimeStats( &pDest->AbortedTrans, &pSrc->AbortedTrans);
}

/****************************************************************************
Desc:	This routine updates statistics from one BLOCKIO_STATS structure into
		another.
****************************************************************************/
void flmUpdateBlockIOStats(
	BLOCKIO_STATS *	pDest,
	BLOCKIO_STATS *	pSrc
	)
{
	flmUpdateDiskIOStats( &pDest->BlockReads, &pSrc->BlockReads);
	flmUpdateDiskIOStats( &pDest->OldViewBlockReads,
									&pSrc->OldViewBlockReads);
	pDest->uiBlockChkErrs += pSrc->uiBlockChkErrs;
	pDest->uiOldViewBlockChkErrs += pSrc->uiOldViewBlockChkErrs;
	pDest->uiOldViewErrors += pSrc->uiOldViewErrors;
	flmUpdateDiskIOStats( &pDest->BlockWrites, &pSrc->BlockWrites);
}

/****************************************************************************
Desc:	This routine updates statistics from one LFILE_STATS structure into
		another.
****************************************************************************/
FSTATIC void flmUpdateLFileStats(
	LFILE_STATS *	pDest,
	LFILE_STATS *	pSrc
	)
{

	// Set uiFlags in case the number of levels has changed.

	pDest->uiFlags = pSrc->uiFlags;
	pDest->bHaveStats = TRUE;
	flmUpdateBlockIOStats( &pDest->RootBlockStats, &pSrc->RootBlockStats);
	flmUpdateBlockIOStats( &pDest->MiddleBlockStats, &pSrc->MiddleBlockStats);
	flmUpdateBlockIOStats( &pDest->LeafBlockStats, &pSrc->LeafBlockStats);
	pDest->ui64BlockSplits += pSrc->ui64BlockSplits;
	pDest->ui64BlockCombines += pSrc->ui64BlockCombines;
}

/****************************************************************************
Desc:	This routine updates statistics from one DB_STATS structure into
		another.
****************************************************************************/
FSTATIC RCODE flmUpdateDbStats(
	DB_STATS *	pDestDb,
	DB_STATS *	pSrcDb
	)
{
	RCODE				rc = FERR_OK;
	FLMUINT			uiSrcLFile;
	FLMUINT			uiDestLFile;
	LFILE_STATS *	pDestLFile;
	LFILE_STATS *	pSrcLFile;
	FLMUINT			uiLowLFileStart;
	FLMUINT			uiSaveNumLFiles;

	flmUpdateRTransStats( &pDestDb->ReadTransStats,
								 &pSrcDb->ReadTransStats);
								 
	flmUpdateUTransStats( &pDestDb->UpdateTransStats,
								 &pSrcDb->UpdateTransStats);
								 
	pDestDb->bHaveStats = TRUE;
	pDestDb->ui64NumCursors += pSrcDb->ui64NumCursors;
	pDestDb->ui64NumCursorReads += pSrcDb->ui64NumCursorReads;
	
	flmUpdateCountTimeStats( &pDestDb->RecordAdds,
									 &pSrcDb->RecordAdds);
									 
	flmUpdateCountTimeStats( &pDestDb->RecordDeletes,
									 &pSrcDb->RecordDeletes);
									 
	flmUpdateCountTimeStats( &pDestDb->RecordModifies,
									 &pSrcDb->RecordModifies);
									 
	pDestDb->ui64NumRecordReads += pSrcDb->ui64NumRecordReads;
	
	flmUpdateBlockIOStats( &pDestDb->LFHBlockStats,
								  &pSrcDb->LFHBlockStats);
								  
	flmUpdateBlockIOStats( &pDestDb->AvailBlockStats,
								  &pSrcDb->AvailBlockStats);
								  
	flmUpdateDiskIOStats( &pDestDb->LogHdrWrites,
								 &pSrcDb->LogHdrWrites);
								 
	flmUpdateDiskIOStats( &pDestDb->LogBlockWrites,
								 &pSrcDb->LogBlockWrites);
								 
	flmUpdateDiskIOStats( &pDestDb->LogBlockRestores,
								 &pSrcDb->LogBlockRestores);
								 
	flmUpdateDiskIOStats( &pDestDb->LogBlockReads,
								 &pSrcDb->LogBlockReads);
								 
	pDestDb->uiLogBlockChkErrs += pSrcDb->uiLogBlockChkErrs;
	pDestDb->uiReadErrors += pSrcDb->uiReadErrors;
	pDestDb->uiWriteErrors += pSrcDb->uiWriteErrors;
	
	flmUpdateCountTimeStats( &pDestDb->LockStats.NoLocks, 
									 &pSrcDb->LockStats.NoLocks);
									 
	flmUpdateCountTimeStats( &pDestDb->LockStats.WaitingForLock,
									 &pSrcDb->LockStats.WaitingForLock);
									 
	flmUpdateCountTimeStats( &pDestDb->LockStats.HeldLock,
									 &pSrcDb->LockStats.HeldLock);

	// Go through the LFILE statistics.

	for (uiDestLFile = 0, uiSrcLFile = 0, uiLowLFileStart = 0,
			pSrcLFile = pSrcDb->pLFileStats;
			uiSrcLFile < pSrcDb->uiNumLFileStats;
			uiSrcLFile++, pSrcLFile++)
	{
		if (!pSrcLFile->bHaveStats)
			continue;

		// Find or add the store in the destination store array.

		uiSaveNumLFiles = pDestDb->uiNumLFileStats;
		if (RC_BAD( rc = flmStatGetLFile( pDestDb, pSrcLFile->uiLFileNum,
									(FLMUINT)((pSrcLFile->uiFlags & LFILE_IS_INDEX)
												? (FLMUINT)LF_INDEX
												: (FLMUINT)LF_CONTAINER),
									uiLowLFileStart, &pDestLFile, NULL,
									&uiLowLFileStart)))
		{
			goto Exit;
		}

		if (uiLowLFileStart < pDestDb->uiNumLFileStats - 1)
		{
			uiLowLFileStart++;
		}

		// If we created the LFILE, all we have to do is copy the
		// LFILE statistics.  It will be quicker.

		if (uiSaveNumLFiles != pDestDb->uiNumLFileStats)
		{
			f_memcpy( pDestLFile, pSrcLFile, sizeof( LFILE_STATS));
		}
		else
		{

			// LFILE was already present, need to go through and
			// update the statistics.  

			flmUpdateLFileStats( pDestLFile, pSrcLFile);
		}
	}

Exit:

	return( rc);
}

/****************************************************************************
Desc:	This routine updates statistics from one FLM_STAT structure into
		another.  The source statistics are reset after the
		update.
****************************************************************************/
RCODE	flmStatUpdate(
	FLM_STATS *	pDestStats,
	FLM_STATS *	pSrcStats)
{
	RCODE				rc = FERR_OK;
	FLMUINT			uiSrcDb;
	FLMUINT			uiDestDb;
	DB_STATS *		pDestDb;
	DB_STATS *		pSrcDb;
	FLMUINT			uiLowDbStart;

	// Do not update the statistics if the source statistics were started
	// at an earlier time that the destination statistics start time.

	if (!pDestStats->bCollectingStats ||
		 pSrcStats->uiStartTime < pDestStats->uiStartTime)
	{
		return( FERR_OK);
	}

	// If the destination structure has a mutex, lock it.

	if (pDestStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexLock( pDestStats->hMutex);
	}

	// Go through each of the source's databases

	for ( uiDestDb = 0, uiSrcDb = 0, uiLowDbStart = 0,
			pSrcDb = pSrcStats->pDbStats;
			uiSrcDb < pSrcStats->uiNumDbStats;
			uiSrcDb++, pSrcDb++)
	{
		if (!pSrcDb->bHaveStats)
		{
			continue;
		}

		// Find or add the store in the destination store array.

		if (RC_BAD( rc = flmStatGetDbByName( pDestStats,
									pSrcDb->pszDbName,
									uiLowDbStart, &pDestDb, NULL,
									&uiLowDbStart)))
		{
			goto Exit;
		}

		if (uiLowDbStart < pDestStats->uiNumDbStats - 1)
		{
			uiLowDbStart++;
		}

		if (RC_BAD( rc = flmUpdateDbStats( pDestDb, pSrcDb)))
		{
			goto Exit;
		}
	}

Exit:

	// Unlock the destination's mutex, if there is one.

	if (pDestStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexUnlock( pDestStats->hMutex);
	}

	// Only clear the source statistics AFTER unlocking the destination
	// statistic's semaphore. This is just an attempt to be slightly more
	// efficient.  It could be done before unlock the destination semaphore
	// and still be correct.  But there is no sense in keeping the destination
	// semaphore locked.

	if (RC_OK( rc))
	{
		flmStatReset( pSrcStats, TRUE, FALSE);
	}
	return( rc);
}

/****************************************************************************
Desc:	This routine frees however many queries that are over the limit of
		the number we can save.
Note:	This routine will ALWAYS unlock the query mutex on leaving.  It may
		be locked when entering.
****************************************************************************/
void flmFreeSavedQueries(
	FLMBOOL	bMutexAlreadyLocked
	)
{
	QUERY_HDR *	pQueriesToFree = NULL;

	// Must determine the queries to free inside the mutex lock and then free
	// them outside the mutex lock, because freeing a query may cause an
	// embedded query to be put into the list again, which will cause the
	// mutex to be locked again.

	if (!bMutexAlreadyLocked)
	{
		f_mutexLock( gv_FlmSysData.hQueryMutex);
	}
	while (gv_FlmSysData.uiQueryCnt > gv_FlmSysData.uiMaxQueries)
	{
		gv_FlmSysData.pOldestQuery = gv_FlmSysData.pOldestQuery->pPrev;
		gv_FlmSysData.uiQueryCnt--;
	}

	// Whatever is found after pOldestQuery should be freed.  Unlink
	// those from the list and point to them with pQueriesToFree.

	if (!gv_FlmSysData.pOldestQuery)
	{
		pQueriesToFree = gv_FlmSysData.pNewestQuery;
		gv_FlmSysData.pNewestQuery = NULL;
	}
	else if (gv_FlmSysData.pOldestQuery->pNext)
	{
		pQueriesToFree = gv_FlmSysData.pOldestQuery->pNext;
		pQueriesToFree->pPrev = NULL;
		gv_FlmSysData.pOldestQuery->pNext = NULL;
	}
	f_mutexUnlock( gv_FlmSysData.hQueryMutex);

	// Now clean up each of the queries in the pQueriesToFree list.
	// This can be done outside the mutex lock because we are now
	// dealing with a completely local list that no other thread can
	// see.  Also, the mutex must NOT be locked at this point because
	// flmCurFree may free an embedded query, which will want
	// to lock the mutex again.

	while (pQueriesToFree)
	{
		QUERY_HDR *	pQueryHdrToFree = pQueriesToFree;

		pQueriesToFree = pQueriesToFree->pNext;
		flmCurFree( (CURSOR *)pQueryHdrToFree->hCursor, FALSE);
		f_free( &pQueryHdrToFree);
	}
}

/****************************************************************************
Desc:	This routine saves a query so it can be analyzed later.
****************************************************************************/
void flmSaveQuery(
	HFCURSOR		hCursor
	)
{
	QUERY_HDR *	pQueryHdr = NULL;
	FLMBOOL		bNeedToCleanup = TRUE;
	FLMBOOL		bMutexLocked = FALSE;

	// Allocate memory for the new query

	if (RC_BAD( f_calloc( sizeof( QUERY_HDR), &pQueryHdr)))
	{
		goto Exit;
	}

	pQueryHdr->hCursor = hCursor;

	f_mutexLock( gv_FlmSysData.hQueryMutex);
	bMutexLocked = TRUE;

	// uiMaxQueries was originally checked outside of the mutex lock.
	// Make sure it is still non-zero.

	if (gv_FlmSysData.uiMaxQueries)
	{

		// Link query to head of list.

		bNeedToCleanup = FALSE;
		if ((pQueryHdr->pNext = gv_FlmSysData.pNewestQuery) != NULL)
		{
			pQueryHdr->pNext->pPrev = pQueryHdr;
		}
		else
		{
			gv_FlmSysData.pOldestQuery = pQueryHdr;
		}
		gv_FlmSysData.pNewestQuery = pQueryHdr;

		if (++gv_FlmSysData.uiQueryCnt > gv_FlmSysData.uiMaxQueries)
		{
			flmFreeSavedQueries( TRUE);

			// flmFreeSavedQueries will always unlock the mutex.

			bMutexLocked = FALSE;
		}
	}

Exit:

	if (bMutexLocked)
	{
		f_mutexUnlock( gv_FlmSysData.hQueryMutex);
	}

	// Must clean up the query if we didn't get it into the list for
	// some reason.

	if (bNeedToCleanup)
	{
		if (pQueryHdr)
		{
			f_free( &pQueryHdr);
		}
		flmCurFree( (CURSOR *)hCursor, FALSE);
	}
}

/****************************************************************************
Desc:	This routine copies statistics from one FLM_STAT structure into
		another.  This is used to retrieve statistics.
****************************************************************************/
FSTATIC RCODE flmStatCopy(
	FLM_STATS *	pDestStats,
	FLM_STATS *	pSrcStats
	)
{
	RCODE				rc = FERR_OK;
	FLMUINT			uiDb;
	DB_STATS *		pDestDb;
	DB_STATS *		pSrcDb;
	FLMUINT			uiCount;
	FLMUINT			uiLoop;
	DB_STATS *		pDbStats;
	LFILE_STATS *	pLFile;

	flmStatInit( pDestStats, FALSE);

	// If the source structure has a mutex, lock it.

	if (pSrcStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexLock( pSrcStats->hMutex);
	}

	f_memcpy( pDestStats, pSrcStats, sizeof( FLM_STATS));

	// Zero out the database array.  We need to do this
	// so that if we get an error, we can correctly release memory for
	// the destination structure.

	pDestStats->hMutex = F_MUTEX_NULL;
	pDestStats->uiNumDbStats = 0;
	pDestStats->uiDbStatArraySize = 0;
	pDestStats->pDbStats = NULL;
	uiCount = 0;
	if (pSrcStats->uiNumDbStats)
	{
		for (uiLoop = 0, pDbStats = pSrcStats->pDbStats;
			  uiLoop < pSrcStats->uiNumDbStats;
			  uiLoop++, pDbStats++)
		{
			if (pDbStats->bHaveStats)
			{
				uiCount++;
			}
		}
	}
	if (uiCount)
	{
		if (RC_BAD( rc = f_calloc( 
			(FLMUINT)sizeof( DB_STATS) * uiCount, &pDestStats->pDbStats)))
		{
			goto Exit;
		}
		for (uiLoop = 0, uiCount = 0, pDbStats = pSrcStats->pDbStats;
			  uiLoop < pSrcStats->uiNumDbStats;
			  uiLoop++, pDbStats++)
		{
			if (pDbStats->bHaveStats)
			{
				pDestDb = &pDestStats->pDbStats [uiCount];
				f_memcpy( pDestDb, pDbStats, sizeof( DB_STATS));

				// Zero out each store's LFILE statistics.  We need to do this
				// so that if we get an error, we can correctly release memory for
				// the destination structure.

				pDestDb->uiNumLFileStats = 0;
				pDestDb->uiLFileStatArraySize = 0;
				pDestDb->pLFileStats = NULL;
				uiCount++;
			}
		}
		pDestStats->uiDbStatArraySize =
		pDestStats->uiNumDbStats = uiCount;
	}

	for (uiDb = pSrcStats->uiNumDbStats,
			pDestDb = pDestStats->pDbStats,
			pSrcDb = pSrcStats->pDbStats;
		  uiDb;
		  uiDb--, pSrcDb++)
	{
		if (!pSrcDb->bHaveStats)
		{
			continue;
		}

		pDestDb->uiNumLFileStats = 0;
		pDestDb->uiLFileStatArraySize = 0;
		pDestDb->pLFileStats = NULL;
		uiCount = 0;
		for (uiLoop = 0, pLFile = pSrcDb->pLFileStats;
			  uiLoop < pSrcDb->uiNumLFileStats;
			  uiLoop++, pLFile++)
		{
			if (pLFile->bHaveStats)
			{
				uiCount++;
			}
		}
		if (uiCount)
		{
			if (RC_BAD( rc = f_calloc(
				(FLMUINT)sizeof( LFILE_STATS) * uiCount, &pDestDb->pLFileStats)))
			{
				goto Exit;
			}
			uiCount = 0;
			for (uiLoop = 0, pLFile = pSrcDb->pLFileStats;
			  	uiLoop < pSrcDb->uiNumLFileStats;
			  	uiLoop++, pLFile++)
			{
				if (pLFile->bHaveStats)
				{
					f_memcpy( &pDestDb->pLFileStats [uiCount],
								pLFile, sizeof( LFILE_STATS));
					uiCount++;
				}
			}
			pDestDb->uiLFileStatArraySize =
			pDestDb->uiNumLFileStats = uiCount;
		}
		pDestDb++;
	}

Exit:

	// Unlock the source's mutex, if there is one.

	if (pSrcStats->hMutex != F_MUTEX_NULL)
	{
		f_mutexUnlock( pSrcStats->hMutex);
	}

	if (RC_BAD( rc))
	{
		FlmFreeStats( pDestStats);
	}
	return( rc);
}

/****************************************************************************
Desc : Returns statistics that have been collected for a share.
Notes: The statistics returned will be the statistics for ALL
		 databases associated with the share structure.
****************************************************************************/
FLMEXP RCODE FLMAPI FlmGetStats(
	FLM_STATS *	pFlmStats)
{
	RCODE			rc = FERR_OK;

	// Get the statistics
	
	if( RC_BAD( rc = flmStatCopy( pFlmStats, &gv_FlmSysData.Stats)))
	{
		goto Exit;
	}

Exit:

	return( rc);
}

/****************************************************************************
Desc:	This routine locates the appropriate BLOCKIO_STATS structure for a
		given block of data.  NULL is returned if an appropriate one cannot
		be found.
VISIT: uiBlkType passed in is a guess.  Remove this parm and start using
		 pBlk.
****************************************************************************/
BLOCKIO_STATS * flmGetBlockIOStatPtr(
	DB_STATS *		pDbStats,
	LFILE_STATS *	pLFileStats,
	FLMBYTE *		pBlk,
	FLMUINT			uiBlkType)
{
	if (uiBlkType == BHT_FREE)
	{
		pDbStats->bHaveStats = TRUE;
		return( &pDbStats->AvailBlockStats);
	}
	else if (uiBlkType == BHT_LFH_BLK)
	{
		pDbStats->bHaveStats = TRUE;
		return( &pDbStats->LFHBlockStats);
	}
	else if (pLFileStats)
	{
		pLFileStats->bHaveStats =
		pDbStats->bHaveStats = TRUE;
		
		// Consider invalid type.

		if ((BH_GET_TYPE( pBlk) != BHT_LEAF) &&
			 (BH_GET_TYPE( pBlk) != BHT_NON_LEAF) &&
			 (BH_GET_TYPE( pBlk) != BHT_NON_LEAF_DATA) &&
			 (BH_GET_TYPE( pBlk) != BHT_NON_LEAF_COUNTS))
		{
			return( &pLFileStats->LeafBlockStats);
		}
		if ((FB2UD( &(pBlk [BH_NEXT_BLK])) == BT_END) &&
	 		 (FB2UD( &(pBlk [BH_PREV_BLK])) == BT_END))
		{
			return( &pLFileStats->RootBlockStats);
		}
		else if (BH_GET_TYPE( pBlk) != BHT_LEAF)
		{
			return( &pLFileStats->MiddleBlockStats);
		}
		else
		{
			return( &pLFileStats->LeafBlockStats);
		}
	}
	else
	{
		return( (BLOCKIO_STATS *)NULL);
	}
}

/********************************************************************
Desc: Determine if a given year is a leap year.
*********************************************************************/
FINLINE FLMUINT flmLeapYear(
	FLMUINT		uiYear)
{
	if (uiYear % 4 != 0)
	{
		return( 0);
	}
	if (uiYear % 100 != 0)
	{
		return( 1);
	}
	if (uiYear % 400 != 0)
	{
		return( 0);
	}
	return( 1);
}

/********************************************************************
Desc: Calculate days in a given month of a given year.
*********************************************************************/
FSTATIC FLMUINT flmDaysInMonth(
	FLMUINT	uiYear,
	FLMUINT	uiMonth
	)
{
	switch (uiMonth + 1)
	{
		case 4:
		case 6:
		case 9:
		case 11:
			return( 30);
		case 2:
			return( 28 + flmLeapYear( uiYear));
		default:
			return( 31);
	}
}

/********************************************************************
Desc: Adjust the time.
*********************************************************************/
FSTATIC void flmAdjustTime(
	F_TMSTAMP *	pTime,
	FLMINT		iStartPoint
	)
{
	switch (iStartPoint)
	{
		case 1:
			goto Adj_1;
		case 2:
			goto Adj_2;
		case 3:
			goto Adj_3;
		case 4:
			goto Adj_4;
		case 5:
			goto Adj_5;
		case 6:
			goto Adj_6;
	}
Adj_1:
	if (pTime->hundredth >= 100)
	{
		pTime->second++;
		pTime->hundredth = 0;
	}
Adj_2:
	if (pTime->second == 60)
	{
		pTime->minute++;
		pTime->second = 0;
	}
Adj_3:
	if (pTime->minute == 60)
	{
		pTime->hour++;
		pTime->minute = 0;
	}
Adj_4:
	if (pTime->hour == 24)
	{
		pTime->day++;
		pTime->hour = 0;
	}
Adj_5:
	if ((FLMUINT)pTime->day > flmDaysInMonth( pTime->year, pTime->month))
	{
		pTime->month++;
		pTime->day = 1;
	}
Adj_6:
	if (pTime->month > 11)
	{
		pTime->year++;
		pTime->month = 1;
	}
}

/********************************************************************
Desc: Calculate the elapsed time, including milliseconds.
*********************************************************************/
void flmAddElapTime(
	F_TMSTAMP *	pStartTime,
	FLMUINT64 *	pui64ElapMilli
	)
{
	F_TMSTAMP	StartTime;
	F_TMSTAMP	EndTime;
	FLMUINT		uiSec = 0;
	FLMUINT		uiHundredth = 0;

	f_timeGetTimeStamp( &EndTime);
	f_memcpy( &StartTime, pStartTime, sizeof( F_TMSTAMP));

	if (StartTime.year < EndTime.year)
	{
		if (StartTime.hundredth)
		{
			uiHundredth += (FLMUINT)(100 - StartTime.hundredth);
			StartTime.hundredth = 0;
			StartTime.second++;
			flmAdjustTime( &StartTime, 2);
		}
		if (StartTime.second)
		{
			uiSec += (FLMUINT)(60 - StartTime.second);
			StartTime.second = 0;
			StartTime.minute++;
			flmAdjustTime( &StartTime, 3);
		}
		if (StartTime.minute)
		{
			uiSec += (FLMUINT)((60 - StartTime.minute) * 60);
			StartTime.minute = 0;
			StartTime.hour++;
			flmAdjustTime( &StartTime, 4);
		}
		if (StartTime.hour)
		{
			uiSec += (FLMUINT)((24 - StartTime.hour) * 3600);
			StartTime.hour = 0;
			StartTime.day++;
			flmAdjustTime( &StartTime, 5);
		}
		if (StartTime.day > 1)
		{
			uiSec += (FLMUINT)(flmDaysInMonth( StartTime.year, StartTime.month) -
									StartTime.day + 1) * (FLMUINT)86400;
			StartTime.day = 1;
			StartTime.month++;
			flmAdjustTime( &StartTime, 6);
		}
		if (StartTime.month > 1)
		{
			while (StartTime.month <= 11)
			{
				uiSec += (FLMUINT)((FLMUINT)flmDaysInMonth( StartTime.year,
										StartTime.month) * (FLMUINT)86400);
				StartTime.month++;
			}
			StartTime.year++;
		}
		while (StartTime.year < EndTime.year)
		{
			uiSec += (FLMUINT)((FLMUINT)(365 + flmLeapYear( StartTime.year)) *
							(FLMUINT)86400);
			StartTime.year++;
		}
	}

	if (StartTime.month < EndTime.month)
	{
		if (StartTime.hundredth)
		{
			uiHundredth += (FLMUINT)(100 - StartTime.hundredth);
			StartTime.hundredth = 0;
			StartTime.second++;
			flmAdjustTime( &StartTime, 2);
		}
		if (StartTime.second)
		{
			uiSec += (FLMUINT)(60 - StartTime.second);
			StartTime.second = 0;
			StartTime.minute++;
			flmAdjustTime( &StartTime, 3);
		}
		if (StartTime.minute)
		{
			uiSec += (FLMUINT)((60 - StartTime.minute) * 60);
			StartTime.minute = 0;
			StartTime.hour++;
			flmAdjustTime( &StartTime, 4);
		}
		if (StartTime.hour)
		{
			uiSec += (FLMUINT)((24 - StartTime.hour) * 3600);
			StartTime.hour = 0;
			StartTime.day++;
			flmAdjustTime( &StartTime, 5);
		}
		if (StartTime.day > 1)
		{
			uiSec += (FLMUINT)(flmDaysInMonth( StartTime.year, StartTime.month) -
									StartTime.day + 1) * (FLMUINT)86400;
			StartTime.day = 1;
			StartTime.month++;
			flmAdjustTime( &StartTime, 6);
		}
		while (StartTime.month < EndTime.month)
		{
			uiSec += (FLMUINT)((FLMUINT)flmDaysInMonth( StartTime.year,
									StartTime.month) * (FLMUINT)86400);
			StartTime.month++;
		}
	}

	if (StartTime.day < EndTime.day)
	{
		if (StartTime.hundredth)
		{
			uiHundredth += (FLMUINT)(100 - StartTime.hundredth);
			StartTime.hundredth = 0;
			StartTime.second++;
			flmAdjustTime( &StartTime, 2);
		}
		if (StartTime.second)
		{
			uiSec += (FLMUINT)(60 - StartTime.second);
			StartTime.second = 0;
			StartTime.minute++;
			flmAdjustTime( &StartTime, 3);
		}
		if (StartTime.minute)
		{
			uiSec += (FLMUINT)((60 - StartTime.minute) * 60);
			StartTime.minute = 0;
			StartTime.hour++;
			flmAdjustTime( &StartTime, 4);
		}
		if (StartTime.hour)
		{
			uiSec += (FLMUINT)((24 - StartTime.hour) * 3600);
			StartTime.hour = 0;
			StartTime.day++;
			flmAdjustTime( &StartTime, 5);
		}
		uiSec += (FLMUINT)(EndTime.day - StartTime.day) * (FLMUINT)86400;
		StartTime.day = 1;
		StartTime.month++;
		flmAdjustTime( &StartTime, 6);
	}

	if (StartTime.hour < EndTime.hour)
	{
		if (StartTime.hundredth)
		{
			uiHundredth += (FLMUINT)(100 - StartTime.hundredth);
			StartTime.hundredth = 0;
			StartTime.second++;
			flmAdjustTime( &StartTime, 2);
		}
		if (StartTime.second)
		{
			uiSec += (FLMUINT)(60 - StartTime.second);
			StartTime.second = 0;
			StartTime.minute++;
			flmAdjustTime( &StartTime, 3);
		}
		if (StartTime.minute)
		{
			uiSec += (FLMUINT)((60 - StartTime.minute) * 60);
			StartTime.minute = 0;
			StartTime.hour++;
			flmAdjustTime( &StartTime, 4);
		}
		uiSec += (FLMUINT)((EndTime.hour - StartTime.hour) * 3600);
		StartTime.hour = 0;
		StartTime.day++;
		flmAdjustTime( &StartTime, 5);
	}

	if (StartTime.minute < EndTime.minute)
	{
		if (StartTime.hundredth)
		{
			uiHundredth += (FLMUINT)(100 - StartTime.hundredth);
			StartTime.hundredth = 0;
			StartTime.second++;
			flmAdjustTime( &StartTime, 2);
		}
		if (StartTime.second)
		{
			uiSec += (FLMUINT)(60 - StartTime.second);
			StartTime.second = 0;
			StartTime.minute++;
			flmAdjustTime( &StartTime, 3);
		}
		uiSec += (FLMUINT)((EndTime.minute - StartTime.minute) * 60);
		StartTime.minute = 0;
		StartTime.hour++;
		flmAdjustTime( &StartTime, 4);
	}

	if (StartTime.second < EndTime.second)
	{
		if (StartTime.hundredth)
		{
			uiHundredth += (FLMUINT)(100 - StartTime.hundredth);
			StartTime.hundredth = 0;
			StartTime.second++;
			flmAdjustTime( &StartTime, 2);
		}
		uiSec += (FLMUINT)(EndTime.second - StartTime.second);
		StartTime.second = 0;
		StartTime.minute++;
		flmAdjustTime( &StartTime, 3);
	}

	if (StartTime.hundredth < EndTime.hundredth)
	{
		uiHundredth += (FLMUINT)(EndTime.hundredth - StartTime.hundredth);
	}
	if (uiSec)
	{
		*(pui64ElapMilli) += (FLMUINT64)(uiHundredth * 10 + uiSec * 1000);
	}
	else
	{
		*(pui64ElapMilli) += (FLMUINT64)(uiHundredth * 10);
	}
}
