/*++

Copyright (c) 1996,1997  Microsoft Corporation

Module Name:

	STATS.C

Abstract:

	Session Statistics routines

Author:

	Aaron Ogus (aarono)

Environment:

	Win32/COM

Revision History:

	Date   Author  Description
   ======  ======  ============================================================
   7/30/97 aarono  Original
   6/6/98  aarono  Turn on throttling and windowing

--*/

#include <windows.h>
#include "newdpf.h"
#include <dplay.h>
#include <dplaysp.h>
#include <dplaypr.h>
#include "mydebug.h"
#include "arpd.h"
#include "arpdint.h"
#include "protocol.h"
#include "macros.h"
#include "command.h"

#define STARTING_LONG_LATENCY 			1  /*  1  ms (intentionally low so first sample fills)      */
#define STARTING_SHORT_LATENCY 		15000  /* 15 seconds (intentionally high so first sample fills) */
#define STARTING_AVERAGE_LATENCY 	 2000  /*  2 seconds (good start for internet) */
#define STARTING_AVERAGE_DEVIATION      0  

#define STARTING_MAXRETRY                16  /* Maximum number of retries */
#define STARTING_MINDROPTIME          15000  /* Minimum time to retry before dropping connection (ms) */
#define STARTING_MAXDROPTIME          60000  /* Maximum time to retry before dropping connection (ms) */

#define STARTING_BANDWIDTH          (28800/10) /* 28 kbps modem */

#define LATENCY_SHORT_BITS				4
#define LATENCY_LONG_BITS               7

#define STAT_LOCAL_LATENCY_SAMPLES 		2^(LATENCY_SHORT_BITS) /* 2^4 */
#define STAT_LONG_LATENCY_SAMPLES       2^(LATENCY_LONG_BITS)  /* 2^7 */

#define TARGET_CLOCK_OFFSET 		10000000


#define Fp(_x) ((_x)<<8)
#define unFp(_x)((_x)>>8)

// Latency Averages and deviation averages are stored as fixed point 24.8

VOID InitSessionStats(PSESSION pSession)
{
	pSession->ShortestLatency     = STARTING_SHORT_LATENCY;
	pSession->LongestLatency      = STARTING_LONG_LATENCY;
	
	pSession->FpAverageLatency      = 1000;
	pSession->FpLocalAverageLatency = 1000;

	pSession->FpLocalAvgDeviation   = 300;
	pSession->FpAvgDeviation        = 300;

	pSession->Bandwidth = 28800/10;
	pSession->HighestBandwidth=28800/10;
	
	pSession->MaxRetry    = STARTING_MAXRETRY;
	pSession->MinDropTime = STARTING_MINDROPTIME;
	pSession->MaxDropTime = STARTING_MAXDROPTIME;
}


// called with SESSIONLOCK.
VOID UpdateSessionStats(PSESSION pSession, PSENDSTAT pStat, PCMDINFO pCmdInfo, BOOL fBadDrop)
{
	DWORD tLatency;
	DWORD nBytesReceived;
	DWORD tDeviation;
	DWORD BytesLost=0;
	DWORD BackLog=0;

	DWORD fThrottleAdjusted=FALSE;

	DWORD tRemoteDelta; // change in time on remote from last received ACK until this was ACKed.
	
	DWORD tBiasedDelta; // a biased difference in local and remote clocks.
	INT   tDelta; // the unbiased difference (signed)
	static DWORD cBiasReset;

	
	// Get the statistics information we need.
	tLatency = pCmdInfo->tReceived-pStat->tSent;

	ASSERT((int)tLatency >= 0);
	if(!tLatency){
		DPF(8,"0ms observed latency, using 1ms\n");
		tLatency=1;
	}	

	Lock(&pSession->SessionStatLock);
	
		// Calculates the number of bytes received at remote since this send was done.
		pSession->RemoteBytesReceived = pCmdInfo->bytes;
		pSession->tRemoteBytesReceived = pCmdInfo->tRemoteACK;
		nBytesReceived = pSession->RemoteBytesReceived - pStat->RemoteBytesReceived;

		BytesLost = pStat->LocalBytesSent-(pSession->RemoteBytesReceived+pSession->BytesLost);

		if((int)BytesLost >= 0){
		
			pSession->BytesLost += BytesLost;

			// Note, Backlog may be as little as 1/2 this value.
			BackLog = pSession->BytesSent -( pSession->RemoteBytesReceived + pSession->BytesLost );

			if((int)BackLog < 0){
				DPF(8,"Hmmm, upside down backlog?\n");
				DPF(8,"pSession->BytesSent             %d\n",pSession->BytesSent);
				DPF(8,"pSession->RemoteBytesReceived   %d\n",pSession->RemoteBytesReceived); 
				DPF(8,"pSession->BytesLost             %d\n",pSession->BytesLost);
				DPF(8,"Calculated BackLog              %d\n",BackLog);
				BackLog=0;
			}
			
		} else if((int)BytesLost < 0){
			// Can be caused by out of order receives
			DPF(1,"Out of order remote receive lots of these may affect throttling...\n");
			DPF(8,"Hmmm, upside down byte counting?\n"); 
			DPF(8,"pStat->LocalBytesSent           %d\n",pStat->LocalBytesSent);
			DPF(8,"pSession->RemoteBytesReceived   %d\n",pSession->RemoteBytesReceived); 
			DPF(8,"pSession->BytesLost             %d\n",pSession->BytesLost);
			DPF(8,"Calculated Bytes Lost           %d\n",BytesLost);
			BytesLost=0;
			// fixup lost count.
			pSession->BytesLost=pSession->RemoteBytesReceived-pStat->LocalBytesSent;
		}

	Unlock(&pSession->SessionStatLock);

	if(pSession->MaxCSends==1){
	
		DWORD Bias;
		
		// 1st ACK, adjust windows to normal operation.
		pSession->MaxCSends     = MAX_SMALL_CSENDS;	 
		pSession->MaxCDGSends   = MAX_SMALL_DG_CSENDS;
		pSession->WindowSize	= MAX_SMALL_WINDOW;
		pSession->DGWindowSize  = MAX_SMALL_WINDOW;

		pSession->FpAverageLatency      = 2*tLatency; // start high to avoid overthrottle
		pSession->FpLocalAverageLatency = 2*tLatency;

		pSession->FpLocalAvgDeviation   = 1+tLatency/3;
		pSession->FpAvgDeviation        = 1+tLatency/3;



		Bias = pCmdInfo->tRemoteACK - pStat->tSent;

		if(Bias > TARGET_CLOCK_OFFSET){
			Bias = -1*(Bias-TARGET_CLOCK_OFFSET);
		} else {
			Bias = TARGET_CLOCK_OFFSET - Bias;
		}

		pSession->RemAvgACKBias = Bias;
		
		pSession->RemAvgACKDelta = (pCmdInfo->tRemoteACK - pStat->tSent)+pSession->RemAvgACKBias;

		ASSERT(pSession->RemAvgACKDelta == TARGET_CLOCK_OFFSET);
	}

	//
	// Calculate shift in outbound latency.
	//
	tBiasedDelta = (pCmdInfo->tRemoteACK - pStat->tSent)+pSession->RemAvgACKBias;
	tDelta = tBiasedDelta-TARGET_CLOCK_OFFSET;

	if(tDelta < 0 || pStat->bResetBias || tDelta > (int)tLatency){
		DWORD Bias;

		// Either clock drift or lower server load shows latency down, so reset baseline.
		
		Bias = pCmdInfo->tRemoteACK - pStat->tSent;

		if(Bias > TARGET_CLOCK_OFFSET){
			Bias = -1*(Bias-TARGET_CLOCK_OFFSET);
		} else {
			Bias = TARGET_CLOCK_OFFSET - Bias;
		}
		cBiasReset++;

		pSession->RemAvgACKBias = Bias;
		tBiasedDelta = (pCmdInfo->tRemoteACK - pStat->tSent)+pSession->RemAvgACKBias;
		tDelta = tBiasedDelta-TARGET_CLOCK_OFFSET;
	}

	pSession->RemAvgACKDelta -= pSession->RemAvgACKDelta >> 7; // -1/128th
	pSession->RemAvgACKDelta += tBiasedDelta >> 7;			   // +1/128th of new value 

	// keep the residue so we don't creep down due to rounding error.
	pSession->RemAvgACKDeltaResidue += tBiasedDelta & 0x7f;
	if(pSession->RemAvgACKDeltaResidue>>7){
		pSession->RemAvgACKDelta += pSession->RemAvgACKDeltaResidue>>7;
		pSession->RemAvgACKDeltaResidue &= 0x7f;
	}


	DPF(8,"tRemoteACK %d tSent %d Bias %d tBiasedDelta %d tDelta %d\n", pCmdInfo->tRemoteACK, pStat->tSent, 
		pSession->RemAvgACKBias, tBiasedDelta, tDelta);
	
	
	//
	// Update latency statistics
	//
	
	ASSERT(!(nBytesReceived & 0x80000000)); // received in interval +ve
	ASSERT(!(tLatency & 0x80000000));       // latency is +ve

	if(tLatency < pSession->ShortestLatency){
		pSession->ShortestLatency=tLatency;
		DPF(8,"Shortest Latency %d ms\n",tLatency);
	}

	if(tLatency > pSession->LongestLatency){
		pSession->LongestLatency=tLatency;
		DPF(8,"Longest Latency %d ms\n", tLatency);
	}

	pSession->LastLatency=tLatency;

	// Throw out 1/16 of local latency and add in the new statistic.
	// Note we only use local latency for retry calculations.

	if(pSession->FpLocalAverageLatency){
		if(Fp(tLatency) > pSession->FpAverageLatency){
			pSession->FpLocalAverageLatency -= (pSession->FpLocalAverageLatency >> LATENCY_SHORT_BITS);
			pSession->FpLocalAverageLatency += (tLatency << (8-LATENCY_SHORT_BITS));
		} else {
			// Ratched down when we get a latency that is below average, so we can better
			// detect backlog due to latency.
			pSession->FpLocalAverageLatency = Fp(tLatency);
		}
	} else {
		// this only happens once at startup.
		pSession->FpLocalAverageLatency = Fp(tLatency);
		pSession->FpAverageLatency = Fp(tLatency);
	}

	if(Fp(tLatency) > pSession->FpAverageLatency){

		// Thow out 1/128 of average latency and add in the new statistic.
		pSession->FpAverageLatency -= (pSession->FpAverageLatency >> LATENCY_LONG_BITS);
		pSession->FpAverageLatency += (tLatency << (8-LATENCY_LONG_BITS));

	} else {
		// Ratched down when we get a latency that is below average, so we can better
		// detect backlog due to latency.
		pSession->FpAverageLatency = Fp(tLatency);
	}
	
	tDeviation=unFp(pSession->FpLocalAverageLatency)-tLatency;
	if((int)tDeviation < 0){
		tDeviation = 0-tDeviation;
	}

	pSession->FpLocalAvgDeviation -= (pSession->FpLocalAvgDeviation >> LATENCY_SHORT_BITS);
	pSession->FpLocalAvgDeviation += (tDeviation << (8-LATENCY_SHORT_BITS));

	pSession->FpAvgDeviation -= (pSession->FpAvgDeviation >> LATENCY_LONG_BITS);
	pSession->FpAvgDeviation += (tDeviation << (8-LATENCY_LONG_BITS));


	DPF(8,"Got ACK, tLat: %d Avg: %d.%d Dev:  %d AvgDev: %d.%d \n",
			tLatency, pSession->FpLocalAverageLatency >> 8, ((pSession->FpLocalAverageLatency&0xFF)*100)/256,
			tDeviation, pSession->FpLocalAvgDeviation >> 8, ((pSession->FpLocalAvgDeviation&0xFF)*100)/256);


	//
	// Do Bandwidth calculations
	//
	
   	tRemoteDelta= pCmdInfo->tRemoteACK - pStat->tRemoteBytesReceived;
   	if(!tRemoteDelta){
   		tRemoteDelta=1;
   	}

	if(pStat->tRemoteBytesReceived){
		pSession->Bandwidth = (1000*nBytesReceived)/(tRemoteDelta);
		// could adjust throttle here if Bandwidth is higher, but this
		// might pimp high speed links. (BUGBUG:).
	} else {
		// backup calculation, not as good.  Only used early in the link
		// before we have received an ACK from the remote prior to issuing
		// a send.
		pSession->Bandwidth = (2000*nBytesReceived)/tLatency;	// 2000, not 1000 since tLatency is round trip.
	}	
	if(pSession->Bandwidth > pSession->HighestBandwidth){
		pSession->HighestBandwidth = pSession->Bandwidth;
	}


	DPF(8,"tRemoteDelta %d Remote bytes Received %d\n",tRemoteDelta,nBytesReceived);

	// Adjust sending...
	
	if ( BackLog && pSession->Bandwidth)
	{

		DWORD tAvgLat;
		DWORD tBackLog;
		DWORD ExcessBackLog; // amount of backlog (bytes) we need to clear before hitting avg latency again.
		DWORD tLatCheck;
		DWORD AvgLat133; // 133% of local average latency (tolerance for slow links)
		DWORD AvgLat200; // 200% of local average latency (tolerance for fast links)


		if(pSession->fFastLink){
			tAvgLat=unFp(pSession->FpAverageLatency);
			tLatCheck = (tAvgLat*3)/2;
			AvgLat133 = max(100,3*unFp(pSession->FpAvgDeviation)+(unFp(pSession->FpAverageLatency)*4)/3); // don't throttle <100ms lat
			AvgLat200 = max(100,3*unFp(pSession->FpAvgDeviation)+unFp(pSession->FpAverageLatency)*2);
		} else {
			tAvgLat=unFp(pSession->FpLocalAverageLatency);
			tLatCheck = (tAvgLat*3)/2;
			AvgLat133 = max(100,3*unFp(pSession->FpLocalAvgDeviation)+(unFp(pSession->FpLocalAverageLatency)*4)/3); // don't throttle <100ms lat
			AvgLat200 = max(100,3*unFp(pSession->FpLocalAvgDeviation)+unFp(pSession->FpLocalAverageLatency)*2);
		}
		
		if(tLatCheck < AvgLat133){
			tLatCheck = AvgLat133; 
		}

		if(tLatency > tLatCheck){
			// check link speed
			if(pSession->fFastLink){
				if(pSession->Bandwidth <= 10000){
					pSession->fFastLink=FALSE;
				}
			} else {
				if(pSession->Bandwidth >= 25000){
					pSession->fFastLink=TRUE;
				}
			}
		}

		if(pSession->fFastLink && tLatCheck < AvgLat200){
			tLatCheck=AvgLat200;
		}

		DPF(8,"tLat %d, tLatCheck %d, tDelta %d, tLat/3 %d\n",tLatency,tLatCheck,tDelta,tLatency/3); 
		DPF(8,"pSession->ShortestLatency %d, Shortest+MaxPacketTime %d\n",pSession->ShortestLatency,
			pSession->ShortestLatency+(pSession->MaxPacketSize*1000)/pSession->Bandwidth);

		
		if((tLatency > tLatCheck && tDelta > (int)(tLatency/3)) ||
		    ((!pSession->fFastLink)&&
		     (tLatency > pSession->ShortestLatency+((pSession->MaxPacketSize*2000)/pSession->Bandwidth))
		    )
		  )
		{
				
			#ifdef DEBUG
			if(pSession->SendRateThrottle){
				DPF(8,"BackLog %d, SendRate %d BackLog ms %d, tLatency %d tAvgLat %d Used Bandwidth %d tBacklog %d \n",
						BackLog,
						pSession->SendRateThrottle, 
						(BackLog*1000 / pSession->SendRateThrottle),
						tLatency, 
						tAvgLat, 
						pSession->Bandwidth,
						((BackLog*1000) / pSession->Bandwidth)
						);
			}	
			#endif

			tBackLog = (BackLog * 1000)/pSession->Bandwidth;
			
			if(tBackLog > 4*tLatency){
				DPF(8,"1: tBackLog %d was >> tLatency %d, using 4*tLatency instead\n",tBackLog,tLatency);
				tBackLog=4*tLatency; //never wait more than 4 latency periods
			}
			if(tBackLog > 8000){
				DPF(8,"Disalowing backlog > 8 seconds, using 8 instead\n");
				tBackLog=8000;
			}

			// if the backlog is greater than the bandwidth*latency, then we need to slow down our sending.
			// don't slow down due to backlog until we are over 100ms on way latency (200 round trip)
		
			if((tBackLog > 200) && (tBackLog > tAvgLat)){
			
				BOOL fWait=TRUE;

				// at max we cut send rate in 1/2.

				if(pSession->SendRateThrottle/2 > pSession->Bandwidth){
					DPF(8,"Asked for too aggresive throttle adjust %d, going from %d to %d\n",pSession->Bandwidth,pSession->SendRateThrottle,pSession->SendRateThrottle/2);
					pSession->SendRateThrottle /= 2;
					// Recheck if we are really backlogged at the new rate
					tBackLog = (BackLog * 1000)/pSession->SendRateThrottle;
					if(tBackLog > tLatency){
						DPF(8,"2: tBackLog %d was > tLatency %d, using tLatency instead\n",tBackLog,tLatency);
						tBackLog=tLatency;// never wait more than last latency period
					}
				} else {
					// set new throttle rate and current observed bandwidth (+5% to avoid overthrottle)
					pSession->SendRateThrottle=pSession->Bandwidth+pSession->Bandwidth/16;
				}

				// don't adjust for a while.
				pSession->bhitThrottle=FALSE;
				pSession->tLastThrottleAdjust = pCmdInfo->tReceived;

				if(fWait && (tBackLog > tAvgLat)){
				
					ExcessBackLog = ((tBackLog-tAvgLat)*pSession->Bandwidth)/1000;
					
					DPF(8,"Throttling back due to BACKLOG, excess = %d\n",ExcessBackLog);

					#ifdef DEBUG
					if(tBackLog-tAvgLat > 30000){
						DPF(5,"WARNING: BACKLOG THROTTLE %d ms seems kinda large\n",tBackLog-tAvgLat);
					}
					#endif

					// wait until backlog is down to avg latency before sending again
					Lock(&pSession->SessionStatLock);
					pSession->bResetBias = 2; // could be in the middle of a send, so count down from 2.
					Unlock(&pSession->SessionStatLock);
					UpdateSendTime(pSession,ExcessBackLog,timeGetTime(),TRUE);
				} else {
					DPF(8,"Not throttling due to BACKLOG because of smaller adjustment\n");
				}
				
			} else {
				DPF(8,"NOT Throttling back due to BACKLOG\n");
				
			}
		}		

	} else if(tDelta > (int)tLatency) {
		// tDelta is bogus due to clock drift, force throttle so we can correct.
		Lock(&pSession->SessionStatLock);
		pSession->bResetBias=2;
		Unlock(&pSession->SessionStatLock);
		pSession->tNextSend=timeGetTime()+2*tLatency;
		DPF(8,"tDelta %d > tLatency %d, need to correct for clock drift, time %d set next send time to %d\n", tDelta, tLatency,timeGetTime(),pSession->tNextSend);
	}	



	//
	// Adjust Throttle if not already adjusted.
	//
	
	if((pSession->ThrottleState==Begin) || 
	   (pCmdInfo->tReceived-pSession->tLastThrottleAdjust) > (1+1*pSession->fFastLink)*unFp(pSession->FpLocalAverageLatency) )
	{
		if(!fThrottleAdjusted){
			DPF(8,"Current Send Rate %d\n", pSession->SendRateThrottle);
			if(!BytesLost && pSession->bhitThrottle){
				pSession->bhitThrottle=FALSE;
				pSession->tLastThrottleAdjust = pCmdInfo->tReceived;
				// Good Send, push up send rate if we hit throttle.
				switch(pSession->ThrottleState){
					case Begin:
						pSession->SendRateThrottle = (pSession->SendRateThrottle*(100+START_GROWTH_RATE))/100;
						pSession->GrowCount++;
						pSession->ShrinkCount=0;
						break;
						
					case MetaStable:
						pSession->SendRateThrottle = (pSession->SendRateThrottle*(100+METASTABLE_GROWTH_RATE))/100;
						pSession->GrowCount++;
						pSession->ShrinkCount=0;
						break;
						
					case Stable:
						pSession->SendRateThrottle = (pSession->SendRateThrottle*(100+STABLE_GROWTH_RATE))/100;
						pSession->GrowCount++;
						pSession->ShrinkCount=0;
						if(pSession->GrowCount > (UINT)(20+60*pSession->fFastLink)){
							pSession->ThrottleState = MetaStable;
							pSession->GrowCount=0;
						}
						break;
					default:	
						DPF(0,"Session in wierd ThrottleState %d\n",pSession->ThrottleState);
						break;
				}	
				DPF(8,"Successful Send Adjusted Throttle, SendRate %d\n",pSession->SendRateThrottle);
			} else if(BytesLost){
				// Figure out how much we dropped
				if(fBadDrop || (BytesLost > pSession->pProtocol->m_dwSPMaxFrame)){
					// Very bad send, back off
					pSession->tLastThrottleAdjust = pCmdInfo->tReceived;
					switch(pSession->ThrottleState){
						case Begin:
							pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-START_ADJUST_LARGE_ERR))/100;
							pSession->GrowCount=0;
							pSession->ShrinkCount++;
							break;
						case MetaStable:
							pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-METASTABLE_ADJUST_LARGE_ERR))/100;
							pSession->GrowCount=0;
							pSession->ShrinkCount++;
							break;
						case Stable:
							pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-STABLE_ADJUST_LARGE_ERR))/100;
							pSession->ShrinkCount++;
							if(pSession->ShrinkCount > 1){
								pSession->ShrinkCount=0;
								pSession->GrowCount=0;
								pSession->ThrottleState=MetaStable;
							}
							break;
						default:
							DPF(0,"Session in wierd ThrottleState %d\n",pSession->ThrottleState);
							break;
					}	
					DPF(8,"VERY BAD SEND Adjusted Throttle, SendRate %d\n",pSession->SendRateThrottle);
				} else {
					// Bad send, back off a bit
					pSession->tLastThrottleAdjust = pCmdInfo->tReceived;
					switch(pSession->ThrottleState){
						case Begin:
							pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-START_ADJUST_SMALL_ERR))/100;
							pSession->GrowCount=0;
							pSession->ShrinkCount=0;
							pSession->ThrottleState = MetaStable;
							break;
						case MetaStable:
							pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-METASTABLE_ADJUST_SMALL_ERR))/100;
							pSession->ShrinkCount++;
							pSession->GrowCount=0;
							break;
						case Stable:
							pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-STABLE_ADJUST_SMALL_ERR))/100;
							pSession->ShrinkCount++;
							pSession->GrowCount=0;
							if(pSession->ShrinkCount > 2){
								pSession->ShrinkCount=0;
								pSession->ThrottleState = MetaStable;
							}	
							break;
						default:
							DPF(0,"Session in wierd ThrottleState %d\n",pSession->ThrottleState);
							break;
					}
					DPF(8,"BAD SEND Adjusted Throttle, SendRate %d\n",pSession->SendRateThrottle);
				} /* if (BadDrop... ) */
				
			} /* if (BytesLost ...) */
			
		}/*if (ThrottleAdjusted) */

	}

	if(!BytesLost && pSession->Bandwidth && pSession->SendRateThrottle < pSession->Bandwidth){
		DPF(8,"Avoid goofyness, throttle was %d, setting to observed bandwidth %d\n",pSession->SendRateThrottle,pSession->Bandwidth);
		pSession->SendRateThrottle=pSession->Bandwidth;
	}
	if(pSession->SendRateThrottle < 100){
		DPF(8,"WARNING: SendRateThrottle %d below 100, keeping at 100 to avoid starvation\n",pSession->SendRateThrottle);
		pSession->SendRateThrottle=100;
	}

#ifdef DEBUG
	{
		IN_WRITESTATS InWS;
		memset((PVOID)&InWS,0xFF,sizeof(IN_WRITESTATS));

	   	InWS.stat_ThrottleRate = pSession->SendRateThrottle;
		InWS.stat_BytesSent	   = pSession->BytesSent;
		InWS.stat_BackLog      = BackLog;      
	 	InWS.stat_BytesLost    = pSession->BytesLost;
	 	//InWS.stat_RemBytesReceived;
		InWS.stat_Latency = tLatency;
		InWS.stat_MinLatency=pSession->ShortestLatency;
		InWS.stat_AvgLatency=unFp(pSession->FpLocalAverageLatency);
		InWS.stat_AvgDevLatency=unFp(pSession->FpLocalAvgDeviation);
		//InWS.stat_USER1=
		//InWS.stat_USER2=
		//InWS.stat_USER3=
		InWS.stat_USER5 = tDelta;
		InWS.stat_USER6 = cBiasReset;
	
		DbgWriteStats(&InWS);
	}
#endif
	
	DPF(8,"Bandwidth %d, Highest %d\n",pSession->Bandwidth, pSession->HighestBandwidth);
	
}

// Called with SessionLock and SendLock
// Statistics are stored on the send in send order on a BILINK.
// most recent sends are at the end of the list.  We scan from
// the end of the list to the beginning until we find the SENDSTAT
// that records the sequence and serial we got ACKED.  We then 
// update our statistics and throw out all SENDSTATs
// before this entry.
VOID UpdateSessionSendStats(PSESSION pSession, PSEND pSend, PCMDINFO pCmdInfo, BOOL fBadDrop)
{
	PSENDSTAT pStatWalker,pStat=NULL;
	BILINK    *pStatBilink;

	pSend->tLastACK=pCmdInfo->tReceived;
	pSend->RetryCount=0;
	// Find the last STAT for this ACK.
	pStatBilink=pSend->StatList.prev;

	while(pStatBilink != &pSend->StatList){
		pStatWalker=CONTAINING_RECORD(pStatBilink, SENDSTAT, StatList);
		if(pStatWalker->serial==pCmdInfo->serial &&
			pStatWalker->sequence==pCmdInfo->sequence)
		{
			ASSERT(pStatWalker->messageid==pSend->messageid);
			ASSERT(pSend->messageid==pCmdInfo->messageid);
			pStat=pStatWalker;
			break;
		}
		pStatBilink=pStatBilink->prev;
	}

	if(pStat){
		UpdateSessionStats(pSession,pStat,pCmdInfo,fBadDrop);

		// Unlink All Previous SENDSTATS;
		pStat->StatList.next->prev=&pSend->StatList;
		pSend->StatList.next=pStat->StatList.next;

		// Put the SENDSTATS back in the pool.
		while(pStatBilink != &pSend->StatList){
			pStatWalker=CONTAINING_RECORD(pStatBilink, SENDSTAT, StatList);
			pStatBilink=pStatBilink->prev;
			ReleaseSendStat(pStatWalker);
		}

	}	

	return;
}


