/** 
 * @file llviewerassetstats.h
 * @brief Client-side collection of asset request statistics
 *
 * $LicenseInfo:firstyear=2010&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_LLVIEWERASSETSTATUS_H
#define	LL_LLVIEWERASSETSTATUS_H


#include "linden_common.h"

#include "llpointer.h"
#include "llrefcount.h"
#include "llviewerassettype.h"
#include "llviewerassetstorage.h"
#include "llsimplestat.h"
#include "llsd.h"
#include "llvoavatar.h"

/**
 * @class LLViewerAssetStats
 * @brief Records performance aspects of asset access operations.
 *
 * This facility is derived from a very similar simulator-based
 * one, LLSimAssetStats.  It's function is to count asset access
 * operations and characterize response times.  Collected data
 * are binned in several dimensions:
 *
 *  - Asset types collapsed into a few aggregated categories
 *  - By simulator UUID
 *  - By transport mechanism (HTTP vs MessageSystem)
 *  - By persistence (temp vs non-temp)
 *
 * Statistics collected are fairly basic at this point:
 *
 *  - Counts of enqueue and dequeue operations
 *  - Min/Max/Mean of asset transfer operations
 *
 * This collector differs from the simulator-based on in a
 * number of ways:
 *
 *  - The front-end/back-end distinction doesn't exist in viewer
 *    code
 *  - Multiple threads must be safely accomodated in the viewer
 *
 * Access to results is by conversion to an LLSD with some standardized
 * key names.  The intent of this structure is that it be emitted as
 * standard syslog-based metrics formatting where it can be picked
 * up by interested parties.
 *
 * For convenience, a set of free functions in namespace
 * LLViewerAssetStatsFF is provided for conditional test-and-call
 * operations.
 */
class LLViewerAssetStats
{
public:
	enum EViewerAssetCategories
	{
		EVACTextureTempHTTPGet,			//< Texture GETs - temp/baked, HTTP
		EVACTextureTempUDPGet,			//< Texture GETs - temp/baked, UDP
		EVACTextureNonTempHTTPGet,		//< Texture GETs - perm, HTTP
		EVACTextureNonTempUDPGet,		//< Texture GETs - perm, UDP
		EVACWearableUDPGet,				//< Wearable GETs
		EVACSoundUDPGet,				//< Sound GETs
		EVACGestureUDPGet,				//< Gesture GETs
		EVACOtherGet,					//< Other GETs
		
		EVACCount						// Must be last
	};

	/**
	 * Type for duration and other time values in the metrics.  Selected
	 * for compatibility with the pre-existing timestamp on the texture
	 * fetcher class, LLTextureFetch.
	 */
	typedef U64 duration_t;

	/**
	 * Type for the region identifier used in stats.  Currently uses
	 * the region handle's type (a U64) rather than the regions's LLUUID
	 * as the latter isn't available immediately.
	 */
	typedef U64 region_handle_t;

	/**
	 * @brief Collected data for a single region visited by the avatar.
	 *
	 * Fairly simple, for each asset bin enumerated above a count
	 * of enqueue and dequeue operations and simple stats on response
	 * times for completed requests.
	 */
	class PerRegionStats : public LLRefCount
	{
	public:
		PerRegionStats(const region_handle_t region_handle)
			: LLRefCount(),
			  mRegionHandle(region_handle)
			{
				reset();
			}

		PerRegionStats(const PerRegionStats & src)
			: LLRefCount(),
			  mRegionHandle(src.mRegionHandle),
			  mTotalTime(src.mTotalTime),
			  mStartTimestamp(src.mStartTimestamp),
			  mFPS(src.mFPS)
			{
				for (int i = 0; i < LL_ARRAY_SIZE(mRequests); ++i)
				{
					mRequests[i] = src.mRequests[i];
				}
			}

		// Default assignment and destructor are correct.
		
		void reset();

		void merge(const PerRegionStats & src);
		
		// Apply current running time to total and reset start point.
		// Return current timestamp as a convenience.
		void accumulateTime(duration_t now);
		
	public:
		region_handle_t		mRegionHandle;
		duration_t			mTotalTime;
		duration_t			mStartTimestamp;
		LLSimpleStatMMM<>	mFPS;
		
		struct prs_group
		{
			LLSimpleStatCounter			mEnqueued;
			LLSimpleStatCounter			mDequeued;
			LLSimpleStatMMM<duration_t>	mResponse;
		}
		mRequests [EVACCount];
	};

public:
	LLViewerAssetStats();
	LLViewerAssetStats(const LLViewerAssetStats &);
	// Default destructor is correct.
	LLViewerAssetStats & operator=(const LLViewerAssetStats &);			// Not defined

	// Clear all metrics data.  This leaves the currently-active region
	// in place but with zero'd data for all metrics.  All other regions
	// are removed from the collection map.
	void reset();

	// Set hidden region argument and establish context for subsequent
	// collection calls.
	void setRegion(region_handle_t region_handle);

	// Asset GET Requests
	void recordGetEnqueued(LLViewerAssetType::EType at, bool with_http, bool is_temp);
	void recordGetDequeued(LLViewerAssetType::EType at, bool with_http, bool is_temp);
	void recordGetServiced(LLViewerAssetType::EType at, bool with_http, bool is_temp, duration_t duration);

	// Frames-Per-Second Samples
	void recordFPS(F32 fps);

	// Avatar-related statistics
	void recordAvatarStats();

	// Merge a source instance into a destination instance.  This is
	// conceptually an 'operator+=()' method:
	// - counts are added
	// - minimums are min'd
	// - maximums are max'd
	// - other scalars are ignored ('this' wins)
	//
	void merge(const LLViewerAssetStats & src);
	
	// Retrieve current metrics for all visited regions (NULL region UUID/handle excluded)
    // Returned LLSD is structured as follows:
	//
	// &stats_group = {
	//   enqueued   : int,
	//   dequeued   : int,
	//   resp_count : int,
	//   resp_min   : float,
	//   resp_max   : float,
	//   resp_mean  : float
	// }
	//
	// &mmm_group = {
	//   count : int,
	//   min   : float,
	//   max   : float,
	//   mean  : float
	// }
	//
	// {
	//   duration: int
	//   regions: {
	//     $: {			// Keys are strings of the region's handle in hex
	//       duration:                 : int,
	//		 fps:					   : &mmm_group,
	//       get_texture_temp_http     : &stats_group,
	//       get_texture_temp_udp      : &stats_group,
	//       get_texture_non_temp_http : &stats_group,
	//       get_texture_non_temp_udp  : &stats_group,
	//       get_wearable_udp          : &stats_group,
	//       get_sound_udp             : &stats_group,
	//       get_gesture_udp           : &stats_group,
	//       get_other                 : &stats_group
	//     }
	//   }
	// }
	//
	// @param	compact_output		If true, omits from conversion any mmm_block
	//								or stats_block that would contain all zero data.
	//								Useful for transmission when the receiver knows
	//								what is expected and will assume zero for missing
	//								blocks.
	LLSD asLLSD(bool compact_output);

protected:
	typedef std::map<region_handle_t, LLPointer<PerRegionStats> > PerRegionContainer;

	// Region of the currently-active region.  Always valid but may
	// be zero after construction or when explicitly set.  Unchanged
	// by a reset() call.
	region_handle_t mRegionHandle;

	// Pointer to metrics collection for currently-active region.  Always
	// valid and unchanged after reset() though contents will be changed.
	// Always points to a collection contained in mRegionStats.
	LLPointer<PerRegionStats> mCurRegionStats;

	// Metrics data for all regions during one collection cycle
	PerRegionContainer mRegionStats;

	// Time of last reset
	duration_t mResetTimestamp;
};


/**
 * Global stats collectors one for each independent thread where
 * assets and other statistics are gathered.  The globals are
 * expected to be created at startup time and then picked up by
 * their respective threads afterwards.  A set of free functions
 * are provided to access methods behind the globals while both
 * minimally disrupting visual flow and supplying a description
 * of intent.
 *
 * Expected thread assignments:
 *
 *  - Main:  main() program execution thread
 *  - Thread1:  TextureFetch worker thread
 */
extern LLViewerAssetStats * gViewerAssetStatsMain;

extern LLViewerAssetStats * gViewerAssetStatsThread1;

namespace LLViewerAssetStatsFF
{
/**
 * @brief Allocation and deallocation of globals.
 *
 * init() should be called before threads are started that will access it though
 * you'll likely get away with calling it afterwards.  cleanup() should only be
 * called after threads are shutdown to prevent races on the global pointers.
 */
void init();

void cleanup();

/**
 * We have many timers, clocks etc. in the runtime.  This is the
 * canonical timestamp for these metrics which is compatible with
 * the pre-existing timestamping in the texture fetcher.
 */
inline LLViewerAssetStats::duration_t get_timestamp()
{
	return LLTimer::getTotalTime();
}

/**
 * Region context, event and duration loggers for the Main thread.
 */
void set_region_main(LLViewerAssetStats::region_handle_t region_handle);

void record_enqueue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp);

void record_dequeue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp);

void record_response_main(LLViewerAssetType::EType at, bool with_http, bool is_temp,
						  LLViewerAssetStats::duration_t duration);

void record_fps_main(F32 fps);

/**
 * Region context, event and duration loggers for Thread 1.
 */
void set_region_thread1(LLViewerAssetStats::region_handle_t region_handle);

void record_enqueue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp);

void record_dequeue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp);

void record_response_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp,
						  LLViewerAssetStats::duration_t duration);

} // namespace LLViewerAssetStatsFF

#endif // LL_LLVIEWERASSETSTATUS_H