/** 
 * @file llstat.cpp
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 * 
 * Copyright (c) 2001-2009, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "llstat.h"
#include "lllivefile.h"
#include "llerrorcontrol.h"
#include "llframetimer.h"
#include "timing.h"
#include "llsd.h"
#include "llsdserialize.h"
#include "llstl.h"
#include "u64.h"


// statics
S32	            LLPerfBlock::sStatsFlags = LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS;       // Control what is being recorded
LLPerfBlock::stat_map_t    LLPerfBlock::sStatMap;    // Map full path string to LLStatTime objects, tracks all active objects
std::string        LLPerfBlock::sCurrentStatPath = "";    // Something like "/total_time/physics/physics step"
LLStat::stat_map_t LLStat::sStatList;

//------------------------------------------------------------------------
// Live config file to trigger stats logging
static const char    STATS_CONFIG_FILE_NAME[]            = "/dev/shm/simperf/simperf_proc_config.llsd";
static const F32    STATS_CONFIG_REFRESH_RATE            = 5.0;        // seconds

class LLStatsConfigFile : public LLLiveFile
{
public:
    LLStatsConfigFile()
        : LLLiveFile(filename(), STATS_CONFIG_REFRESH_RATE),
        mChanged(false), mStatsp(NULL) { }

    static std::string filename();
    
protected:
    /* virtual */ bool loadFile();

public:
    void init(LLPerfStats* statsp);
    static LLStatsConfigFile& instance();
        // return the singleton stats config file

    bool mChanged;

protected:
    LLPerfStats*    mStatsp;
};

std::string LLStatsConfigFile::filename()
{
    return STATS_CONFIG_FILE_NAME;
}

void LLStatsConfigFile::init(LLPerfStats* statsp)
{
    mStatsp = statsp;
}

LLStatsConfigFile& LLStatsConfigFile::instance()
{
    static LLStatsConfigFile the_file;
    return the_file;
}


/* virtual */
// Load and parse the stats configuration file
bool LLStatsConfigFile::loadFile()
{
    if (!mStatsp)
    {
        llwarns << "Tries to load performance configure file without initializing LPerfStats" << llendl;
        return false;
    }
    mChanged = true;
    
    LLSD stats_config;
    {
        llifstream file(filename().c_str());
        if (file.is_open())
        {
            LLSDSerialize::fromXML(stats_config, file);
            if (stats_config.isUndefined())
            {
                llinfos << "Performance statistics configuration file ill-formed, not recording statistics" << llendl;
                mStatsp->setReportPerformanceDuration( 0.f );
                return false;
            }
        }
        else 
        {    // File went away, turn off stats if it was on
            if ( mStatsp->frameStatsIsRunning() )
            {
                llinfos << "Performance statistics configuration file deleted, not recording statistics" << llendl;
                mStatsp->setReportPerformanceDuration( 0.f );
            }
            return true;
        }
    }

    F32 duration = 0.f;
    F32 interval = 0.f;
	S32 flags = LLPerfBlock::LLSTATS_BASIC_STATS;

    const char * w = "duration";
    if (stats_config.has(w))
    {
        duration = (F32)stats_config[w].asReal();
    } 
    w = "interval";
    if (stats_config.has(w))
    {
        interval = (F32)stats_config[w].asReal();
    } 
    w = "flags";
    if (stats_config.has(w))
    {
		flags = (S32)stats_config[w].asInteger();
		if (flags == LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS &&
			duration > 0)
		{   // No flags passed in, but have a duration, so reset to basic stats
			flags = LLPerfBlock::LLSTATS_BASIC_STATS;
		}
    } 

    mStatsp->setReportPerformanceDuration( duration, flags );
    mStatsp->setReportPerformanceInterval( interval );

    if ( duration > 0 )
    {
        if ( interval == 0.f )
        {
            llinfos << "Recording performance stats every frame for " << duration << " sec" << llendl;
        }
        else
        {
            llinfos << "Recording performance stats every " << interval << " seconds for " << duration << " seconds" << llendl;
        }
    }
    else
    {
        llinfos << "Performance stats recording turned off" << llendl;
    }
	return true;
}


//------------------------------------------------------------------------

LLPerfStats::LLPerfStats(const std::string& process_name, S32 process_pid) : 
    mFrameStatsFileFailure(FALSE),
    mSkipFirstFrameStats(FALSE),
    mProcessName(process_name),
    mProcessPID(process_pid),
    mReportPerformanceStatInterval(1.f),
    mReportPerformanceStatEnd(0.0) 
{ }

LLPerfStats::~LLPerfStats()
{
    LLPerfBlock::clearDynamicStats();
    mFrameStatsFile.close();
}

void LLPerfStats::init()
{
    // Initialize the stats config file instance.
    (void) LLStatsConfigFile::instance().init(this);
    (void) LLStatsConfigFile::instance().checkAndReload();
}

// Open file for statistics
void    LLPerfStats::openPerfStatsFile()
{
    if ( !mFrameStatsFile
        && !mFrameStatsFileFailure )
    {
        std::string stats_file = llformat("/dev/shm/simperf/%s_proc.%d.llsd", mProcessName.c_str(), mProcessPID);
        mFrameStatsFile.close();
        mFrameStatsFile.clear();
        mFrameStatsFile.open(stats_file, llofstream::out);
        if ( mFrameStatsFile.fail() )
        {
            llinfos << "Error opening statistics log file " << stats_file << llendl;
            mFrameStatsFileFailure = TRUE;
        }
        else
        {
            LLSD process_info = LLSD::emptyMap();
            process_info["name"] = mProcessName;
            process_info["pid"] = (LLSD::Integer) mProcessPID;
            process_info["stat_rate"] = (LLSD::Integer) mReportPerformanceStatInterval;
            // Add process-specific info.
            addProcessHeaderInfo(process_info);

            mFrameStatsFile << LLSDNotationStreamer(process_info) << std::endl; 
        }
    }
}

// Dump out performance metrics over some time interval
void LLPerfStats::dumpIntervalPerformanceStats()
{
    // Ensure output file is OK
    openPerfStatsFile();

    if ( mFrameStatsFile )
    {
        LLSD stats = LLSD::emptyMap();

        LLStatAccum::TimeScale scale;
        if ( getReportPerformanceInterval() == 0.f )
        {
            scale = LLStatAccum::SCALE_PER_FRAME;
        }
        else if ( getReportPerformanceInterval() < 0.5f )
        {
            scale = LLStatAccum::SCALE_100MS;
        }
        else
        {
            scale = LLStatAccum::SCALE_SECOND;
        }

        // Write LLSD into log
        stats["utc_time"] = (LLSD::String) LLError::utcTime();
        stats["timestamp"] = U64_to_str((totalTime() / 1000) + (gUTCOffset * 1000));    // milliseconds since epoch
        stats["frame_number"] = (LLSD::Integer) LLFrameTimer::getFrameCount();

        // Add process-specific frame info.
        addProcessFrameInfo(stats, scale);
        LLPerfBlock::addStatsToLLSDandReset( stats, scale );

        mFrameStatsFile << LLSDNotationStreamer(stats) << std::endl; 
    }
}

// Set length of performance stat recording.  
// If turning stats on, caller must provide flags
void    LLPerfStats::setReportPerformanceDuration( F32 seconds, S32 flags /* = LLSTATS_NO_OPTIONAL_STATS */ )
{ 
	if ( seconds <= 0.f )
	{
		mReportPerformanceStatEnd = 0.0;
		LLPerfBlock::setStatsFlags(LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS);		// Make sure all recording is off
		mFrameStatsFile.close();
		LLPerfBlock::clearDynamicStats();
	}
	else
	{
		mReportPerformanceStatEnd = LLFrameTimer::getElapsedSeconds() + ((F64) seconds);
		// Clear failure flag to try and create the log file once
		mFrameStatsFileFailure = FALSE;
		mSkipFirstFrameStats = TRUE;		// Skip the first report (at the end of this frame)
		LLPerfBlock::setStatsFlags(flags);
	}
}

void LLPerfStats::updatePerFrameStats()
{
    (void) LLStatsConfigFile::instance().checkAndReload();
	static LLFrameTimer performance_stats_timer;
	if ( frameStatsIsRunning() )
	{
		if ( mReportPerformanceStatInterval == 0 )
		{	// Record info every frame
			if ( mSkipFirstFrameStats )
			{	// Skip the first time - was started this frame
				mSkipFirstFrameStats = FALSE;
			}
			else
			{
				dumpIntervalPerformanceStats();
			}
		}
		else
		{
			performance_stats_timer.setTimerExpirySec( getReportPerformanceInterval() );
			if (performance_stats_timer.checkExpirationAndReset( mReportPerformanceStatInterval ))
			{
				dumpIntervalPerformanceStats();
			}
		}
		
		if ( LLFrameTimer::getElapsedSeconds() > mReportPerformanceStatEnd )
		{	// Reached end of time, clear it to stop reporting
			setReportPerformanceDuration(0.f);			// Don't set mReportPerformanceStatEnd directly	
            llinfos << "Recording performance stats completed" << llendl;
		}
	}
}


//------------------------------------------------------------------------

U64 LLStatAccum::sScaleTimes[NUM_SCALES] =
{
	USEC_PER_SEC / 10,				// 100 millisec
	USEC_PER_SEC * 1,				// seconds
	USEC_PER_SEC * 60,				// minutes
#if ENABLE_LONG_TIME_STATS
	// enable these when more time scales are desired
	USEC_PER_SEC * 60*60,			// hours
	USEC_PER_SEC * 24*60*60,		// days
	USEC_PER_SEC * 7*24*60*60,		// weeks
#endif
};



LLStatAccum::LLStatAccum(bool useFrameTimer)
	: mUseFrameTimer(useFrameTimer),
	  mRunning(FALSE),
	  mLastTime(0),
	  mLastSampleValue(0.0),
	  mLastSampleValid(FALSE)
{
}

LLStatAccum::~LLStatAccum()
{
}



void LLStatAccum::reset(U64 when)
{
	mRunning = TRUE;
	mLastTime = when;

	for (int i = 0; i < NUM_SCALES; ++i)
	{
		mBuckets[i].accum = 0.0;
		mBuckets[i].endTime = when + sScaleTimes[i];
		mBuckets[i].lastValid = false;
	}
}

void LLStatAccum::sum(F64 value)
{
	sum(value, getCurrentUsecs());
}

void LLStatAccum::sum(F64 value, U64 when)
{
	if (!mRunning)
	{
		reset(when);
		return;
	}
	if (when < mLastTime)
	{
		// This happens a LOT on some dual core systems.
		lldebugs << "LLStatAccum::sum clock has gone backwards from "
			<< mLastTime << " to " << when << ", resetting" << llendl;

		reset(when);
		return;
	}

	// how long is this value for
	U64 timeSpan = when - mLastTime;

	for (int i = 0; i < NUM_SCALES; ++i)
	{
		Bucket& bucket = mBuckets[i];

		if (when < bucket.endTime)
		{
			bucket.accum += value;
		}
		else
		{
			U64 timeScale = sScaleTimes[i];

			U64 timeLeft = when - bucket.endTime;
				// how much time is left after filling this bucket
			
			if (timeLeft < timeScale)
			{
				F64 valueLeft = value * timeLeft / timeSpan;

				bucket.lastValid = true;
				bucket.lastAccum = bucket.accum + (value - valueLeft);
				bucket.accum = valueLeft;
				bucket.endTime += timeScale;
			}
			else
			{
				U64 timeTail = timeLeft % timeScale;

				bucket.lastValid = true;
				bucket.lastAccum = value * timeScale / timeSpan;
				bucket.accum = value * timeTail / timeSpan;
				bucket.endTime += (timeLeft - timeTail) + timeScale;
			}
		}
	}

	mLastTime = when;
}


F32 LLStatAccum::meanValue(TimeScale scale) const
{
	if (!mRunning)
	{
		return 0.0;
	}
	if ( scale == SCALE_PER_FRAME )
	{	// Per-frame not supported here
		scale = SCALE_100MS;
	}

	if (scale < 0 || scale >= NUM_SCALES)
	{
		llwarns << "llStatAccum::meanValue called for unsupported scale: "
			<< scale << llendl;
		return 0.0;
	}

	const Bucket& bucket = mBuckets[scale];

	F64 value = bucket.accum;
	U64 timeLeft = bucket.endTime - mLastTime;
	U64 scaleTime = sScaleTimes[scale];

	if (bucket.lastValid)
	{
		value += bucket.lastAccum * timeLeft / scaleTime;
	}
	else if (timeLeft < scaleTime)
	{
		value *= scaleTime / (scaleTime - timeLeft);
	}
	else
	{
		value = 0.0;
	}

	return (F32)(value / scaleTime);
}


U64 LLStatAccum::getCurrentUsecs() const
{
	if (mUseFrameTimer)
	{
		return LLFrameTimer::getTotalTime();
	}
	else
	{
		return totalTime();
	}
}


// ------------------------------------------------------------------------

LLStatRate::LLStatRate(bool use_frame_timer)
	: LLStatAccum(use_frame_timer)
{
}

void LLStatRate::count(U32 value)
{
	sum((F64)value * sScaleTimes[SCALE_SECOND]);
}


void LLStatRate::mark()
 { 
	// Effectively the same as count(1), but sets mLastSampleValue
	U64 when = getCurrentUsecs();

	if ( mRunning 
		 && (when > mLastTime) )
	{	// Set mLastSampleValue to the time from the last mark()
		F64 duration = ((F64)(when - mLastTime)) / sScaleTimes[SCALE_SECOND];
		if ( duration > 0.0 )
		{
			mLastSampleValue = 1.0 / duration;
		}
		else
		{
			mLastSampleValue = 0.0;
		}
	}

	sum( (F64) sScaleTimes[SCALE_SECOND], when);
 }


// ------------------------------------------------------------------------


LLStatMeasure::LLStatMeasure(bool use_frame_timer)
	: LLStatAccum(use_frame_timer)
{
}

void LLStatMeasure::sample(F64 value)
{
	U64 when = getCurrentUsecs();

	if (mLastSampleValid)
	{
		F64 avgValue = (value + mLastSampleValue) / 2.0;
		F64 interval = (F64)(when - mLastTime);

		sum(avgValue * interval, when);
	}
	else
	{
		reset(when);
	}

	mLastSampleValid = TRUE;
	mLastSampleValue = value;
}


// ------------------------------------------------------------------------

LLStatTime::LLStatTime(const std::string & key)
	: LLStatAccum(false),
	  mFrameNumber(LLFrameTimer::getFrameCount()),
	  mTotalTimeInFrame(0),
	  mKey(key)
#if LL_DEBUG
	  , mRunning(FALSE)
#endif
{
}

void LLStatTime::start()
{
	// Reset frame accumluation if the frame number has changed
	U32 frame_number = LLFrameTimer::getFrameCount();
	if ( frame_number != mFrameNumber )
	{
		mFrameNumber = frame_number;
		mTotalTimeInFrame = 0;
	}

	sum(0.0);

#if LL_DEBUG
	// Shouldn't be running already
	llassert( !mRunning );
	mRunning = TRUE;
#endif
}

void LLStatTime::stop()
{
	U64 end_time = getCurrentUsecs();
	U64 duration = end_time - mLastTime;
	sum(F64(duration), end_time);
	//llinfos << "mTotalTimeInFrame incremented from  " << mTotalTimeInFrame << " to " << (mTotalTimeInFrame + duration) << llendl; 
	mTotalTimeInFrame += duration;

#if LL_DEBUG
	mRunning = FALSE;
#endif
}

/* virtual */ F32 LLStatTime::meanValue(TimeScale scale) const
{
    if ( LLStatAccum::SCALE_PER_FRAME == scale )
    {
        return mTotalTimeInFrame;
    }
    else
    {
        return LLStatAccum::meanValue(scale);
    }
}


// ------------------------------------------------------------------------


// Use this constructor for pre-defined LLStatTime objects
LLPerfBlock::LLPerfBlock(LLStatTime* stat ) : mPredefinedStat(stat), mDynamicStat(NULL)
{
    if (mPredefinedStat)
    {
        // If dynamic stats are turned on, this will create a separate entry in the stat map.
        initDynamicStat(mPredefinedStat->mKey);

        // Start predefined stats.  These stats are not part of the stat map.
        mPredefinedStat->start();
    }
}

// Use this constructor for normal, optional LLPerfBlock time slices
LLPerfBlock::LLPerfBlock( const char* key ) : mPredefinedStat(NULL), mDynamicStat(NULL)
{
    if ((sStatsFlags & LLSTATS_BASIC_STATS) == 0)
	{	// These are off unless the base set is enabled
		return;
	}

	initDynamicStat(key);
}

	
// Use this constructor for dynamically created LLPerfBlock time slices
// that are only enabled by specific control flags
LLPerfBlock::LLPerfBlock( const char* key1, const char* key2, S32 flags ) : mPredefinedStat(NULL), mDynamicStat(NULL)
{
    if ((sStatsFlags & flags) == 0)
	{
		return;
	}

    if (NULL == key2 || strlen(key2) == 0)
    {
        initDynamicStat(key1);
    }
    else
    {
        std::ostringstream key;
        key << key1 << "_" << key2;
        initDynamicStat(key.str());
    }
}

// Set up the result data map if dynamic stats are enabled
void LLPerfBlock::initDynamicStat(const std::string& key)
{
    // Early exit if dynamic stats aren't enabled.
    if (sStatsFlags == LLSTATS_NO_OPTIONAL_STATS) 
		return;

    mLastPath = sCurrentStatPath;		// Save and restore current path
    sCurrentStatPath += "/" + key;		// Add key to current path

    // See if the LLStatTime object already exists
    stat_map_t::iterator iter = sStatMap.find(sCurrentStatPath);
    if ( iter == sStatMap.end() )
    {
        // StatEntry object doesn't exist, so create it
        mDynamicStat = new StatEntry( key );
        sStatMap[ sCurrentStatPath ] = mDynamicStat;	// Set the entry for this path
    }
    else
    {
        // Found this path in the map, use the object there
        mDynamicStat = (*iter).second;		// Get StatEntry for the current path
    }

    if (mDynamicStat)
    {
        mDynamicStat->mStat.start();
        mDynamicStat->mCount++;
    }
    else
    {
        llwarns << "Initialized NULL dynamic stat at '" << sCurrentStatPath << "'" << llendl;
       sCurrentStatPath = mLastPath;
    }
}


// Destructor does the time accounting
LLPerfBlock::~LLPerfBlock()
{
    if (mPredefinedStat) mPredefinedStat->stop();
    if (mDynamicStat)
    {
        mDynamicStat->mStat.stop();
        sCurrentStatPath = mLastPath;	// Restore the path in case sStatsEnabled changed during this block
    }
}


// Clear the map of any dynamic stats.  Static routine
void LLPerfBlock::clearDynamicStats()
{
    std::for_each(sStatMap.begin(), sStatMap.end(), DeletePairedPointer());
    sStatMap.clear();
}

// static - Extract the stat info into LLSD
void LLPerfBlock::addStatsToLLSDandReset( LLSD & stats,
										  LLStatAccum::TimeScale scale )
{
    // If we aren't in per-frame scale, we need to go from second to microsecond.
    U32 scale_adjustment = 1;
    if (LLStatAccum::SCALE_PER_FRAME != scale)
    {
        scale_adjustment = USEC_PER_SEC;
    }
	stat_map_t::iterator iter = sStatMap.begin();
	for ( ; iter != sStatMap.end(); ++iter )
	{	// Put the entry into LLSD "/full/path/to/stat/" = microsecond total time
		const std::string & stats_full_path = (*iter).first;

		StatEntry * stat = (*iter).second;
		if (stat)
		{
            if (stat->mCount > 0)
            {
                stats[stats_full_path] = LLSD::emptyMap();
                stats[stats_full_path]["us"] = (LLSD::Integer) (scale_adjustment * stat->mStat.meanValue(scale));
                if (stat->mCount > 1)
                {
                    stats[stats_full_path]["count"] = (LLSD::Integer) stat->mCount;
                }
                stat->mCount = 0;
            }
		}
		else
		{	// WTF?  Shouldn't have a NULL pointer in the map.
            llwarns << "Unexpected NULL dynamic stat at '" << stats_full_path << "'" << llendl;
		}
	}	
}


// ------------------------------------------------------------------------

LLTimer LLStat::sTimer;
LLFrameTimer LLStat::sFrameTimer;

void LLStat::init()
{
	llassert(mNumBins > 0);
	mNumValues = 0;
	mLastValue = 0.f;
	mLastTime = 0.f;
	mCurBin = (mNumBins-1);
	mNextBin = 0;
	mBins      = new F32[mNumBins];
	mBeginTime = new F64[mNumBins];
	mTime      = new F64[mNumBins];
	mDT        = new F32[mNumBins];
	for (U32 i = 0; i < mNumBins; i++)
	{
		mBins[i]      = 0.f;
		mBeginTime[i] = 0.0;
		mTime[i]      = 0.0;
		mDT[i]        = 0.f;
	}

	if (!mName.empty())
	{
		stat_map_t::iterator iter = sStatList.find(mName);
		if (iter != sStatList.end())
			llwarns << "LLStat with duplicate name: " << mName << llendl;
		sStatList.insert(std::make_pair(mName, this));
	}
}

LLStat::LLStat(const U32 num_bins, const BOOL use_frame_timer)
	: mUseFrameTimer(use_frame_timer),
	  mNumBins(num_bins)
{
	init();
}

LLStat::LLStat(std::string name, U32 num_bins, BOOL use_frame_timer)
	: mUseFrameTimer(use_frame_timer),
	  mNumBins(num_bins),
	  mName(name)
{
	init();
}

LLStat::~LLStat()
{
	delete[] mBins;
	delete[] mBeginTime;
	delete[] mTime;
	delete[] mDT;

	if (!mName.empty())
	{
		// handle multiple entries with the same name
		stat_map_t::iterator iter = sStatList.find(mName);
		while (iter != sStatList.end() && iter->second != this)
			++iter;
		sStatList.erase(iter);
	}
}

void LLStat::reset()
{
	U32 i;

	mNumValues = 0;
	mLastValue = 0.f;
	mCurBin = (mNumBins-1);
	delete[] mBins;
	delete[] mBeginTime;
	delete[] mTime;
	delete[] mDT;
	mBins      = new F32[mNumBins];
	mBeginTime = new F64[mNumBins];
	mTime      = new F64[mNumBins];
	mDT        = new F32[mNumBins];
	for (i = 0; i < mNumBins; i++)
	{
		mBins[i]      = 0.f;
		mBeginTime[i] = 0.0;
		mTime[i]      = 0.0;
		mDT[i]        = 0.f;
	}
}

void LLStat::setBeginTime(const F64 time)
{
	mBeginTime[mNextBin] = time;
}

void LLStat::addValueTime(const F64 time, const F32 value)
{
	if (mNumValues < mNumBins)
	{
		mNumValues++;
	}

	// Increment the bin counters.
	mCurBin++;
	if ((U32)mCurBin == mNumBins)
	{
		mCurBin = 0;
	}
	mNextBin++;
	if ((U32)mNextBin == mNumBins)
	{
		mNextBin = 0;
	}

	mBins[mCurBin] = value;
	mTime[mCurBin] = time;
	mDT[mCurBin] = (F32)(mTime[mCurBin] - mBeginTime[mCurBin]);
	//this value is used to prime the min/max calls
	mLastTime = mTime[mCurBin];
	mLastValue = value;

	// Set the begin time for the next stat segment.
	mBeginTime[mNextBin] = mTime[mCurBin];
	mTime[mNextBin] = mTime[mCurBin];
	mDT[mNextBin] = 0.f;
}

void LLStat::start()
{
	if (mUseFrameTimer)
	{
		mBeginTime[mNextBin] = sFrameTimer.getElapsedSeconds();
	}
	else
	{
		mBeginTime[mNextBin] = sTimer.getElapsedTimeF64();
	}
}

void LLStat::addValue(const F32 value)
{
	if (mNumValues < mNumBins)
	{
		mNumValues++;
	}

	// Increment the bin counters.
	mCurBin++;
	if ((U32)mCurBin == mNumBins)
	{
		mCurBin = 0;
	}
	mNextBin++;
	if ((U32)mNextBin == mNumBins)
	{
		mNextBin = 0;
	}

	mBins[mCurBin] = value;
	if (mUseFrameTimer)
	{
		mTime[mCurBin] = sFrameTimer.getElapsedSeconds();
	}
	else
	{
		mTime[mCurBin] = sTimer.getElapsedTimeF64();
	}
	mDT[mCurBin] = (F32)(mTime[mCurBin] - mBeginTime[mCurBin]);

	//this value is used to prime the min/max calls
	mLastTime = mTime[mCurBin];
	mLastValue = value;

	// Set the begin time for the next stat segment.
	mBeginTime[mNextBin] = mTime[mCurBin];
	mTime[mNextBin] = mTime[mCurBin];
	mDT[mNextBin] = 0.f;
}


F32 LLStat::getMax() const
{
	U32 i;
	F32 current_max = mLastValue;
	if (mNumBins == 0)
	{
		current_max = 0.f;
	}
	else
	{
		for (i = 0; (i < mNumBins) && (i < mNumValues); i++)
		{
			// Skip the bin we're currently filling.
			if (i == (U32)mNextBin)
			{
				continue;
			}
			if (mBins[i] > current_max)
			{
				current_max = mBins[i];
			}
		}
	}
	return current_max;
}

F32 LLStat::getMean() const
{
	U32 i;
	F32 current_mean = 0.f;
	U32 samples = 0;
	for (i = 0; (i < mNumBins) && (i < mNumValues); i++)
	{
		// Skip the bin we're currently filling.
		if (i == (U32)mNextBin)
		{
			continue;
		}
		current_mean += mBins[i];
		samples++;
	}

	// There will be a wrap error at 2^32. :)
	if (samples != 0)
	{
		current_mean /= samples;
	}
	else
	{
		current_mean = 0.f;
	}
	return current_mean;
}

F32 LLStat::getMin() const
{
	U32 i;
	F32 current_min = mLastValue;

	if (mNumBins == 0)
	{
		current_min = 0.f;
	}
	else
	{
		for (i = 0; (i < mNumBins) && (i < mNumValues); i++)
		{
			// Skip the bin we're currently filling.
			if (i == (U32)mNextBin)
			{
				continue;
			}
			if (mBins[i] < current_min)
			{
				current_min = mBins[i];
			}
		}
	}
	return current_min;
}

F32 LLStat::getSum() const
{
	U32 i;
	F32 sum = 0.f;
	for (i = 0; (i < mNumBins) && (i < mNumValues); i++)
	{
		// Skip the bin we're currently filling.
		if (i == (U32)mNextBin)
		{
			continue;
		}
		sum += mBins[i];
	}

	return sum;
}

F32 LLStat::getSumDuration() const
{
	U32 i;
	F32 sum = 0.f;
	for (i = 0; (i < mNumBins) && (i < mNumValues); i++)
	{
		// Skip the bin we're currently filling.
		if (i == (U32)mNextBin)
		{
			continue;
		}
		sum += mDT[i];
	}

	return sum;
}

F32 LLStat::getPrev(S32 age) const
{
	S32 bin;
	bin = mCurBin - age;

	while (bin < 0)
	{
		bin += mNumBins;
	}

	if (bin == mNextBin)
	{
		// Bogus for bin we're currently working on.
		return 0.f;
	}
	return mBins[bin];
}

F32 LLStat::getPrevPerSec(S32 age) const
{
	S32 bin;
	bin = mCurBin - age;

	while (bin < 0)
	{
		bin += mNumBins;
	}

	if (bin == mNextBin)
	{
		// Bogus for bin we're currently working on.
		return 0.f;
	}
	return mBins[bin] / mDT[bin];
}

F64 LLStat::getPrevBeginTime(S32 age) const
{
	S32 bin;
	bin = mCurBin - age;

	while (bin < 0)
	{
		bin += mNumBins;
	}

	if (bin == mNextBin)
	{
		// Bogus for bin we're currently working on.
		return 0.f;
	}

	return mBeginTime[bin];
}

F64 LLStat::getPrevTime(S32 age) const
{
	S32 bin;
	bin = mCurBin - age;

	while (bin < 0)
	{
		bin += mNumBins;
	}

	if (bin == mNextBin)
	{
		// Bogus for bin we're currently working on.
		return 0.f;
	}

	return mTime[bin];
}

F32 LLStat::getBin(S32 bin) const
{
	return mBins[bin];
}

F32 LLStat::getBinPerSec(S32 bin) const
{
	return mBins[bin] / mDT[bin];
}

F64 LLStat::getBinBeginTime(S32 bin) const
{
	return mBeginTime[bin];
}

F64 LLStat::getBinTime(S32 bin) const
{
	return mTime[bin];
}

F32 LLStat::getCurrent() const
{
	return mBins[mCurBin];
}

F32 LLStat::getCurrentPerSec() const
{
	return mBins[mCurBin] / mDT[mCurBin];
}

F64 LLStat::getCurrentBeginTime() const
{
	return mBeginTime[mCurBin];
}

F64 LLStat::getCurrentTime() const
{
	return mTime[mCurBin];
}

F32 LLStat::getCurrentDuration() const
{
	return mDT[mCurBin];
}

F32 LLStat::getMeanPerSec() const
{
	U32 i;
	F32 value = 0.f;
	F32 dt    = 0.f;

	for (i = 0; (i < mNumBins) && (i < mNumValues); i++)
	{
		// Skip the bin we're currently filling.
		if (i == (U32)mNextBin)
		{
			continue;
		}
		value += mBins[i];
		dt    += mDT[i];
	}

	if (dt > 0.f)
	{
		return value/dt;
	}
	else
	{
		return 0.f;
	}
}

F32 LLStat::getMeanDuration() const
{
	F32 dur = 0.0f;
	U32 count = 0;
	for (U32 i=0; (i < mNumBins) && (i < mNumValues); i++)
	{
		if (i == (U32)mNextBin)
		{
			continue;
		}
		dur += mDT[i];
		count++;
	}

	if (count > 0)
	{
		dur /= F32(count);
		return dur;
	}
	else
	{
		return 0.f;
	}
}

F32 LLStat::getMaxPerSec() const
{
	U32 i;
	F32 value;

	if (mNextBin != 0)
	{
		value = mBins[0]/mDT[0];
	}
	else if (mNumValues > 0)
	{
		value = mBins[1]/mDT[1];
	}
	else
	{
		value = 0.f;
	}

	for (i = 0; (i < mNumBins) && (i < mNumValues); i++)
	{
		// Skip the bin we're currently filling.
		if (i == (U32)mNextBin)
		{
			continue;
		}
		value = llmax(value, mBins[i]/mDT[i]);
	}
	return value;
}

F32 LLStat::getMinPerSec() const
{
	U32 i;
	F32 value;
	
	if (mNextBin != 0)
	{
		value = mBins[0]/mDT[0];
	}
	else if (mNumValues > 0)
	{
		value = mBins[1]/mDT[1];
	}
	else
	{
		value = 0.f;
	}

	for (i = 0; (i < mNumBins) && (i < mNumValues); i++)
	{
		// Skip the bin we're currently filling.
		if (i == (U32)mNextBin)
		{
			continue;
		}
		value = llmin(value, mBins[i]/mDT[i]);
	}
	return value;
}

F32 LLStat::getMinDuration() const
{
	F32 dur = 0.0f;
	for (U32 i=0; (i < mNumBins) && (i < mNumValues); i++)
	{
		dur = llmin(dur, mDT[i]);
	}
	return dur;
}

U32 LLStat::getNumValues() const
{
	return mNumValues;
}

S32 LLStat::getNumBins() const
{
	return mNumBins;
}

S32 LLStat::getCurBin() const
{
	return mCurBin;
}

S32 LLStat::getNextBin() const
{
	return mNextBin;
}

F64 LLStat::getLastTime() const
{
	return mLastTime;
}
