diff --git a/indra/newview/llviewerassetstats.cpp b/indra/newview/llviewerassetstats.cpp
index 0852573bbdbf02a1c8998bc07553774d93bc028d..a6c4685bf1a2c2bc834c9b92ed607dc575148255 100644
--- a/indra/newview/llviewerassetstats.cpp
+++ b/indra/newview/llviewerassetstats.cpp
@@ -47,7 +47,7 @@
  *   <TBD>
  *
  * Unit Tests:
- *   <TBD>
+ *   indra/newview/tests/llviewerassetstats_test.cpp
  *
  */
 
@@ -55,7 +55,8 @@
 // ------------------------------------------------------
 // Global data definitions
 // ------------------------------------------------------
-LLViewerAssetStats * gViewerAssetStats = NULL;
+LLViewerAssetStats * gViewerAssetStatsMain(0);
+LLViewerAssetStats * gViewerAssetStatsThread1(0);
 
 
 // ------------------------------------------------------
@@ -69,6 +70,21 @@ asset_type_to_category(const LLViewerAssetType::EType at, bool with_http, bool i
 
 }
 
+// ------------------------------------------------------
+// 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();
+	}
+}
+
+
 // ------------------------------------------------------
 // LLViewerAssetStats class definition
 // ------------------------------------------------------
@@ -81,20 +97,55 @@ LLViewerAssetStats::LLViewerAssetStats()
 void
 LLViewerAssetStats::reset()
 {
-	for (int i = 0; i < LL_ARRAY_SIZE(mRequests); ++i)
+	// 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.
+	if (mCurRegionStats)
 	{
-		mRequests[i].mEnqueued.reset();
-		mRequests[i].mDequeued.reset();
-		mRequests[i].mResponse.reset();
+		mCurRegionStats->reset();
 	}
+	else
+	{
+		mCurRegionStats = new PerRegionStats(mRegionID);
+	}
+
+	// And add reference to map
+	mRegionStats[mRegionID] = mCurRegionStats;
 }
 
+
+void
+LLViewerAssetStats::setRegionID(const LLUUID & region_id)
+{
+	if (region_id == mRegionID)
+	{
+		// Already active, ignore.
+		return;
+	}
+	
+	PerRegionContainer::iterator new_stats = mRegionStats.find(region_id);
+	if (mRegionStats.end() == new_stats)
+	{
+		// Haven't seen this region_id before, create a new block make it current.
+		mCurRegionStats = new PerRegionStats(region_id);
+		mRegionStats[region_id] = mCurRegionStats;
+	}
+	else
+	{
+		mCurRegionStats = new_stats->second;
+	}
+	mRegionID = region_id;
+}
+
+
 void
 LLViewerAssetStats::recordGetEnqueued(LLViewerAssetType::EType at, bool with_http, bool is_temp)
 {
 	const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp));
 	
-	++mRequests[int(eac)].mEnqueued;
+	++(mCurRegionStats->mRequests[int(eac)].mEnqueued);
 }
 	
 void
@@ -102,7 +153,7 @@ LLViewerAssetStats::recordGetDequeued(LLViewerAssetType::EType at, bool with_htt
 {
 	const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp));
 
-	++mRequests[int(eac)].mDequeued;
+	++(mCurRegionStats->mRequests[int(eac)].mDequeued);
 }
 
 void
@@ -110,7 +161,7 @@ LLViewerAssetStats::recordGetServiced(LLViewerAssetType::EType at, bool with_htt
 {
 	const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp));
 
-	mRequests[int(eac)].mResponse.record(duration);
+	mCurRegionStats->mRequests[int(eac)].mResponse.record(duration);
 }
 
 const LLSD
@@ -139,16 +190,33 @@ LLViewerAssetStats::asLLSD() const
 	
 	LLSD ret = LLSD::emptyMap();
 
-	for (int i = 0; i < EVACCount; ++i)
+	for (PerRegionContainer::const_iterator it = mRegionStats.begin();
+		 mRegionStats.end() != it;
+		 ++it)
 	{
-		LLSD & slot = ret[tags[i]];
-		slot = LLSD::emptyMap();
-		slot[enq_tag] = LLSD(S32(mRequests[i].mEnqueued.getCount()));
-		slot[deq_tag] = LLSD(S32(mRequests[i].mDequeued.getCount()));
-		slot[rcnt_tag] = LLSD(S32(mRequests[i].mResponse.getCount()));
-		slot[rmin_tag] = LLSD(mRequests[i].mResponse.getMin());
-		slot[rmax_tag] = LLSD(mRequests[i].mResponse.getMax());
-		slot[rmean_tag] = LLSD(mRequests[i].mResponse.getMean());
+		if (it->first.isNull())
+		{
+			// Never emit NULL UUID in results.
+			continue;
+		}
+
+		const PerRegionStats & stats = *it->second;
+		
+		LLSD reg_stat = LLSD::emptyMap();
+		
+		for (int i = 0; i < EVACCount; ++i)
+		{
+			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(stats.mRequests[i].mResponse.getMin());
+			slot[rmax_tag] = LLSD(stats.mRequests[i].mResponse.getMax());
+			slot[rmean_tag] = LLSD(stats.mRequests[i].mResponse.getMean());
+		}
+
+		ret[it->first.asString()] = reg_stat;
 	}
 
 	return ret;
@@ -161,31 +229,81 @@ LLViewerAssetStats::asLLSD() const
 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.
+
+void
+set_region_main(const LLUUID & region_id)
+{
+	if (! gViewerAssetStatsMain)
+		return;
+
+	gViewerAssetStatsMain->setRegionID(region_id);
+}
+
+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, F64 duration)
+{
+	if (! gViewerAssetStatsMain)
+		return;
+
+	gViewerAssetStatsMain->recordGetServiced(at, with_http, is_temp, duration);
+}
+
+
+void
+set_region_thread1(const LLUUID & region_id)
+{
+	if (! gViewerAssetStatsThread1)
+		return;
+
+	gViewerAssetStatsThread1->setRegionID(region_id);
+}
+
 void
-record_enqueue(LLViewerAssetType::EType at, bool with_http, bool is_temp)
+record_enqueue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp)
 {
-	if (! gViewerAssetStats)
+	if (! gViewerAssetStatsThread1)
 		return;
 
-	gViewerAssetStats->recordGetEnqueued(at, with_http, is_temp);
+	gViewerAssetStatsThread1->recordGetEnqueued(at, with_http, is_temp);
 }
 
 void
-record_dequeue(LLViewerAssetType::EType at, bool with_http, bool is_temp)
+record_dequeue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp)
 {
-	if (! gViewerAssetStats)
+	if (! gViewerAssetStatsThread1)
 		return;
 
-	gViewerAssetStats->recordGetDequeued(at, with_http, is_temp);
+	gViewerAssetStatsThread1->recordGetDequeued(at, with_http, is_temp);
 }
 
 void
-record_response(LLViewerAssetType::EType at, bool with_http, bool is_temp, F64 duration)
+record_response_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp, F64 duration)
 {
-	if (! gViewerAssetStats)
+	if (! gViewerAssetStatsThread1)
 		return;
 
-	gViewerAssetStats->recordGetServiced(at, with_http, is_temp, duration);
+	gViewerAssetStatsThread1->recordGetServiced(at, with_http, is_temp, duration);
 }
 
 } // namespace LLViewerAssetStatsFF
diff --git a/indra/newview/llviewerassetstats.h b/indra/newview/llviewerassetstats.h
index 9d66a1e89b3ace6a411a677398763dba01361af2..b8356a5ff5e164158ad897d336682029f4e77e2b 100644
--- a/indra/newview/llviewerassetstats.h
+++ b/indra/newview/llviewerassetstats.h
@@ -36,6 +36,8 @@
 
 #include "linden_common.h"
 
+#include "llpointer.h"
+#include "llrefcount.h"
 #include "llviewerassettype.h"
 #include "llviewerassetstorage.h"
 #include "llsimplestat.h"
@@ -43,50 +45,42 @@
 
 /**
  * @class LLViewerAssetStats
- * @brief Records events and performance of asset put/get operations.
+ * @brief Records performance aspects of asset access operations.
  *
- * The asset system is a combination of common code and server-
- * and viewer-overridden derivations.  The common code is presented
- * in here as the 'front-end' and deriviations (really the server)
- * are presented as 'back-end'.  The distinction isn't perfect as
- * there are legacy asset transfer systems which mostly appear
- * as front-end stats.
+ * 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:
  *
- * Statistics collected are fairly basic:
  *  - Counts of enqueue and dequeue operations
- *  - Counts of duplicated request fetches
  *  - Min/Max/Mean of asset transfer operations
  *
- * While the stats collection interfaces appear to be fairly
- * orthogonal across methods (GET, PUT) and asset types (texture,
- * bodypart, etc.), the actual internal collection granularity
- * varies greatly.  GET's operations found in the cache are
- * treated as a single group as are duplicate requests.  Non-
- * cached items are broken down into three groups:  textures,
- * wearables (bodyparts, clothing) and the rest.  PUT operations
- * are broken down into two categories:  temporary assets and
- * non-temp.  Back-end operations do not distinguish asset types,
- * only GET, PUT (temp) and PUT (non-temp).
- * 
- * No coverage for Estate Assets or Inventory Item Assets which use
- * some different interface conventions.  It could be expanded to cover
- * them.
+ * 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 to be emitted as
+ * 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 LLAssetStatsFF
- * are provided which operate on various counters in a way that
- * is highly-compatible with the simulator code.
+ * For convenience, a set of free functions in namespace
+ * LLViewerAssetStatsFF is provided for conditional test-and-call
+ * operations.
  */
 class LLViewerAssetStats
 {
 public:
-	LLViewerAssetStats();
-	// Default destructor and assignment operator are correct.
-	
 	enum EViewerAssetCategories
 	{
 		EVACTextureTempHTTPGet,			//< Texture GETs
@@ -100,45 +94,109 @@ class LLViewerAssetStats
 		
 		EVACCount						// Must be last
 	};
-	
+
+	/**
+	 * Collected data for a single region visited by the avatar.
+	 */
+	class PerRegionStats : public LLRefCount
+	{
+	public:
+		PerRegionStats(const LLUUID & region_id)
+			: LLRefCount(),
+			  mRegionID(region_id)
+			{
+				reset();
+			}
+		
+		void reset();
+
+	public:
+		LLUUID mRegionID;
+		struct
+		{
+			LLSimpleStatCounter		mEnqueued;
+			LLSimpleStatCounter		mDequeued;
+			LLSimpleStatMMM<>		mResponse;
+		} mRequests [EVACCount];
+	};
+
+public:
+	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 setRegionID(const LLUUID & region_id);
+
 	// Non-Cached 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, F64 duration);
 
-	// Report Generation
+	// Retrieve current metrics for all visited regions.
 	const LLSD asLLSD() const;
 	
 protected:
+	typedef std::map<LLUUID, LLPointer<PerRegionStats> > PerRegionContainer;
 
-	struct 
-	{
-		LLSimpleStatCounter		mEnqueued;
-		LLSimpleStatCounter		mDequeued;
-		LLSimpleStatMMM<>		mResponse;
-	} mRequests [EVACCount];
+	// Region of the currently-active region.  Always valid but may
+	// be a NULL UUID after construction or when explicitly set.  Unchanged
+	// by a reset() call.
+	LLUUID mRegionID;
+
+	// 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;
 };
 
 
 /**
- * Expectation is that the simulator and other asset-handling
- * code will create a single instance of the stats class and
- * make it available here.  The free functions examine this
- * for non-zero and perform their functions conditionally.  The
- * instance methods themselves make no assumption about this.
+ * 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 * gViewerAssetStats;
+extern LLViewerAssetStats * gViewerAssetStatsMain;
+
+extern LLViewerAssetStats * gViewerAssetStatsThread1;
 
 namespace LLViewerAssetStatsFF
 {
 
-void record_enqueue(LLViewerAssetType::EType at, bool with_http, bool is_temp);
+void set_region_main(const LLUUID & region_id);
+
+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, F64 duration);
+
+
+void set_region_thread1(const LLUUID & region_id);
+
+void record_enqueue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp);
 
-void record_dequeue(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(LLViewerAssetType::EType at, bool with_http, bool is_temp, F64 duration);
+void record_response_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp, F64 duration);
 
 } // namespace LLViewerAssetStatsFF
 
diff --git a/indra/newview/tests/llviewerassetstats_test.cpp b/indra/newview/tests/llviewerassetstats_test.cpp
index 50d348c7e38c1582a959a9f0f1affdab18e0a731..affe16c177b84b9a897a5a71bc80e03f64e6983c 100644
--- a/indra/newview/tests/llviewerassetstats_test.cpp
+++ b/indra/newview/tests/llviewerassetstats_test.cpp
@@ -38,6 +38,7 @@
 
 #include "lltut.h"
 #include "../llviewerassetstats.h"
+#include "lluuid.h"
 
 static const char * all_keys[] = 
 {
@@ -73,6 +74,27 @@ static const char * sub_keys[] =
 	"resp_mean"
 };
 
+static const LLUUID region1("4e2d81a3-6263-6ffe-ad5c-8ce04bee07e8");
+static const LLUUID region2("68762cc8-b68b-4e45-854b-e830734f2d4a");
+
+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);
+}
+
+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);
+}
+
 namespace tut
 {
 	struct tst_viewerassetstats_index
@@ -86,29 +108,40 @@ namespace tut
 	void tst_viewerassetstats_index_object_t::test<1>()
 	{
 		// Check that helpers aren't bothered by missing global stats
-		ensure("Global gViewerAssetStats should be NULL", (NULL == gViewerAssetStats));
+		ensure("Global gViewerAssetStatsMain should be NULL", (NULL == gViewerAssetStatsMain));
 
-		LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false);
+		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false);
 
-		LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false);
+		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false);
 
-		LLViewerAssetStatsFF::record_response(LLViewerAssetType::AT_GESTURE, false, false, 12.3);
+		LLViewerAssetStatsFF::record_response_main(LLViewerAssetType::AT_GESTURE, false, false, 12.3);
 	}
 
 	// Create a non-global instance and check the structure
 	template<> template<>
 	void tst_viewerassetstats_index_object_t::test<2>()
 	{
-		ensure("Global gViewerAssetStats should be NULL", (NULL == gViewerAssetStats));
+		ensure("Global gViewerAssetStatsMain should be NULL", (NULL == gViewerAssetStatsMain));
 
 		LLViewerAssetStats * it = new LLViewerAssetStats();
 
-		ensure("Global gViewerAssetStats should still be NULL", (NULL == gViewerAssetStats));
-		
-		LLSD sd = it->asLLSD();
-		
-		delete it;
+		ensure("Global gViewerAssetStatsMain should still be NULL", (NULL == gViewerAssetStatsMain));
 
+		LLSD sd_full = it->asLLSD();
+
+		// Default (NULL) region ID doesn't produce LLSD results so should
+		// get an empty map back from output
+		ensure("Null LLSD initially", is_empty_map(sd_full));
+
+		// Once the region is set, we will get a response even with no data collection
+		it->setRegionID(region1);
+		sd_full = it->asLLSD();
+		ensure("Correct single-key LLSD map", is_single_key_map(sd_full, region1.asString()));
+
+		LLSD sd = sd_full[region1.asString()];
+
+		delete it;
+			
 		// Check the structure of the LLSD
 		for (int i = 0; i < LL_ARRAY_SIZE(all_keys); ++i)
 		{
@@ -131,8 +164,11 @@ namespace tut
 	void tst_viewerassetstats_index_object_t::test<3>()
 	{
 		LLViewerAssetStats * it = new LLViewerAssetStats();
+		it->setRegionID(region1);
 		
 		LLSD sd = it->asLLSD();
+		ensure("Correct single-key LLSD map", is_single_key_map(sd, region1.asString()));
+		sd = sd[region1.asString()];
 		
 		delete it;
 
@@ -145,15 +181,57 @@ namespace tut
 	template<> template<>
 	void tst_viewerassetstats_index_object_t::test<4>()
 	{
-		gViewerAssetStats = new LLViewerAssetStats();
+		gViewerAssetStatsMain = new LLViewerAssetStats();
+		LLViewerAssetStatsFF::set_region_main(region1);
+
+		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();
+		ensure("Correct single-key LLSD map", is_single_key_map(sd, region1.asString()));
+		sd = sd[region1.asString()];
+		
+		// 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()[region1.asString()];
+		
+		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);
 
-		LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false);
-		LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false);
+		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false);
 
-		LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, false);
-		LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false);
+		LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false);
+		LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false);
 
-		LLSD sd = gViewerAssetStats->asLLSD();
+		LLSD sd = gViewerAssetStatsThread1->asLLSD();
+		ensure("Other collector is empty", is_empty_map(sd));
+		sd = gViewerAssetStatsMain->asLLSD();
+		ensure("Correct single-key LLSD map", is_single_key_map(sd, region1.asString()));
+		sd = sd[region1.asString()];
 		
 		// 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()));
@@ -163,11 +241,196 @@ namespace tut
 		ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger()));
 
 		// Reset and check zeros...
-		gViewerAssetStats->reset();
-		sd = gViewerAssetStats->asLLSD();
+		// Reset leaves current region in place
+		gViewerAssetStatsMain->reset();
+		sd = gViewerAssetStatsMain->asLLSD()[region1.asString()];
+		
+		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);
+
+		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);
+
+		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();
+
+		ensure("Correct double-key LLSD map", is_double_key_map(sd, region1.asString(), region2.asString()));
+		LLSD sd1 = sd[region1.asString()];
+		LLSD sd2 = sd[region2.asString()];
+		
+		// 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 0", (0 == 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 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();
+		ensure("Correct single-key LLSD map", is_single_key_map(sd, region2.asString()));
+		sd2 = sd[region2.asString()];
+		
+		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);
+
+		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);
+
+		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);
+
+		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);
+
+		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();
+
+		ensure("Correct double-key LLSD map", is_double_key_map(sd, region1.asString(), region2.asString()));
+		LLSD sd1 = sd[region1.asString()];
+		LLSD sd2 = sd[region2.asString()];
+		
+		// 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();
+		ensure("Correct single-key LLSD map", is_single_key_map(sd, region2.asString()));
+		sd2 = sd[region2.asString()];
+		
+		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()));
+	}
+
+	// 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);
+
+		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();
+		ensure("Other collector is empty", is_empty_map(sd));
+		sd = gViewerAssetStatsMain->asLLSD();
+		ensure("Correct single-key LLSD map", is_single_key_map(sd, region1.asString()));
+		sd = sd[region1.asString()];
+		
+		// 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 = gViewerAssetStatsMain->asLLSD()[region1.asString()];
 		
-		delete gViewerAssetStats;
-		gViewerAssetStats = NULL;
+		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()));