diff options
Diffstat (limited to 'indra')
| -rw-r--r-- | indra/llcommon/llqueuedthread.h | 2 | ||||
| -rw-r--r-- | indra/llmessage/llassetstorage.cpp | 4 | ||||
| -rw-r--r-- | indra/newview/CMakeLists.txt | 12 | ||||
| -rw-r--r-- | indra/newview/llagent.cpp | 3 | ||||
| -rw-r--r-- | indra/newview/llappviewer.cpp | 123 | ||||
| -rw-r--r-- | indra/newview/llappviewer.h | 4 | ||||
| -rw-r--r-- | indra/newview/llsimplestat.h | 158 | ||||
| -rw-r--r-- | indra/newview/lltexturefetch.cpp | 669 | ||||
| -rw-r--r-- | indra/newview/lltexturefetch.h | 88 | ||||
| -rw-r--r-- | indra/newview/llviewerassetstats.cpp | 619 | ||||
| -rw-r--r-- | indra/newview/llviewerassetstats.h | 334 | ||||
| -rw-r--r-- | indra/newview/llviewerassetstorage.cpp | 129 | ||||
| -rw-r--r-- | indra/newview/llviewerassetstorage.h | 11 | ||||
| -rw-r--r-- | indra/newview/llviewerregion.cpp | 1 | ||||
| -rw-r--r-- | indra/newview/tests/llsimplestat_test.cpp | 586 | ||||
| -rw-r--r-- | indra/newview/tests/llviewerassetstats_test.cpp | 990 | 
16 files changed, 3706 insertions, 27 deletions
| diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index c75e0e2bbf..a53b22f6fc 100644 --- a/indra/llcommon/llqueuedthread.h +++ b/indra/llcommon/llqueuedthread.h @@ -179,7 +179,7 @@ public:  	void waitOnPending();  	void printQueueStats(); -	S32 getPending(); +	virtual S32 getPending();  	bool getThreaded() { return mThreaded ? true : false; }  	// Request accessors diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp index b26d412e9f..27a368df3d 100644 --- a/indra/llmessage/llassetstorage.cpp +++ b/indra/llmessage/llassetstorage.cpp @@ -513,6 +513,10 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, LL  } +// +// *NOTE:  Logic here is replicated in LLViewerAssetStorage::_queueDataRequest. +// Changes here may need to be replicated in the viewer's derived class. +//  void LLAssetStorage::_queueDataRequest(const LLUUID& uuid, LLAssetType::EType atype,  									   LLGetAssetCallback callback,  									   void *user_data, BOOL duplicate, diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 2dd32b7aa4..1176edc130 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -481,6 +481,7 @@ set(viewer_SOURCE_FILES      llvectorperfoptions.cpp      llversioninfo.cpp      llviewchildren.cpp +    llviewerassetstats.cpp      llviewerassetstorage.cpp      llviewerassettype.cpp      llviewerattachmenu.cpp @@ -1014,6 +1015,7 @@ set(viewer_HEADER_FILES      llvectorperfoptions.h      llversioninfo.h      llviewchildren.h +    llviewerassetstats.h      llviewerassetstorage.h      llviewerassettype.h      llviewerattachmenu.h @@ -1955,6 +1957,16 @@ if (LL_TESTS)      "${test_libs}"      ) +  LL_ADD_INTEGRATION_TEST(llsimplestat +	"" +    "${test_libs}" +    ) + +  LL_ADD_INTEGRATION_TEST(llviewerassetstats +	llviewerassetstats.cpp +    "${test_libs}" +    ) +    #ADD_VIEWER_BUILD_TEST(llmemoryview viewer)    #ADD_VIEWER_BUILD_TEST(llagentaccess viewer)    #ADD_VIEWER_BUILD_TEST(llworldmap viewer) diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 001a6a8851..ea3c2eb312 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -637,6 +637,9 @@ void LLAgent::setRegion(LLViewerRegion *regionp)  			// Update all of the regions.  			LLWorld::getInstance()->updateAgentOffset(mAgentOriginGlobal);  		} + +		// Pass new region along to metrics components that care about this level of detail. +		LLAppViewer::metricsUpdateRegion(regionp->getHandle());  	}  	mRegionp = regionp; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index b460885a53..5c0c5adabe 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -192,6 +192,7 @@  #include "llparcel.h"  #include "llavatariconctrl.h"  #include "llgroupiconctrl.h" +#include "llviewerassetstats.h"  // Include for security api initialization  #include "llsecapi.h" @@ -336,6 +337,14 @@ static std::string gWindowTitle;  LLAppViewer::LLUpdaterInfo *LLAppViewer::sUpdaterInfo = NULL ; +//---------------------------------------------------------------------------- +// Metrics logging control constants +//---------------------------------------------------------------------------- +static const F32 METRICS_INTERVAL_DEFAULT = 600.0; +static const F32 METRICS_INTERVAL_QA = 30.0; +static F32 app_metrics_interval = METRICS_INTERVAL_DEFAULT; +static bool app_metrics_qa_mode = false; +  void idle_afk_check()  {  	// check idle timers @@ -655,6 +664,21 @@ bool LLAppViewer::init()      // Called before threads are created.      LLCurl::initClass();      LLMachineID::init(); +	 +	{ +		// Viewer metrics initialization +		static LLCachedControl<bool> metrics_submode(gSavedSettings, +													 "QAModeMetrics", +													 false, +													 "Enables QA features (logging, faster cycling) for metrics collector"); + +		if (metrics_submode) +		{ +			app_metrics_qa_mode = true; +			app_metrics_interval = METRICS_INTERVAL_QA; +		} +		LLViewerAssetStatsFF::init(); +	}      initThreads();      writeSystemInfo(); @@ -1717,6 +1741,8 @@ bool LLAppViewer::cleanup()  	LLWatchdog::getInstance()->cleanup(); +	LLViewerAssetStatsFF::cleanup(); +	  	llinfos << "Shutting down message system" << llendflush;  	end_messaging_system(); @@ -1783,7 +1809,10 @@ bool LLAppViewer::initThreads()  	// Image decoding  	LLAppViewer::sImageDecodeThread = new LLImageDecodeThread(enable_threads && true);  	LLAppViewer::sTextureCache = new LLTextureCache(enable_threads && true); -	LLAppViewer::sTextureFetch = new LLTextureFetch(LLAppViewer::getTextureCache(), sImageDecodeThread, enable_threads && true); +	LLAppViewer::sTextureFetch = new LLTextureFetch(LLAppViewer::getTextureCache(), +													sImageDecodeThread, +													enable_threads && true, +													app_metrics_qa_mode);  	LLImage::initClass();  	if (LLFastTimer::sLog || LLFastTimer::sMetricLog) @@ -3045,6 +3074,9 @@ void LLAppViewer::requestQuit()  		return;  	} +	// Try to send metrics back to the grid +	metricsSend(!gDisconnected); +	  	LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral*)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, TRUE);  	effectp->setPositionGlobal(gAgent.getPositionGlobal());  	effectp->setColor(LLColor4U(gAgent.getEffectColor())); @@ -3822,6 +3854,11 @@ void LLAppViewer::idle()  				llinfos << "Unknown object updates: " << gObjectList.mNumUnknownUpdates << llendl;  				gObjectList.mNumUnknownUpdates = 0;  			} + +			// ViewerMetrics FPS piggy-backing on the debug timer. +			// The 5-second interval is nice for this purpose.  If the object debug +			// bit moves or is disabled, please give this a suitable home. +			LLViewerAssetStatsFF::record_fps_main(gFPSClamped);  		}  	} @@ -3864,6 +3901,18 @@ void LLAppViewer::idle()  		gInventory.idleNotifyObservers();  	} +	// Metrics logging (LLViewerAssetStats, etc.) +	{ +		static LLTimer report_interval; + +		// *TODO:  Add configuration controls for this +		if (report_interval.getElapsedTimeF32() >= app_metrics_interval) +		{ +			metricsSend(! gDisconnected); +			report_interval.reset(); +		} +	} +  	if (gDisconnected)      {  		return; @@ -4766,3 +4815,75 @@ bool LLAppViewer::getMasterSystemAudioMute()  {  	return gSavedSettings.getBOOL("MuteAudio");  } + +//---------------------------------------------------------------------------- +// Metrics-related methods (static and otherwise) +//---------------------------------------------------------------------------- + +/** + * LLViewerAssetStats collects data on a per-region (as defined by the agent's + * location) so we need to tell it about region changes which become a kind of + * hidden variable/global state in the collectors.  For collectors not running + * on the main thread, we need to send a message to move the data over safely + * and cheaply (amortized over a run). + */ +void LLAppViewer::metricsUpdateRegion(U64 region_handle) +{ +	if (0 != region_handle) +	{ +		LLViewerAssetStatsFF::set_region_main(region_handle); +		if (LLAppViewer::sTextureFetch) +		{ +			// Send a region update message into 'thread1' to get the new region. +			LLAppViewer::sTextureFetch->commandSetRegion(region_handle); +		} +		else +		{ +			// No 'thread1', a.k.a. TextureFetch, so update directly +			LLViewerAssetStatsFF::set_region_thread1(region_handle); +		} +	} +} + + +/** + * Attempts to start a multi-threaded metrics report to be sent back to + * the grid for consumption. + */ +void LLAppViewer::metricsSend(bool enable_reporting) +{ +	if (! gViewerAssetStatsMain) +		return; + +	if (LLAppViewer::sTextureFetch) +	{ +		LLViewerRegion * regionp = gAgent.getRegion(); + +		if (enable_reporting && regionp) +		{ +			std::string	caps_url = regionp->getCapability("ViewerMetrics"); + +			// Make a copy of the main stats to send into another thread. +			// Receiving thread takes ownership. +			LLViewerAssetStats * main_stats(new LLViewerAssetStats(*gViewerAssetStatsMain)); +			 +			// Send a report request into 'thread1' to get the rest of the data +			// and provide some additional parameters while here. +			LLAppViewer::sTextureFetch->commandSendMetrics(caps_url, +														   gAgentSessionID, +														   gAgentID, +														   main_stats); +			main_stats = 0;		// Ownership transferred +		} +		else +		{ +			LLAppViewer::sTextureFetch->commandDataBreak(); +		} +	} + +	// Reset even if we can't report.  Rather than gather up a huge chunk of +	// data, we'll keep to our sampling interval and retain the data +	// resolution in time. +	gViewerAssetStatsMain->reset(); +} + diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 7c946b04a5..a18e6cbb02 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -169,6 +169,10 @@ public:  	// mute/unmute the system's master audio  	virtual void setMasterSystemAudioMute(bool mute);  	virtual bool getMasterSystemAudioMute(); + +	// Metrics policy helper statics. +	static void metricsUpdateRegion(U64 region_handle); +	static void metricsSend(bool enable_reporting);  protected:  	virtual bool initWindow(); // Initialize the viewer's window. diff --git a/indra/newview/llsimplestat.h b/indra/newview/llsimplestat.h new file mode 100644 index 0000000000..a90e503adb --- /dev/null +++ b/indra/newview/llsimplestat.h @@ -0,0 +1,158 @@ +/**  + * @file llsimplestat.h + * @brief Runtime statistics accumulation. + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + *  + * Copyright (c) 2010, 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$ + */ + +#ifndef LL_SIMPLESTAT_H +#define LL_SIMPLESTAT_H + +// History +// +// The original source for this code is the server repositories' +// llcommon/llstat.h file.  This particular code was added after the +// viewer/server code schism but before the effort to convert common +// code to libraries was complete.  Rather than add to merge issues, +// the needed code was cut'n'pasted into this new header as it isn't +// too awful a burden.  Post-modularization, we can look at removing +// this redundancy. + + +/** + * @class LLSimpleStatCounter + * @brief Just counts events. + * + * Really not needed but have a pattern in mind in the future. + * Interface limits what can be done at that's just fine. + * + * *TODO:  Update/transfer unit tests + * Unit tests:  indra/test/llcommon_llstat_tut.cpp + */ +class LLSimpleStatCounter +{ +public: +	inline LLSimpleStatCounter()		{ reset(); } +	// Default destructor and assignment operator are valid + +	inline void reset()					{ mCount = 0; } + +	inline void merge(const LLSimpleStatCounter & src) +										{ mCount += src.mCount; } +	 +	inline U32 operator++()				{ return ++mCount; } + +	inline U32 getCount() const			{ return mCount; } +		 +protected: +	U32			mCount; +}; + + +/** + * @class LLSimpleStatMMM + * @brief Templated collector of min, max and mean data for stats. + * + * Fed a stream of data samples, keeps a running account of the + * min, max and mean seen since construction or the last reset() + * call.  A freshly-constructed or reset instance returns counts + * and values of zero. + * + * Overflows and underflows (integer, inf or -inf) and NaN's + * are the caller's problem.  As is loss of precision when + * the running sum's exponent (when parameterized by a floating + * point of some type) differs from a given data sample's. + * + * Unit tests:  indra/test/llcommon_llstat_tut.cpp + */ +template <typename VALUE_T = F32> +class LLSimpleStatMMM +{ +public: +	typedef VALUE_T Value; +	 +public: +	LLSimpleStatMMM()				{ reset(); } +	// Default destructor and assignment operator are valid + +	/** +	 * Resets the object returning all counts and derived +	 * values back to zero. +	 */ +	void reset() +		{ +			mCount = 0; +			mMin = Value(0); +			mMax = Value(0); +			mTotal = Value(0); +		} + +	void record(Value v) +		{ +			if (mCount) +			{ +				mMin = llmin(mMin, v); +				mMax = llmax(mMax, v); +			} +			else +			{ +				mMin = v; +				mMax = v; +			} +			mTotal += v; +			++mCount; +		} + +	void merge(const LLSimpleStatMMM<VALUE_T> & src) +		{ +			if (! mCount) +			{ +				*this = src; +			} +			else if (src.mCount) +			{ +				mMin = llmin(mMin, src.mMin); +				mMax = llmax(mMax, src.mMax); +				mCount += src.mCount; +				mTotal += src.mTotal; +			} +		} +	 +	inline U32 getCount() const		{ return mCount; } +	inline Value getMin() const		{ return mMin; } +	inline Value getMax() const		{ return mMax; } +	inline Value getMean() const	{ return mCount ? mTotal / mCount : mTotal; } +		 +protected: +	U32			mCount; +	Value		mMin; +	Value		mMax; +	Value		mTotal; +}; + +#endif // LL_SIMPLESTAT_H diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 13fd51f473..4f63abb152 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -27,6 +27,7 @@  #include "llviewerprecompiledheaders.h"  #include <iostream> +#include <map>  #include "llstl.h" @@ -49,6 +50,7 @@  #include "llviewertexture.h"  #include "llviewerregion.h"  #include "llviewerstats.h" +#include "llviewerassetstats.h"  #include "llworld.h"  ////////////////////////////////////////////////////////////////////////////// @@ -143,7 +145,7 @@ public:  	/*virtual*/ bool deleteOK(); // called from update() (WORK THREAD)  	~LLTextureFetchWorker(); -	void relese() { --mActiveCount; } +	// void relese() { --mActiveCount; }  	S32 callbackHttpGet(const LLChannelDescriptors& channels,  						 const LLIOPipe::buffer_ptr_t& buffer, @@ -161,9 +163,11 @@ public:  		mGetReason = reason;  	} -	void setCanUseHTTP(bool can_use_http) {mCanUseHTTP = can_use_http;} -	bool getCanUseHTTP()const {return mCanUseHTTP ;} +	void setCanUseHTTP(bool can_use_http) { mCanUseHTTP = can_use_http; } +	bool getCanUseHTTP() const { return mCanUseHTTP; } +	LLTextureFetch & getFetcher() { return *mFetcher; } +	  protected:  	LLTextureFetchWorker(LLTextureFetch* fetcher, const std::string& url, const LLUUID& id, const LLHost& host,  						 F32 priority, S32 discard, S32 size); @@ -277,6 +281,8 @@ private:  	S32 mLastPacket;  	U16 mTotalPackets;  	U8 mImageCodec; + +	LLViewerAssetStats::duration_t mMetricsStartTime;  };  ////////////////////////////////////////////////////////////////////////////// @@ -344,6 +350,18 @@ public:  			}  			mFetcher->removeFromHTTPQueue(mID, data_size); + +			if (worker->mMetricsStartTime) +			{ +				LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE, +															  true, +															  LLImageBase::TYPE_AVATAR_BAKE == worker->mType, +															  LLViewerAssetStatsFF::get_timestamp() - worker->mMetricsStartTime); +				worker->mMetricsStartTime = 0; +			} +			LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE, +														 true, +														 LLImageBase::TYPE_AVATAR_BAKE == worker->mType);  		}  		else  		{ @@ -368,6 +386,229 @@ private:  ////////////////////////////////////////////////////////////////////////////// +// Cross-thread messaging for asset metrics. + +/** + * @brief Base class for cross-thread requests made of the fetcher + * + * I believe the intent of the LLQueuedThread class was to + * have these operations derived from LLQueuedThread::QueuedRequest + * but the texture fetcher has elected to manage the queue + * in its own manner.  So these are free-standing objects which are + * managed in simple FIFO order on the mCommands queue of the + * LLTextureFetch object. + * + * What each represents is a simple command sent from an + * outside thread into the TextureFetch thread to be processed + * in order and in a timely fashion (though not an absolute + * higher priority than other operations of the thread). + * Each operation derives a new class from the base customizing + * members, constructors and the doWork() method to effect + * the command. + * + * The flow is one-directional.  There are two global instances + * of the LLViewerAssetStats collector, one for the main program's + * thread pointed to by gViewerAssetStatsMain and one for the + * TextureFetch thread pointed to by gViewerAssetStatsThread1. + * Common operations has each thread recording metrics events + * into the respective collector unconcerned with locking and + * the state of any other thread.  But when the agent moves into + * a different region or the metrics timer expires and a report + * needs to be sent back to the grid, messaging across threads + * is required to distribute data and perform global actions. + * In pseudo-UML, it looks like: + * + *                       Main                 Thread1 + *                        .                      . + *                        .                      . + *                     +-----+                   . + *                     | AM  |                   . + *                     +--+--+                   . + *      +-------+         |                      . + *      | Main  |      +--+--+                   . + *      |       |      | SRE |---.               . + *      | Stats |      +-----+    \              . + *      |       |         |        \  (uuid)  +-----+ + *      | Coll. |      +--+--+      `-------->| SR  | + *      +-------+      | MSC |                +--+--+ + *         | ^         +-----+                   | + *         | |  (uuid)  / .                   +-----+ (uuid) + *         |  `--------'  .                   | MSC |---------. + *         |              .                   +-----+         | + *         |           +-----+                   .            v + *         |           | TE  |                   .        +-------+ + *         |           +--+--+                   .        | Thd1  | + *         |              |                      .        |       | + *         |           +-----+                   .        | Stats | + *          `--------->| RSC |                   .        |       | + *                     +--+--+                   .        | Coll. | + *                        |                      .        +-------+ + *                     +--+--+                   .            | + *                     | SME |---.               .            | + *                     +-----+    \              .            | + *                        .        \ (clone)  +-----+         | + *                        .         `-------->| SM  |         | + *                        .                   +--+--+         | + *                        .                      |            | + *                        .                   +-----+         | + *                        .                   | RSC |<--------' + *                        .                   +-----+ + *                        .                      | + *                        .                   +-----+ + *                        .                   | CP  |--> HTTP POST + *                        .                   +-----+ + *                        .                      . + *                        .                      . + * + * + * Key: + * + * SRE - Set Region Enqueued.  Enqueue a 'Set Region' command in + *       the other thread providing the new UUID of the region. + *       TFReqSetRegion carries the data. + * SR  - Set Region.  New region UUID is sent to the thread-local + *       collector. + * SME - Send Metrics Enqueued.  Enqueue a 'Send Metrics' command + *       including an ownership transfer of a cloned LLViewerAssetStats. + *       TFReqSendMetrics carries the data. + * SM  - Send Metrics.  Global metrics reporting operation.  Takes + *       the cloned stats from the command, merges it with the + *       thread's local stats, converts to LLSD and sends it on + *       to the grid. + * AM  - Agent Moved.  Agent has completed some sort of move to a + *       new region. + * TE  - Timer Expired.  Metrics timer has expired (on the order + *       of 10 minutes). + * CP  - CURL Post + * MSC - Modify Stats Collector.  State change in the thread-local + *       collector.  Typically a region change which affects the + *       global pointers used to find the 'current stats'. + * RSC - Read Stats Collector.  Extract collector data cloning it + *       (i.e. deep copy) when necessary. + * + */ +class LLTextureFetch::TFRequest // : public LLQueuedThread::QueuedRequest +{ +public: +	// Default ctors and assignment operator are correct. + +	virtual ~TFRequest() +		{} + +	// Patterned after QueuedRequest's method but expected behavior +	// is different.  Always expected to complete on the first call +	// and work dispatcher will assume the same and delete the +	// request after invocation. +	virtual bool doWork(LLTextureFetch * fetcher) = 0; +}; + +namespace  +{ + +/** + * @brief Implements a 'Set Region' cross-thread command. + * + * When an agent moves to a new region, subsequent metrics need + * to be binned into a new or existing stats collection in 1:1 + * relationship with the region.  We communicate this region + * change across the threads involved in the communication with + * this message. + * + * Corresponds to LLTextureFetch::commandSetRegion() + */ +class TFReqSetRegion : public LLTextureFetch::TFRequest +{ +public: +	TFReqSetRegion(U64 region_handle) +		: LLTextureFetch::TFRequest(), +		  mRegionHandle(region_handle) +		{} +	TFReqSetRegion & operator=(const TFReqSetRegion &);	// Not defined + +	virtual ~TFReqSetRegion() +		{} + +	virtual bool doWork(LLTextureFetch * fetcher); +		 +public: +	const U64 mRegionHandle; +}; + + +/** + * @brief Implements a 'Send Metrics' cross-thread command. + * + * This is the big operation.  The main thread gathers metrics + * for a period of minutes into LLViewerAssetStats and other + * objects then makes a snapshot of the data by cloning the + * collector.  This command transfers the clone, along with a few + * additional arguments (UUIDs), handing ownership to the + * TextureFetch thread.  It then merges its own data into the + * cloned copy, converts to LLSD and kicks off an HTTP POST of + * the resulting data to the currently active metrics collector. + * + * Corresponds to LLTextureFetch::commandSendMetrics() + */ +class TFReqSendMetrics : public LLTextureFetch::TFRequest +{ +public: +    /** +	 * Construct the 'Send Metrics' command to have the TextureFetch +	 * thread add and log metrics data. +	 * +	 * @param	caps_url		URL of a "ViewerMetrics" Caps target +	 *							to receive the data.  Does not have to +	 *							be associated with a particular region. +	 * +	 * @param	session_id		UUID of the agent's session. +	 * +	 * @param	agent_id		UUID of the agent.  (Being pure here...) +	 * +	 * @param	main_stats		Pointer to a clone of the main thread's +	 *							LLViewerAssetStats data.  Thread1 takes +	 *							ownership of the copy and disposes of it +	 *							when done. +	 */ +	TFReqSendMetrics(const std::string & caps_url, +					 const LLUUID & session_id, +					 const LLUUID & agent_id, +					 LLViewerAssetStats * main_stats) +		: LLTextureFetch::TFRequest(), +		  mCapsURL(caps_url), +		  mSessionID(session_id), +		  mAgentID(agent_id), +		  mMainStats(main_stats) +		{} +	TFReqSendMetrics & operator=(const TFReqSendMetrics &);	// Not defined + +	virtual ~TFReqSendMetrics(); + +	virtual bool doWork(LLTextureFetch * fetcher); +		 +public: +	const std::string mCapsURL; +	const LLUUID mSessionID; +	const LLUUID mAgentID; +	LLViewerAssetStats * mMainStats; +}; + +/* + * Examines the merged viewer metrics report and if found to be too long, + * will attempt to truncate it in some reasonable fashion. + * + * @param		max_regions		Limit of regions allowed in report. + * + * @param		metrics			Full, merged viewer metrics report. + * + * @returns		If data was truncated, returns true. + */ +bool truncate_viewer_metrics(int max_regions, LLSD & metrics); + +} // end of anonymous namespace + + +////////////////////////////////////////////////////////////////////////////// +  //static  const char* LLTextureFetchWorker::sStateDescs[] = {  	"INVALID", @@ -385,6 +626,9 @@ const char* LLTextureFetchWorker::sStateDescs[] = {  	"DONE",  }; +// static +volatile bool LLTextureFetch::svMetricsDataBreak(true);	// Start with a data break +  // called from MAIN THREAD  LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher, @@ -434,7 +678,8 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher,  	  mFirstPacket(0),  	  mLastPacket(-1),  	  mTotalPackets(0), -	  mImageCodec(IMG_CODEC_INVALID) +	  mImageCodec(IMG_CODEC_INVALID), +	  mMetricsStartTime(0)  {  	mCanUseNET = mUrl.empty() ; @@ -602,6 +847,7 @@ bool LLTextureFetchWorker::doWork(S32 param)  			return true; // abort  		}  	} +  	if(mImagePriority < F_ALMOST_ZERO)  	{  		if (mState == INIT || mState == LOAD_FROM_NETWORK || mState == LOAD_FROM_SIMULATOR) @@ -811,7 +1057,15 @@ bool LLTextureFetchWorker::doWork(S32 param)  			mRequestedDiscard = mDesiredDiscard;  			mSentRequest = QUEUED;  			mFetcher->addToNetworkQueue(this); +			if (! mMetricsStartTime) +			{ +				mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); +			} +			LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, +														 false, +														 LLImageBase::TYPE_AVATAR_BAKE == mType);  			setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); +			  			return false;  		}  		else @@ -820,6 +1074,12 @@ bool LLTextureFetchWorker::doWork(S32 param)  			//llassert_always(mFetcher->mNetworkQueue.find(mID) != mFetcher->mNetworkQueue.end());  			// Make certain this is in the network queue  			//mFetcher->addToNetworkQueue(this); +			//if (! mMetricsStartTime) +			//{ +			//   mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); +			//} +			//LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, false, +			//                                             LLImageBase::TYPE_AVATAR_BAKE == mType);  			//setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);  			return false;  		} @@ -843,11 +1103,30 @@ bool LLTextureFetchWorker::doWork(S32 param)  			}  			setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);  			mState = DECODE_IMAGE; -			mWriteToCacheState = SHOULD_WRITE ; +			mWriteToCacheState = SHOULD_WRITE; + +			if (mMetricsStartTime) +			{ +				LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE, +															  false, +															  LLImageBase::TYPE_AVATAR_BAKE == mType, +															  LLViewerAssetStatsFF::get_timestamp() - mMetricsStartTime); +				mMetricsStartTime = 0; +			} +			LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE, +														 false, +														 LLImageBase::TYPE_AVATAR_BAKE == mType);  		}  		else  		{  			mFetcher->addToNetworkQueue(this); // failsafe +			if (! mMetricsStartTime) +			{ +				mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); +			} +			LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, +														 false, +														 LLImageBase::TYPE_AVATAR_BAKE == mType);  			setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);  		}  		return false; @@ -909,6 +1188,14 @@ bool LLTextureFetchWorker::doWork(S32 param)  				mState = WAIT_HTTP_REQ;	  				mFetcher->addToHTTPQueue(mID); +				if (! mMetricsStartTime) +				{ +					mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); +				} +				LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, +															 true, +															 LLImageBase::TYPE_AVATAR_BAKE == mType); +  				// Will call callbackHttpGet when curl request completes  				std::vector<std::string> headers;  				headers.push_back("Accept: image/x-j2c"); @@ -1523,7 +1810,7 @@ bool LLTextureFetchWorker::writeToCacheComplete()  //////////////////////////////////////////////////////////////////////////////  // public -LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded) +LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded, bool qa_mode)  	: LLWorkerThread("TextureFetch", threaded),  	  mDebugCount(0),  	  mDebugPause(FALSE), @@ -1535,8 +1822,10 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image  	  mImageDecodeThread(imagedecodethread),  	  mTextureBandwidth(0),  	  mHTTPTextureBits(0), -	  mCurlGetRequest(NULL) +	  mCurlGetRequest(NULL), +	  mQAMode(qa_mode)  { +	mCurlPOSTRequestCount = 0;  	mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS");  	mTextureInfo.setUpLogging(gSavedSettings.getBOOL("LogTextureDownloadsToViewerLog"), gSavedSettings.getBOOL("LogTextureDownloadsToSimulator"), gSavedSettings.getU32("TextureLoggingThreshold"));  } @@ -1545,6 +1834,13 @@ LLTextureFetch::~LLTextureFetch()  {  	clearDeleteList() ; +	while (! mCommands.empty()) +	{ +		TFRequest * req(mCommands.front()); +		mCommands.erase(mCommands.begin()); +		delete req; +	} +	  	// ~LLQueuedThread() called here  } @@ -1825,8 +2121,76 @@ bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority)  	return res;  } +// Replicates and expands upon the base class's +// getPending() implementation.  getPending() and +// runCondition() replicate one another's logic to +// an extent and are sometimes used for the same +// function (deciding whether or not to sleep/pause +// a thread).  So the implementations need to stay +// in step, at least until this can be refactored and +// the redundancy eliminated. +// +// May be called from any thread + +//virtual +S32 LLTextureFetch::getPending() +{ +	S32 res; +	lockData(); +    { +        LLMutexLock lock(&mQueueMutex); +         +        res = mRequestQueue.size(); +        res += mCurlPOSTRequestCount; +        res += mCommands.size(); +    } +	unlockData(); +	return res; +} + +// virtual +bool LLTextureFetch::runCondition() +{ +	// Caller is holding the lock on LLThread's condition variable. +	 +	// LLQueuedThread, unlike its base class LLThread, makes this a +	// private method which is unfortunate.  I want to use it directly +	// but I'm going to have to re-implement the logic here (or change +	// declarations, which I don't want to do right now). +	// +	// Changes here may need to be reflected in getPending(). +	 +	bool have_no_commands(false); +	{ +		LLMutexLock lock(&mQueueMutex); +		 +		have_no_commands = mCommands.empty(); +	} +	 +    bool have_no_curl_requests(0 == mCurlPOSTRequestCount); +	 +	return ! (have_no_commands +			  && have_no_curl_requests +			  && (mRequestQueue.empty() && mIdleThread));		// From base class +} +  ////////////////////////////////////////////////////////////////////////////// +// MAIN THREAD (unthreaded envs), WORKER THREAD (threaded envs) +void LLTextureFetch::commonUpdate() +{ +	// Run a cross-thread command, if any. +	cmdDoWork(); +	 +	// Update Curl on same thread as mCurlGetRequest was constructed +	S32 processed = mCurlGetRequest->process(); +	if (processed > 0) +	{ +		lldebugs << "processed: " << processed << " messages." << llendl; +	} +} + +  // MAIN THREAD  //virtual  S32 LLTextureFetch::update(U32 max_time_ms) @@ -1852,12 +2216,7 @@ S32 LLTextureFetch::update(U32 max_time_ms)  	if (!mThreaded)  	{ -		// Update Curl on same thread as mCurlGetRequest was constructed -		S32 processed = mCurlGetRequest->process(); -		if (processed > 0) -		{ -			lldebugs << "processed: " << processed << " messages." << llendl; -		} +		commonUpdate();  	}  	return res; @@ -1912,12 +2271,7 @@ void LLTextureFetch::threadedUpdate()  	}  	process_timer.reset(); -	// Update Curl on same thread as mCurlGetRequest was constructed -	S32 processed = mCurlGetRequest->process(); -	if (processed > 0) -	{ -		lldebugs << "processed: " << processed << " messages." << llendl; -	} +	commonUpdate();  #if 0  	const F32 INFO_TIME = 1.0f;  @@ -2367,3 +2721,280 @@ void LLTextureFetch::dump()  	}  } +////////////////////////////////////////////////////////////////////////////// + +// cross-thread command methods + +void LLTextureFetch::commandSetRegion(U64 region_handle) +{ +	TFReqSetRegion * req = new TFReqSetRegion(region_handle); + +	cmdEnqueue(req); +} + +void LLTextureFetch::commandSendMetrics(const std::string & caps_url, +										const LLUUID & session_id, +										const LLUUID & agent_id, +										LLViewerAssetStats * main_stats) +{ +	TFReqSendMetrics * req = new TFReqSendMetrics(caps_url, session_id, agent_id, main_stats); + +	cmdEnqueue(req); +} + +void LLTextureFetch::commandDataBreak() +{ +	// The pedantically correct way to implement this is to create a command +	// request object in the above fashion and enqueue it.  However, this is +	// simple data of an advisorial not operational nature and this case +	// of shared-write access is tolerable. + +	LLTextureFetch::svMetricsDataBreak = true; +} + +void LLTextureFetch::cmdEnqueue(TFRequest * req) +{ +	lockQueue(); +	mCommands.push_back(req); +	unlockQueue(); + +	unpause(); +} + +LLTextureFetch::TFRequest * LLTextureFetch::cmdDequeue() +{ +	TFRequest * ret = 0; +	 +	lockQueue(); +	if (! mCommands.empty()) +	{ +		ret = mCommands.front(); +		mCommands.erase(mCommands.begin()); +	} +	unlockQueue(); + +	return ret; +} + +void LLTextureFetch::cmdDoWork() +{ +	if (mDebugPause) +	{ +		return;  // debug: don't do any work +	} + +	TFRequest * req = cmdDequeue(); +	if (req) +	{ +		// One request per pass should really be enough for this. +		req->doWork(this); +		delete req; +	} +} + + +////////////////////////////////////////////////////////////////////////////// + +// Private (anonymous) class methods implementing the command scheme. + +namespace +{ + +/** + * Implements the 'Set Region' command. + * + * Thread:  Thread1 (TextureFetch) + */ +bool +TFReqSetRegion::doWork(LLTextureFetch *) +{ +	LLViewerAssetStatsFF::set_region_thread1(mRegionHandle); + +	return true; +} + + +TFReqSendMetrics::~TFReqSendMetrics() +{ +	delete mMainStats; +	mMainStats = 0; +} + + +/** + * Implements the 'Send Metrics' command.  Takes over + * ownership of the passed LLViewerAssetStats pointer. + * + * Thread:  Thread1 (TextureFetch) + */ +bool +TFReqSendMetrics::doWork(LLTextureFetch * fetcher) +{ +	/* +	 * HTTP POST responder.  Doesn't do much but tries to +	 * detect simple breaks in recording the metrics stream. +	 * +	 * The 'volatile' modifiers don't indicate signals, +	 * mmap'd memory or threads, really.  They indicate that +	 * the referenced data is part of a pseudo-closure for +	 * this responder rather than being required for correct +	 * operation. +     * +     * We don't try very hard with the POST request.  We give +     * it one shot and that's more-or-less it.  With a proper +     * refactoring of the LLQueuedThread usage, these POSTs +     * could be put in a request object and made more reliable. +	 */ +	class lcl_responder : public LLCurl::Responder +	{ +	public: +		lcl_responder(LLTextureFetch * fetcher, +					  S32 expected_sequence, +                      volatile const S32 & live_sequence, +                      volatile bool & reporting_break, +					  volatile bool & reporting_started) +			: LLCurl::Responder(), +			  mFetcher(fetcher), +              mExpectedSequence(expected_sequence), +              mLiveSequence(live_sequence), +			  mReportingBreak(reporting_break), +			  mReportingStarted(reporting_started) +			{ +                mFetcher->incrCurlPOSTCount(); +            } +         +        ~lcl_responder() +            { +                mFetcher->decrCurlPOSTCount(); +            } + +		// virtual +		void error(U32 status_num, const std::string & reason) +			{ +                if (mLiveSequence == mExpectedSequence) +                { +                    mReportingBreak = true; +                } +				LL_WARNS("Texture") << "Break in metrics stream due to POST failure to metrics collection service.  Reason:  " +									<< reason << LL_ENDL; +			} + +		// virtual +		void result(const LLSD & content) +			{ +                if (mLiveSequence == mExpectedSequence) +                { +                    mReportingBreak = false; +                    mReportingStarted = true; +                } +			} + +	private: +		LLTextureFetch * mFetcher; +        S32 mExpectedSequence; +        volatile const S32 & mLiveSequence; +		volatile bool & mReportingBreak; +		volatile bool & mReportingStarted; + +	}; // class lcl_responder +	 +	if (! gViewerAssetStatsThread1) +		return true; + +	static volatile bool reporting_started(false); +	static volatile S32 report_sequence(0); +     +	// We've taken over ownership of the stats copy at this +	// point.  Get a working reference to it for merging here +	// but leave it in 'this'.  Destructor will rid us of it. +	LLViewerAssetStats & main_stats = *mMainStats; + +	// Merge existing stats into those from main, convert to LLSD +	main_stats.merge(*gViewerAssetStatsThread1); +	LLSD merged_llsd = main_stats.asLLSD(true); + +	// Add some additional meta fields to the content +	merged_llsd["session_id"] = mSessionID; +	merged_llsd["agent_id"] = mAgentID; +	merged_llsd["message"] = "ViewerAssetMetrics";					// Identifies the type of metrics +	merged_llsd["sequence"] = report_sequence;						// Sequence number +	merged_llsd["initial"] = ! reporting_started;					// Initial data from viewer +	merged_llsd["break"] = LLTextureFetch::svMetricsDataBreak;		// Break in data prior to this report +		 +	// Update sequence number +	if (S32_MAX == ++report_sequence) +		report_sequence = 0; + +	// Limit the size of the stats report if necessary. +	merged_llsd["truncated"] = truncate_viewer_metrics(10, merged_llsd); + +	if (! mCapsURL.empty()) +	{ +		LLCurlRequest::headers_t headers; +		fetcher->getCurlRequest().post(mCapsURL, +									   headers, +									   merged_llsd, +									   new lcl_responder(fetcher, +														 report_sequence, +                                                         report_sequence, +                                                         LLTextureFetch::svMetricsDataBreak, +														 reporting_started)); +	} +	else +	{ +		LLTextureFetch::svMetricsDataBreak = true; +	} + +	// In QA mode, Metrics submode, log the result for ease of testing +	if (fetcher->isQAMode()) +	{ +		LL_INFOS("Textures") << merged_llsd << LL_ENDL; +	} + +	gViewerAssetStatsThread1->reset(); + +	return true; +} + + +bool +truncate_viewer_metrics(int max_regions, LLSD & metrics) +{ +	static const LLSD::String reg_tag("regions"); +	static const LLSD::String duration_tag("duration"); +	 +	LLSD & reg_map(metrics[reg_tag]); +	if (reg_map.size() <= max_regions) +	{ +		return false; +	} + +	// Build map of region hashes ordered by duration +	typedef std::multimap<LLSD::Real, int> reg_ordered_list_t; +	reg_ordered_list_t regions_by_duration; + +	int ind(0); +	LLSD::array_const_iterator it_end(reg_map.endArray()); +	for (LLSD::array_const_iterator it(reg_map.beginArray()); it_end != it; ++it, ++ind) +	{ +		LLSD::Real duration = (*it)[duration_tag].asReal(); +		regions_by_duration.insert(reg_ordered_list_t::value_type(duration, ind)); +	} + +	// Build a replacement regions array with the longest-persistence regions +	LLSD new_region(LLSD::emptyArray()); +	reg_ordered_list_t::const_reverse_iterator it2_end(regions_by_duration.rend()); +	reg_ordered_list_t::const_reverse_iterator it2(regions_by_duration.rbegin()); +	for (int i(0); i < max_regions && it2_end != it2; ++i, ++it2) +	{ +		new_region.append(reg_map[it2->second]); +	} +	reg_map = new_region; +	 +	return true; +} + +} // end of anonymous namespace + + + diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index 796109df06..ad00a7ea36 100644 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -33,6 +33,7 @@  #include "llworkerthread.h"  #include "llcurl.h"  #include "lltextureinfo.h" +#include "llapr.h"  class LLViewerTexture;  class LLTextureFetchWorker; @@ -40,6 +41,7 @@ class HTTPGetResponder;  class LLTextureCache;  class LLImageDecodeThread;  class LLHost; +class LLViewerAssetStats;  // Interface class  class LLTextureFetch : public LLWorkerThread @@ -48,9 +50,11 @@ class LLTextureFetch : public LLWorkerThread  	friend class HTTPGetResponder;  public: -	LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded); +	LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded, bool qa_mode);  	~LLTextureFetch(); +	class TFRequest; +	  	/*virtual*/ S32 update(U32 max_time_ms);	  	void shutDownTextureCacheThread() ; //called in the main thread after the TextureCacheThread shuts down.  	void shutDownImageDecodeThread() ;  //called in the main thread after the ImageDecodeThread shuts down. @@ -77,28 +81,77 @@ public:  	S32 getNumHTTPRequests() ;  	// Public for access by callbacks +    S32 getPending();  	void lockQueue() { mQueueMutex.lock(); }  	void unlockQueue() { mQueueMutex.unlock(); }  	LLTextureFetchWorker* getWorker(const LLUUID& id);  	LLTextureFetchWorker* getWorkerAfterLock(const LLUUID& id);  	LLTextureInfo* getTextureInfo() { return &mTextureInfo; } -	 + +	// Commands available to other threads to control metrics gathering operations. +	void commandSetRegion(U64 region_handle); +	void commandSendMetrics(const std::string & caps_url, +							const LLUUID & session_id, +							const LLUUID & agent_id, +							LLViewerAssetStats * main_stats); +	void commandDataBreak(); + +	LLCurlRequest & getCurlRequest()	{ return *mCurlGetRequest; } + +	bool isQAMode() const				{ return mQAMode; } + +	// Curl POST counter maintenance +	inline void incrCurlPOSTCount()		{ mCurlPOSTRequestCount++; } +	inline void decrCurlPOSTCount()		{ mCurlPOSTRequestCount--; } +  protected:  	void addToNetworkQueue(LLTextureFetchWorker* worker);  	void removeFromNetworkQueue(LLTextureFetchWorker* worker, bool cancel);  	void addToHTTPQueue(const LLUUID& id);  	void removeFromHTTPQueue(const LLUUID& id, S32 received_size = 0);  	void removeRequest(LLTextureFetchWorker* worker, bool cancel); -	// Called from worker thread (during doWork) -	void processCurlRequests();	 + +	// Overrides from the LLThread tree +	bool runCondition();  private:  	void sendRequestListToSimulators();  	/*virtual*/ void startThread(void);  	/*virtual*/ void endThread(void);  	/*virtual*/ void threadedUpdate(void); - +	void commonUpdate(); + +	// Metrics command helpers +	/** +	 * Enqueues a command request at the end of the command queue +	 * and wakes up the thread as needed. +	 * +	 * Takes ownership of the TFRequest object. +	 * +	 * Method locks the command queue. +	 */ +	void cmdEnqueue(TFRequest *); + +	/** +	 * Returns the first TFRequest object in the command queue or +	 * NULL if none is present. +	 * +	 * Caller acquires ownership of the object and must dispose of it. +	 * +	 * Method locks the command queue. +	 */ +	TFRequest * cmdDequeue(); + +	/** +	 * Processes the first command in the queue disposing of the +	 * request on completion.  Successive calls are needed to perform +	 * additional commands. +	 * +	 * Method locks the command queue. +	 */ +	void cmdDoWork(); +	  public:  	LLUUID mDebugID;  	S32 mDebugCount; @@ -107,7 +160,7 @@ public:  	S32 mBadPacketCount;  private: -	LLMutex mQueueMutex;        //to protect mRequestMap only +	LLMutex mQueueMutex;        //to protect mRequestMap and mCommands only  	LLMutex mNetworkQueueMutex; //to protect mNetworkQueue, mHTTPTextureQueue and mCancelQueue.  	LLTextureCache* mTextureCache; @@ -129,6 +182,29 @@ private:  	LLTextureInfo mTextureInfo;  	U32 mHTTPTextureBits; + +	// Out-of-band cross-thread command queue.  This command queue +	// is logically tied to LLQueuedThread's list of +	// QueuedRequest instances and so must be covered by the +	// same locks. +	typedef std::vector<TFRequest *> command_queue_t; +	command_queue_t mCommands; + +	// If true, modifies some behaviors that help with QA tasks. +	const bool mQAMode; + +	// Count of POST requests outstanding.  We maintain the count +	// indirectly in the CURL request responder's ctor and dtor and +	// use it when determining whether or not to sleep the thread.  Can't +	// use the LLCurl module's request counter as it isn't thread compatible. +	// *NOTE:  Don't mix Atomic and static, apr_initialize must be called first. +	LLAtomic32<S32> mCurlPOSTRequestCount; +	 +public: +	// A probabilistically-correct indicator that the current +	// attempt to log metrics follows a break in the metrics stream +	// reporting due to either startup or a problem POSTing data. +	static volatile bool svMetricsDataBreak;  };  #endif // LL_LLTEXTUREFETCH_H diff --git a/indra/newview/llviewerassetstats.cpp b/indra/newview/llviewerassetstats.cpp new file mode 100644 index 0000000000..5ad7725b3e --- /dev/null +++ b/indra/newview/llviewerassetstats.cpp @@ -0,0 +1,619 @@ +/**  + * @file llviewerassetstats.cpp + * @brief  + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + *  + * Copyright (c) 2010, 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 "llviewerprecompiledheaders.h" + +#include "llviewerassetstats.h" +#include "llregionhandle.h" + +#include "stdtypes.h" + +/* + * Classes and utility functions for per-thread and per-region + * asset and experiential metrics to be aggregated grid-wide. + * + * The basic metrics grouping is LLViewerAssetStats::PerRegionStats. + * This provides various counters and simple statistics for asset + * fetches binned into a few categories.  One of these is maintained + * for each region encountered and the 'current' region is available + * as a simple reference.  Each thread (presently two) interested + * in participating in these stats gets an instance of the + * LLViewerAssetStats class so that threads are completely + * independent. + * + * The idea of a current region is used for simplicity and speed + * of categorization.  Each metrics event could have taken a + * region uuid argument resulting in a suitable lookup.  Arguments + * against this design include: + * + *  -  Region uuid not trivially available to caller. + *  -  Cost (cpu, disruption in real work flow) too high. + *  -  Additional precision not really meaningful. + * + * By itself, the LLViewerAssetStats class is thread- and + * viewer-agnostic and can be used anywhere without assumptions + * of global pointers and other context.  For the viewer, + * a set of free functions are provided in the namespace + * LLViewerAssetStatsFF which *do* implement viewer-native + * policies about per-thread globals and will do correct + * defensive tests of same. + * + * References + * + * Project: + *   <TBD> + * + * Test Plan: + *   <TBD> + * + * Jiras: + *   <TBD> + * + * Unit Tests: + *   indra/newview/tests/llviewerassetstats_test.cpp + * + */ + + +// ------------------------------------------------------ +// Global data definitions +// ------------------------------------------------------ +LLViewerAssetStats * gViewerAssetStatsMain(0); +LLViewerAssetStats * gViewerAssetStatsThread1(0); + + +// ------------------------------------------------------ +// Local declarations +// ------------------------------------------------------ +namespace +{ + +static LLViewerAssetStats::EViewerAssetCategories +asset_type_to_category(const LLViewerAssetType::EType at, bool with_http, bool is_temp); + +} + +// ------------------------------------------------------ +// LLViewerAssetStats::PerRegionStats struct definition +// ------------------------------------------------------ +void +LLViewerAssetStats::PerRegionStats::reset() +{ +	for (int i(0); i < LL_ARRAY_SIZE(mRequests); ++i) +	{ +		mRequests[i].mEnqueued.reset(); +		mRequests[i].mDequeued.reset(); +		mRequests[i].mResponse.reset(); +	} +	mFPS.reset(); +	 +	mTotalTime = 0; +	mStartTimestamp = LLViewerAssetStatsFF::get_timestamp(); +} + + +void +LLViewerAssetStats::PerRegionStats::merge(const LLViewerAssetStats::PerRegionStats & src) +{ +	// mRegionHandle, mTotalTime, mStartTimestamp are left alone. +	 +	// mFPS +	if (src.mFPS.getCount() && mFPS.getCount()) +	{ +		mFPS.merge(src.mFPS); +	} + +	// Requests +	for (int i = 0; i < LL_ARRAY_SIZE(mRequests); ++i) +	{ +		mRequests[i].mEnqueued.merge(src.mRequests[i].mEnqueued); +		mRequests[i].mDequeued.merge(src.mRequests[i].mDequeued); +		mRequests[i].mResponse.merge(src.mRequests[i].mResponse); +	} +} + + +void +LLViewerAssetStats::PerRegionStats::accumulateTime(duration_t now) +{ +	mTotalTime += (now - mStartTimestamp); +	mStartTimestamp = now; +} + + +// ------------------------------------------------------ +// LLViewerAssetStats class definition +// ------------------------------------------------------ +LLViewerAssetStats::LLViewerAssetStats() +	: mRegionHandle(U64(0)) +{ +	reset(); +} + + +LLViewerAssetStats::LLViewerAssetStats(const LLViewerAssetStats & src) +	: mRegionHandle(src.mRegionHandle), +	  mResetTimestamp(src.mResetTimestamp) +{ +	const PerRegionContainer::const_iterator it_end(src.mRegionStats.end()); +	for (PerRegionContainer::const_iterator it(src.mRegionStats.begin()); it_end != it; ++it) +	{ +		mRegionStats[it->first] = new PerRegionStats(*it->second); +	} +	mCurRegionStats = mRegionStats[mRegionHandle]; +} + + +void +LLViewerAssetStats::reset() +{ +	// Empty the map of all region stats +	mRegionStats.clear(); + +	// If we have a current stats, reset it, otherwise, as at construction, +	// create a new one as we must always have a current stats block. +	if (mCurRegionStats) +	{ +		mCurRegionStats->reset(); +	} +	else +	{ +		mCurRegionStats = new PerRegionStats(mRegionHandle); +	} + +	// And add reference to map +	mRegionStats[mRegionHandle] = mCurRegionStats; + +	// Start timestamp consistent with per-region collector +	mResetTimestamp = mCurRegionStats->mStartTimestamp; +} + + +void +LLViewerAssetStats::setRegion(region_handle_t region_handle) +{ +	if (region_handle == mRegionHandle) +	{ +		// Already active, ignore. +		return; +	} + +	// Get duration for current set +	const duration_t now = LLViewerAssetStatsFF::get_timestamp(); +	mCurRegionStats->accumulateTime(now); + +	// Prepare new set +	PerRegionContainer::iterator new_stats = mRegionStats.find(region_handle); +	if (mRegionStats.end() == new_stats) +	{ +		// Haven't seen this region_id before, create a new block and make it current. +		mCurRegionStats = new PerRegionStats(region_handle); +		mRegionStats[region_handle] = mCurRegionStats; +	} +	else +	{ +		mCurRegionStats = new_stats->second; +	} +	mCurRegionStats->mStartTimestamp = now; +	mRegionHandle = region_handle; +} + + +void +LLViewerAssetStats::recordGetEnqueued(LLViewerAssetType::EType at, bool with_http, bool is_temp) +{ +	const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp)); +	 +	++(mCurRegionStats->mRequests[int(eac)].mEnqueued); +} +	 +void +LLViewerAssetStats::recordGetDequeued(LLViewerAssetType::EType at, bool with_http, bool is_temp) +{ +	const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp)); + +	++(mCurRegionStats->mRequests[int(eac)].mDequeued); +} + +void +LLViewerAssetStats::recordGetServiced(LLViewerAssetType::EType at, bool with_http, bool is_temp, duration_t duration) +{ +	const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp)); + +	mCurRegionStats->mRequests[int(eac)].mResponse.record(duration); +} + +void +LLViewerAssetStats::recordFPS(F32 fps) +{ +	mCurRegionStats->mFPS.record(fps); +} + +LLSD +LLViewerAssetStats::asLLSD(bool compact_output) +{ +	// Top-level tags +	static const LLSD::String tags[EVACCount] =  +		{ +			LLSD::String("get_texture_temp_http"), +			LLSD::String("get_texture_temp_udp"), +			LLSD::String("get_texture_non_temp_http"), +			LLSD::String("get_texture_non_temp_udp"), +			LLSD::String("get_wearable_udp"), +			LLSD::String("get_sound_udp"), +			LLSD::String("get_gesture_udp"), +			LLSD::String("get_other") +		}; + +	// Stats Group Sub-tags. +	static const LLSD::String enq_tag("enqueued"); +	static const LLSD::String deq_tag("dequeued"); +	static const LLSD::String rcnt_tag("resp_count"); +	static const LLSD::String rmin_tag("resp_min"); +	static const LLSD::String rmax_tag("resp_max"); +	static const LLSD::String rmean_tag("resp_mean"); + +	// MMM Group Sub-tags. +	static const LLSD::String cnt_tag("count"); +	static const LLSD::String min_tag("min"); +	static const LLSD::String max_tag("max"); +	static const LLSD::String mean_tag("mean"); + +	const duration_t now = LLViewerAssetStatsFF::get_timestamp(); +	mCurRegionStats->accumulateTime(now); + +	LLSD regions = LLSD::emptyArray(); +	for (PerRegionContainer::iterator it = mRegionStats.begin(); +		 mRegionStats.end() != it; +		 ++it) +	{ +		if (0 == it->first) +		{ +			// Never emit NULL UUID/handle in results. +			continue; +		} + +		PerRegionStats & stats = *it->second; +		 +		LLSD reg_stat = LLSD::emptyMap(); +		 +		for (int i = 0; i < LL_ARRAY_SIZE(tags); ++i) +		{ +			PerRegionStats::prs_group & group(stats.mRequests[i]); +			 +			if ((! compact_output) || +				group.mEnqueued.getCount() || +				group.mDequeued.getCount() || +				group.mResponse.getCount()) +			{ +				LLSD & slot = reg_stat[tags[i]]; +				slot = LLSD::emptyMap(); +				slot[enq_tag] = LLSD(S32(stats.mRequests[i].mEnqueued.getCount())); +				slot[deq_tag] = LLSD(S32(stats.mRequests[i].mDequeued.getCount())); +				slot[rcnt_tag] = LLSD(S32(stats.mRequests[i].mResponse.getCount())); +				slot[rmin_tag] = LLSD(F64(stats.mRequests[i].mResponse.getMin() * 1.0e-6)); +				slot[rmax_tag] = LLSD(F64(stats.mRequests[i].mResponse.getMax() * 1.0e-6)); +				slot[rmean_tag] = LLSD(F64(stats.mRequests[i].mResponse.getMean() * 1.0e-6)); +			} +		} + +		if ((! compact_output) || stats.mFPS.getCount()) +		{ +			LLSD & slot = reg_stat["fps"]; +			slot = LLSD::emptyMap(); +			slot[cnt_tag] = LLSD(S32(stats.mFPS.getCount())); +			slot[min_tag] = LLSD(F64(stats.mFPS.getMin())); +			slot[max_tag] = LLSD(F64(stats.mFPS.getMax())); +			slot[mean_tag] = LLSD(F64(stats.mFPS.getMean())); +		} + +		U32 grid_x(0), grid_y(0); +		grid_from_region_handle(it->first, &grid_x, &grid_y); +		reg_stat["grid_x"] = LLSD::Integer(grid_x); +		reg_stat["grid_y"] = LLSD::Integer(grid_y); +		reg_stat["duration"] = LLSD::Real(stats.mTotalTime * 1.0e-6);		 +		regions.append(reg_stat); +	} + +	LLSD ret = LLSD::emptyMap(); +	ret["regions"] = regions; +	ret["duration"] = LLSD::Real((now - mResetTimestamp) * 1.0e-6); +	 +	return ret; +} + +void +LLViewerAssetStats::merge(const LLViewerAssetStats & src) +{ +	// mRegionHandle, mCurRegionStats and mResetTimestamp are left untouched. +	// Just merge the stats bodies + +	const PerRegionContainer::const_iterator it_end(src.mRegionStats.end()); +	for (PerRegionContainer::const_iterator it(src.mRegionStats.begin()); it_end != it; ++it) +	{ +		PerRegionContainer::iterator dst(mRegionStats.find(it->first)); +		if (mRegionStats.end() == dst) +		{ +			// Destination is missing data, just make a private copy +			mRegionStats[it->first] = new PerRegionStats(*it->second); +		} +		else +		{ +			dst->second->merge(*it->second); +		} +	} +} + + +// ------------------------------------------------------ +// Global free-function definitions (LLViewerAssetStatsFF namespace) +// ------------------------------------------------------ + +namespace LLViewerAssetStatsFF +{ + +// +// Target thread is elaborated in the function name.  This could +// have been something 'templatey' like specializations iterated +// over a set of constants but with so few, this is clearer I think. +// +// As for the threads themselves... rather than do fine-grained +// locking as we gather statistics, this code creates a collector +// for each thread, allocated and run independently.  Logging +// happens at relatively infrequent intervals and at that time +// the data is sent to a single thread to be aggregated into +// a single entity with locks, thread safety and other niceties. +// +// A particularly fussy implementation would distribute the +// per-thread pointers across separate cache lines.  But that should +// be beyond current requirements. +// + +// 'main' thread - initial program thread + +void +set_region_main(LLViewerAssetStats::region_handle_t region_handle) +{ +	if (! gViewerAssetStatsMain) +		return; + +	gViewerAssetStatsMain->setRegion(region_handle); +} + +void +record_enqueue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp) +{ +	if (! gViewerAssetStatsMain) +		return; + +	gViewerAssetStatsMain->recordGetEnqueued(at, with_http, is_temp); +} + +void +record_dequeue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp) +{ +	if (! gViewerAssetStatsMain) +		return; + +	gViewerAssetStatsMain->recordGetDequeued(at, with_http, is_temp); +} + +void +record_response_main(LLViewerAssetType::EType at, bool with_http, bool is_temp, LLViewerAssetStats::duration_t duration) +{ +	if (! gViewerAssetStatsMain) +		return; + +	gViewerAssetStatsMain->recordGetServiced(at, with_http, is_temp, duration); +} + +void +record_fps_main(F32 fps) +{ +	if (! gViewerAssetStatsMain) +		return; + +	gViewerAssetStatsMain->recordFPS(fps); +} + + +// 'thread1' - should be for TextureFetch thread + +void +set_region_thread1(LLViewerAssetStats::region_handle_t region_handle) +{ +	if (! gViewerAssetStatsThread1) +		return; + +	gViewerAssetStatsThread1->setRegion(region_handle); +} + +void +record_enqueue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp) +{ +	if (! gViewerAssetStatsThread1) +		return; + +	gViewerAssetStatsThread1->recordGetEnqueued(at, with_http, is_temp); +} + +void +record_dequeue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp) +{ +	if (! gViewerAssetStatsThread1) +		return; + +	gViewerAssetStatsThread1->recordGetDequeued(at, with_http, is_temp); +} + +void +record_response_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp, LLViewerAssetStats::duration_t duration) +{ +	if (! gViewerAssetStatsThread1) +		return; + +	gViewerAssetStatsThread1->recordGetServiced(at, with_http, is_temp, duration); +} + + +void +init() +{ +	if (! gViewerAssetStatsMain) +	{ +		gViewerAssetStatsMain = new LLViewerAssetStats(); +	} +	if (! gViewerAssetStatsThread1) +	{ +		gViewerAssetStatsThread1 = new LLViewerAssetStats(); +	} +} + +void +cleanup() +{ +	delete gViewerAssetStatsMain; +	gViewerAssetStatsMain = 0; + +	delete gViewerAssetStatsThread1; +	gViewerAssetStatsThread1 = 0; +} + + +} // namespace LLViewerAssetStatsFF + + +// ------------------------------------------------------ +// Local function definitions +// ------------------------------------------------------ + +namespace +{ + +LLViewerAssetStats::EViewerAssetCategories +asset_type_to_category(const LLViewerAssetType::EType at, bool with_http, bool is_temp) +{ +	// For statistical purposes, we divide GETs into several +	// populations of asset fetches: +	//  - textures which are de-prioritized in the asset system +	//  - wearables (clothing, bodyparts) which directly affect +	//    user experiences when they log in +	//  - sounds +	//  - gestures +	//  - everything else. +	// +	llassert_always(26 == LLViewerAssetType::AT_COUNT); + +	// Multiple asset definitions are floating around so this requires some +	// maintenance and attention. +	static const LLViewerAssetStats::EViewerAssetCategories asset_to_bin_map[LLViewerAssetType::AT_COUNT] = +		{ +			LLViewerAssetStats::EVACTextureTempHTTPGet,			// (0) AT_TEXTURE +			LLViewerAssetStats::EVACSoundUDPGet,				// AT_SOUND +			LLViewerAssetStats::EVACOtherGet,					// AT_CALLINGCARD +			LLViewerAssetStats::EVACOtherGet,					// AT_LANDMARK +			LLViewerAssetStats::EVACOtherGet,					// AT_SCRIPT +			LLViewerAssetStats::EVACWearableUDPGet,				// AT_CLOTHING +			LLViewerAssetStats::EVACOtherGet,					// AT_OBJECT +			LLViewerAssetStats::EVACOtherGet,					// AT_NOTECARD +			LLViewerAssetStats::EVACOtherGet,					// AT_CATEGORY +			LLViewerAssetStats::EVACOtherGet,					// AT_ROOT_CATEGORY +			LLViewerAssetStats::EVACOtherGet,					// (10) AT_LSL_TEXT +			LLViewerAssetStats::EVACOtherGet,					// AT_LSL_BYTECODE +			LLViewerAssetStats::EVACOtherGet,					// AT_TEXTURE_TGA +			LLViewerAssetStats::EVACWearableUDPGet,				// AT_BODYPART +			LLViewerAssetStats::EVACOtherGet,					// AT_TRASH +			LLViewerAssetStats::EVACOtherGet,					// AT_SNAPSHOT_CATEGORY +			LLViewerAssetStats::EVACOtherGet,					// AT_LOST_AND_FOUND +			LLViewerAssetStats::EVACSoundUDPGet,				// AT_SOUND_WAV +			LLViewerAssetStats::EVACOtherGet,					// AT_IMAGE_TGA +			LLViewerAssetStats::EVACOtherGet,					// AT_IMAGE_JPEG +			LLViewerAssetStats::EVACGestureUDPGet,				// (20) AT_ANIMATION +			LLViewerAssetStats::EVACGestureUDPGet,				// AT_GESTURE +			LLViewerAssetStats::EVACOtherGet,					// AT_SIMSTATE +			LLViewerAssetStats::EVACOtherGet,					// AT_FAVORITE +			LLViewerAssetStats::EVACOtherGet,					// AT_LINK +			LLViewerAssetStats::EVACOtherGet,					// AT_LINK_FOLDER +#if 0 +			// When LLViewerAssetType::AT_COUNT == 49 +			LLViewerAssetStats::EVACOtherGet,					// AT_FOLDER_ENSEMBLE_START +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					// (30) +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					// (40) +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					// AT_FOLDER_ENSEMBLE_END +			LLViewerAssetStats::EVACOtherGet,					// AT_CURRENT_OUTFIT +			LLViewerAssetStats::EVACOtherGet,					// AT_OUTFIT +			LLViewerAssetStats::EVACOtherGet					// AT_MY_OUTFITS +#endif +		}; +	 +	if (at < 0 || at >= LLViewerAssetType::AT_COUNT) +	{ +		return LLViewerAssetStats::EVACOtherGet; +	} +	LLViewerAssetStats::EViewerAssetCategories ret(asset_to_bin_map[at]); +	if (LLViewerAssetStats::EVACTextureTempHTTPGet == ret) +	{ +		// Indexed with [is_temp][with_http] +		static const LLViewerAssetStats::EViewerAssetCategories texture_bin_map[2][2] = +			{ +				{ +					LLViewerAssetStats::EVACTextureNonTempUDPGet, +					LLViewerAssetStats::EVACTextureNonTempHTTPGet, +				}, +				{ +					LLViewerAssetStats::EVACTextureTempUDPGet, +					LLViewerAssetStats::EVACTextureTempHTTPGet, +				} +			}; + +		ret = texture_bin_map[is_temp][with_http]; +	} +	return ret; +} + +} // anonymous namespace diff --git a/indra/newview/llviewerassetstats.h b/indra/newview/llviewerassetstats.h new file mode 100644 index 0000000000..905ceefad5 --- /dev/null +++ b/indra/newview/llviewerassetstats.h @@ -0,0 +1,334 @@ +/**  + * @file llviewerassetstats.h + * @brief Client-side collection of asset request statistics + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + *  + * Copyright (c) 2010, 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$ + */ + +#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" + +/** + * @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); + +	// 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 diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp index 2e7ef0fec3..36c8b42a52 100644 --- a/indra/newview/llviewerassetstorage.cpp +++ b/indra/newview/llviewerassetstorage.cpp @@ -33,6 +33,61 @@  #include "message.h"  #include "llagent.h" +#include "lltransfersourceasset.h" +#include "lltransfertargetvfile.h" +#include "llviewerassetstats.h" + +///---------------------------------------------------------------------------- +/// LLViewerAssetRequest +///---------------------------------------------------------------------------- + +/** + * @brief Local class to encapsulate asset fetch requests with a timestamp. + * + * Derived from the common LLAssetRequest class, this is currently used + * only for fetch/get operations and its only function is to wrap remote + * asset fetch requests so that they can be timed. + */ +class LLViewerAssetRequest : public LLAssetRequest +{ +public: +	LLViewerAssetRequest(const LLUUID &uuid, const LLAssetType::EType type) +		: LLAssetRequest(uuid, type), +		  mMetricsStartTime(0) +		{ +		} +	 +	LLViewerAssetRequest & operator=(const LLViewerAssetRequest &);	// Not defined +	// Default assignment operator valid +	 +	// virtual +	~LLViewerAssetRequest() +		{ +			recordMetrics(); +		} + +protected: +	void recordMetrics() +		{ +			if (mMetricsStartTime) +			{ +				// Okay, it appears this request was used for useful things.  Record +				// the expected dequeue and duration of request processing. +				LLViewerAssetStatsFF::record_dequeue_main(mType, false, false); +				LLViewerAssetStatsFF::record_response_main(mType, false, false, +														   (LLViewerAssetStatsFF::get_timestamp() +															- mMetricsStartTime)); +				mMetricsStartTime = 0; +			} +		} +	 +public: +	LLViewerAssetStats::duration_t		mMetricsStartTime; +}; + +///---------------------------------------------------------------------------- +/// LLViewerAssetStorage +///----------------------------------------------------------------------------  LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer,  										   LLVFS *vfs, LLVFS *static_vfs,  @@ -258,3 +313,77 @@ void LLViewerAssetStorage::storeAssetData(  		}  	}  } + + +/** + * @brief Allocate and queue an asset fetch request for the viewer + * + * This is a nearly-verbatim copy of the base class's implementation + * with the following changes: + *  -  Use a locally-derived request class + *  -  Start timing for metrics when request is queued + * + * This is an unfortunate implementation choice but it's forced by + * current conditions.  A refactoring that might clean up the layers + * of responsibility or introduce factories or more virtualization + * of methods would enable a more attractive solution. + * + * If LLAssetStorage::_queueDataRequest changes, this must change + * as well. + */ + +// virtual +void LLViewerAssetStorage::_queueDataRequest( +	const LLUUID& uuid, +	LLAssetType::EType atype, +	LLGetAssetCallback callback, +	void *user_data, +	BOOL duplicate, +	BOOL is_priority) +{ +	if (mUpstreamHost.isOk()) +	{ +		// stash the callback info so we can find it after we get the response message +		LLViewerAssetRequest *req = new LLViewerAssetRequest(uuid, atype); +		req->mDownCallback = callback; +		req->mUserData = user_data; +		req->mIsPriority = is_priority; +		if (!duplicate) +		{ +			// Only collect metrics for non-duplicate requests.  Others  +			// are piggy-backing and will artificially lower averages. +			req->mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); +		} +		 +		mPendingDownloads.push_back(req); +	 +		if (!duplicate) +		{ +			// send request message to our upstream data provider +			// Create a new asset transfer. +			LLTransferSourceParamsAsset spa; +			spa.setAsset(uuid, atype); + +			// Set our destination file, and the completion callback. +			LLTransferTargetParamsVFile tpvf; +			tpvf.setAsset(uuid, atype); +			tpvf.setCallback(downloadCompleteCallback, req); + +			llinfos << "Starting transfer for " << uuid << llendl; +			LLTransferTargetChannel *ttcp = gTransferManager.getTargetChannel(mUpstreamHost, LLTCT_ASSET); +			ttcp->requestTransfer(spa, tpvf, 100.f + (is_priority ? 1.f : 0.f)); + +			LLViewerAssetStatsFF::record_enqueue_main(atype, false, false); +		} +	} +	else +	{ +		// uh-oh, we shouldn't have gotten here +		llwarns << "Attempt to move asset data request upstream w/o valid upstream provider" << llendl; +		if (callback) +		{ +			callback(mVFS, uuid, atype, user_data, LL_ERR_CIRCUIT_GONE, LL_EXSTAT_NO_UPSTREAM); +		} +	} +} + diff --git a/indra/newview/llviewerassetstorage.h b/indra/newview/llviewerassetstorage.h index 6346b79f03..ca9b9943fa 100644 --- a/indra/newview/llviewerassetstorage.h +++ b/indra/newview/llviewerassetstorage.h @@ -63,6 +63,17 @@ public:  		bool is_priority = false,  		bool user_waiting=FALSE,  		F64 timeout=LL_ASSET_STORAGE_TIMEOUT); + +protected: +	using LLAssetStorage::_queueDataRequest; + +	// virtual +	void _queueDataRequest(const LLUUID& uuid, +						   LLAssetType::EType type, +						   void (*callback) (LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32, LLExtStat), +						   void *user_data, +						   BOOL duplicate, +						   BOOL is_priority);  };  #endif diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index ca07e7c4cf..551ba18dd5 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1414,6 +1414,7 @@ void LLViewerRegion::setSeedCapability(const std::string& url)  	capabilityNames.append("UpdateNotecardTaskInventory");  	capabilityNames.append("UpdateScriptTask");  	capabilityNames.append("UploadBakedTexture"); +	capabilityNames.append("ViewerMetrics");  	capabilityNames.append("ViewerStartAuction");  	capabilityNames.append("ViewerStats");  	capabilityNames.append("WebFetchInventoryDescendents"); diff --git a/indra/newview/tests/llsimplestat_test.cpp b/indra/newview/tests/llsimplestat_test.cpp new file mode 100644 index 0000000000..60a8cac995 --- /dev/null +++ b/indra/newview/tests/llsimplestat_test.cpp @@ -0,0 +1,586 @@ +/**  + * @file llsimplestats_test.cpp + * @date 2010-10-22 + * @brief Test cases for some of llsimplestat.h + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + *  + * Copyright (c) 2010, 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 <tut/tut.hpp> + +#include "lltut.h" +#include "../llsimplestat.h" +#include "llsd.h" +#include "llmath.h" + +// @brief Used as a pointer cast type to get access to LLSimpleStatCounter +class TutStatCounter: public LLSimpleStatCounter +{ +public: +	TutStatCounter();							// Not defined +	~TutStatCounter();							// Not defined +	void operator=(const TutStatCounter &);		// Not defined +	 +	void setRawCount(U32 c)				{ mCount = c; } +	U32 getRawCount() const				{ return mCount; } +}; + + +namespace tut +{ +	struct stat_counter_index +	{}; +	typedef test_group<stat_counter_index> stat_counter_index_t; +	typedef stat_counter_index_t::object stat_counter_index_object_t; +	tut::stat_counter_index_t tut_stat_counter_index("stat_counter_test"); + +	// Testing LLSimpleStatCounter's external interface +	template<> template<> +	void stat_counter_index_object_t::test<1>() +	{ +		LLSimpleStatCounter c1; +		ensure("Initialized counter is zero", (0 == c1.getCount())); + +		ensure("Counter increment return is 1", (1 == ++c1)); +		ensure("Counter increment return is 2", (2 == ++c1)); + +		ensure("Current counter is 2", (2 == c1.getCount())); + +		c1.reset(); +		ensure("Counter is 0 after reset", (0 == c1.getCount())); +		 +		ensure("Counter increment return is 1", (1 == ++c1)); +	} + +	// Testing LLSimpleStatCounter's internal state +	template<> template<> +	void stat_counter_index_object_t::test<2>() +	{ +		LLSimpleStatCounter c1; +		TutStatCounter * tc1 = (TutStatCounter *) &c1; +		 +		ensure("Initialized private counter is zero", (0 == tc1->getRawCount())); + +		++c1; +		++c1; +		 +		ensure("Current private counter is 2", (2 == tc1->getRawCount())); + +		c1.reset(); +		ensure("Raw counter is 0 after reset", (0 == tc1->getRawCount())); +	} + +	// Testing LLSimpleStatCounter's wrapping behavior +	template<> template<> +	void stat_counter_index_object_t::test<3>() +	{ +		LLSimpleStatCounter c1; +		TutStatCounter * tc1 = (TutStatCounter *) &c1; + +		tc1->setRawCount(U32_MAX); +		ensure("Initialized private counter is zero", (U32_MAX == c1.getCount())); + +		ensure("Increment of max value wraps to 0", (0 == ++c1)); +	} + +	// Testing LLSimpleStatMMM's external behavior +	template<> template<> +	void stat_counter_index_object_t::test<4>() +	{ +		LLSimpleStatMMM<> m1; +		typedef LLSimpleStatMMM<>::Value lcl_float; +		lcl_float zero(0); + +		// Freshly-constructed +		ensure("Constructed MMM<> has 0 count", (0 == m1.getCount())); +		ensure("Constructed MMM<> has 0 min", (zero == m1.getMin())); +		ensure("Constructed MMM<> has 0 max", (zero == m1.getMax())); +		ensure("Constructed MMM<> has 0 mean no div-by-zero", (zero == m1.getMean())); + +		// Single insert +		m1.record(1.0); +		ensure("Single insert MMM<> has 1 count", (1 == m1.getCount())); +		ensure("Single insert MMM<> has 1.0 min", (1.0 == m1.getMin())); +		ensure("Single insert MMM<> has 1.0 max", (1.0 == m1.getMax())); +		ensure("Single insert MMM<> has 1.0 mean", (1.0 == m1.getMean())); +		 +		// Second insert +		m1.record(3.0); +		ensure("2nd insert MMM<> has 2 count", (2 == m1.getCount())); +		ensure("2nd insert MMM<> has 1.0 min", (1.0 == m1.getMin())); +		ensure("2nd insert MMM<> has 3.0 max", (3.0 == m1.getMax())); +		ensure_approximately_equals("2nd insert MMM<> has 2.0 mean", m1.getMean(), lcl_float(2.0), 1); + +		// Third insert +		m1.record(5.0); +		ensure("3rd insert MMM<> has 3 count", (3 == m1.getCount())); +		ensure("3rd insert MMM<> has 1.0 min", (1.0 == m1.getMin())); +		ensure("3rd insert MMM<> has 5.0 max", (5.0 == m1.getMax())); +		ensure_approximately_equals("3rd insert MMM<> has 3.0 mean", m1.getMean(), lcl_float(3.0), 1); + +		// Fourth insert +		m1.record(1000000.0); +		ensure("4th insert MMM<> has 4 count", (4 == m1.getCount())); +		ensure("4th insert MMM<> has 1.0 min", (1.0 == m1.getMin())); +		ensure("4th insert MMM<> has 100000.0 max", (1000000.0 == m1.getMax())); +		ensure_approximately_equals("4th insert MMM<> has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1); + +		// Reset +		m1.reset(); +		ensure("Reset MMM<> has 0 count", (0 == m1.getCount())); +		ensure("Reset MMM<> has 0 min", (zero == m1.getMin())); +		ensure("Reset MMM<> has 0 max", (zero == m1.getMax())); +		ensure("Reset MMM<> has 0 mean no div-by-zero", (zero == m1.getMean())); +	} + +	// Testing LLSimpleStatMMM's response to large values +	template<> template<> +	void stat_counter_index_object_t::test<5>() +	{ +		LLSimpleStatMMM<> m1; +		typedef LLSimpleStatMMM<>::Value lcl_float; +		lcl_float zero(0); + +		// Insert overflowing values +		const lcl_float bignum(F32_MAX / 2); + +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(zero); + +		ensure("Overflowed MMM<> has 8 count", (8 == m1.getCount())); +		ensure("Overflowed MMM<> has 0 min", (zero == m1.getMin())); +		ensure("Overflowed MMM<> has huge max", (bignum == m1.getMax())); +		ensure("Overflowed MMM<> has fetchable mean", (1.0 == m1.getMean() || true)); +		// We should be infinte but not interested in proving the IEEE standard here. +		LLSD sd1(m1.getMean()); +		// std::cout << "Thingy:  " << m1.getMean() << " and as LLSD:  " << sd1 << std::endl; +		ensure("Overflowed MMM<> produces LLSDable Real", (sd1.isReal())); +	} + +	// Testing LLSimpleStatMMM<F32>'s external behavior +	template<> template<> +	void stat_counter_index_object_t::test<6>() +	{ +		LLSimpleStatMMM<F32> m1; +		typedef LLSimpleStatMMM<F32>::Value lcl_float; +		lcl_float zero(0); + +		// Freshly-constructed +		ensure("Constructed MMM<F32> has 0 count", (0 == m1.getCount())); +		ensure("Constructed MMM<F32> has 0 min", (zero == m1.getMin())); +		ensure("Constructed MMM<F32> has 0 max", (zero == m1.getMax())); +		ensure("Constructed MMM<F32> has 0 mean no div-by-zero", (zero == m1.getMean())); + +		// Single insert +		m1.record(1.0); +		ensure("Single insert MMM<F32> has 1 count", (1 == m1.getCount())); +		ensure("Single insert MMM<F32> has 1.0 min", (1.0 == m1.getMin())); +		ensure("Single insert MMM<F32> has 1.0 max", (1.0 == m1.getMax())); +		ensure("Single insert MMM<F32> has 1.0 mean", (1.0 == m1.getMean())); +		 +		// Second insert +		m1.record(3.0); +		ensure("2nd insert MMM<F32> has 2 count", (2 == m1.getCount())); +		ensure("2nd insert MMM<F32> has 1.0 min", (1.0 == m1.getMin())); +		ensure("2nd insert MMM<F32> has 3.0 max", (3.0 == m1.getMax())); +		ensure_approximately_equals("2nd insert MMM<F32> has 2.0 mean", m1.getMean(), lcl_float(2.0), 1); + +		// Third insert +		m1.record(5.0); +		ensure("3rd insert MMM<F32> has 3 count", (3 == m1.getCount())); +		ensure("3rd insert MMM<F32> has 1.0 min", (1.0 == m1.getMin())); +		ensure("3rd insert MMM<F32> has 5.0 max", (5.0 == m1.getMax())); +		ensure_approximately_equals("3rd insert MMM<F32> has 3.0 mean", m1.getMean(), lcl_float(3.0), 1); + +		// Fourth insert +		m1.record(1000000.0); +		ensure("4th insert MMM<F32> has 4 count", (4 == m1.getCount())); +		ensure("4th insert MMM<F32> has 1.0 min", (1.0 == m1.getMin())); +		ensure("4th insert MMM<F32> has 1000000.0 max", (1000000.0 == m1.getMax())); +		ensure_approximately_equals("4th insert MMM<F32> has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1); + +		// Reset +		m1.reset(); +		ensure("Reset MMM<F32> has 0 count", (0 == m1.getCount())); +		ensure("Reset MMM<F32> has 0 min", (zero == m1.getMin())); +		ensure("Reset MMM<F32> has 0 max", (zero == m1.getMax())); +		ensure("Reset MMM<F32> has 0 mean no div-by-zero", (zero == m1.getMean())); +	} + +	// Testing LLSimpleStatMMM's response to large values +	template<> template<> +	void stat_counter_index_object_t::test<7>() +	{ +		LLSimpleStatMMM<F32> m1; +		typedef LLSimpleStatMMM<F32>::Value lcl_float; +		lcl_float zero(0); + +		// Insert overflowing values +		const lcl_float bignum(F32_MAX / 2); + +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(zero); + +		ensure("Overflowed MMM<F32> has 8 count", (8 == m1.getCount())); +		ensure("Overflowed MMM<F32> has 0 min", (zero == m1.getMin())); +		ensure("Overflowed MMM<F32> has huge max", (bignum == m1.getMax())); +		ensure("Overflowed MMM<F32> has fetchable mean", (1.0 == m1.getMean() || true)); +		// We should be infinte but not interested in proving the IEEE standard here. +		LLSD sd1(m1.getMean()); +		// std::cout << "Thingy:  " << m1.getMean() << " and as LLSD:  " << sd1 << std::endl; +		ensure("Overflowed MMM<F32> produces LLSDable Real", (sd1.isReal())); +	} + +	// Testing LLSimpleStatMMM<F64>'s external behavior +	template<> template<> +	void stat_counter_index_object_t::test<8>() +	{ +		LLSimpleStatMMM<F64> m1; +		typedef LLSimpleStatMMM<F64>::Value lcl_float; +		lcl_float zero(0); + +		// Freshly-constructed +		ensure("Constructed MMM<F64> has 0 count", (0 == m1.getCount())); +		ensure("Constructed MMM<F64> has 0 min", (zero == m1.getMin())); +		ensure("Constructed MMM<F64> has 0 max", (zero == m1.getMax())); +		ensure("Constructed MMM<F64> has 0 mean no div-by-zero", (zero == m1.getMean())); + +		// Single insert +		m1.record(1.0); +		ensure("Single insert MMM<F64> has 1 count", (1 == m1.getCount())); +		ensure("Single insert MMM<F64> has 1.0 min", (1.0 == m1.getMin())); +		ensure("Single insert MMM<F64> has 1.0 max", (1.0 == m1.getMax())); +		ensure("Single insert MMM<F64> has 1.0 mean", (1.0 == m1.getMean())); +		 +		// Second insert +		m1.record(3.0); +		ensure("2nd insert MMM<F64> has 2 count", (2 == m1.getCount())); +		ensure("2nd insert MMM<F64> has 1.0 min", (1.0 == m1.getMin())); +		ensure("2nd insert MMM<F64> has 3.0 max", (3.0 == m1.getMax())); +		ensure_approximately_equals("2nd insert MMM<F64> has 2.0 mean", m1.getMean(), lcl_float(2.0), 1); + +		// Third insert +		m1.record(5.0); +		ensure("3rd insert MMM<F64> has 3 count", (3 == m1.getCount())); +		ensure("3rd insert MMM<F64> has 1.0 min", (1.0 == m1.getMin())); +		ensure("3rd insert MMM<F64> has 5.0 max", (5.0 == m1.getMax())); +		ensure_approximately_equals("3rd insert MMM<F64> has 3.0 mean", m1.getMean(), lcl_float(3.0), 1); + +		// Fourth insert +		m1.record(1000000.0); +		ensure("4th insert MMM<F64> has 4 count", (4 == m1.getCount())); +		ensure("4th insert MMM<F64> has 1.0 min", (1.0 == m1.getMin())); +		ensure("4th insert MMM<F64> has 1000000.0 max", (1000000.0 == m1.getMax())); +		ensure_approximately_equals("4th insert MMM<F64> has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1); + +		// Reset +		m1.reset(); +		ensure("Reset MMM<F64> has 0 count", (0 == m1.getCount())); +		ensure("Reset MMM<F64> has 0 min", (zero == m1.getMin())); +		ensure("Reset MMM<F64> has 0 max", (zero == m1.getMax())); +		ensure("Reset MMM<F64> has 0 mean no div-by-zero", (zero == m1.getMean())); +	} + +	// Testing LLSimpleStatMMM's response to large values +	template<> template<> +	void stat_counter_index_object_t::test<9>() +	{ +		LLSimpleStatMMM<F64> m1; +		typedef LLSimpleStatMMM<F64>::Value lcl_float; +		lcl_float zero(0); + +		// Insert overflowing values +		const lcl_float bignum(F64_MAX / 2); + +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(zero); + +		ensure("Overflowed MMM<F64> has 8 count", (8 == m1.getCount())); +		ensure("Overflowed MMM<F64> has 0 min", (zero == m1.getMin())); +		ensure("Overflowed MMM<F64> has huge max", (bignum == m1.getMax())); +		ensure("Overflowed MMM<F64> has fetchable mean", (1.0 == m1.getMean() || true)); +		// We should be infinte but not interested in proving the IEEE standard here. +		LLSD sd1(m1.getMean()); +		// std::cout << "Thingy:  " << m1.getMean() << " and as LLSD:  " << sd1 << std::endl; +		ensure("Overflowed MMM<F64> produces LLSDable Real", (sd1.isReal())); +	} + +	// Testing LLSimpleStatMMM<U64>'s external behavior +	template<> template<> +	void stat_counter_index_object_t::test<10>() +	{ +		LLSimpleStatMMM<U64> m1; +		typedef LLSimpleStatMMM<U64>::Value lcl_int; +		lcl_int zero(0); + +		// Freshly-constructed +		ensure("Constructed MMM<U64> has 0 count", (0 == m1.getCount())); +		ensure("Constructed MMM<U64> has 0 min", (zero == m1.getMin())); +		ensure("Constructed MMM<U64> has 0 max", (zero == m1.getMax())); +		ensure("Constructed MMM<U64> has 0 mean no div-by-zero", (zero == m1.getMean())); + +		// Single insert +		m1.record(1); +		ensure("Single insert MMM<U64> has 1 count", (1 == m1.getCount())); +		ensure("Single insert MMM<U64> has 1 min", (1 == m1.getMin())); +		ensure("Single insert MMM<U64> has 1 max", (1 == m1.getMax())); +		ensure("Single insert MMM<U64> has 1 mean", (1 == m1.getMean())); +		 +		// Second insert +		m1.record(3); +		ensure("2nd insert MMM<U64> has 2 count", (2 == m1.getCount())); +		ensure("2nd insert MMM<U64> has 1 min", (1 == m1.getMin())); +		ensure("2nd insert MMM<U64> has 3 max", (3 == m1.getMax())); +		ensure("2nd insert MMM<U64> has 2 mean", (2 == m1.getMean())); + +		// Third insert +		m1.record(5); +		ensure("3rd insert MMM<U64> has 3 count", (3 == m1.getCount())); +		ensure("3rd insert MMM<U64> has 1 min", (1 == m1.getMin())); +		ensure("3rd insert MMM<U64> has 5 max", (5 == m1.getMax())); +		ensure("3rd insert MMM<U64> has 3 mean", (3 == m1.getMean())); + +		// Fourth insert +		m1.record(U64L(1000000000000)); +		ensure("4th insert MMM<U64> has 4 count", (4 == m1.getCount())); +		ensure("4th insert MMM<U64> has 1 min", (1 == m1.getMin())); +		ensure("4th insert MMM<U64> has 1000000000000ULL max", (U64L(1000000000000) == m1.getMax())); +		ensure("4th insert MMM<U64> has 250000000002ULL mean", (U64L( 250000000002) == m1.getMean())); + +		// Reset +		m1.reset(); +		ensure("Reset MMM<U64> has 0 count", (0 == m1.getCount())); +		ensure("Reset MMM<U64> has 0 min", (zero == m1.getMin())); +		ensure("Reset MMM<U64> has 0 max", (zero == m1.getMax())); +		ensure("Reset MMM<U64> has 0 mean no div-by-zero", (zero == m1.getMean())); +	} + +	// Testing LLSimpleStatMMM's response to large values +	template<> template<> +	void stat_counter_index_object_t::test<11>() +	{ +		LLSimpleStatMMM<U64> m1; +		typedef LLSimpleStatMMM<U64>::Value lcl_int; +		lcl_int zero(0); + +		// Insert overflowing values +		const lcl_int bignum(U64L(0xffffffffffffffff) / 2); + +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(bignum); +		m1.record(zero); + +		ensure("Overflowed MMM<U64> has 8 count", (8 == m1.getCount())); +		ensure("Overflowed MMM<U64> has 0 min", (zero == m1.getMin())); +		ensure("Overflowed MMM<U64> has huge max", (bignum == m1.getMax())); +		ensure("Overflowed MMM<U64> has fetchable mean", (zero == m1.getMean() || true)); +	} + +    // Testing LLSimpleStatCounter's merge() method +	template<> template<> +	void stat_counter_index_object_t::test<12>() +	{ +		LLSimpleStatCounter c1; +		LLSimpleStatCounter c2; + +		++c1; +		++c1; +		++c1; +		++c1; + +		++c2; +		++c2; +		c2.merge(c1); +		 +		ensure_equals("4 merged into 2 results in 6", 6, c2.getCount()); + +		ensure_equals("Source of merge is undamaged", 4, c1.getCount()); +	} + +    // Testing LLSimpleStatMMM's merge() method +	template<> template<> +	void stat_counter_index_object_t::test<13>() +	{ +		LLSimpleStatMMM<> m1; +		LLSimpleStatMMM<> m2; + +		m1.record(3.5); +		m1.record(4.5); +		m1.record(5.5); +		m1.record(6.5); + +		m2.record(5.0); +		m2.record(7.0); +		m2.record(9.0); +		 +		m2.merge(m1); + +		ensure_equals("Count after merge (p1)", 7, m2.getCount()); +		ensure_approximately_equals("Min after merge (p1)", F32(3.5), m2.getMin(), 22); +		ensure_approximately_equals("Max after merge (p1)", F32(9.0), m2.getMax(), 22); +		ensure_approximately_equals("Mean after merge (p1)", F32(41.000/7.000), m2.getMean(), 22); +		 + +		ensure_equals("Source count of merge is undamaged (p1)", 4, m1.getCount()); +		ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(3.5), m1.getMin(), 22); +		ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(6.5), m1.getMax(), 22); +		ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(5.0), m1.getMean(), 22); + +		m2.reset(); + +		m2.record(-22.0); +		m2.record(-1.0); +		m2.record(30.0); +		 +		m2.merge(m1); + +		ensure_equals("Count after merge (p2)", 7, m2.getCount()); +		ensure_approximately_equals("Min after merge (p2)", F32(-22.0), m2.getMin(), 22); +		ensure_approximately_equals("Max after merge (p2)", F32(30.0), m2.getMax(), 22); +		ensure_approximately_equals("Mean after merge (p2)", F32(27.000/7.000), m2.getMean(), 22); + +	} + +    // Testing LLSimpleStatMMM's merge() method when src contributes nothing +	template<> template<> +	void stat_counter_index_object_t::test<14>() +	{ +		LLSimpleStatMMM<> m1; +		LLSimpleStatMMM<> m2; + +		m2.record(5.0); +		m2.record(7.0); +		m2.record(9.0); +		 +		m2.merge(m1); + +		ensure_equals("Count after merge (p1)", 3, m2.getCount()); +		ensure_approximately_equals("Min after merge (p1)", F32(5.0), m2.getMin(), 22); +		ensure_approximately_equals("Max after merge (p1)", F32(9.0), m2.getMax(), 22); +		ensure_approximately_equals("Mean after merge (p1)", F32(7.000), m2.getMean(), 22); + +		ensure_equals("Source count of merge is undamaged (p1)", 0, m1.getCount()); +		ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(0), m1.getMin(), 22); +		ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(0), m1.getMax(), 22); +		ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(0), m1.getMean(), 22); + +		m2.reset(); + +		m2.record(-22.0); +		m2.record(-1.0); +		 +		m2.merge(m1); + +		ensure_equals("Count after merge (p2)", 2, m2.getCount()); +		ensure_approximately_equals("Min after merge (p2)", F32(-22.0), m2.getMin(), 22); +		ensure_approximately_equals("Max after merge (p2)", F32(-1.0), m2.getMax(), 22); +		ensure_approximately_equals("Mean after merge (p2)", F32(-11.5), m2.getMean(), 22); +	} + +    // Testing LLSimpleStatMMM's merge() method when dst contributes nothing +	template<> template<> +	void stat_counter_index_object_t::test<15>() +	{ +		LLSimpleStatMMM<> m1; +		LLSimpleStatMMM<> m2; + +		m1.record(5.0); +		m1.record(7.0); +		m1.record(9.0); +		 +		m2.merge(m1); + +		ensure_equals("Count after merge (p1)", 3, m2.getCount()); +		ensure_approximately_equals("Min after merge (p1)", F32(5.0), m2.getMin(), 22); +		ensure_approximately_equals("Max after merge (p1)", F32(9.0), m2.getMax(), 22); +		ensure_approximately_equals("Mean after merge (p1)", F32(7.000), m2.getMean(), 22); + +		ensure_equals("Source count of merge is undamaged (p1)", 3, m1.getCount()); +		ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(5.0), m1.getMin(), 22); +		ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(9.0), m1.getMax(), 22); +		ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(7.0), m1.getMean(), 22); + +		m1.reset(); +		m2.reset(); +		 +		m1.record(-22.0); +		m1.record(-1.0); +		 +		m2.merge(m1); + +		ensure_equals("Count after merge (p2)", 2, m2.getCount()); +		ensure_approximately_equals("Min after merge (p2)", F32(-22.0), m2.getMin(), 22); +		ensure_approximately_equals("Max after merge (p2)", F32(-1.0), m2.getMax(), 22); +		ensure_approximately_equals("Mean after merge (p2)", F32(-11.5), m2.getMean(), 22); +	} + +    // Testing LLSimpleStatMMM's merge() method when neither dst nor src contributes +	template<> template<> +	void stat_counter_index_object_t::test<16>() +	{ +		LLSimpleStatMMM<> m1; +		LLSimpleStatMMM<> m2; + +		m2.merge(m1); + +		ensure_equals("Count after merge (p1)", 0, m2.getCount()); +		ensure_approximately_equals("Min after merge (p1)", F32(0), m2.getMin(), 22); +		ensure_approximately_equals("Max after merge (p1)", F32(0), m2.getMax(), 22); +		ensure_approximately_equals("Mean after merge (p1)", F32(0), m2.getMean(), 22); + +		ensure_equals("Source count of merge is undamaged (p1)", 0, m1.getCount()); +		ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(0), m1.getMin(), 22); +		ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(0), m1.getMax(), 22); +		ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(0), m1.getMean(), 22); +	} +} diff --git a/indra/newview/tests/llviewerassetstats_test.cpp b/indra/newview/tests/llviewerassetstats_test.cpp new file mode 100644 index 0000000000..1bb4fb7c0c --- /dev/null +++ b/indra/newview/tests/llviewerassetstats_test.cpp @@ -0,0 +1,990 @@ +/**  + * @file llviewerassetstats_tut.cpp + * @date 2010-10-28 + * @brief Test cases for some of newview/llviewerassetstats.cpp + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + *  + * Copyright (c) 2010, 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 <tut/tut.hpp> +#include <iostream> + +#include "lltut.h" +#include "../llviewerassetstats.h" +#include "lluuid.h" +#include "llsdutil.h" +#include "llregionhandle.h" + +static const char * all_keys[] =  +{ +	"duration", +	"fps", +	"get_other", +	"get_texture_temp_http", +	"get_texture_temp_udp", +	"get_texture_non_temp_http", +	"get_texture_non_temp_udp", +	"get_wearable_udp", +	"get_sound_udp", +	"get_gesture_udp" +}; + +static const char * resp_keys[] =  +{ +	"get_other", +	"get_texture_temp_http", +	"get_texture_temp_udp", +	"get_texture_non_temp_http", +	"get_texture_non_temp_udp", +	"get_wearable_udp", +	"get_sound_udp", +	"get_gesture_udp" +}; + +static const char * sub_keys[] = +{ +	"dequeued", +	"enqueued", +	"resp_count", +	"resp_max", +	"resp_min", +	"resp_mean" +}; + +static const char * mmm_resp_keys[] =  +{ +	"fps" +}; + +static const char * mmm_sub_keys[] = +{ +	"count", +	"max", +	"min", +	"mean" +}; + +static const LLUUID region1("4e2d81a3-6263-6ffe-ad5c-8ce04bee07e8"); +static const LLUUID region2("68762cc8-b68b-4e45-854b-e830734f2d4a"); +static const U64 region1_handle(0x0000040000003f00ULL); +static const U64 region2_handle(0x0000030000004200ULL); +static const std::string region1_handle_str("0000040000003f00"); +static const std::string region2_handle_str("0000030000004200"); + +#if 0 +static bool +is_empty_map(const LLSD & sd) +{ +	return sd.isMap() && 0 == sd.size(); +} + +static bool +is_single_key_map(const LLSD & sd, const std::string & key) +{ +	return sd.isMap() && 1 == sd.size() && sd.has(key); +} +#endif + +static bool +is_double_key_map(const LLSD & sd, const std::string & key1, const std::string & key2) +{ +	return sd.isMap() && 2 == sd.size() && sd.has(key1) && sd.has(key2); +} + +static bool +is_no_stats_map(const LLSD & sd) +{ +	return is_double_key_map(sd, "duration", "regions"); +} + +static bool +is_single_slot_array(const LLSD & sd, U64 region_handle) +{ +	U32 grid_x(0), grid_y(0); +	grid_from_region_handle(region_handle, &grid_x, &grid_y); +	 +	return (sd.isArray() && +			1 == sd.size() && +			sd[0].has("grid_x") && +			sd[0].has("grid_y") && +			sd[0]["grid_x"].isInteger() && +			sd[0]["grid_y"].isInteger() && +			grid_x == sd[0]["grid_x"].asInteger() && +			grid_y == sd[0]["grid_y"].asInteger()); +} + +static bool +is_double_slot_array(const LLSD & sd, U64 region_handle1, U64 region_handle2) +{ +	U32 grid_x1(0), grid_y1(0); +	U32 grid_x2(0), grid_y2(0); +	grid_from_region_handle(region_handle1, &grid_x1, &grid_y1); +	grid_from_region_handle(region_handle2, &grid_x2, &grid_y2); +	 +	return (sd.isArray() && +			2 == sd.size() && +			sd[0].has("grid_x") && +			sd[0].has("grid_y") && +			sd[0]["grid_x"].isInteger() && +			sd[0]["grid_y"].isInteger() && +			sd[1].has("grid_x") && +			sd[1].has("grid_y") && +			sd[1]["grid_x"].isInteger() && +			sd[1]["grid_y"].isInteger() && +			((grid_x1 == sd[0]["grid_x"].asInteger() && +			  grid_y1 == sd[0]["grid_y"].asInteger() && +			  grid_x2 == sd[1]["grid_x"].asInteger() && +			  grid_y2 == sd[1]["grid_y"].asInteger()) || +			 (grid_x1 == sd[1]["grid_x"].asInteger() && +			  grid_y1 == sd[1]["grid_y"].asInteger() && +			  grid_x2 == sd[0]["grid_x"].asInteger() && +			  grid_y2 == sd[0]["grid_y"].asInteger()))); +} + +static LLSD +get_region(const LLSD & sd, U64 region_handle1) +{ +	U32 grid_x(0), grid_y(0); +	grid_from_region_handle(region_handle1, &grid_x, &grid_y); + +	for (LLSD::array_const_iterator it(sd["regions"].beginArray()); +		 sd["regions"].endArray() != it; +		 ++it) +	{ +		if ((*it).has("grid_x") && +			(*it).has("grid_y") && +			(*it)["grid_x"].isInteger() && +			(*it)["grid_y"].isInteger() && +			(*it)["grid_x"].asInteger() == grid_x && +			(*it)["grid_y"].asInteger() == grid_y) +		{ +			return *it; +		} +	} +	return LLSD(); +} + +namespace tut +{ +	struct tst_viewerassetstats_index +	{}; +	typedef test_group<tst_viewerassetstats_index> tst_viewerassetstats_index_t; +	typedef tst_viewerassetstats_index_t::object tst_viewerassetstats_index_object_t; +	tut::tst_viewerassetstats_index_t tut_tst_viewerassetstats_index("tst_viewerassetstats_test"); + +	// Testing free functions without global stats allocated +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<1>() +	{ +		// Check that helpers aren't bothered by missing global stats +		ensure("Global gViewerAssetStatsMain should be NULL", (NULL == gViewerAssetStatsMain)); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); + +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + +		LLViewerAssetStatsFF::record_response_main(LLViewerAssetType::AT_GESTURE, false, false, 12300000ULL); +	} + +	// Create a non-global instance and check the structure +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<2>() +	{ +		ensure("Global gViewerAssetStatsMain should be NULL", (NULL == gViewerAssetStatsMain)); + +		LLViewerAssetStats * it = new LLViewerAssetStats(); + +		ensure("Global gViewerAssetStatsMain should still be NULL", (NULL == gViewerAssetStatsMain)); + +		LLSD sd_full = it->asLLSD(false); + +		// Default (NULL) region ID doesn't produce LLSD results so should +		// get an empty map back from output +		ensure("Stat-less LLSD initially", is_no_stats_map(sd_full)); + +		// Once the region is set, we will get a response even with no data collection +		it->setRegion(region1_handle); +		sd_full = it->asLLSD(false); +		ensure("Correct single-key LLSD map root", is_double_key_map(sd_full, "duration", "regions")); +		ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd_full["regions"], region1_handle)); +		 +		LLSD sd = sd_full["regions"][0]; + +		delete it; +			 +		// Check the structure of the LLSD +		for (int i = 0; i < LL_ARRAY_SIZE(all_keys); ++i) +		{ +			std::string line = llformat("Has '%s' key", all_keys[i]); +			ensure(line, sd.has(all_keys[i])); +		} + +		for (int i = 0; i < LL_ARRAY_SIZE(resp_keys); ++i) +		{ +			for (int j = 0; j < LL_ARRAY_SIZE(sub_keys); ++j) +			{ +				std::string line = llformat("Key '%s' has '%s' key", resp_keys[i], sub_keys[j]); +				ensure(line, sd[resp_keys[i]].has(sub_keys[j])); +			} +		} + +		for (int i = 0; i < LL_ARRAY_SIZE(mmm_resp_keys); ++i) +		{ +			for (int j = 0; j < LL_ARRAY_SIZE(mmm_sub_keys); ++j) +			{ +				std::string line = llformat("Key '%s' has '%s' key", mmm_resp_keys[i], mmm_sub_keys[j]); +				ensure(line, sd[mmm_resp_keys[i]].has(mmm_sub_keys[j])); +			} +		} +	} + +	// Create a non-global instance and check some content +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<3>() +	{ +		LLViewerAssetStats * it = new LLViewerAssetStats(); +		it->setRegion(region1_handle); +		 +		LLSD sd = it->asLLSD(false); +		ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); +		ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); +		sd = sd[0]; +		 +		delete it; + +		// Check a few points on the tree for content +		ensure("sd[get_texture_temp_http][dequeued] is 0", (0 == sd["get_texture_temp_http"]["dequeued"].asInteger())); +		ensure("sd[get_sound_udp][resp_min] is 0", (0.0 == sd["get_sound_udp"]["resp_min"].asReal())); +	} + +	// Create a global instance and verify free functions do something useful +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<4>() +	{ +		gViewerAssetStatsMain = new LLViewerAssetStats(); +		LLViewerAssetStatsFF::set_region_main(region1_handle); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + +		LLSD sd = gViewerAssetStatsMain->asLLSD(false); +		ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); +		ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); +		sd = sd["regions"][0]; +		 +		// Check a few points on the tree for content +		ensure("sd[get_texture_non_temp_udp][enqueued] is 1", (1 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); +		ensure("sd[get_texture_temp_udp][enqueued] is 0", (0 == sd["get_texture_temp_udp"]["enqueued"].asInteger())); +		ensure("sd[get_texture_non_temp_http][enqueued] is 0", (0 == sd["get_texture_non_temp_http"]["enqueued"].asInteger())); +		ensure("sd[get_texture_temp_http][enqueued] is 0", (0 == sd["get_texture_temp_http"]["enqueued"].asInteger())); +		ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + +		// Reset and check zeros... +		// Reset leaves current region in place +		gViewerAssetStatsMain->reset(); +		sd = gViewerAssetStatsMain->asLLSD(false)["regions"][region1_handle_str]; +		 +		delete gViewerAssetStatsMain; +		gViewerAssetStatsMain = NULL; + +		ensure("sd[get_texture_non_temp_udp][enqueued] is reset", (0 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); +		ensure("sd[get_gesture_udp][dequeued] is reset", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); +	} + +	// Create two global instances and verify no interactions +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<5>() +	{ +		gViewerAssetStatsThread1 = new LLViewerAssetStats(); +		gViewerAssetStatsMain = new LLViewerAssetStats(); +		LLViewerAssetStatsFF::set_region_main(region1_handle); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + +		LLSD sd = gViewerAssetStatsThread1->asLLSD(false); +		ensure("Other collector is empty", is_no_stats_map(sd)); +		sd = gViewerAssetStatsMain->asLLSD(false); +		ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); +		ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); +		sd = sd["regions"][0]; +		 +		// Check a few points on the tree for content +		ensure("sd[get_texture_non_temp_udp][enqueued] is 1", (1 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); +		ensure("sd[get_texture_temp_udp][enqueued] is 0", (0 == sd["get_texture_temp_udp"]["enqueued"].asInteger())); +		ensure("sd[get_texture_non_temp_http][enqueued] is 0", (0 == sd["get_texture_non_temp_http"]["enqueued"].asInteger())); +		ensure("sd[get_texture_temp_http][enqueued] is 0", (0 == sd["get_texture_temp_http"]["enqueued"].asInteger())); +		ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + +		// Reset and check zeros... +		// Reset leaves current region in place +		gViewerAssetStatsMain->reset(); +		sd = gViewerAssetStatsMain->asLLSD(false)["regions"][0]; +		 +		delete gViewerAssetStatsMain; +		gViewerAssetStatsMain = NULL; +		delete gViewerAssetStatsThread1; +		gViewerAssetStatsThread1 = NULL; + +		ensure("sd[get_texture_non_temp_udp][enqueued] is reset", (0 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); +		ensure("sd[get_gesture_udp][dequeued] is reset", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); +	} + +    // Check multiple region collection +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<6>() +	{ +		gViewerAssetStatsMain = new LLViewerAssetStats(); + +		LLViewerAssetStatsFF::set_region_main(region1_handle); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + +		LLViewerAssetStatsFF::set_region_main(region2_handle); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + +		LLSD sd = gViewerAssetStatsMain->asLLSD(false); + +		// std::cout << sd << std::endl; +		 +		ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions")); +		ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle)); +		LLSD sd1 = get_region(sd, region1_handle); +		LLSD sd2 = get_region(sd, region2_handle); +		ensure("Region1 is present in results", sd1.isMap()); +		ensure("Region2 is present in results", sd2.isMap()); +		 +		// Check a few points on the tree for content +		ensure_equals("sd1[get_texture_non_temp_udp][enqueued] is 1", sd1["get_texture_non_temp_udp"]["enqueued"].asInteger(), 1); +		ensure_equals("sd1[get_texture_temp_udp][enqueued] is 0", sd1["get_texture_temp_udp"]["enqueued"].asInteger(), 0); +		ensure_equals("sd1[get_texture_non_temp_http][enqueued] is 0", sd1["get_texture_non_temp_http"]["enqueued"].asInteger(), 0); +		ensure_equals("sd1[get_texture_temp_http][enqueued] is 0", sd1["get_texture_temp_http"]["enqueued"].asInteger(), 0); +		ensure_equals("sd1[get_gesture_udp][dequeued] is 0", sd1["get_gesture_udp"]["dequeued"].asInteger(), 0); + +		// Check a few points on the tree for content +		ensure("sd2[get_gesture_udp][enqueued] is 4", (4 == sd2["get_gesture_udp"]["enqueued"].asInteger())); +		ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger())); +		ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); + +		// Reset and check zeros... +		// Reset leaves current region in place +		gViewerAssetStatsMain->reset(); +		sd = gViewerAssetStatsMain->asLLSD(false); +		ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); +		ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle)); +		sd2 = sd["regions"][0]; +		 +		delete gViewerAssetStatsMain; +		gViewerAssetStatsMain = NULL; + +		ensure("sd2[get_texture_non_temp_udp][enqueued] is reset", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); +		ensure("sd2[get_gesture_udp][enqueued] is reset", (0 == sd2["get_gesture_udp"]["enqueued"].asInteger())); +	} + +    // Check multiple region collection jumping back-and-forth between regions +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<7>() +	{ +		gViewerAssetStatsMain = new LLViewerAssetStats(); + +		LLViewerAssetStatsFF::set_region_main(region1_handle); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + +		LLViewerAssetStatsFF::set_region_main(region2_handle); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + +		LLViewerAssetStatsFF::set_region_main(region1_handle); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, true, true); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, true, true); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + +		LLViewerAssetStatsFF::set_region_main(region2_handle); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + +		LLSD sd = gViewerAssetStatsMain->asLLSD(false); + +		ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions")); +		ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle)); +		LLSD sd1 = get_region(sd, region1_handle); +		LLSD sd2 = get_region(sd, region2_handle); +		ensure("Region1 is present in results", sd1.isMap()); +		ensure("Region2 is present in results", sd2.isMap()); +		 +		// Check a few points on the tree for content +		ensure("sd1[get_texture_non_temp_udp][enqueued] is 1", (1 == sd1["get_texture_non_temp_udp"]["enqueued"].asInteger())); +		ensure("sd1[get_texture_temp_udp][enqueued] is 0", (0 == sd1["get_texture_temp_udp"]["enqueued"].asInteger())); +		ensure("sd1[get_texture_non_temp_http][enqueued] is 0", (0 == sd1["get_texture_non_temp_http"]["enqueued"].asInteger())); +		ensure("sd1[get_texture_temp_http][enqueued] is 1", (1 == sd1["get_texture_temp_http"]["enqueued"].asInteger())); +		ensure("sd1[get_gesture_udp][dequeued] is 0", (0 == sd1["get_gesture_udp"]["dequeued"].asInteger())); + +		// Check a few points on the tree for content +		ensure("sd2[get_gesture_udp][enqueued] is 8", (8 == sd2["get_gesture_udp"]["enqueued"].asInteger())); +		ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger())); +		ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); + +		// Reset and check zeros... +		// Reset leaves current region in place +		gViewerAssetStatsMain->reset(); +		sd = gViewerAssetStatsMain->asLLSD(false); +		ensure("Correct single-key LLSD map root", is_double_key_map(sd, "duration", "regions")); +		ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle)); +		sd2 = get_region(sd, region2_handle); +		ensure("Region2 is present in results", sd2.isMap()); +		 +		delete gViewerAssetStatsMain; +		gViewerAssetStatsMain = NULL; + +		ensure_equals("sd2[get_texture_non_temp_udp][enqueued] is reset", sd2["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0); +		ensure_equals("sd2[get_gesture_udp][enqueued] is reset", sd2["get_gesture_udp"]["enqueued"].asInteger(), 0); +	} + +	// Non-texture assets ignore transport and persistence flags +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<8>() +	{ +		gViewerAssetStatsThread1 = new LLViewerAssetStats(); +		gViewerAssetStatsMain = new LLViewerAssetStats(); +		LLViewerAssetStatsFF::set_region_main(region1_handle); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, true); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, true); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, true, false); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, true, false); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, true, true); +		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, true, true); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, false, false); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, false, true); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, true, false); + +		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		LLSD sd = gViewerAssetStatsThread1->asLLSD(false); +		ensure("Other collector is empty", is_no_stats_map(sd)); +		sd = gViewerAssetStatsMain->asLLSD(false); +		ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); +		ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); +		sd = get_region(sd, region1_handle); +		ensure("Region1 is present in results", sd.isMap()); +		 +		// Check a few points on the tree for content +		ensure("sd[get_gesture_udp][enqueued] is 0", (0 == sd["get_gesture_udp"]["enqueued"].asInteger())); +		ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + +		ensure("sd[get_wearable_udp][enqueued] is 4", (4 == sd["get_wearable_udp"]["enqueued"].asInteger())); +		ensure("sd[get_wearable_udp][dequeued] is 4", (4 == sd["get_wearable_udp"]["dequeued"].asInteger())); + +		ensure("sd[get_other][enqueued] is 4", (4 == sd["get_other"]["enqueued"].asInteger())); +		ensure("sd[get_other][dequeued] is 0", (0 == sd["get_other"]["dequeued"].asInteger())); + +		// Reset and check zeros... +		// Reset leaves current region in place +		gViewerAssetStatsMain->reset(); +		sd = get_region(gViewerAssetStatsMain->asLLSD(false), region1_handle); +		ensure("Region1 is present in results", sd.isMap()); +		 +		delete gViewerAssetStatsMain; +		gViewerAssetStatsMain = NULL; +		delete gViewerAssetStatsThread1; +		gViewerAssetStatsThread1 = NULL; + +		ensure_equals("sd[get_texture_non_temp_udp][enqueued] is reset", sd["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0); +		ensure_equals("sd[get_gesture_udp][dequeued] is reset", sd["get_gesture_udp"]["dequeued"].asInteger(), 0); +	} + + +	// LLViewerAssetStats::merge() basic functions work +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<9>() +	{ +		LLViewerAssetStats s1; +		LLViewerAssetStats s2; + +		s1.setRegion(region1_handle); +		s2.setRegion(region1_handle); + +		s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 5000000); +		s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 6000000); +		s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 8000000); +		s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 7000000); +		s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 9000000); +		 +		s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 2000000); +		s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 3000000); +		s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 4000000); + +		s2.merge(s1); + +		LLSD s2_llsd = get_region(s2.asLLSD(false), region1_handle); +		ensure("Region1 is present in results", s2_llsd.isMap()); +		 +		ensure_equals("count after merge", s2_llsd["get_texture_temp_http"]["resp_count"].asInteger(), 8); +		ensure_approximately_equals("min after merge", s2_llsd["get_texture_temp_http"]["resp_min"].asReal(), 2.0, 22); +		ensure_approximately_equals("max after merge", s2_llsd["get_texture_temp_http"]["resp_max"].asReal(), 9.0, 22); +		ensure_approximately_equals("max after merge", s2_llsd["get_texture_temp_http"]["resp_mean"].asReal(), 5.5, 22); +	} + +	// LLViewerAssetStats::merge() basic functions work without corrupting source data +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<10>() +	{ +		LLViewerAssetStats s1; +		LLViewerAssetStats s2; + +		s1.setRegion(region1_handle); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 23289200); +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 282900); + +		 +		s2.setRegion(region2_handle); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 6500000); +		s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 10000); + +		{ +			s2.merge(s1); +			 +			LLSD src = s1.asLLSD(false); +			LLSD dst = s2.asLLSD(false); + +			ensure_equals("merge src has single region", src["regions"].size(), 1); +			ensure_equals("merge dst has dual regions", dst["regions"].size(), 2); +			 +			// Remove time stamps, they're a problem +			src.erase("duration"); +			src["regions"][0].erase("duration"); +			dst.erase("duration"); +			dst["regions"][0].erase("duration"); +			dst["regions"][1].erase("duration"); + +			LLSD s1_llsd = get_region(src, region1_handle); +			ensure("Region1 is present in src", s1_llsd.isMap()); +			LLSD s2_llsd = get_region(dst, region1_handle); +			ensure("Region1 is present in dst", s2_llsd.isMap()); + +			ensure("result from src is in dst", llsd_equals(s1_llsd, s2_llsd)); +		} + +		s1.setRegion(region1_handle); +		s2.setRegion(region1_handle); +		s1.reset(); +		s2.reset(); +		 +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 23289200); +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 282900); + +		 +		s2.setRegion(region1_handle); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 6500000); +		s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 10000); + +		{ +			s2.merge(s1); +			 +			LLSD src = s1.asLLSD(false); +			LLSD dst = s2.asLLSD(false); + +			ensure_equals("merge src has single region (p2)", src["regions"].size(), 1); +			ensure_equals("merge dst has single region (p2)", dst["regions"].size(), 1); + +			// Remove time stamps, they're a problem +			src.erase("duration"); +			src["regions"][0].erase("duration"); +			dst.erase("duration"); +			dst["regions"][0].erase("duration"); +			 +			LLSD s1_llsd = get_region(src, region1_handle); +			ensure("Region1 is present in src", s1_llsd.isMap()); +			LLSD s2_llsd = get_region(dst, region1_handle); +			ensure("Region1 is present in dst", s2_llsd.isMap()); + +			ensure_equals("src counts okay (enq)", s1_llsd["get_other"]["enqueued"].asInteger(), 4); +			ensure_equals("src counts okay (deq)", s1_llsd["get_other"]["dequeued"].asInteger(), 4); +			ensure_equals("src resp counts okay", s1_llsd["get_other"]["resp_count"].asInteger(), 2); +			ensure_approximately_equals("src respmin okay", s1_llsd["get_other"]["resp_min"].asReal(), 0.2829, 20); +			ensure_approximately_equals("src respmax okay", s1_llsd["get_other"]["resp_max"].asReal(), 23.2892, 20); +			 +			ensure_equals("dst counts okay (enq)", s2_llsd["get_other"]["enqueued"].asInteger(), 12); +			ensure_equals("src counts okay (deq)", s2_llsd["get_other"]["dequeued"].asInteger(), 11); +			ensure_equals("dst resp counts okay", s2_llsd["get_other"]["resp_count"].asInteger(), 4); +			ensure_approximately_equals("dst respmin okay", s2_llsd["get_other"]["resp_min"].asReal(), 0.010, 20); +			ensure_approximately_equals("dst respmax okay", s2_llsd["get_other"]["resp_max"].asReal(), 23.2892, 20); +		} +	} + + +    // Maximum merges are interesting when one side contributes nothing +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<11>() +	{ +		LLViewerAssetStats s1; +		LLViewerAssetStats s2; + +		s1.setRegion(region1_handle); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		// Want to test negative numbers here but have to work in U64 +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); + +		s2.setRegion(region1_handle); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		{ +			s2.merge(s1); +			 +			LLSD src = s1.asLLSD(false); +			LLSD dst = s2.asLLSD(false); + +			ensure_equals("merge src has single region", src["regions"].size(), 1); +			ensure_equals("merge dst has single region", dst["regions"].size(), 1); +			 +			// Remove time stamps, they're a problem +			src.erase("duration"); +			src["regions"][0].erase("duration"); +			dst.erase("duration"); +			dst["regions"][0].erase("duration"); + +			LLSD s2_llsd = get_region(dst, region1_handle); +			ensure("Region1 is present in dst", s2_llsd.isMap()); +			 +			ensure_equals("dst counts come from src only", s2_llsd["get_other"]["resp_count"].asInteger(), 3); + +			ensure_approximately_equals("dst maximum with count 0 does not contribute to merged maximum", +										s2_llsd["get_other"]["resp_max"].asReal(), F64(0.0), 20); +		} + +		// Other way around +		s1.setRegion(region1_handle); +		s2.setRegion(region1_handle); +		s1.reset(); +		s2.reset(); + +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		// Want to test negative numbers here but have to work in U64 +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); + +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		{ +			s1.merge(s2); +			 +			LLSD src = s2.asLLSD(false); +			LLSD dst = s1.asLLSD(false); + +			ensure_equals("merge src has single region", src["regions"].size(), 1); +			ensure_equals("merge dst has single region", dst["regions"].size(), 1); +			 +			// Remove time stamps, they're a problem +			src.erase("duration"); +			src["regions"][0].erase("duration"); +			dst.erase("duration"); +			dst["regions"][0].erase("duration"); + +			LLSD s2_llsd = get_region(dst, region1_handle); +			ensure("Region1 is present in dst", s2_llsd.isMap()); + +			ensure_equals("dst counts come from src only (flipped)", s2_llsd["get_other"]["resp_count"].asInteger(), 3); + +			ensure_approximately_equals("dst maximum with count 0 does not contribute to merged maximum (flipped)", +										s2_llsd["get_other"]["resp_max"].asReal(), F64(0.0), 20); +		} +	} + +    // Minimum merges are interesting when one side contributes nothing +	template<> template<> +	void tst_viewerassetstats_index_object_t::test<12>() +	{ +		LLViewerAssetStats s1; +		LLViewerAssetStats s2; + +		s1.setRegion(region1_handle); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 3800000); +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2700000); +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2900000); + +		s2.setRegion(region1_handle); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		{ +			s2.merge(s1); +			 +			LLSD src = s1.asLLSD(false); +			LLSD dst = s2.asLLSD(false); + +			ensure_equals("merge src has single region", src["regions"].size(), 1); +			ensure_equals("merge dst has single region", dst["regions"].size(), 1); +			 +			// Remove time stamps, they're a problem +			src.erase("duration"); +			src["regions"][0].erase("duration"); +			dst.erase("duration"); +			dst["regions"][0].erase("duration"); + +			LLSD s2_llsd = get_region(dst, region1_handle); +			ensure("Region1 is present in dst", s2_llsd.isMap()); + +			ensure_equals("dst counts come from src only", s2_llsd["get_other"]["resp_count"].asInteger(), 3); + +			ensure_approximately_equals("dst minimum with count 0 does not contribute to merged minimum", +										s2_llsd["get_other"]["resp_min"].asReal(), F64(2.7), 20); +		} + +		// Other way around +		s1.setRegion(region1_handle); +		s2.setRegion(region1_handle); +		s1.reset(); +		s2.reset(); + +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 3800000); +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2700000); +		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2900000); + +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); +		s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + +		{ +			s1.merge(s2); +			 +			LLSD src = s2.asLLSD(false); +			LLSD dst = s1.asLLSD(false); + +			ensure_equals("merge src has single region", src["regions"].size(), 1); +			ensure_equals("merge dst has single region", dst["regions"].size(), 1); +			 +			// Remove time stamps, they're a problem +			src.erase("duration"); +			src["regions"][0].erase("duration"); +			dst.erase("duration"); +			dst["regions"][0].erase("duration"); + +			LLSD s2_llsd = get_region(dst, region1_handle); +			ensure("Region1 is present in dst", s2_llsd.isMap()); + +			ensure_equals("dst counts come from src only (flipped)", s2_llsd["get_other"]["resp_count"].asInteger(), 3); + +			ensure_approximately_equals("dst minimum with count 0 does not contribute to merged minimum (flipped)", +										s2_llsd["get_other"]["resp_min"].asReal(), F64(2.7), 20); +		} +	} + +} | 
