diff options
44 files changed, 5062 insertions, 421 deletions
| @@ -43,3 +43,4 @@ dc6483491b4af559060bccaef8e9045a303212dd 2.4.0-beta1  dc6483491b4af559060bccaef8e9045a303212dd 2.4.0-beta1  3bc1f50a72e117f4d4ad8d555f0c785ea8cc201e 2.4.0-beta1  25bd6007e3d2fc15db9326ed4b18a24a5969a46a 2.4.0-beta2 +1ed382c6a08ba3850b6ce9061bc551ddece0ea07 2.4.0-release diff --git a/BuildParams b/BuildParams index 1676e003ba..57237afc1e 100755 --- a/BuildParams +++ b/BuildParams @@ -242,4 +242,22 @@ viewer-experience.public_build = false  viewer-experience.viewer_channel = "Second Life SkyLight Viewer"  viewer-experience.login_channel = "Second Life SkyLight Viewer" +# ================================================================= +# asset delivery 2010 projects +# ================================================================= +viewer-asset-delivery.viewer_channel = "Second Life Development" +viewer-asset-delivery.login_channel = "Second Life Development" +viewer-asset-delivery.build_viewer_update_version_manager = false +viewer-asset-delivery.email = monty@lindenlab.com +viewer-asset-delivery.build_server = false +viewer-asset-delivery.build_server_tests = false + +viewer-asset-delivery-metrics.viewer_channel = "Second Life Development" +viewer-asset-delivery-metrics.login_channel = "Second Life Development" +viewer-asset-delivery-metrics.build_viewer_update_version_manager = false +viewer-asset-delivery-metrics.email = monty@lindenlab.com +viewer-asset-delivery-metrics.build_server = false +viewer-asset-delivery-metrics.build_server_tests = false + +  # eof diff --git a/doc/contributions.txt b/doc/contributions.txt index be2b56a74c..e38a33b54d 100644 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -374,6 +374,9 @@ Khyota Wulluf  	VWR-9966  Kitty Barnett  	VWR-19699 +	STORM-288 +	STORM-799 +	STORM-800  Kunnis Basiat  	VWR-82  	VWR-102 @@ -776,10 +779,12 @@ WolfPup Lowenhar  	STORM-143  	STORM-255  	STORM-256 +	STORM-288  	STORM-535  	STORM-544  	STORM-654  	STORM-674 +	STORM-776  	VWR-20741  	VWR-20933  Zai Lynch diff --git a/indra/llcommon/llfasttimer_class.h b/indra/llcommon/llfasttimer_class.h index 2a645315c9..038a2d246a 100644 --- a/indra/llcommon/llfasttimer_class.h +++ b/indra/llcommon/llfasttimer_class.h @@ -31,16 +31,14 @@  #define FAST_TIMER_ON 1  #define TIME_FAST_TIMERS 0 -#define DEBUG_FAST_TIMER_THREADS 0 +#define DEBUG_FAST_TIMER_THREADS 1  class LLMutex;  #include <queue>  #include "llsd.h" -#if DEBUG_FAST_TIMER_THREADS -void assert_main_thread(); -#endif +LL_COMMON_API void assert_main_thread();  class LL_COMMON_API LLFastTimer  { 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/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index f0b7ac5def..b4617c5453 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -62,7 +62,7 @@ U32 ll_thread_local sThreadID = 0;  U32 LLThread::sIDIter = 0; -void assert_main_thread() +LL_COMMON_API void assert_main_thread()  {  	static U32 s_thread_id = LLThread::currentID();  	if (LLThread::currentID() != s_thread_id) diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp index c5864b30c7..aada16cc9a 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/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index e51f28e2e9..4f7b4be526 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -806,6 +806,69 @@ std::string LLUrlEntryPlace::getLocation(const std::string &url) const  }  // +// LLUrlEntryRegion Describes secondlife:///app/region/REGION_NAME/X/Y/Z URLs, e.g. +// secondlife:///app/region/Ahern/128/128/0 +// +LLUrlEntryRegion::LLUrlEntryRegion() +{ +	mPattern = boost::regex("secondlife:///app/region/[^/\\s]+(/\\d+)?(/\\d+)?(/\\d+)?/?", +							boost::regex::perl|boost::regex::icase); +	mMenuName = "menu_url_slurl.xml"; +	mTooltip = LLTrans::getString("TooltipSLURL"); +} + +std::string LLUrlEntryRegion::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ +	// +	// we handle SLURLs in the following formats: +	//   - secondlife:///app/region/Place/X/Y/Z +	//   - secondlife:///app/region/Place/X/Y +	//   - secondlife:///app/region/Place/X +	//   - secondlife:///app/region/Place +	// + +	LLSD path_array = LLURI(url).pathArray(); +	S32 path_parts = path_array.size(); + +	if (path_parts < 3) // no region name +	{ +		llwarns << "Failed to parse url [" << url << "]" << llendl; +		return url; +	} + +	std::string label = unescapeUrl(path_array[2]); // region name + +	if (path_parts > 3) // secondlife:///app/region/Place/X +	{ +		std::string x = path_array[3]; +		label += " (" + x; + +		if (path_parts > 4) // secondlife:///app/region/Place/X/Y +		{ +			std::string y = path_array[4]; +			label += "," + y; + +			if (path_parts > 5) // secondlife:///app/region/Place/X/Y/Z +			{ +				std::string z = path_array[5]; +				label = label + "," + z; +			} +		} + +		label += ")"; +	} + +	return label; +} + +std::string LLUrlEntryRegion::getLocation(const std::string &url) const +{ +	LLSD path_array = LLURI(url).pathArray(); +	std::string region_name = unescapeUrl(path_array[2]); +	return region_name; +} + +//  // LLUrlEntryTeleport Describes a Second Life teleport Url, e.g.,  // secondlife:///app/teleport/Ahern/50/50/50/  // x-grid-location-info://lincoln.lindenlab.com/app/teleport/Ahern/50/50/50/ diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 43a667c390..1791739061 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -302,6 +302,18 @@ public:  };  /// +/// LLUrlEntryRegion Describes a Second Life location Url, e.g., +/// secondlife:///app/region/Ahern/128/128/0 +/// +class LLUrlEntryRegion : public LLUrlEntryBase +{ +public: +	LLUrlEntryRegion(); +	/*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); +	/*virtual*/ std::string getLocation(const std::string &url) const; +}; + +///  /// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g.,  /// secondlife:///app/teleport/Ahern/50/50/50/  /// diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 478b412d5e..523ee5d78c 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -54,6 +54,7 @@ LLUrlRegistry::LLUrlRegistry()  	registerUrl(new LLUrlEntryGroup());  	registerUrl(new LLUrlEntryParcel());  	registerUrl(new LLUrlEntryTeleport()); +	registerUrl(new LLUrlEntryRegion());  	registerUrl(new LLUrlEntryWorldMap());  	registerUrl(new LLUrlEntryObjectIM());  	registerUrl(new LLUrlEntryPlace()); diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index 59c0826ad7..8f0a48018f 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -103,6 +103,45 @@ namespace tut  		ensure_equals(testname, url, expected);  	} +	void dummyCallback(const std::string &url, const std::string &label, const std::string& icon) +	{ +	} + +	void testLabel(const std::string &testname, LLUrlEntryBase &entry, +				   const char *text, const std::string &expected) +	{ +		boost::regex regex = entry.getPattern(); +		std::string label = ""; +		boost::cmatch result; +		bool found = boost::regex_search(text, result, regex); +		if (found) +		{ +			S32 start = static_cast<U32>(result[0].first - text); +			S32 end = static_cast<U32>(result[0].second - text); +			std::string url = std::string(text+start, end-start); +			label = entry.getLabel(url, boost::bind(dummyCallback, _1, _2, _3)); +		} +		ensure_equals(testname, label, expected); +	} + +	void testLocation(const std::string &testname, LLUrlEntryBase &entry, +					  const char *text, const std::string &expected) +	{ +		boost::regex regex = entry.getPattern(); +		std::string location = ""; +		boost::cmatch result; +		bool found = boost::regex_search(text, result, regex); +		if (found) +		{ +			S32 start = static_cast<U32>(result[0].first - text); +			S32 end = static_cast<U32>(result[0].second - text); +			std::string url = std::string(text+start, end-start); +			location = entry.getLocation(url); +		} +		ensure_equals(testname, location, expected); +	} + +  	template<> template<>  	void object::test<1>()  	{ @@ -697,4 +736,114 @@ namespace tut  				  "<nolink>My Object</nolink>",  				  "My Object");  	} + +	template<> template<> +	void object::test<13>() +	{ +		// +		// test LLUrlEntryRegion - secondlife:///app/region/<location> URLs +		// +		LLUrlEntryRegion url; + +		// Regex tests. +		testRegex("no valid region", url, +				  "secondlife:///app/region/", +				  ""); + +		testRegex("invalid coords", url, +				  "secondlife:///app/region/Korea2/a/b/c", +				  "secondlife:///app/region/Korea2/"); // don't count invalid coords + +		testRegex("Ahern (50,50,50) [1]", url, +				  "secondlife:///app/region/Ahern/50/50/50/", +				  "secondlife:///app/region/Ahern/50/50/50/"); + +		testRegex("Ahern (50,50,50) [2]", url, +				  "XXX secondlife:///app/region/Ahern/50/50/50/ XXX", +				  "secondlife:///app/region/Ahern/50/50/50/"); + +		testRegex("Ahern (50,50,50) [3]", url, +				  "XXX secondlife:///app/region/Ahern/50/50/50 XXX", +				  "secondlife:///app/region/Ahern/50/50/50"); + +		testRegex("Ahern (50,50,50) multicase", url, +				  "XXX secondlife:///app/region/Ahern/50/50/50/ XXX", +				  "secondlife:///app/region/Ahern/50/50/50/"); + +		testRegex("Ahern (50,50) [1]", url, +				  "XXX secondlife:///app/region/Ahern/50/50/ XXX", +				  "secondlife:///app/region/Ahern/50/50/"); + +		testRegex("Ahern (50,50) [2]", url, +				  "XXX secondlife:///app/region/Ahern/50/50 XXX", +				  "secondlife:///app/region/Ahern/50/50"); + +		// DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat +		testRegex("Region with brackets", url, +				  "XXX secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30 XXX", +				  "secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30"); + +		// DEV-35459: SLURLs and teleport Links not parsed properly +		testRegex("Region with quote", url, +				  "XXX secondlife:///app/region/A'ksha%20Oasis/41/166/701 XXX", +			          "secondlife:///app/region/A%27ksha%20Oasis/41/166/701"); + +		// Rendering tests. +		testLabel("Render /app/region/Ahern/50/50/50/", url, +			"secondlife:///app/region/Ahern/50/50/50/", +			"Ahern (50,50,50)"); + +		testLabel("Render /app/region/Ahern/50/50/50", url, +			"secondlife:///app/region/Ahern/50/50/50", +			"Ahern (50,50,50)"); + +		testLabel("Render /app/region/Ahern/50/50/", url, +			"secondlife:///app/region/Ahern/50/50/", +			"Ahern (50,50)"); + +		testLabel("Render /app/region/Ahern/50/50", url, +			"secondlife:///app/region/Ahern/50/50", +			"Ahern (50,50)"); + +		testLabel("Render /app/region/Ahern/50/", url, +			"secondlife:///app/region/Ahern/50/", +			"Ahern (50)"); + +		testLabel("Render /app/region/Ahern/50", url, +			"secondlife:///app/region/Ahern/50", +			"Ahern (50)"); + +		testLabel("Render /app/region/Ahern/", url, +			"secondlife:///app/region/Ahern/", +			"Ahern"); + +		testLabel("Render /app/region/Ahern/ within context", url, +			"XXX secondlife:///app/region/Ahern/ XXX", +			"Ahern"); + +		testLabel("Render /app/region/Ahern", url, +			"secondlife:///app/region/Ahern", +			"Ahern"); + +		testLabel("Render /app/region/Ahern within context", url, +			"XXX secondlife:///app/region/Ahern XXX", +			"Ahern"); + +		testLabel("Render /app/region/Product%20Engine/", url, +			"secondlife:///app/region/Product%20Engine/", +			"Product Engine"); + +		testLabel("Render /app/region/Product%20Engine", url, +			"secondlife:///app/region/Product%20Engine", +			"Product Engine"); + +		// Location parsing texts. +		testLocation("Location /app/region/Ahern/50/50/50/", url, +			"secondlife:///app/region/Ahern/50/50/50/", +			"Ahern"); + +		testLocation("Location /app/region/Product%20Engine", url, +			"secondlife:///app/region/Product%20Engine", +			"Product Engine"); +	}  } diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index fe3e850a03..c6b1266206 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -493,6 +493,7 @@ set(viewer_SOURCE_FILES      llvectorperfoptions.cpp      llversioninfo.cpp      llviewchildren.cpp +    llviewerassetstats.cpp      llviewerassetstorage.cpp      llviewerassettype.cpp      llviewerattachmenu.cpp @@ -1032,6 +1033,7 @@ set(viewer_HEADER_FILES      llvectorperfoptions.h      llversioninfo.h      llviewchildren.h +    llviewerassetstats.h      llviewerassetstorage.h      llviewerassettype.h      llviewerattachmenu.h @@ -1990,6 +1992,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 4a23081069..cc8491911d 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/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 80734b0d41..0e080e713b 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -2759,75 +2759,6 @@ BOOL LLAppearanceMgr::getIsProtectedCOFItem(const LLUUID& obj_id) const  	*/  } -// Shim class to allow arbitrary boost::bind -// expressions to be run as one-time idle callbacks. -// -// TODO: rework idle function spec to take a boost::function in the first place. -class OnIdleCallbackOneTime -{ -public: -	OnIdleCallbackOneTime(nullary_func_t callable): -		mCallable(callable) -	{ -	} -	static void onIdle(void *data) -	{ -		gIdleCallbacks.deleteFunction(onIdle, data); -		OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data); -		self->call(); -		delete self; -	} -	void call() -	{ -		mCallable(); -	} -private: -	nullary_func_t mCallable; -}; - -void doOnIdleOneTime(nullary_func_t callable) -{ -	OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); -	gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); -} - -// Shim class to allow generic boost functions to be run as -// recurring idle callbacks.  Callable should return true when done, -// false to continue getting called. -// -// TODO: rework idle function spec to take a boost::function in the first place. -class OnIdleCallbackRepeating -{ -public: -	OnIdleCallbackRepeating(bool_func_t callable): -		mCallable(callable) -	{ -	} -	// Will keep getting called until the callable returns true. -	static void onIdle(void *data) -	{ -		OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data); -		bool done = self->call(); -		if (done) -		{ -			gIdleCallbacks.deleteFunction(onIdle, data); -			delete self; -		} -	} -	bool call() -	{ -		return mCallable(); -	} -private: -	bool_func_t mCallable; -}; - -void doOnIdleRepeating(bool_func_t callable) -{ -	OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); -	gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); -} -  class CallAfterCategoryFetchStage2: public LLInventoryFetchItemsObserver  {  public: diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index c65d9dc9ee..4b1d95cf25 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -248,15 +248,6 @@ private:  LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id,const std::string& name); -typedef boost::function<void ()> nullary_func_t; -typedef boost::function<bool ()> bool_func_t; - -// Call a given callable once in idle loop. -void doOnIdleOneTime(nullary_func_t callable); - -// Repeatedly call a callable in idle loop until it returns true. -void doOnIdleRepeating(bool_func_t callable); -  // Invoke a given callable after category contents are fully fetched.  void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index ea2348ea25..08f5cb4685 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -195,6 +195,7 @@  #include "llparcel.h"  #include "llavatariconctrl.h"  #include "llgroupiconctrl.h" +#include "llviewerassetstats.h"  // Include for security api initialization  #include "llsecapi.h" @@ -338,6 +339,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 @@ -659,6 +668,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(); @@ -1741,6 +1765,8 @@ bool LLAppViewer::cleanup()  	LLWatchdog::getInstance()->cleanup(); +	LLViewerAssetStatsFF::cleanup(); +	  	llinfos << "Shutting down message system" << llendflush;  	end_messaging_system(); @@ -1807,7 +1833,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) @@ -3076,6 +3105,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())); @@ -3857,6 +3889,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);  		}  	} @@ -3899,6 +3936,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; @@ -4806,3 +4855,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/llcallbacklist.cpp b/indra/newview/llcallbacklist.cpp index a54c77b4a0..357a6582d1 100644 --- a/indra/newview/llcallbacklist.cpp +++ b/indra/newview/llcallbacklist.cpp @@ -115,6 +115,71 @@ void LLCallbackList::callFunctions()  	}  } +// Shim class to allow arbitrary boost::bind +// expressions to be run as one-time idle callbacks. +class OnIdleCallbackOneTime +{ +public: +	OnIdleCallbackOneTime(nullary_func_t callable): +		mCallable(callable) +	{ +	} +	static void onIdle(void *data) +	{ +		gIdleCallbacks.deleteFunction(onIdle, data); +		OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data); +		self->call(); +		delete self; +	} +	void call() +	{ +		mCallable(); +	} +private: +	nullary_func_t mCallable; +}; + +void doOnIdleOneTime(nullary_func_t callable) +{ +	OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); +	gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); +} + +// Shim class to allow generic boost functions to be run as +// recurring idle callbacks.  Callable should return true when done, +// false to continue getting called. +class OnIdleCallbackRepeating +{ +public: +	OnIdleCallbackRepeating(bool_func_t callable): +		mCallable(callable) +	{ +	} +	// Will keep getting called until the callable returns true. +	static void onIdle(void *data) +	{ +		OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data); +		bool done = self->call(); +		if (done) +		{ +			gIdleCallbacks.deleteFunction(onIdle, data); +			delete self; +		} +	} +	bool call() +	{ +		return mCallable(); +	} +private: +	bool_func_t mCallable; +}; + +void doOnIdleRepeating(bool_func_t callable) +{ +	OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); +	gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); +} +  #ifdef _DEBUG  void test1(void *data) diff --git a/indra/newview/llcallbacklist.h b/indra/newview/llcallbacklist.h index 07ac21f5e9..97f3bfd9ee 100644 --- a/indra/newview/llcallbacklist.h +++ b/indra/newview/llcallbacklist.h @@ -52,6 +52,15 @@ protected:  	callback_list_t	mCallbackList;  }; +typedef boost::function<void ()> nullary_func_t; +typedef boost::function<bool ()> bool_func_t; + +// Call a given callable once in idle loop. +void doOnIdleOneTime(nullary_func_t callable); + +// Repeatedly call a callable in idle loop until it returns true. +void doOnIdleRepeating(bool_func_t callable); +  extern LLCallbackList gIdleCallbacks;  #endif diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 87ada50e9a..1050e86060 100755 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -94,8 +94,7 @@  #include "lltoggleablemenu.h"  #include "llvfile.h"  #include "llvfs.h" - - +#include "llcallbacklist.h"  #include "glod/glod.h" @@ -108,7 +107,6 @@ const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PRE  const S32 PREVIEW_HPAD = PREVIEW_RESIZE_HANDLE_SIZE;  const S32 PREF_BUTTON_HEIGHT = 16 + 7 + 16;  const S32 PREVIEW_TEXTURE_HEIGHT = 300; -const S32 NUM_LOD = 4;  void drawBoxOutline(const LLVector3& pos, const LLVector3& size); @@ -483,12 +481,10 @@ void LLFloaterModelPreview::onPreviewLODCommit(LLUICtrl* ctrl, void* userdata)  	S32 which_mode = 0; -	LLCtrlSelectionInterface* iface = fp->childGetSelectionInterface("preview_lod_combo"); -	if (iface) -	{ -		which_mode = iface->getFirstSelectedIndex(); -	} -	which_mode = (NUM_LOD-1)-which_mode; // combo box list of lods is in reverse order +	LLComboBox* combo = (LLComboBox*) ctrl;
 +	
 +	which_mode = (NUM_LOD-1)-combo->getFirstSelectedIndex(); // combo box list of lods is in reverse order
 +
  	fp->mModelPreview->setPreviewLOD(which_mode);  } @@ -1581,8 +1577,8 @@ void LLModelLoader::run()  		}  		processElement(scene); -		 -		mPreview->loadModelCallback(mLod); + +		doOnIdleOneTime(boost::bind(&LLModelPreview::loadModelCallback,mPreview,mLod));  	}  } @@ -2034,6 +2030,8 @@ LLModelPreview::~LLModelPreview()  U32 LLModelPreview::calcResourceCost()  { +	assert_main_thread(); +	  	rebuildUploadData();  	if ( mModelLoader->getLoadState() != LLModelLoader::ERROR_PARSING ) @@ -2115,6 +2113,8 @@ U32 LLModelPreview::calcResourceCost()  void LLModelPreview::rebuildUploadData()  { +	assert_main_thread(); +	  	mUploadData.clear();  	mTextureSet.clear(); @@ -2223,6 +2223,8 @@ void LLModelPreview::clearModel(S32 lod)  void LLModelPreview::loadModel(std::string filename, S32 lod)  { +	assert_main_thread(); +	  	LLMutexLock lock(this);  	if (mModelLoader) @@ -2278,6 +2280,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod)  void LLModelPreview::setPhysicsFromLOD(S32 lod)  { +	assert_main_thread(); +	  	if (lod >= 0 && lod <= 3)  	{  		mModel[LLModel::LOD_PHYSICS] = mModel[lod]; @@ -2333,7 +2337,9 @@ void LLModelPreview::clearGLODGroup()  }  void LLModelPreview::loadModelCallback(S32 lod) -{ //NOT the main thread +{ +	assert_main_thread(); +  	LLMutexLock lock(this);  	if (!mModelLoader)  	{ @@ -2388,6 +2394,8 @@ void LLModelPreview::resetPreviewTarget()  void LLModelPreview::generateNormals()  { +	assert_main_thread(); +	  	S32 which_lod = mPreviewLOD; @@ -2655,7 +2663,7 @@ bool LLModelPreview::containsRiggedAsset( void )  	}  	return false;  } -void LLModelPreview::genLODs(S32 which_lod) +void LLModelPreview::genLODs(S32 which_lod, U32 decimation)  {  	if (mBaseModel.empty())  	{ @@ -2872,7 +2880,7 @@ void LLModelPreview::genLODs(S32 which_lod)  		{  			if (lod < start)  			{ -				triangle_count /= 3; +				triangle_count /= decimation;  			}  		}  		else @@ -3030,6 +3038,8 @@ void LLModelPreview::genLODs(S32 which_lod)  void LLModelPreview::updateStatusMessages()  { +	assert_main_thread(); +	  	//triangle/vertex/submesh count for each mesh asset for each lod  	std::vector<S32> tris[LLModel::NUM_LODS];  	std::vector<S32> verts[LLModel::NUM_LODS]; @@ -3314,7 +3324,7 @@ void LLModelPreview::updateStatusMessages()  	} -	else if (mFMP->childGetValue("lod_auto_generate").asBoolean()) +	else if (mFMP->childGetValue("lod_none").asBoolean())  	{  		for (U32 i = 0; i < num_file_controls; ++i)  		{ @@ -3323,6 +3333,31 @@ void LLModelPreview::updateStatusMessages()  		for (U32 i = 0; i < num_lod_controls; ++i)  		{ +			mFMP->childDisable(lod_controls[i]); +		} + +		if (!mModel[mPreviewLOD].empty()) +		{ +			mModel[mPreviewLOD].clear(); +			mScene[mPreviewLOD].clear(); +			mVertexBuffer[mPreviewLOD].clear(); + +			//this can cause phasing issues with the UI, so reenter this function and return +			updateStatusMessages(); +			return; +		} +		 +	 +	} +	else +	{	// auto generate, also the default case for wizard which has no radio selection +		for (U32 i = 0; i < num_file_controls; ++i) +		{ +			mFMP->childDisable(file_controls[i]); +		} + +		for (U32 i = 0; i < num_lod_controls; ++i) +		{  			mFMP->childEnable(lod_controls[i]);  		} @@ -3355,29 +3390,6 @@ void LLModelPreview::updateStatusMessages()  			}  		}  	} -	else -	{ // "None" is chosen -		for (U32 i = 0; i < num_file_controls; ++i) -		{ -			mFMP->childDisable(file_controls[i]); -		} - -		for (U32 i = 0; i < num_lod_controls; ++i) -		{ -			mFMP->childDisable(lod_controls[i]); -		} - -		if (!mModel[mPreviewLOD].empty()) -		{ -			mModel[mPreviewLOD].clear(); -			mScene[mPreviewLOD].clear(); -			mVertexBuffer[mPreviewLOD].clear(); - -			//this can cause phasing issues with the UI, so reenter this function and return -			updateStatusMessages(); -			return; -		} -	}  	if (mFMP->childGetValue("physics_load_from_file").asBoolean())  	{ @@ -3561,6 +3573,8 @@ void LLModelPreview::update()  //-----------------------------------------------------------------------------  BOOL LLModelPreview::render()  { +	assert_main_thread(); +	  	LLMutexLock lock(this);  	mNeedsUpdate = FALSE; @@ -4086,6 +4100,8 @@ void LLModelPreview::setPreviewLOD(S32 lod)  //static   void LLFloaterModelPreview::onBrowseLOD(void* data)  { +	assert_main_thread(); +	  	LLFloaterModelPreview* mp = (LLFloaterModelPreview*) data;  	mp->loadModel(mp->mModelPreview->mPreviewLOD);  } @@ -4093,6 +4109,8 @@ void LLFloaterModelPreview::onBrowseLOD(void* data)  //static  void LLFloaterModelPreview::onUpload(void* user_data)  { +	assert_main_thread(); +	  	LLFloaterModelPreview* mp = (LLFloaterModelPreview*) user_data;  	mp->mModelPreview->rebuildUploadData(); diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h index f7873cddbb..8c7ab39e55 100644 --- a/indra/newview/llfloatermodelpreview.h +++ b/indra/newview/llfloatermodelpreview.h @@ -52,6 +52,8 @@ class domTranslate;  class LLMenuButton;  class LLToggleableMenu; +const S32 NUM_LOD = 4;
 +  class LLModelLoader : public LLThread  {  public: @@ -261,7 +263,7 @@ class LLModelPreview : public LLViewerDynamicTexture, public LLMutex  	void clearModel(S32 lod);  	void loadModel(std::string filename, S32 lod);  	void loadModelCallback(S32 lod); -	void genLODs(S32 which_lod = -1); +	void genLODs(S32 which_lod = -1, U32 decimation = 3);  	void generateNormals();  	void consolidate();  	void clearMaterials(); @@ -281,7 +283,7 @@ class LLModelPreview : public LLViewerDynamicTexture, public LLMutex  	friend class LLFloaterModelPreview::DecompRequest;  	friend class LLPhysicsDecomp; -	LLFloater* mFMP; +	LLFloater*  mFMP;  	BOOL        mNeedsUpdate;  	bool		mDirty; diff --git a/indra/newview/llfloatermodelwizard.cpp b/indra/newview/llfloatermodelwizard.cpp index 79c29ef017..aca5d67e60 100644 --- a/indra/newview/llfloatermodelwizard.cpp +++ b/indra/newview/llfloatermodelwizard.cpp @@ -28,19 +28,73 @@  #include "llviewerprecompiledheaders.h" +#include "llbutton.h"  #include "lldrawable.h" +#include "llcombobox.h"  #include "llfloater.h"  #include "llfloatermodelwizard.h"  #include "llfloatermodelpreview.h"  #include "llfloaterreg.h" +#include "llslider.h" +#include "lltoolmgr.h" +#include "llviewerwindow.h" +static	const std::string stateNames[]={ +	"choose_file", +	"optimize", +	"physics", +	"review", +	"upload"};  LLFloaterModelWizard::LLFloaterModelWizard(const LLSD& key)  	: LLFloater(key)  {  } +void LLFloaterModelWizard::setState(int state) +{ +	mState = state; +	setButtons(state); + +	for(size_t t=0; t<LL_ARRAY_SIZE(stateNames); ++t) +	{ +		LLView *view = getChild<LLView>(stateNames[t]+"_panel"); +		if (view)  +		{ +			view->setVisible(state == (int) t ? TRUE : FALSE); +		} +	} + +	if (state == OPTIMIZE) +	{ +		mModelPreview->genLODs(-1); +	} +} + +void LLFloaterModelWizard::setButtons(int state) +{ +	for(size_t i=0; i<LL_ARRAY_SIZE(stateNames); ++i) +	{ +		LLButton *button = getChild<LLButton>(stateNames[i]+"_btn"); + +		if (i < state) +		{ +			button->setEnabled(TRUE); +			button->setToggleState(FALSE); +		} +		else if (i == state) +		{ +			button->setEnabled(TRUE); +			button->setToggleState(TRUE); +		} +		else +		{ +			button->setEnabled(FALSE); +		} +	} +} +  void LLFloaterModelWizard::loadModel()  {  	 mModelPreview->mLoading = TRUE; @@ -48,6 +102,131 @@ void LLFloaterModelWizard::loadModel()  	(new LLMeshFilePicker(mModelPreview, 3))->getFile();  } +void LLFloaterModelWizard::onClickCancel() +{ +	closeFloater(); +} + +void LLFloaterModelWizard::onClickBack() +{ +	setState(llmax((int) CHOOSE_FILE, mState-1)); +} + +void LLFloaterModelWizard::onClickNext() +{ +	setState(llmin((int) UPLOAD, mState+1)); +} + +bool LLFloaterModelWizard::onEnableNext() +{ +	return true; +} + +bool LLFloaterModelWizard::onEnableBack() +{ +	return true; +} + + +//----------------------------------------------------------------------------- +// handleMouseDown() +//----------------------------------------------------------------------------- +BOOL LLFloaterModelWizard::handleMouseDown(S32 x, S32 y, MASK mask) +{ +	if (mPreviewRect.pointInRect(x, y)) +	{ +		bringToFront( x, y ); +		gFocusMgr.setMouseCapture(this); +		gViewerWindow->hideCursor(); +		mLastMouseX = x; +		mLastMouseY = y; +		return TRUE; +	} +	 +	return LLFloater::handleMouseDown(x, y, mask); +} + +//----------------------------------------------------------------------------- +// handleMouseUp() +//----------------------------------------------------------------------------- +BOOL LLFloaterModelWizard::handleMouseUp(S32 x, S32 y, MASK mask) +{ +	gFocusMgr.setMouseCapture(FALSE); +	gViewerWindow->showCursor(); +	return LLFloater::handleMouseUp(x, y, mask); +} + +//----------------------------------------------------------------------------- +// handleHover() +//----------------------------------------------------------------------------- +BOOL LLFloaterModelWizard::handleHover	(S32 x, S32 y, MASK mask) +{ +	MASK local_mask = mask & ~MASK_ALT; +	 +	if (mModelPreview && hasMouseCapture()) +	{ +		if (local_mask == MASK_PAN) +		{ +			// pan here +			mModelPreview->pan((F32)(x - mLastMouseX) * -0.005f, (F32)(y - mLastMouseY) * -0.005f); +		} +		else if (local_mask == MASK_ORBIT) +		{ +			F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; +			F32 pitch_radians = (F32)(y - mLastMouseY) * 0.02f; +			 +			mModelPreview->rotate(yaw_radians, pitch_radians); +		} +		else  +		{ +			 +			F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; +			F32 zoom_amt = (F32)(y - mLastMouseY) * 0.02f; +			 +			mModelPreview->rotate(yaw_radians, 0.f); +			mModelPreview->zoom(zoom_amt); +		} +		 +		 +		mModelPreview->refresh(); +		 +		LLUI::setMousePositionLocal(this, mLastMouseX, mLastMouseY); +	} +	 +	if (!mPreviewRect.pointInRect(x, y) || !mModelPreview) +	{ +		return LLFloater::handleHover(x, y, mask); +	} +	else if (local_mask == MASK_ORBIT) +	{ +		gViewerWindow->setCursor(UI_CURSOR_TOOLCAMERA); +	} +	else if (local_mask == MASK_PAN) +	{ +		gViewerWindow->setCursor(UI_CURSOR_TOOLPAN); +	} +	else +	{ +		gViewerWindow->setCursor(UI_CURSOR_TOOLZOOMIN); +	} +	 +	return TRUE; +} + +//----------------------------------------------------------------------------- +// handleScrollWheel() +//----------------------------------------------------------------------------- +BOOL LLFloaterModelWizard::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ +	if (mPreviewRect.pointInRect(x, y) && mModelPreview) +	{ +		mModelPreview->zoom((F32)clicks * -0.2f); +		mModelPreview->refresh(); +	} +	 +	return TRUE; +} +  BOOL LLFloaterModelWizard::postBuild()  { @@ -56,7 +235,20 @@ BOOL LLFloaterModelWizard::postBuild()  	childSetValue("import_scale", (F32) 0.67335826);  	getChild<LLUICtrl>("browse")->setCommitCallback(boost::bind(&LLFloaterModelWizard::loadModel, this)); +	getChild<LLUICtrl>("cancel")->setCommitCallback(boost::bind(&LLFloaterModelWizard::onClickCancel, this)); +	getChild<LLUICtrl>("back")->setCommitCallback(boost::bind(&LLFloaterModelWizard::onClickBack, this)); +	getChild<LLUICtrl>("next")->setCommitCallback(boost::bind(&LLFloaterModelWizard::onClickNext, this)); +	childSetCommitCallback("preview_lod_combo", onPreviewLODCommit, this); +	getChild<LLUICtrl>("accuracy_slider")->setCommitCallback(boost::bind(&LLFloaterModelWizard::onAccuracyPerformance, this, _2)); + +	childSetAction("ok_btn", onUpload, this); +	LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; +	 +	enable_registrar.add("Next.OnEnable", boost::bind(&LLFloaterModelWizard::onEnableNext, this)); +	enable_registrar.add("Back.OnEnable", boost::bind(&LLFloaterModelWizard::onEnableBack, this)); + +	  	mPreviewRect = preview_panel->getRect();  	mModelPreview = new LLModelPreview(512, 512, this); @@ -64,9 +256,55 @@ BOOL LLFloaterModelWizard::postBuild()  	center(); +	setState(CHOOSE_FILE); + +	childSetTextArg("import_dimensions", "[X]", LLStringUtil::null); +	childSetTextArg("import_dimensions", "[Y]", LLStringUtil::null); +	childSetTextArg("import_dimensions", "[Z]", LLStringUtil::null); +  	return TRUE;  } +void LLFloaterModelWizard::onUpload(void* user_data) +{ +	LLFloaterModelWizard* mp = (LLFloaterModelWizard*) user_data; +	 +	mp->mModelPreview->rebuildUploadData(); +	 +	gMeshRepo.uploadModel(mp->mModelPreview->mUploadData, mp->mModelPreview->mPreviewScale,  +						  mp->childGetValue("upload_textures").asBoolean(), mp->childGetValue("upload_skin"), mp->childGetValue("upload_joints")); +	 +	mp->closeFloater(false); +} + + +void LLFloaterModelWizard::onAccuracyPerformance(const LLSD& data) +{ +	int val = (int) data.asInteger(); + +	mModelPreview->genLODs(-1, NUM_LOD-val); + +	mModelPreview->refresh(); +} + +void LLFloaterModelWizard::onPreviewLODCommit(LLUICtrl* ctrl, void* userdata) +{ +	LLFloaterModelWizard *fp =(LLFloaterModelWizard *)userdata; +	 +	if (!fp->mModelPreview) +	{ +		return; +	} +	 +	S32 which_mode = 0; +	 +	LLComboBox* combo = (LLComboBox*) ctrl; +	 +	which_mode = (NUM_LOD-1)-combo->getFirstSelectedIndex(); // combo box list of lods is in reverse order + +	fp->mModelPreview->setPreviewLOD(which_mode); +} +  void LLFloaterModelWizard::draw()  {  	LLFloater::draw(); @@ -74,14 +312,14 @@ void LLFloaterModelWizard::draw()  	mModelPreview->update(); -	if (mModelPreview && mModelPreview->mModelLoader) +	if (mModelPreview)  	{  		gGL.color3f(1.f, 1.f, 1.f);  		gGL.getTexUnit(0)->bind(mModelPreview); - -		LLView* preview_panel = getChild<LLView>("preview_panel"); +		LLView *view = getChild<LLView>(stateNames[mState]+"_panel"); +		LLView* preview_panel = view->getChild<LLView>("preview_panel");  		LLRect rect = preview_panel->getRect();  		if (rect != mPreviewRect) diff --git a/indra/newview/llfloatermodelwizard.h b/indra/newview/llfloatermodelwizard.h index c766697d47..b7fd28aa9d 100644 --- a/indra/newview/llfloatermodelwizard.h +++ b/indra/newview/llfloatermodelwizard.h @@ -35,24 +35,43 @@ public:  	virtual ~LLFloaterModelWizard() {};  	/*virtual*/	BOOL	postBuild();  	void			draw(); -	void loadModel(); -	//void onSave(); -	//void onReset(); -	//void onCancel(); -	///*virtual*/ void onOpen(const LLSD& key); +	BOOL handleMouseDown(S32 x, S32 y, MASK mask); +	BOOL handleMouseUp(S32 x, S32 y, MASK mask); +	BOOL handleHover(S32 x, S32 y, MASK mask); +	BOOL handleScrollWheel(S32 x, S32 y, S32 clicks);  +  private: -	 +	enum EWizardState +	{ +		CHOOSE_FILE = 0, +		OPTIMIZE, +		PHYSICS, +		REVIEW, +		UPLOAD +	}; + +	void setState(int state); +	void setButtons(int state); +	void onClickCancel(); +	void onClickBack(); +	void onClickNext(); +	bool onEnableNext(); +	bool onEnableBack(); +	void loadModel(); +	static void	onPreviewLODCommit(LLUICtrl*,void*); +	void onAccuracyPerformance(const LLSD& data); +	static void onUpload(void* data); +  	LLModelPreview*	mModelPreview;  	LLRect			mPreviewRect; +	int mState; + +	S32				mLastMouseX; +	S32				mLastMouseY; + +  }; -/* -namespace LLFloaterDisplayNameUtil -{ -	// Register with LLFloaterReg -	void registerFloater(); -} -*/  #endif diff --git a/indra/newview/llinspecttoast.cpp b/indra/newview/llinspecttoast.cpp index 58b3f0309f..d7b82667d1 100644 --- a/indra/newview/llinspecttoast.cpp +++ b/indra/newview/llinspecttoast.cpp @@ -46,6 +46,7 @@ public:  	virtual ~LLInspectToast();  	/*virtual*/ void onOpen(const LLSD& notification_id); +	/*virtual*/ BOOL handleToolTip(S32 x, S32 y, MASK mask);  private:  	void onToastDestroy(LLToast * toast); @@ -73,6 +74,7 @@ LLInspectToast::~LLInspectToast()  	LLTransientFloaterMgr::getInstance()->removeControlView(this);  } +// virtual  void LLInspectToast::onOpen(const LLSD& notification_id)  {  	LLInspect::onOpen(notification_id); @@ -103,6 +105,15 @@ void LLInspectToast::onOpen(const LLSD& notification_id)  	LLUI::positionViewNearMouse(this);  } +// virtual +BOOL LLInspectToast::handleToolTip(S32 x, S32 y, MASK mask) +{ +	// We don't like the way LLInspect handles tooltips +	// (black tooltips look weird), +	// so force using the default implementation (STORM-511). +	return LLFloater::handleToolTip(x, y, mask); +} +  void LLInspectToast::onToastDestroy(LLToast * toast)  {  	closeFloater(false); diff --git a/indra/newview/llpanelavatar.cpp b/indra/newview/llpanelavatar.cpp index 1249d5d856..a9bcdef47c 100644 --- a/indra/newview/llpanelavatar.cpp +++ b/indra/newview/llpanelavatar.cpp @@ -341,10 +341,11 @@ LLPanelAvatarNotes::~LLPanelAvatarNotes()  	if(getAvatarId().notNull())  	{  		LLAvatarTracker::instance().removeParticularFriendObserver(getAvatarId(), this); -		if(LLVoiceClient::instanceExists()) -		{ -			LLVoiceClient::getInstance()->removeObserver((LLVoiceClientStatusObserver*)this); -		} +	} + +	if(LLVoiceClient::instanceExists()) +	{ +		LLVoiceClient::getInstance()->removeObserver((LLVoiceClientStatusObserver*)this);  	}  } @@ -758,10 +759,11 @@ LLPanelAvatarProfile::~LLPanelAvatarProfile()  	if(getAvatarId().notNull())  	{  		LLAvatarTracker::instance().removeParticularFriendObserver(getAvatarId(), this); -		if(LLVoiceClient::instanceExists()) -		{ -			LLVoiceClient::getInstance()->removeObserver((LLVoiceClientStatusObserver*)this); -		} +	} + +	if(LLVoiceClient::instanceExists()) +	{ +		LLVoiceClient::getInstance()->removeObserver((LLVoiceClientStatusObserver*)this);  	}  } diff --git a/indra/newview/llremoteparcelrequest.cpp b/indra/newview/llremoteparcelrequest.cpp index 0dff087553..e5ef51bdd1 100644 --- a/indra/newview/llremoteparcelrequest.cpp +++ b/indra/newview/llremoteparcelrequest.cpp @@ -140,22 +140,25 @@ void LLRemoteParcelInfoProcessor::processParcelInfoReply(LLMessageSystem* msg, v  	typedef std::vector<observer_multimap_t::iterator> deadlist_t;  	deadlist_t dead_iters; -	observer_multimap_t::iterator oi; -	observer_multimap_t::iterator start = observers.lower_bound(parcel_data.parcel_id); +	observer_multimap_t::iterator oi = observers.lower_bound(parcel_data.parcel_id);  	observer_multimap_t::iterator end = observers.upper_bound(parcel_data.parcel_id); -	for (oi = start; oi != end; ++oi) +	while (oi != end)  	{ -		LLRemoteParcelInfoObserver * observer = oi->second.get(); +		// increment the loop iterator now since it may become invalid below +		observer_multimap_t::iterator cur_oi = oi++; + +		LLRemoteParcelInfoObserver * observer = cur_oi->second.get();  		if(observer)  		{ +			// may invalidate cur_oi if the observer removes itself   			observer->processParcelInfo(parcel_data);  		}  		else  		{  			// the handle points to an expired observer, so don't keep it  			// around anymore -			dead_iters.push_back(oi); +			dead_iters.push_back(cur_oi);  		}  	} diff --git a/indra/newview/llsidepaneliteminfo.cpp b/indra/newview/llsidepaneliteminfo.cpp index be797ea937..c8c6858b81 100644 --- a/indra/newview/llsidepaneliteminfo.cpp +++ b/indra/newview/llsidepaneliteminfo.cpp @@ -71,12 +71,12 @@ void LLItemPropertiesObserver::changed(U32 mask)  	const std::set<LLUUID>& mChangedItemIDs = gInventory.getChangedIDs();  	std::set<LLUUID>::const_iterator it; -	const LLUUID& object_id = mFloater->getObjectID(); +	const LLUUID& item_id = mFloater->getItemID();  	for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++)  	{  		// set dirty for 'item profile panel' only if changed item is the item for which 'item profile panel' is shown (STORM-288) -		if (*it == object_id) +		if (*it == item_id)  		{  			// if there's a change we're interested in.  			if((mask & (LLInventoryObserver::LABEL | LLInventoryObserver::INTERNAL | LLInventoryObserver::REMOVE)) != 0) @@ -196,6 +196,11 @@ const LLUUID& LLSidepanelItemInfo::getObjectID() const  	return mObjectID;  } +const LLUUID& LLSidepanelItemInfo::getItemID() const +{ +	return mItemID; +} +  void LLSidepanelItemInfo::reset()  {  	LLSidepanelInventorySubpanel::reset(); diff --git a/indra/newview/llsidepaneliteminfo.h b/indra/newview/llsidepaneliteminfo.h index 6416e2cfe4..25be145f64 100644 --- a/indra/newview/llsidepaneliteminfo.h +++ b/indra/newview/llsidepaneliteminfo.h @@ -55,6 +55,7 @@ public:  	void setEditMode(BOOL edit);  	const LLUUID& getObjectID() const; +	const LLUUID& getItemID() const;  protected:  	/*virtual*/ void refresh(); 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/lltoastgroupnotifypanel.cpp b/indra/newview/lltoastgroupnotifypanel.cpp index 563c27c4d7..75178a6ef8 100644 --- a/indra/newview/lltoastgroupnotifypanel.cpp +++ b/indra/newview/lltoastgroupnotifypanel.cpp @@ -77,6 +77,7 @@ LLToastGroupNotifyPanel::LLToastGroupNotifyPanel(LLNotificationPtr& notification  	from << from_name << "/" << groupData.mName;  	LLTextBox* pTitleText = getChild<LLTextBox>("title");  	pTitleText->setValue(from.str()); +	pTitleText->setToolTip(from.str());  	//message subject  	const std::string& subject = payload["subject"].asString(); diff --git a/indra/newview/llviewerassetstats.cpp b/indra/newview/llviewerassetstats.cpp new file mode 100644 index 0000000000..9d98302210 --- /dev/null +++ b/indra/newview/llviewerassetstats.cpp @@ -0,0 +1,618 @@ +/**  + * @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(50 == 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 +			LLViewerAssetStats::EVACOtherGet,					//  +			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,					// +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					//  +			LLViewerAssetStats::EVACOtherGet,					// AT_MESH +																// (50) +		}; +	 +	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/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 8991c21518..dadda29416 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -128,6 +128,45 @@ static bool handleSetShaderChanged(const LLSD& newvalue)  	return true;  } +static bool handleRenderPerfTestChanged(const LLSD& newvalue) +{ +       bool status = !newvalue.asBoolean(); +       if (!status) +       { +               gPipeline.clearRenderTypeMask(LLPipeline::RENDER_TYPE_WL_SKY, +                                                                         LLPipeline::RENDER_TYPE_GROUND, +                                                                        LLPipeline::RENDER_TYPE_TERRAIN, +                                                                         LLPipeline::RENDER_TYPE_GRASS, +                                                                         LLPipeline::RENDER_TYPE_TREE, +                                                                         LLPipeline::RENDER_TYPE_WATER, +                                                                         LLPipeline::RENDER_TYPE_PASS_GRASS, +                                                                         LLPipeline::RENDER_TYPE_HUD, +                                                                         LLPipeline::RENDER_TYPE_PARTICLES, +                                                                         LLPipeline::RENDER_TYPE_CLOUDS, +                                                                         LLPipeline::RENDER_TYPE_HUD_PARTICLES, +                                                                         LLPipeline::END_RENDER_TYPES);  +               gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_FEATURE_UI, false); +       } +       else  +       { +               gPipeline.setRenderTypeMask(LLPipeline::RENDER_TYPE_WL_SKY, +                                                                         LLPipeline::RENDER_TYPE_GROUND, +                                                                         LLPipeline::RENDER_TYPE_TERRAIN, +                                                                         LLPipeline::RENDER_TYPE_GRASS, +                                                                         LLPipeline::RENDER_TYPE_TREE, +                                                                         LLPipeline::RENDER_TYPE_WATER, +                                                                         LLPipeline::RENDER_TYPE_PASS_GRASS, +                                                                         LLPipeline::RENDER_TYPE_HUD, +                                                                         LLPipeline::RENDER_TYPE_PARTICLES, +                                                                         LLPipeline::RENDER_TYPE_CLOUDS, +                                                                         LLPipeline::RENDER_TYPE_HUD_PARTICLES, +                                                                         LLPipeline::END_RENDER_TYPES); +               gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_FEATURE_UI, true); +       } + +       return true; +} +  bool handleRenderTransparentWaterChanged(const LLSD& newvalue)  {  	LLWorld::getInstance()->updateWaterObjects(); @@ -565,6 +604,7 @@ void settings_setup_listeners()  	gSavedSettings.getControl("RenderShadowDetail")->getSignal()->connect(boost::bind(&handleSetShaderChanged, _2));  	gSavedSettings.getControl("RenderDeferredSSAO")->getSignal()->connect(boost::bind(&handleSetShaderChanged, _2));  	gSavedSettings.getControl("RenderDeferredGI")->getSignal()->connect(boost::bind(&handleSetShaderChanged, _2)); +	gSavedSettings.getControl("RenderPerformanceTest")->getSignal()->connect(boost::bind(&handleRenderPerfTestChanged, _2));  	gSavedSettings.getControl("TextureMemory")->getSignal()->connect(boost::bind(&handleVideoMemoryChanged, _2));  	gSavedSettings.getControl("AuditTexture")->getSignal()->connect(boost::bind(&handleAuditTextureChanged, _2));  	gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&handleChatFontSizeChanged, _2)); diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 75caf45175..a90c90a774 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1425,6 +1425,7 @@ void LLViewerRegion::setSeedCapability(const std::string& url)  	capabilityNames.append("UpdateScriptTask");  	capabilityNames.append("UploadBakedTexture");  	capabilityNames.append("UploadObjectAsset"); +	capabilityNames.append("ViewerMetrics");  	capabilityNames.append("ViewerStartAuction");  	capabilityNames.append("ViewerStats");  	capabilityNames.append("WebFetchInventoryDescendents"); diff --git a/indra/newview/skins/default/xui/en/floater_model_wizard.xml b/indra/newview/skins/default/xui/en/floater_model_wizard.xml index 4a4b8075c8..f5eda05f1a 100644 --- a/indra/newview/skins/default/xui/en/floater_model_wizard.xml +++ b/indra/newview/skins/default/xui/en/floater_model_wizard.xml @@ -1,230 +1,587 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes" ?> -<floater - legacy_header_height="18" - layout="topleft" - name="Model Wizard" - help_topic="model_wizard" - bg_opaque_image_overlay="0.5 0.5 0.5 1" - height="450" - save_rect="true" - title="UPLOAD MODEL WIZARD"  - width="530"> -	<panel -		   height="600"> -		<button -	     top="30" -		 left="410" -		 height="32" -		 name="upload"  -		 enabled="false"  -		 label="5. Upload"  -		 border="false"  -		 image_unselected="model_wizard/middle_button_off.png" -		 image_selected="model_wizard/middle_button_press.png" -		image_hover_unselected="model_wizard/middle_button_over.png" -		image_disabled="model_wizard/middle_button_disabled.png" -		image_disabled_selected="model_wizard/middle_button_disabled.png" -		 width="110"/> -		<button -	     top="30" -		 left="310" -		 height="32" -		  tab_stop="false" -		 name="review" -		 label="4. Review" -		 enabled="false" -		 border="false" -		 image_unselected="model_wizard/middle_button_off.png" -		 image_selected="model_wizard/middle_button_press.png" -		image_hover_unselected="model_wizard/middle_button_over.png" -		image_disabled="model_wizard/middle_button_disabled.png" -		image_disabled_selected="model_wizard/middle_button_disabled.png" -		 width="110"/> -		<button -	     top="30" -		 left="210" -		 height="32" -		 name="physics" -		 label="3. Physics" -		 tab_stop="false" -		 enabled="false" -		 border="false" -		 image_unselected="model_wizard/middle_button_off.png" -		 image_selected="model_wizard/middle_button_press.png" -		image_hover_unselected="model_wizard/middle_button_over.png" -		image_disabled="model_wizard/middle_button_disabled.png" -		image_disabled_selected="model_wizard/middle_button_disabled.png" -		 width="110"/> -		<button -	     top="30" -		 left="115" -		 name="optimize" -		 label="2. Optimize" -		  tab_stop="false" -		 height="32" -		 border="false" -		 image_unselected="model_wizard/middle_button_off.png" -		 image_selected="model_wizard/middle_button_press.png" -		image_hover_unselected="model_wizard/middle_button_over.png" -		image_disabled="model_wizard/middle_button_disabled.png" -		image_disabled_selected="model_wizard/middle_button_disabled.png" -		 width="110"/> -		<button -	     top="30" -		 left="15" -		 name="choose_file" -		 enabled="false" -		 label="1. Choose File" -		 height="32" -		 image_unselected="model_wizard/left_button_off.png" -		 image_selected="model_wizard/left_button_press.png" -		image_hover_unselected="model_wizard/left_button_over.png" -		image_disabled="model_wizard/left_button_disabled.png" -		image_disabled_selected="model_wizard/left_button_disabled.png" -		 width="110"/> -		<panel -		 top_pad="20" -		 height="20" -		 width="500"  -		 bg_opaque_color="DkGray2" -		 background_visible="true" -		 background_opaque="true"  -		 left="20"> -		<text -		 width="200" -		 left="10"  -		 top="2"  -		 height="10" -		 font="SansSerifBig" -		 layout="topleft" -		 > -			  Upload Model -		</text></panel> -		<text -		 top_pad="14" -		 width="460" -		 height="20" -		 font="SansSerifSmall" -		 layout="topleft" -		 word_wrap="true" -		 left_delta="0"> -			This wizard will help you import mesh models to Second Life.  First specify a file containing the model you wish to import.  Second Life supports COLLADA (.dae) files. -		</text> - -		<panel -			top_delta="40" -			left="15" -		 height="240" -		 width="500" -		 bg_opaque_color="DkGray2" -		 background_visible="true" -		 background_opaque="true"> - -			<text -	 type="string" -	 length="1" -	 follows="left|top" -	 top="10"  -	 height="10" -	 layout="topleft" -	 left_delta="10" -	 name="Cache location" -	 width="300"> -				Filename: -			</text> -			<line_editor -			 border_style="line" -			 border_thickness="1" -			 follows="left|top" -			 font="SansSerifSmall" -			 height="20" -			 layout="topleft" -	 left_delta="0" -			 max_length="4096" -			 name="lod_file" -			 top_pad="5" -			 width="220" /> -			<button -			 follows="left|top" -			 height="23" -			 label="Browse..." -			 label_selected="Browse..." -			 layout="topleft" -			 left_pad="10" -			 name="browse" -			 top_delta="-1" -			 width="75"> -			</button> -			<text -	top_delta="-15" -	width="200" -	height="15" -	font="SansSerifSmall" -	layout="topleft" -	left_pad="24"> -				Model Preview: -			</text> - -			<!-- Placeholder panel for 3D preview render --> -			 -			<panel -					left_delta="-2" -				   top_pad="0" -					name="preview_panel" -					bevel_style="none" -				   border_style="line" -					border="true" -				   height="150" -			  follows="all" -				   width="150"> -			</panel> -			<text -			 top_pad="10" -			 width="130" -			 height="15" -			 left="340" -			 word_wrap="true" -		 > -				Dimensions (meters): -			</text> -			<text -			 top_pad="5" -			 width="150" -			 height="15" -			 name="import_dimensions" -			 left_delta="0"> -				X:  [X] |  Y:  [Y] | Z: [Z] -			</text> -			 -			<text -			 top="100" -			 width="320" -			 height="40" -			 left="10" -			 word_wrap="true" -		 > -				Note: -Advanced users familiar with 3d content creation tools may prefer to use the Advanced Mesh Import window. -			</text> -		</panel> -		<button -		 top="410" -		 right="-175"  -		 width="80" -		 height="20" -		 label="<< Back" /> -		<button -		 top="410" -		 right="-92" -		 width="80" -		 height="20" -		 label="Next >> " /> -		<button -		 top="410" -		 right="-15" -		 width="70" -		 height="20" -		 label="Cancel" /> -	</panel> -	<spinner visible="false" left="10" height="20" follows="top|left" width="80" top_pad="-50" value="1.0" min_val="0.01" max_val="64.0" name="import_scale"/> -</floater> +<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
 +<floater
 + legacy_header_height="18"
 + layout="topleft"
 + name="Model Wizard"
 + help_topic="model_wizard"
 + bg_opaque_image_overlay="0.5 0.5 0.5 1"
 + height="450"
 + save_rect="true"
 + title="UPLOAD MODEL WIZARD"
 + width="530">
 +	<button
 +	 top="30"
 +	 tab_stop="false"
 +	 left="410"
 +	 height="32"
 +	 name="upload_btn"
 +	 enabled="false"
 +	 label="5. Upload"
 +	 border="false"
 +	 image_unselected="model_wizard/middle_button_off.png"
 +	 image_selected="model_wizard/middle_button_press.png"
 +	 image_hover_unselected="model_wizard/middle_button_over.png"
 +	 image_disabled="model_wizard/middle_button_disabled.png"
 +	 image_disabled_selected="model_wizard/middle_button_disabled.png"
 +	 width="110"/>
 +	<button
 +	 top="30"
 +	 left="310"
 +	 height="32"
 +	 tab_stop="false"
 +	 name="review_btn"
 +	 label="4. Review"
 +	 enabled="false"
 +	 border="false"
 +	 image_unselected="model_wizard/middle_button_off.png"
 +	 image_selected="model_wizard/middle_button_press.png"
 +	 image_hover_unselected="model_wizard/middle_button_over.png"
 +	 image_disabled="model_wizard/middle_button_disabled.png"
 +	 image_disabled_selected="model_wizard/middle_button_disabled.png"
 +	 width="110"/>
 +	<button
 +	 top="30"
 +	 left="210"
 +	 height="32"
 +	 name="physics_btn"
 +	 label="3. Physics"
 +	 tab_stop="false"
 +	 enabled="false"
 +	 border="false"
 +	 image_unselected="model_wizard/middle_button_off.png"
 +	 image_selected="model_wizard/middle_button_press.png"
 +	 image_hover_unselected="model_wizard/middle_button_over.png"
 +	 image_disabled="model_wizard/middle_button_disabled.png"
 +	 image_disabled_selected="model_wizard/middle_button_disabled.png"
 +	 width="110"/>
 +	<button
 +	 top="30"
 +	 left="115"
 +	 name="optimize_btn"
 +	 label="2. Optimize"
 +	 tab_stop="false"
 +	 height="32"
 +	 border="false"
 +	 image_unselected="model_wizard/middle_button_off.png"
 +	 image_selected="model_wizard/middle_button_press.png"
 +	 image_hover_unselected="model_wizard/middle_button_over.png"
 +	 image_disabled="model_wizard/middle_button_disabled.png"
 +	 image_disabled_selected="model_wizard/middle_button_disabled.png"
 +	 width="110"/>
 +	<button
 +	 top="30"
 +	 left="15"
 +	 name="choose_file_btn"
 +	 tab_stop="false"
 +	 enabled="false"
 +	 label="1. Choose File"
 +	 height="32"
 +	 image_unselected="model_wizard/left_button_off.png"
 +	 image_selected="model_wizard/left_button_press.png"
 +	 image_hover_unselected="model_wizard/left_button_over.png"
 +	 image_disabled="model_wizard/left_button_disabled.png"
 +	 image_disabled_selected="model_wizard/left_button_disabled.png"
 +	 width="110"/>
 +	<panel
 +		 height="388"
 +		 top_pad="0"
 +		 name="choose_file_panel"
 +		 visible="true"
 +		 width="530"
 +		 left="0">
 +		<panel
 +		 height="20"
 +		 top_pad="20"
 +		 width="500"
 +		 name="header_panel"
 +		 bg_opaque_color="DkGray2"
 +		 background_visible="true"
 +		 background_opaque="true"
 +		 left="20">
 +			<text
 +			 width="200"
 +			 left="10"
 +			 top="2"
 +			 name="header_text"
 +			 height="10"
 +			 font="SansSerifBig"
 +			 layout="topleft">
 +				Upload Model
 +			</text>
 +		</panel>
 +		<text
 +		 top_pad="14"
 +		 width="460"
 +		 height="20"
 +		 name="description"
 +		 font="SansSerifSmall"
 +		 layout="topleft"
 +		 word_wrap="true"
 +		 left_delta="0">
 +			This wizard will help you import mesh models to Second Life.  First specify a file containing the model you wish to import.  Second Life supports COLLADA (.dae) files.
 +		</text>
 +		<panel
 +		 top_delta="40"
 +		 left="15"
 +		 height="240"
 +		 width="500"
 +		 name="content"
 +		 bg_opaque_color="DkGray2"
 +		 background_visible="true"
 +		 background_opaque="true">
 +			<text
 +			 type="string"
 +			 length="1"
 +			 follows="left|top"
 +			 top="10"
 +			 height="10"
 +			 layout="topleft"
 +			 left_delta="10"
 +			 name="Cache location"
 +			 width="300">
 +				Filename:
 +			</text>
 +			<line_editor
 +			 border_style="line"
 +			 border_thickness="1"
 +			 follows="left|top"
 +			 font="SansSerifSmall"
 +			 height="20"
 +			 layout="topleft"
 +			 left_delta="0"
 +			 max_length="4096"
 +			 name="lod_file"
 +			 top_pad="5"
 +			 width="220" />
 +			<button
 +			 follows="left|top"
 +			 height="23"
 +			 label="Browse..."
 +			 label_selected="Browse..."
 +			 layout="topleft"
 +			 left_pad="10"
 +			 name="browse"
 +			 top_delta="-1"
 +			 width="75">
 +			</button>
 +			<text
 +			 top_delta="-15"
 +			 width="200"
 +			 height="15"
 +			 font="SansSerifSmall"
 +			 layout="topleft"
 +			 left_pad="24">
 +				Model Preview:
 +			</text>
 +			<!-- Placeholder panel for 3D preview render -->
 +			<panel
 +			 left_delta="-2"
 +			 top_pad="0"
 +			 name="preview_panel"
 +			 bevel_style="none"
 +			 border_style="line"
 +			 border="true"
 +			 height="150"
 +			 follows="all"
 +			 width="150">
 +			</panel>
 +			<text
 +			 top_pad="10"
 +			 width="130"
 +			 height="15"
 +			 left="340"
 +			 word_wrap="true">
 +				Dimensions (meters):
 +			</text>
 +			<text
 +			 top_pad="5"
 +			 width="160"
 +			 height="15"
 +			 name="import_dimensions"
 +			 left_delta="0">
 +				X:  [X] |  Y:  [Y] | Z: [Z]
 +			</text>
 +			<text
 +			 top="100"
 +			 width="320"
 +			 height="40"
 +			 left="10"
 +			 word_wrap="true">
 +				Note:
 +Advanced users familiar with 3d content creation tools may prefer to use the Advanced Mesh Import window.
 +			</text>
 +		</panel>
 +	</panel>
 +
 +
 +	<panel
 +		 height="388"
 +		 top_delta="0"
 +		 name="optimize_panel"
 +		 visible="false"
 +		 width="530"
 +		 left="0">
 +		<panel
 +		 height="20"
 +		 top_pad="20"
 +		 name="header_panel"
 +		 width="500"
 +		 bg_opaque_color="DkGray2"
 +		 background_visible="true"
 +		 background_opaque="true"
 +		 left="20">
 +			<text
 +			 width="200"
 +			 left="10"
 +			 name="header_text"
 +			 top="2"
 +			 height="10"
 +			 font="SansSerifBig"
 +			 layout="topleft">
 +				Optimize
 +			</text>
 +		</panel>
 +		<text
 +		 top_pad="14"
 +		 width="460"
 +		 height="20"
 +		 font="SansSerifSmall"
 +		 layout="topleft"
 +		 name="description"
 +		 word_wrap="true"
 +		 left_delta="0">
 +			This wizard is optimizing your model. This may take several minutes. To stop the process click the back button
 +		</text>
 +		<panel
 +		 top_delta="40"
 +		 visible="false"
 +		 left="15"
 +		 height="240"
 +		 width="500"
 +		 name="content"
 +		 bg_opaque_color="DkGray2"
 +		 background_visible="true"
 +		 background_opaque="true">
 +			<text
 +			 top="20"
 +			 width="300"
 +			 height="12"
 +			 font="SansSerifBold"
 +			 left="112">Generating Level of Detail</text>
 +			<progress_bar
 +			  name="optimize_progress_bar"
 +              image_fill="model_wizard\progress_light.png"
 +			  color_bg="1 1 1 1"
 +			  color_bar="1 1 1 0.96"
 +			  follows="left|right|top"
 +			  width="260"
 +			  height="16"
 +			  image_bar="model_wizard\progress_bar_bg.png"
 +			  top_pad="14"
 +			  left="110"/>
 +			<icon
 +			 top_pad="10"
 +			 left_delta="0"
 +			 width="13"
 +			 height="12"
 +			 image_name="model_wizard\check_mark.png"/>
 +			<text
 +			 top_delta="0"
 +			 left_delta="18"
 +			 name="high_detail_text"
 +			 width="200"
 +			 height="14">Generate Level of Detail: High</text>
 +			<icon
 +			 top_pad="10"
 +			 left_delta="-18"
 +			 width="13"
 +			 height="12"
 +			 image_name="model_wizard\check_mark.png"/>
 +			<text
 +			 top_delta="0"
 +			 left_delta="18"
 +			 name="medium_detail_text"
 +			 width="200"
 +			 height="14">Generate Level of Detail: Medium</text>
 +			<icon
 +			 top_pad="10"
 +			 left_delta="-18"
 +			 width="13"
 +			 height="12"
 +			 image_name="model_wizard\check_mark.png"/>
 +			<text
 +			 top_delta="0"
 +			 left_delta="18"
 +			 name="low_detail_text"
 +			 width="200"
 +			 height="14">Generate Level of Detail: Low</text>
 +			<icon
 +			 top_pad="10"
 +			 left_delta="-18"
 +			 width="13"
 +			 height="12"
 +			 image_name="model_wizard\check_mark.png"/>
 +			<text
 +			 top_delta="0"
 +			 left_delta="18"
 +			 name="lowest_detail_text"
 +			 width="200"
 +			 height="14">Generate Level of Detail: Lowest</text>
 +		</panel>
 +		<panel
 +				 top_delta="0"
 +				 left_delta="0"
 +				 height="240"
 +				 width="500"
 +				 name="content2"
 +				 bg_opaque_color="DkGray2"
 +				 background_visible="true"
 +				 background_opaque="true">
 +			<text top="10" left="10" width="85"  follows="left|top" height="15" name="lod_label">
 +				Model Preview:
 +			</text>
 +			<combo_box left_pad="5" top_delta="-2"  follows="left|top" list_position="below" height="18"
 +	     name="preview_lod_combo" width="90" tool_tip="LOD to view in preview render">
 +				<combo_item name="high">
 +					High
 +				</combo_item>
 +				<combo_item name="medium">
 +					Medium
 +				</combo_item>
 +				<combo_item name="lowest">
 +					Lowest
 +				</combo_item>
 +				<combo_item name="low">
 +					Low
 +				</combo_item>
 +			</combo_box>
 +			<panel
 +				 left="10"
 +				 top_pad="5"
 +				 name="preview_panel"
 +				 bevel_style="none"
 +				 border_style="line"
 +				 border="true"
 +				 height="175"
 +				 follows="all"
 +				 width="175">
 +			</panel>
 +			<text top="50" left="210"  font="SansSerifSmallBold" width="300" height="4">Performance                            Accuracy</text>
 +
 +			<slider
 +		   follows="left|top"
 +		   height="20"
 +		   increment="1"
 +		   layout="topleft"
 +		   left="220"
 +		   max_val="2"
 +		   initial_value="1"
 +		   min_val="0"
 +		   name="accuracy_slider"
 +		   show_text="false"
 +		   top="105"
 +		   width="250" />
 +			<text font="SansSerifSmall" top_pad="4"  width="300" left_delta="6" height="4">|                                      |                                      |</text>
 +
 +		</panel>
 +	</panel>
 +
 +
 +
 +	<panel
 +		 height="388"
 +		 top_delta="0"
 +		 name="physics_panel"
 +		 visible="false"
 +		 width="530"
 +		 left="0">
 +		<panel
 +		 height="20"
 +		 top_pad="20"
 +		 name="header_panel"
 +		 width="500"
 +		 bg_opaque_color="DkGray2"
 +		 background_visible="true"
 +		 background_opaque="true"
 +		 left="20">
 +			<text
 +			 width="200"
 +			 left="10"
 +			 name="header_text"
 +			 top="2"
 +			 height="10"
 +			 font="SansSerifBig"
 +			 layout="topleft">
 +				Physics
 +			</text>
 +		</panel>
 +		<text
 +		 top_pad="14"
 +		 width="460"
 +		 height="20"
 +		 font="SansSerifSmall"
 +		 layout="topleft"
 +		 name="description"
 +		 word_wrap="true"
 +		 left_delta="0">
 +			The wizard will create a physical shape, which determines how the object interacts with other objects and avatars. Set the slider to the detail level most appropriate for how your object will be used:
 +		</text>
 +		<panel
 +		 top_delta="40"
 +		 left="15"
 +		 height="240"
 +		 width="500"
 +		 name="content"
 +		 bg_opaque_color="DkGray2"
 +		 background_visible="true"
 +		 background_opaque="true"/>
 +	</panel>
 +
 +
 +	<panel
 +		 height="388"
 +		 top_delta="0"
 +		 name="review_panel"
 +		 visible="false"
 +		 width="530"
 +		 left="0">
 +		<panel
 +		 height="20"
 +		 top_pad="20"
 +		 name="header_panel"
 +		 width="500"
 +		 bg_opaque_color="DkGray2"
 +		 background_visible="true"
 +		 background_opaque="true"
 +		 left="20">
 +			<text
 +			 width="200"
 +			 left="10"
 +			 name="header_text"
 +			 top="2"
 +			 height="10"
 +			 font="SansSerifBig"
 +			 layout="topleft">
 +				Review
 +			</text>
 +		</panel>
 +		<text
 +		 top_pad="14"
 +		 width="460"
 +		 height="20"
 +		 font="SansSerifSmall"
 +		 layout="topleft"
 +		 name="description"
 +		 word_wrap="true"
 +		 left_delta="0">
 +			Review the details below then click. Upload to upload your model. Your L$ balance will be charged when you click Upload.
 +		</text>
 +		<panel
 +		 top_delta="40"
 +		 left="15"
 +		 height="240"
 +		 width="500"
 +		 name="content"
 +		 bg_opaque_color="DkGray2"
 +		 background_visible="true"
 +		 background_opaque="true"/>
 +	</panel>
 +
 +
 +
 +
 +	<panel
 +		 height="388"
 +		 top_delta="0"
 +		 name="upload_panel"
 +		 visible="false"
 +		 width="530"
 +		 left="0">
 +		<panel
 +		 height="20"
 +		 top_pad="20"
 +		 name="header_panel"
 +		 width="500"
 +		 bg_opaque_color="DkGray2"
 +		 background_visible="true"
 +		 background_opaque="true"
 +		 left="20">
 +			<text
 +			 width="200"
 +			 left="10"
 +			 name="header_text"
 +			 top="2"
 +			 height="10"
 +			 font="SansSerifBig"
 +			 layout="topleft">
 +				Upload Complete!
 +			</text>
 +		</panel>
 +		<text
 +		 top_pad="14"
 +		 width="460"
 +		 height="20"
 +		 font="SansSerifSmall"
 +		 layout="topleft"
 +		 name="description"
 +		 word_wrap="true"
 +		 left_delta="0">
 +			Congratulations! Your model has been sucessfully uploaded.  You will find the model in the Objects folder in your inventory.
 +		</text>
 +		<panel
 +		 top_delta="40"
 +		 left="15"
 +		 height="240"
 +		 width="500"
 +		 name="content"
 +		 bg_opaque_color="DkGray2"
 +		 background_visible="true"
 +		 background_opaque="true">
 +			<button top="10" follows="top|left" height="20" label="Upload"
 +				   left="15" width="80" name="ok_btn" tool_tip="Upload to simulator"/>
 +		</panel>
 +	</panel>
 +
 +
 +
 +	<button
 +	 top="410"
 +	 right="-245"
 +	 width="90"
 +	 height="20"
 +	 name="back"
 +	 label="<< Back" />
 +	<button
 +	 top_delta="0"
 +	 right="-150"
 +	 width="90"
 +	 height="20"
 +	 name="next"
 +	 label="Next >> " />
 +	<button
 +	 top_delta="0"
 +	 right="-15"
 +	 width="90"
 +	 height="20"
 +	 name="cancel"
 +	 label="Cancel" />
 +	<spinner visible="false" left="10" height="20" follows="top|left" width="80" top_pad="-50" value="1.0" min_val="0.01" max_val="64.0" name="import_scale"/>
 +
 +	<string name="status_idle">Idle</string>
 +	<string name="status_reading_file">Loading...</string>
 +	<string name="status_generating_meshes">Generating Meshes...</string>
 +	<string name="high">High</string>
 +	<string name="medium">Medium</string>
 +	<string name="low">Low</string>
 +	<string name="lowest">Lowest</string>
 +	<string name="mesh_status_good">Ship it!</string>
 +	<string name="mesh_status_na">N/A</string>
 +	<string name="mesh_status_none">None</string>
 +	<string name="mesh_status_submesh_mismatch">Levels of detail have a different number of textureable faces.</string>
 +	<string name="mesh_status_mesh_mismatch">Levels of detail have a different number of mesh instances.</string>
 +	<string name="mesh_status_too_many_vertices">Level of detail has too many vertices.</string>
 +	<string name="mesh_status_missing_lod">Missing required level of detail.</string>
 +	<string name="layer_all">All</string>
 +	<!-- Text to display in physics layer combo box for "all layers" -->
 +
 +</floater>
 diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 6c1e9a3082..df556691f0 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -1000,6 +1000,29 @@                   parameter="perm_prefs" />              </menu_item_call>          </menu> +        <menu_item_separator/> +        <menu_item_call +         enabled="false" +         label="Undo" +         name="Undo" +         shortcut="control|Z"> +            <on_click +             function="Edit.Undo" +             userdata="" /> +            <on_enable +             function="Edit.EnableUndo" /> +        </menu_item_call> +        <menu_item_call +         enabled="false" +         label="Redo" +         name="Redo" +         shortcut="control|Y"> +            <on_click +             function="Edit.Redo" +             userdata="" /> +            <on_enable +             function="Edit.EnableRedo" /> +        </menu_item_call>              </menu>      <menu       create_jump_keys="true" diff --git a/indra/newview/skins/default/xui/en/panel_my_profile.xml b/indra/newview/skins/default/xui/en/panel_my_profile.xml index 1b41f602cd..5b8abaca6f 100644 --- a/indra/newview/skins/default/xui/en/panel_my_profile.xml +++ b/indra/newview/skins/default/xui/en/panel_my_profile.xml @@ -185,7 +185,7 @@                </expandable_text>              </panel>              <text -             follows="left|top" +             follows="left|top|right"               height="15"         font.style="BOLD"         font="SansSerifMedium" @@ -200,7 +200,7 @@               use_ellipses="true"           />              <text -             follows="left|top" +             follows="left|top|right"             font.style="BOLD"               height="10"               layout="topleft" @@ -213,7 +213,7 @@              <text_editor               allow_scroll="false"               bg_visible="false" -             follows="left|top" +             follows="left|top|right"               h_pad="0"               height="15"               layout="topleft" @@ -226,7 +226,7 @@               width="300"               word_wrap="true" />              <text -             follows="left|top" +             follows="left|top|right"         font.style="BOLD"               height="15"               layout="topleft" @@ -250,7 +250,7 @@              <text_editor              allow_scroll="false"              bg_visible="false" -            follows="left|top" +            follows="left|top|right"              h_pad="0"              height="28"              layout="topleft" @@ -266,7 +266,7 @@                Linden.              </text_editor>              <text -             follows="left|top" +             follows="left|top|right"         font.style="BOLD"               height="15"               layout="topleft" @@ -277,7 +277,7 @@               value="Partner:"               width="300" />              <panel -             follows="left|top" +             follows="left|top|right"               height="15"               layout="topleft"               left="10" @@ -285,7 +285,7 @@               top_pad="0"               width="300">                <text -               follows="left|top" +               follows="left|top|right"                 height="10"                 initial_value="(retrieving)"                 layout="topleft" @@ -297,7 +297,7 @@             width="300" />              </panel>              <text -             follows="left|top" +             follows="left|top|right"         font.style="BOLD"               height="13"               layout="topleft" diff --git a/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml b/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml index d6e4c56113..37aab059a9 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml @@ -82,7 +82,7 @@       control_name="AllowMultipleViewers"       follows="top|left"       height="15" -     label="Allow Multiple Viewer" +     label="Allow Multiple Viewers"       layout="topleft"       left="30"       name="allow_multiple_viewer_check" 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); +		} +	} + +} | 
