/** 
 * @file llstat.h
 * @brief Runtime statistics accumulation.
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#ifndef LL_LLSTAT_H
#define LL_LLSTAT_H

#include <deque>
#include <map>

#include "lltimer.h"
#include "llframetimer.h"
#include "llfile.h"

class	LLSD;

// Set this if longer stats are needed
#define ENABLE_LONG_TIME_STATS	0

//
// Accumulates statistics for an arbitrary length of time.
// Does this by maintaining a chain of accumulators, each one
// accumulation the results of the parent.  Can scale to arbitrary
// amounts of time with very low memory cost.
//

class LL_COMMON_API LLStatAccum
{
protected:
	LLStatAccum(bool use_frame_timer);
	virtual ~LLStatAccum();

public:
	enum TimeScale {
		SCALE_100MS,
		SCALE_SECOND,
		SCALE_MINUTE,
#if ENABLE_LONG_TIME_STATS
		SCALE_HOUR,
		SCALE_DAY,
		SCALE_WEEK,
#endif
		NUM_SCALES,			// Use to size storage arrays
		SCALE_PER_FRAME		// For latest frame information - should be after NUM_SCALES since this doesn't go into the time buckets
	};

	static U64 sScaleTimes[NUM_SCALES];

	virtual F32 meanValue(TimeScale scale) const;
		// see the subclasses for the specific meaning of value

	F32 meanValueOverLast100ms()  const { return meanValue(SCALE_100MS);  }
	F32 meanValueOverLastSecond() const	{ return meanValue(SCALE_SECOND); }
	F32 meanValueOverLastMinute() const	{ return meanValue(SCALE_MINUTE); }

	void reset(U64 when);

	void sum(F64 value);
	void sum(F64 value, U64 when);

	U64 getCurrentUsecs() const;
		// Get current microseconds based on timer type

	BOOL	mUseFrameTimer;
	BOOL	mRunning;

	U64		mLastTime;
	
	struct Bucket
	{
		Bucket() :
			accum(0.0),
			endTime(0),
			lastValid(false),
			lastAccum(0.0)
		{}

		F64	accum;
		U64	endTime;

		bool	lastValid;
		F64	lastAccum;
	};

	Bucket	mBuckets[NUM_SCALES];

	BOOL 	mLastSampleValid;
	F64 	mLastSampleValue;
};

class LL_COMMON_API LLStatMeasure : public LLStatAccum
	// gathers statistics about things that are measured
	// ex.: tempature, time dilation
{
public:
	LLStatMeasure(bool use_frame_timer = true);

	void sample(F64);
	void sample(S32 v) { sample((F64)v); }
	void sample(U32 v) { sample((F64)v); }
	void sample(S64 v) { sample((F64)v); }
	void sample(U64 v) { sample((F64)v); }
};


class LL_COMMON_API LLStatRate : public LLStatAccum
	// gathers statistics about things that can be counted over time
	// ex.: LSL instructions executed, messages sent, simulator frames completed
	// renders it in terms of rate of thing per second
{
public:
	LLStatRate(bool use_frame_timer = true);

	void count(U32);
		// used to note that n items have occured
	
	void mark();
		// used for counting the rate thorugh a point in the code
};


class LL_COMMON_API LLStatTime : public LLStatAccum
	// gathers statistics about time spent in a block of code
	// measure average duration per second in the block
{
public:
	LLStatTime( const std::string & key = "undefined" );

	U32		mFrameNumber;		// Current frame number
	U64		mTotalTimeInFrame;	// Total time (microseconds) accumulated during the last frame

	void	setKey( const std::string & key )		{ mKey = key;	};

	virtual F32 meanValue(TimeScale scale) const;

private:
	void start();				// Start and stop measuring time block
	void stop();

	std::string		mKey;		// Tag representing this time block

#if LL_DEBUG
	BOOL			mRunning;	// TRUE if start() has been called
#endif

	friend class LLPerfBlock;
};

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


// Use this class on the stack to record statistics about an area of code
class LL_COMMON_API LLPerfBlock
{
public:
    struct StatEntry
    {
            StatEntry(const std::string& key) : mStat(LLStatTime(key)), mCount(0) {}
            LLStatTime  mStat;
            U32         mCount;
    };
    typedef std::map<std::string, StatEntry*>		stat_map_t;

	// Use this constructor for pre-defined LLStatTime objects
	LLPerfBlock(LLStatTime* stat);

	// Use this constructor for normal, optional LLPerfBlock time slices
	LLPerfBlock( const char* key );

	// Use this constructor for dynamically created LLPerfBlock time slices
	// that are only enabled by specific control flags
	LLPerfBlock( const char* key1, const char* key2, S32 flags = LLSTATS_BASIC_STATS );

	~LLPerfBlock();

	enum
	{	// Stats bitfield flags
		LLSTATS_NO_OPTIONAL_STATS	= 0x00,		// No optional stats gathering, just pre-defined LLStatTime objects
		LLSTATS_BASIC_STATS			= 0x01,		// Gather basic optional runtime stats
		LLSTATS_SCRIPT_FUNCTIONS	= 0x02,		// Include LSL function calls
	};
	static void setStatsFlags( S32 flags )	{ sStatsFlags = flags;	};
	static S32  getStatsFlags()				{ return sStatsFlags;	};

	static void clearDynamicStats();		// Reset maps to clear out dynamic objects
	static void addStatsToLLSDandReset( LLSD & stats,		// Get current information and clear time bin
										LLStatAccum::TimeScale scale );

private:
	// Initialize dynamically created LLStatTime objects
    void initDynamicStat(const std::string& key);

	std::string				mLastPath;				// Save sCurrentStatPath when this is called
	LLStatTime * 			mPredefinedStat;		// LLStatTime object to get data
	StatEntry *				mDynamicStat;   		// StatEntryobject to get data

	static S32				sStatsFlags;			// Control what is being recorded
    static stat_map_t		sStatMap;				// Map full path string to LLStatTime objects
	static std::string		sCurrentStatPath;		// Something like "frame/physics/physics step"
};

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

class LL_COMMON_API LLPerfStats
{
public:
    LLPerfStats(const std::string& process_name = "unknown", S32 process_pid = 0);
    virtual ~LLPerfStats();

    virtual void init();    // Reset and start all stat timers
    virtual void updatePerFrameStats();
    // Override these function to add process-specific information to the performance log header and per-frame logging.
    virtual void addProcessHeaderInfo(LLSD& info) { /* not implemented */ }
    virtual void addProcessFrameInfo(LLSD& info, LLStatAccum::TimeScale scale) { /* not implemented */ }

    // High-resolution frame stats
    BOOL    frameStatsIsRunning()                                { return (mReportPerformanceStatEnd > 0.);        };
    F32     getReportPerformanceInterval() const                { return mReportPerformanceStatInterval;        };
    void    setReportPerformanceInterval( F32 interval )        { mReportPerformanceStatInterval = interval;    };
    void    setReportPerformanceDuration( F32 seconds, S32 flags = LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS );
    void    setProcessName(const std::string& process_name) { mProcessName = process_name; }
    void    setProcessPID(S32 process_pid) { mProcessPID = process_pid; }

protected:
    void    openPerfStatsFile();                    // Open file for high resolution metrics logging
    void    dumpIntervalPerformanceStats();

    llofstream      mFrameStatsFile;            // File for per-frame stats
    BOOL            mFrameStatsFileFailure;        // Flag to prevent repeat opening attempts
    BOOL            mSkipFirstFrameStats;        // Flag to skip one (partial) frame report
    std::string     mProcessName;
    S32             mProcessPID;

private:
    F32 mReportPerformanceStatInterval;    // Seconds between performance stats
    F64 mReportPerformanceStatEnd;        // End time (seconds) for performance stats
};

// ----------------------------------------------------------------------------
class LL_COMMON_API LLStat
{
private:
	typedef std::multimap<std::string, LLStat*> stat_map_t;
	static stat_map_t sStatList;

	void init();

public:
	LLStat(U32 num_bins = 32, BOOL use_frame_timer = FALSE);
	LLStat(std::string name, U32 num_bins = 32, BOOL use_frame_timer = FALSE);
	~LLStat();

	void reset();

	void start();	// Start the timer for the current "frame", otherwise uses the time tracked from
					// the last addValue
	void addValue(const F32 value = 1.f); // Adds the current value being tracked, and tracks the DT.
	void addValue(const S32 value) { addValue((F32)value); }
	void addValue(const U32 value) { addValue((F32)value); }

	void setBeginTime(const F64 time);
	void addValueTime(const F64 time, const F32 value = 1.f);
	
	S32 getCurBin() const;
	S32 getNextBin() const;
	
	F32 getCurrent() const;
	F32 getCurrentPerSec() const;
	F64 getCurrentBeginTime() const;
	F64 getCurrentTime() const;
	F32 getCurrentDuration() const;
	
	F32 getPrev(S32 age) const;				// Age is how many "addValues" previously - zero is current
	F32 getPrevPerSec(S32 age) const;		// Age is how many "addValues" previously - zero is current
	F64 getPrevBeginTime(S32 age) const;
	F64 getPrevTime(S32 age) const;
	
	F32 getBin(S32 bin) const;
	F32 getBinPerSec(S32 bin) const;
	F64 getBinBeginTime(S32 bin) const;
	F64 getBinTime(S32 bin) const;

	F32 getMax() const;
	F32 getMaxPerSec() const;
	
	F32 getMean() const;
	F32 getMeanPerSec() const;
	F32 getMeanDuration() const;

	F32 getMin() const;
	F32 getMinPerSec() const;
	F32 getMinDuration() const;

	F32 getSum() const;
	F32 getSumDuration() const;

	U32 getNumValues() const;
	S32 getNumBins() const;

	F64 getLastTime() const;
private:
	BOOL mUseFrameTimer;
	U32 mNumValues;
	U32 mNumBins;
	F32 mLastValue;
	F64 mLastTime;
	F32 *mBins;
	F64 *mBeginTime;
	F64 *mTime;
	F32 *mDT;
	S32 mCurBin;
	S32 mNextBin;
	
	std::string mName;

	static LLTimer sTimer;
	static LLFrameTimer sFrameTimer;
	
public:
	static LLStat* getStat(const std::string& name)
	{
		// return the first stat that matches 'name'
		stat_map_t::iterator iter = sStatList.find(name);
		if (iter != sStatList.end())
			return iter->second;
		else
			return NULL;
	}
};
	
#endif // LL_STAT_