diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index c667fba86f2cca415c786bc7ef30f76280ab7fba..3640d0164241fa0413bc598b47e3ef143ae71091 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -3804,6 +3804,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(frame_rate_clamped);
 		}
 	}
 
@@ -4805,23 +4810,17 @@ void LLAppViewer::metricsSend(bool enable_reporting)
 		{
 			std::string	caps_url = regionp->getCapability("ViewerMetrics");
 
-			// *NOTE:  Pay attention here.  LLSD's are not safe for thread sharing
-			// and their ownership is difficult to transfer across threads.  We do
-			// it here by having only one reference (the new'd pointer) to the LLSD
-			// or any subtree of it.  This pointer is then transfered to the other
-			// thread using correct thread logic to do all data ordering.
-			LLSD * envelope = new LLSD(LLSD::emptyMap());
-			{
-				(*envelope) = gViewerAssetStatsMain->asLLSD();
-				(*envelope)["session_id"] = gAgentSessionID;
-				(*envelope)["agent_id"] = gAgentID;
-			}
-		
+			// 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 have it sent to the stats collector.  LLSD ownership transfers
-			// with this call.
-			LLAppViewer::sTextureFetch->commandSendMetrics(caps_url, envelope);
-			envelope = 0;			// transfer noted
+			// and provide some additional parameters while here.
+			LLAppViewer::sTextureFetch->commandSendMetrics(caps_url,
+														   gAgentSessionID,
+														   gAgentID,
+														   main_stats);
+			main_stats = 0;		// Ownership transferred
 		}
 		else
 		{
diff --git a/indra/newview/llsimplestat.h b/indra/newview/llsimplestat.h
index f8f4be0390c82bba56fd71c81d1061cd5fe43db7..a90e503adba7a429dd484ddf5eb13bce23e17d72 100644
--- a/indra/newview/llsimplestat.h
+++ b/indra/newview/llsimplestat.h
@@ -62,6 +62,9 @@ class LLSimpleStatCounter
 
 	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; }
@@ -125,6 +128,21 @@ class LLSimpleStatMMM
 			++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; }
diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp
index 73d78c9334559c6043853f13f8712201de369bc6..e1f9d7bdcc1ff0a74ef9cb97dfed111bf770f031 100644
--- a/indra/newview/lltexturefetch.cpp
+++ b/indra/newview/lltexturefetch.cpp
@@ -442,18 +442,18 @@ namespace
  *         |           | TE  |                   .        +-------+
  *         |           +--+--+                   .        | Thd1  |
  *         |              |                      .        |       |
- *         |  (llsd)   +-----+                   .        | Stats |
+ *         |           +-----+                   .        | Stats |
  *          `--------->| RSC |                   .        |       |
  *                     +--+--+                   .        | Coll. |
  *                        |                      .        +-------+
  *                     +--+--+                   .            |
  *                     | SME |---.               .            |
  *                     +-----+    \              .            |
- *                        .        \ (llsd)   +-----+         |
+ *                        .        \ (clone)  +-----+         |
  *                        .         `-------->| SM  |         |
  *                        .                   +--+--+         |
  *                        .                      |            |
- *                        .                   +-----+  (llsd) |
+ *                        .                   +-----+         |
  *                        .                   | RSC |<--------'
  *                        .                   +-----+
  *                        .                      |
@@ -472,11 +472,12 @@ namespace
  * 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 an LLSD.
+ *       including an ownership transfer of a cloned LLViewerAssetStats.
  *       TFReqSendMetrics carries the data.
  * SM  - Send Metrics.  Global metrics reporting operation.  Takes
- *       the remote LLSD from the command, merges it with and LLSD
- *       from the local collector and sends it to the grid.
+ *       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
@@ -485,7 +486,8 @@ namespace
  * 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 in LLSD form.
+ * RSC - Read Stats Collector.  Extract collector data cloning it
+ *       (i.e. deep copy) when necessary.
  *
  */
 class TFRequest // : public LLQueuedThread::QueuedRequest
@@ -539,11 +541,12 @@ class TFReqSetRegion : public TFRequest
  *
  * This is the big operation.  The main thread gathers metrics
  * for a period of minutes into LLViewerAssetStats and other
- * objects then builds an LLSD to represent the data.  It uses
- * this command to transfer the LLSD, content *and* ownership,
- * to the TextureFetch thread which adds its own metrics and
- * kicks of an HTTP POST of the resulting data to the currently
- * active metrics collector.
+ * 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()
  */
@@ -558,16 +561,24 @@ class TFReqSendMetrics : public TFRequest
 	 *							to receive the data.  Does not have to
 	 *							be associated with a particular region.
 	 *
-	 * @param	report_main		Pointer to LLSD containing main
-	 *							thread metrics.  Ownership transfers
-	 *							to the new thread using very carefully
-	 *							constructed code.
+	 * @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,
-					 LLSD * report_main)
+					 const LLUUID & session_id,
+					 const LLUUID & agent_id,
+					 LLViewerAssetStats * main_stats)
 		: TFRequest(),
 		  mCapsURL(caps_url),
-		  mReportMain(report_main)
+		  mSessionID(session_id),
+		  mAgentID(agent_id),
+		  mMainStats(main_stats)
 		{}
 	TFReqSendMetrics & operator=(const TFReqSendMetrics &);	// Not defined
 
@@ -577,7 +588,9 @@ class TFReqSendMetrics : public TFRequest
 		
 public:
 	const std::string mCapsURL;
-	LLSD * mReportMain;
+	const LLUUID mSessionID;
+	const LLUUID mAgentID;
+	LLViewerAssetStats * mMainStats;
 };
 
 /*
@@ -2727,9 +2740,11 @@ void LLTextureFetch::commandSetRegion(U64 region_handle)
 }
 
 void LLTextureFetch::commandSendMetrics(const std::string & caps_url,
-										LLSD * report_main)
+										const LLUUID & session_id,
+										const LLUUID & agent_id,
+										LLViewerAssetStats * main_stats)
 {
-	TFReqSendMetrics * req = new TFReqSendMetrics(caps_url, report_main);
+	TFReqSendMetrics * req = new TFReqSendMetrics(caps_url, session_id, agent_id, main_stats);
 
 	cmdEnqueue(req);
 }
@@ -2808,14 +2823,14 @@ TFReqSetRegion::doWork(LLTextureFetch *)
 
 TFReqSendMetrics::~TFReqSendMetrics()
 {
-	delete mReportMain;
-	mReportMain = 0;
+	delete mMainStats;
+	mMainStats = 0;
 }
 
 
 /**
  * Implements the 'Send Metrics' command.  Takes over
- * ownership of the passed LLSD pointer.
+ * ownership of the passed LLViewerAssetStats pointer.
  *
  * Thread:  Thread1 (TextureFetch)
  */
@@ -2893,33 +2908,36 @@ TFReqSendMetrics::doWork(LLTextureFetch * fetcher)
 	static volatile bool reporting_started(false);
 	static volatile S32 report_sequence(0);
     
-	// We've already taken over ownership of the LLSD at this point
-	// and can do normal LLSD sharing operations at this point.  But
-	// still being careful, regardless.
-	LLSD & main_stats = *mReportMain;
-
-	LLSD thread1_stats = gViewerAssetStatsThread1->asLLSD();			// 'duration' & 'regions' from this LLSD
-	thread1_stats["message"] = "ViewerAssetMetrics";					// Identifies the type of metrics
-	thread1_stats["sequence"] = report_sequence;						// Sequence number
-	thread1_stats["initial"] = ! reporting_started;						// Initial data from viewer
-	thread1_stats["break"] = LLTextureFetch::svMetricsDataBreak;		// Break in data prior to this report
+	// 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();
+
+	// 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;
 
-	// Merge the two LLSDs into a single report
-	LLViewerAssetStatsFF::merge_stats(main_stats, thread1_stats);
-
 	// Limit the size of the stats report if necessary.
-	thread1_stats["truncated"] = truncate_viewer_metrics(10, thread1_stats);
+	merged_llsd["truncated"] = truncate_viewer_metrics(10, merged_llsd);
 
 	if (! mCapsURL.empty())
 	{
 		LLCurlRequest::headers_t headers;
 		fetcher->getCurlRequest().post(mCapsURL,
 									   headers,
-									   thread1_stats,
+									   merged_llsd,
 									   new lcl_responder(report_sequence,
                                                          report_sequence,
                                                          LLTextureFetch::svMetricsDataBreak,
@@ -2933,7 +2951,7 @@ TFReqSendMetrics::doWork(LLTextureFetch * fetcher)
 	// In QA mode, Metrics submode, log the result for ease of testing
 	if (fetcher->isQAMode())
 	{
-		LL_INFOS("Textures") << thread1_stats << LL_ENDL;
+		LL_INFOS("Textures") << merged_llsd << LL_ENDL;
 	}
 
 	gViewerAssetStatsThread1->reset();
diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h
index af30d1bb3bfbea54add5f57096ff5c7e9b186b91..a8fd3ce244aa35ba9612b61d95964ce753cad40a 100644
--- a/indra/newview/lltexturefetch.h
+++ b/indra/newview/lltexturefetch.h
@@ -40,6 +40,8 @@ class HTTPGetResponder;
 class LLTextureCache;
 class LLImageDecodeThread;
 class LLHost;
+class LLViewerAssetStats;
+
 namespace { class TFRequest; }
 
 // Interface class
@@ -88,7 +90,10 @@ class LLTextureFetch : public LLWorkerThread
 
 	// Commands available to other threads to control metrics gathering operations.
 	void commandSetRegion(U64 region_handle);
-	void commandSendMetrics(const std::string & caps_url, LLSD * report_main);
+	void commandSendMetrics(const std::string & caps_url,
+							const LLUUID & session_id,
+							const LLUUID & agent_id,
+							LLViewerAssetStats * main_stats);
 	void commandDataBreak();
 
 	LLCurlRequest & getCurlRequest()	{ return *mCurlGetRequest; }
diff --git a/indra/newview/llviewerassetstats.cpp b/indra/newview/llviewerassetstats.cpp
index d7987862773155892eefab4ed33a6269ec967eb1..399d62d2fc63de255bd4dca24a95eb379d9cbcf9 100644
--- a/indra/newview/llviewerassetstats.cpp
+++ b/indra/newview/llviewerassetstats.cpp
@@ -113,11 +113,34 @@ LLViewerAssetStats::PerRegionStats::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)
 {
@@ -136,6 +159,19 @@ LLViewerAssetStats::LLViewerAssetStats()
 }
 
 
+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()
 {
@@ -215,6 +251,12 @@ LLViewerAssetStats::recordGetServiced(LLViewerAssetType::EType at, bool with_htt
 	mCurRegionStats->mRequests[int(eac)].mResponse.record(duration);
 }
 
+void
+LLViewerAssetStats::recordFPS(F32 fps)
+{
+	mCurRegionStats->mFPS.record(fps);
+}
+
 LLSD
 LLViewerAssetStats::asLLSD()
 {
@@ -231,7 +273,7 @@ LLViewerAssetStats::asLLSD()
 			LLSD::String("get_other")
 		};
 
-	// Sub-tags.  If you add or delete from this list, mergeRegionsLLSD() must be updated.
+	// 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");
@@ -239,6 +281,12 @@ LLViewerAssetStats::asLLSD()
 	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);
 
@@ -257,7 +305,7 @@ LLViewerAssetStats::asLLSD()
 		
 		LLSD reg_stat = LLSD::emptyMap();
 		
-		for (int i = 0; i < EVACCount; ++i)
+		for (int i = 0; i < LL_ARRAY_SIZE(tags); ++i)
 		{
 			LLSD & slot = reg_stat[tags[i]];
 			slot = LLSD::emptyMap();
@@ -269,6 +317,15 @@ LLViewerAssetStats::asLLSD()
 			slot[rmean_tag] = LLSD(F64(stats.mRequests[i].mResponse.getMean() * 1.0e-6));
 		}
 
+		{
+			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()));
+		}
+
 		reg_stat["duration"] = LLSD::Real(stats.mTotalTime * 1.0e-6);
 		std::stringstream reg_handle;
 		reg_handle.width(16);
@@ -284,181 +341,24 @@ LLViewerAssetStats::asLLSD()
 	return ret;
 }
 
-/* static */ void
-LLViewerAssetStats::mergeRegionsLLSD(const LLSD & src, LLSD & dst)
+void
+LLViewerAssetStats::merge(const LLViewerAssetStats & src)
 {
-	// Merge operator definitions
-	static const int MOP_ADD_INT(0);
-	static const int MOP_MIN_REAL(1);
-	static const int MOP_MAX_REAL(2);
-	static const int MOP_MEAN_REAL(3);	// Requires a 'mMergeOpArg' to weight the input terms
-
-	static const LLSD::String regions_key("regions");
-	static const LLSD::String resp_count_key("resp_count");
-	
-	static const struct
-		{
-			LLSD::String		mName;
-			int					mMergeOp;
-		}
-	key_list[] =
-		{
-			// Order is important below.  We modify the data in-place and
-			// so operations like MOP_MEAN_REAL which need the "resp_count"
-			// value for weighting must be performed before "resp_count"
-			// is modified or the weight will be wrong.  Key list is
-			// defined in asLLSD() and must track it.
-
-			{ "resp_mean", MOP_MEAN_REAL },
-			{ "enqueued", MOP_ADD_INT },
-			{ "dequeued", MOP_ADD_INT },
-			{ "resp_min", MOP_MIN_REAL },
-			{ "resp_max", MOP_MAX_REAL },
-			{ resp_count_key, MOP_ADD_INT }			// Keep last
-		};
+	// mRegionHandle, mCurRegionStats and mResetTimestamp are left untouched.
+	// Just merge the stats bodies
 
-	// Trivial checks
-	if (! src.has(regions_key))
+	const PerRegionContainer::const_iterator it_end(src.mRegionStats.end());
+	for (PerRegionContainer::const_iterator it(src.mRegionStats.begin()); it_end != it; ++it)
 	{
-		return;
-	}
-
-	if (! dst.has(regions_key))
-	{
-		dst[regions_key] = src[regions_key];
-		return;
-	}
-	
-	// Non-trivial cases requiring a deep merge.
-	const LLSD & root_src(src[regions_key]);
-	LLSD & root_dst(dst[regions_key]);
-	
-	const LLSD::map_const_iterator it_uuid_end(root_src.endMap());
-	for (LLSD::map_const_iterator it_uuid(root_src.beginMap()); it_uuid_end != it_uuid; ++it_uuid)
-	{
-		if (! root_dst.has(it_uuid->first))
+		PerRegionContainer::iterator dst(mRegionStats.find(it->first));
+		if (mRegionStats.end() == dst)
 		{
-			// src[<region>] without matching dst[<region>]
-			root_dst[it_uuid->first] = it_uuid->second;
+			// Destination is missing data, just make a private copy
+			mRegionStats[it->first] = new PerRegionStats(*it->second);
 		}
 		else
 		{
-			// src[<region>] with matching dst[<region>]
-			// We have matching source and destination regions.
-			// Now iterate over each asset bin in the region status.  Could iterate over
-			// an explicit list but this will do as well.
-			LLSD & reg_dst(root_dst[it_uuid->first]);
-			const LLSD & reg_src(root_src[it_uuid->first]);
-
-			const LLSD::map_const_iterator it_sets_end(reg_src.endMap());
-			for (LLSD::map_const_iterator it_sets(reg_src.beginMap()); it_sets_end != it_sets; ++it_sets)
-			{
-				static const LLSD::String no_touch_1("duration");
-
-				if (no_touch_1 == it_sets->first)
-				{
-					continue;
-				}
-				else if (! reg_dst.has(it_sets->first))
-				{
-					// src[<region>][<asset>] without matching dst[<region>][<asset>]
-					reg_dst[it_sets->first] = it_sets->second;
-				}
-				else
-				{
-					// src[<region>][<asset>] with matching dst[<region>][<asset>]
-					// Matching stats bin in both source and destination regions.
-					// Iterate over those bin keys we know how to merge, leave the remainder untouched.
-					LLSD & bin_dst(reg_dst[it_sets->first]);
-					const LLSD & bin_src(reg_src[it_sets->first]);
-
-					// The "resp_count" value is needed repeatedly in operations.
-					const LLSD::Integer bin_src_count(bin_src[resp_count_key].asInteger());
-					const LLSD::Integer bin_dst_count(bin_dst[resp_count_key].asInteger());
-			
-					for (int key_index(0); key_index < LL_ARRAY_SIZE(key_list); ++key_index)
-					{
-						const LLSD::String & key_name(key_list[key_index].mName);
-						
-						if (! bin_src.has(key_name))
-						{
-							// Missing src[<region>][<asset>][<field>]
-							continue;
-						}
-
-						const LLSD & src_value(bin_src[key_name]);
-				
-						if (! bin_dst.has(key_name))
-						{
-							// src[<region>][<asset>][<field>] without matching dst[<region>][<asset>][<field>]
-							bin_dst[key_name] = src_value;
-						}
-						else
-						{
-							// src[<region>][<asset>][<field>] with matching dst[<region>][<asset>][<field>]
-							LLSD & dst_value(bin_dst[key_name]);
-					
-							switch (key_list[key_index].mMergeOp)
-							{
-							case MOP_ADD_INT:
-								// Simple counts, just add
-								dst_value = dst_value.asInteger() + src_value.asInteger();
-								break;
-						
-							case MOP_MIN_REAL:
-								// Minimum
-								if (bin_src_count)
-								{
-									// If src has non-zero count, it's min is meaningful
-									if (bin_dst_count)
-									{
-										dst_value = llmin(dst_value.asReal(), src_value.asReal());
-									}
-									else
-									{
-										dst_value = src_value;
-									}
-								}
-								break;
-
-							case MOP_MAX_REAL:
-								// Maximum
-								if (bin_src_count)
-								{
-									// If src has non-zero count, it's max is meaningful
-									if (bin_dst_count)
-									{
-										dst_value = llmax(dst_value.asReal(), src_value.asReal());
-									}
-									else
-									{
-										dst_value = src_value;
-									}
-								}
-								break;
-
-							case MOP_MEAN_REAL:
-							    {
-									// Mean
-									F64 src_weight(bin_src_count);
-									F64 dst_weight(bin_dst_count);
-									F64 tot_weight(src_weight + dst_weight);
-									if (tot_weight >= F64(0.5))
-									{
-										dst_value = (((dst_value.asReal() * dst_weight)
-													  + (src_value.asReal() * src_weight))
-													 / tot_weight);
-									}
-								}
-								break;
-						
-							default:
-								break;
-							}
-						}
-					}
-				}
-			}
+			dst->second->merge(*it->second);
 		}
 	}
 }
@@ -526,6 +426,15 @@ record_response_main(LLViewerAssetType::EType at, bool with_http, bool is_temp,
 	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
 
@@ -590,41 +499,6 @@ cleanup()
 }
 
 
-void
-merge_stats(const LLSD & src, LLSD & dst)
-{
-	static const LLSD::String regions_key("regions");
-
-	// Trivial cases first
-	if (! src.isMap())
-	{
-		return;
-	}
-
-	if (! dst.isMap())
-	{
-		dst = src;
-		return;
-	}
-	
-	// Okay, both src and dst are maps at this point.
-	// Collector class know how to merge the regions part.
-	LLViewerAssetStats::mergeRegionsLLSD(src, dst);
-
-	// Now merge non-regions bits manually.
-	const LLSD::map_const_iterator it_end(src.endMap());
-	for (LLSD::map_const_iterator it(src.beginMap()); it_end != it; ++it)
-	{
-		if (regions_key == it->first)
-			continue;
-
-		if (dst.has(it->first))
-			continue;
-
-		dst[it->first] = it->second;
-	}
-}
-
 } // namespace LLViewerAssetStatsFF
 
 
diff --git a/indra/newview/llviewerassetstats.h b/indra/newview/llviewerassetstats.h
index ed2d0f3922d0c5091b28d308a2e6e8c0176376ce..af6bf5b6951abd2da4c3c8ffa03d6eb885349c78 100644
--- a/indra/newview/llviewerassetstats.h
+++ b/indra/newview/llviewerassetstats.h
@@ -125,29 +125,48 @@ class LLViewerAssetStats
 			{
 				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;
+		region_handle_t		mRegionHandle;
+		duration_t			mTotalTime;
+		duration_t			mStartTimestamp;
+		LLSimpleStatMMM<>	mFPS;
 		
 		struct
 		{
 			LLSimpleStatCounter			mEnqueued;
 			LLSimpleStatCounter			mDequeued;
 			LLSimpleStatMMM<duration_t>	mResponse;
-		} mRequests [EVACCount];
+		}
+		mRequests [EVACCount];
 	};
 
 public:
 	LLViewerAssetStats();
+	LLViewerAssetStats(const LLViewerAssetStats &);
 	// Default destructor is correct.
 	LLViewerAssetStats & operator=(const LLViewerAssetStats &);			// Not defined
 
@@ -165,6 +184,18 @@ class LLViewerAssetStats
 	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:
 	//
@@ -177,11 +208,19 @@ class LLViewerAssetStats
 	//   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,
@@ -195,15 +234,6 @@ class LLViewerAssetStats
 	// }
 	LLSD asLLSD();
 
-	// Merges the "regions" maps in two LLSDs structured as per asLLSD().
-	// This takes two LLSDs as returned by asLLSD() and intelligently
-	// merges the metrics contained in the maps indexed by "regions".
-	// The remainder of the top-level map of the LLSDs is left unchanged
-	// in expectation that callers will add other information at this
-	// level.  The "regions" information must be correctly formed or the
-	// final result is undefined (little defensive action).
-	static void mergeRegionsLLSD(const LLSD & src, LLSD & dst);
-
 protected:
 	typedef std::map<region_handle_t, LLPointer<PerRegionStats> > PerRegionContainer;
 
@@ -278,6 +308,8 @@ void record_dequeue_main(LLViewerAssetType::EType at, bool with_http, bool is_te
 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.
@@ -291,18 +323,6 @@ void record_dequeue_thread1(LLViewerAssetType::EType at, bool with_http, bool is
 void record_response_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp,
 						  LLViewerAssetStats::duration_t duration);
 
-/**
- * @brief Merge two LLSD reports from different collector instances
- *
- * Use this to merge the LLSD's from two threads.  For top-level,
- * non-region data the destination (dst) is considered authoritative
- * if the key is present in both source and destination.  For
- * regions, a numerical merge is performed when data are present in
- * both source and destination and the 'right thing' is done for
- * counts, minimums, maximums and averages.
- */
-void merge_stats(const LLSD & src, LLSD & dst);
-
 } // namespace LLViewerAssetStatsFF
 
 #endif // LL_LLVIEWERASSETSTATUS_H
diff --git a/indra/newview/tests/llsimplestat_test.cpp b/indra/newview/tests/llsimplestat_test.cpp
index 5efc9cf857b3f927fc82008f2b55dd57b17bbfb8..60a8cac995ba1ad1f280c21406a45f44b50f73d1 100644
--- a/indra/newview/tests/llsimplestat_test.cpp
+++ b/indra/newview/tests/llsimplestat_test.cpp
@@ -425,4 +425,162 @@ namespace tut
 		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
index 153056b3cd4f05c17f4d72941695c7285de7f0e3..9c5426601721351a90fb1285a85487d3e25b81d2 100644
--- a/indra/newview/tests/llviewerassetstats_test.cpp
+++ b/indra/newview/tests/llviewerassetstats_test.cpp
@@ -44,6 +44,7 @@
 static const char * all_keys[] = 
 {
 	"duration",
+	"fps",
 	"get_other",
 	"get_texture_temp_http",
 	"get_texture_temp_udp",
@@ -76,6 +77,19 @@ static const char * sub_keys[] =
 	"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(0x00000401000003f7ULL);
@@ -172,6 +186,15 @@ namespace tut
 				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
@@ -461,293 +484,395 @@ namespace tut
 		ensure("sd[get_gesture_udp][dequeued] is reset", (0 == sd["get_gesture_udp"]["dequeued"].asInteger()));
 	}
 
-	// Check that the LLSD merger knows what it's doing (basic test)
+
+	// LLViewerAssetStats::merge() basic functions work
 	template<> template<>
 	void tst_viewerassetstats_index_object_t::test<9>()
 	{
-		LLSD::String reg1_name = region1_handle_str;
-		LLSD::String reg2_name = region2_handle_str;
-
-		LLSD reg1_stats = LLSD::emptyMap();
-		LLSD reg2_stats = LLSD::emptyMap();
-
-		LLSD & tmp_other1 = reg1_stats["get_other"];
-		tmp_other1["enqueued"] = 4;
-		tmp_other1["dequeued"] = 4;
-		tmp_other1["resp_count"] = 8;
-		tmp_other1["resp_max"] = F64(23.2892);
-		tmp_other1["resp_min"] = F64(0.2829);
-		tmp_other1["resp_mean"] = F64(2.298928);
-
-		LLSD & tmp_other2 = reg2_stats["get_other"];
-		tmp_other2["enqueued"] = 8;
-		tmp_other2["dequeued"] = 7;
-		tmp_other2["resp_count"] = 3;
-		tmp_other2["resp_max"] = F64(6.5);
-		tmp_other2["resp_min"] = F64(0.01);
-		tmp_other2["resp_mean"] = F64(4.1);
+		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);
 		
-		{
-			LLSD src = LLSD::emptyMap();
-			LLSD dst = LLSD::emptyMap();
+		s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 2000000);
+		s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 3000000);
+		s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 4000000);
 
-			src["regions"][reg1_name] = reg1_stats;
-			src["duration"] = 24;
-			dst["regions"][reg2_name] = reg2_stats;
-			dst["duration"] = 36;
+		s2.merge(s1);
 
-			LLViewerAssetStats::mergeRegionsLLSD(src, dst);
+		LLSD s2_llsd = s2.asLLSD();
 		
-			ensure("region 1 in merged stats", llsd_equals(reg1_stats, dst["regions"][reg1_name]));
-			ensure("region 2 still in merged stats", llsd_equals(reg2_stats, dst["regions"][reg2_name]));
-		}
+		ensure_equals("count after merge", 8, s2_llsd["regions"][region1_handle_str]["get_texture_temp_http"]["resp_count"].asInteger());
+		ensure_approximately_equals("min after merge", 2.0, s2_llsd["regions"][region1_handle_str]["get_texture_temp_http"]["resp_min"].asReal(), 22);
+		ensure_approximately_equals("max after merge", 9.0, s2_llsd["regions"][region1_handle_str]["get_texture_temp_http"]["resp_max"].asReal(), 22);
+		ensure_approximately_equals("max after merge", 5.5, s2_llsd["regions"][region1_handle_str]["get_texture_temp_http"]["resp_mean"].asReal(), 22);
 
-		{
-			LLSD src = LLSD::emptyMap();
-			LLSD dst = LLSD::emptyMap();
-
-			src["regions"][reg1_name] = reg1_stats;
-			src["duration"] = 24;
-			dst["regions"][reg1_name] = reg2_stats;
-			dst["duration"] = 36;
-
-			LLViewerAssetStats::mergeRegionsLLSD(src, dst);
-
-			ensure("src not ruined", llsd_equals(reg1_stats, src["regions"][reg1_name]));
-			ensure_equals("added enqueued counts", dst["regions"][reg1_name]["get_other"]["enqueued"].asInteger(), 12);
-			ensure_equals("added dequeued counts", dst["regions"][reg1_name]["get_other"]["dequeued"].asInteger(), 11);
-			ensure_equals("added response counts", dst["regions"][reg1_name]["get_other"]["resp_count"].asInteger(), 11);
-			ensure_approximately_equals("min'd minimum response times", dst["regions"][reg1_name]["get_other"]["resp_min"].asReal(), 0.01, 20);
-			ensure_approximately_equals("max'd maximum response times", dst["regions"][reg1_name]["get_other"]["resp_max"].asReal(), 23.2892, 20);
-			ensure_approximately_equals("weighted mean of means", dst["regions"][reg1_name]["get_other"]["resp_mean"].asReal(), 2.7901295, 20);
-		}
 	}
 
-	// Maximum merges are interesting when one side contributes nothing
+	// LLViewerAssetStats::merge() basic functions work without corrupting source data
 	template<> template<>
 	void tst_viewerassetstats_index_object_t::test<10>()
 	{
-		LLSD::String reg1_name = region1_handle_str;
-		LLSD::String reg2_name = region2_handle_str;
-
-		LLSD reg1_stats = LLSD::emptyMap();
-		LLSD reg2_stats = LLSD::emptyMap();
-
-		LLSD & tmp_other1 = reg1_stats["get_other"];
-		tmp_other1["enqueued"] = 4;
-		tmp_other1["dequeued"] = 4;
-		tmp_other1["resp_count"] = 7;
-		tmp_other1["resp_max"] = F64(-23.2892);
-		tmp_other1["resp_min"] = F64(-123.2892);
-		tmp_other1["resp_mean"] = F64(-58.28298);
-
-		LLSD & tmp_other2 = reg2_stats["get_other"];
-		tmp_other2["enqueued"] = 8;
-		tmp_other2["dequeued"] = 7;
-		tmp_other2["resp_count"] = 0;
-		tmp_other2["resp_max"] = F64(0);
-		tmp_other2["resp_min"] = F64(0);
-		tmp_other2["resp_mean"] = F64(0);
-		
-		{
-			LLSD src = LLSD::emptyMap();
-			LLSD dst = LLSD::emptyMap();
+		LLViewerAssetStats s1;
+		LLViewerAssetStats s2;
 
-			src["regions"][reg1_name] = reg1_stats;
-			src["duration"] = 24;
-			dst["regions"][reg1_name] = reg2_stats;
-			dst["duration"] = 36;
+		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);
 
-			LLViewerAssetStats::mergeRegionsLLSD(src, dst);
-		
-			ensure_approximately_equals("dst maximum with count 0 does not contribute to merged maximum",
-										dst["regions"][reg1_name]["get_other"]["resp_max"].asReal(), F64(-23.2892), 20);
-		}
-
-		{
-			LLSD src = LLSD::emptyMap();
-			LLSD dst = LLSD::emptyMap();
+		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);
 
-			src["regions"][reg1_name] = reg2_stats;
-			src["duration"] = 24;
-			dst["regions"][reg1_name] = reg1_stats;
-			dst["duration"] = 36;
+		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 23289200);
+		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 282900);
 
-			LLViewerAssetStats::mergeRegionsLLSD(src, dst);
 		
-			ensure_approximately_equals("src maximum with count 0 does not contribute to merged maximum",
-										dst["regions"][reg1_name]["get_other"]["resp_max"].asReal(), F64(-23.2892), 20);
-		}
-	}
+		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);
 
-    // Minimum merges are interesting when one side contributes nothing
-	template<> template<>
-	void tst_viewerassetstats_index_object_t::test<11>()
-	{
-		LLSD::String reg1_name = region1_handle_str;
-		LLSD::String reg2_name = region2_handle_str;
-
-		LLSD reg1_stats = LLSD::emptyMap();
-		LLSD reg2_stats = LLSD::emptyMap();
-
-		LLSD & tmp_other1 = reg1_stats["get_other"];
-		tmp_other1["enqueued"] = 4;
-		tmp_other1["dequeued"] = 4;
-		tmp_other1["resp_count"] = 7;
-		tmp_other1["resp_max"] = F64(123.2892);
-		tmp_other1["resp_min"] = F64(23.2892);
-		tmp_other1["resp_mean"] = F64(58.28298);
-
-		LLSD & tmp_other2 = reg2_stats["get_other"];
-		tmp_other2["enqueued"] = 8;
-		tmp_other2["dequeued"] = 7;
-		tmp_other2["resp_count"] = 0;
-		tmp_other2["resp_max"] = F64(0);
-		tmp_other2["resp_min"] = F64(0);
-		tmp_other2["resp_mean"] = F64(0);
-		
 		{
-			LLSD src = LLSD::emptyMap();
-			LLSD dst = LLSD::emptyMap();
-
-			src["regions"][reg1_name] = reg1_stats;
-			src["duration"] = 24;
-			dst["regions"][reg1_name] = reg2_stats;
-			dst["duration"] = 36;
+			s2.merge(s1);
+			
+			LLSD src = s1.asLLSD();
+			LLSD dst = s2.asLLSD();
+
+			// Remove time stamps, they're a problem
+			src.erase("duration");
+			src["regions"][region1_handle_str].erase("duration");
+			dst.erase("duration");
+			dst["regions"][region1_handle_str].erase("duration");
+			dst["regions"][region2_handle_str].erase("duration");
+
+			ensure_equals("merge src has single region", 1, src["regions"].size());
+			ensure_equals("merge dst has dual regions", 2, dst["regions"].size());
+			ensure("result from src is in dst", llsd_equals(src["regions"][region1_handle_str],
+															dst["regions"][region1_handle_str]));
+		}
 
-			LLViewerAssetStats::mergeRegionsLLSD(src, dst);
+		s1.setRegion(region1_handle);
+		s2.setRegion(region1_handle);
+		s1.reset();
+		s2.reset();
 		
-			ensure_approximately_equals("dst minimum with count 0 does not contribute to merged minimum",
-										dst["regions"][reg1_name]["get_other"]["resp_min"].asReal(), F64(23.2892), 20);
-		}
+		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);
 
-		{
-			LLSD src = LLSD::emptyMap();
-			LLSD dst = LLSD::emptyMap();
+		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);
 
-			src["regions"][reg1_name] = reg2_stats;
-			src["duration"] = 24;
-			dst["regions"][reg1_name] = reg1_stats;
-			dst["duration"] = 36;
+		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 23289200);
+		s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 282900);
 
-			LLViewerAssetStats::mergeRegionsLLSD(src, dst);
 		
-			ensure_approximately_equals("src minimum with count 0 does not contribute to merged minimum",
-										dst["regions"][reg1_name]["get_other"]["resp_min"].asReal(), F64(23.2892), 20);
+		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();
+			LLSD dst = s2.asLLSD();
+
+			// Remove time stamps, they're a problem
+			src.erase("duration");
+			src["regions"][region1_handle_str].erase("duration");
+			dst.erase("duration");
+			dst["regions"][region1_handle_str].erase("duration");
+
+			ensure_equals("src counts okay (enq)", 4, src["regions"][region1_handle_str]["get_other"]["enqueued"].asInteger());
+			ensure_equals("src counts okay (deq)", 4, src["regions"][region1_handle_str]["get_other"]["dequeued"].asInteger());
+			ensure_equals("src resp counts okay", 2, src["regions"][region1_handle_str]["get_other"]["resp_count"].asInteger());
+			ensure_approximately_equals("src respmin okay", 0.2829, src["regions"][region1_handle_str]["get_other"]["resp_min"].asReal(), 20);
+			ensure_approximately_equals("src respmax okay", 23.2892, src["regions"][region1_handle_str]["get_other"]["resp_max"].asReal(), 20);
+			
+			ensure_equals("dst counts okay (enq)", 12, dst["regions"][region1_handle_str]["get_other"]["enqueued"].asInteger());
+			ensure_equals("src counts okay (deq)", 11, dst["regions"][region1_handle_str]["get_other"]["dequeued"].asInteger());
+			ensure_equals("dst resp counts okay", 4, dst["regions"][region1_handle_str]["get_other"]["resp_count"].asInteger());
+			ensure_approximately_equals("dst respmin okay", 0.010, dst["regions"][region1_handle_str]["get_other"]["resp_min"].asReal(), 20);
+			ensure_approximately_equals("dst respmax okay", 23.2892, dst["regions"][region1_handle_str]["get_other"]["resp_max"].asReal(), 20);
 		}
 	}
 
-    // resp_count missing is taken as '0' for maximum calculation
+
+    // Maximum merges are interesting when one side contributes nothing
 	template<> template<>
-	void tst_viewerassetstats_index_object_t::test<12>()
+	void tst_viewerassetstats_index_object_t::test<11>()
 	{
-		LLSD::String reg1_name = region1_handle_str;
-		LLSD::String reg2_name = region2_handle_str;
-
-		LLSD reg1_stats = LLSD::emptyMap();
-		LLSD reg2_stats = LLSD::emptyMap();
-
-		LLSD & tmp_other1 = reg1_stats["get_other"];
-		tmp_other1["enqueued"] = 4;
-		tmp_other1["dequeued"] = 4;
-		tmp_other1["resp_count"] = 7;
-		tmp_other1["resp_max"] = F64(-23.2892);
-		tmp_other1["resp_min"] = F64(-123.2892);
-		tmp_other1["resp_mean"] = F64(-58.28298);
-
-		LLSD & tmp_other2 = reg2_stats["get_other"];
-		tmp_other2["enqueued"] = 8;
-		tmp_other2["dequeued"] = 7;
-		// tmp_other2["resp_count"] = 0;
-		tmp_other2["resp_max"] = F64(0);
-		tmp_other2["resp_min"] = F64(0);
-		tmp_other2["resp_mean"] = F64(0);
-		
+		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);
+
 		{
-			LLSD src = LLSD::emptyMap();
-			LLSD dst = LLSD::emptyMap();
+			s2.merge(s1);
+			
+			LLSD src = s1.asLLSD();
+			LLSD dst = s2.asLLSD();
 
-			src["regions"][reg1_name] = reg1_stats;
-			src["duration"] = 24;
-			dst["regions"][reg1_name] = reg2_stats;
-			dst["duration"] = 36;
+			// Remove time stamps, they're a problem
+			src.erase("duration");
+			src["regions"][region1_handle_str].erase("duration");
+			dst.erase("duration");
+			dst["regions"][region1_handle_str].erase("duration");
 
-			LLViewerAssetStats::mergeRegionsLLSD(src, dst);
-		
-			ensure_approximately_equals("dst maximum with undefined count does not contribute to merged maximum",
-										dst["regions"][reg1_name]["get_other"]["resp_max"].asReal(), F64(-23.2892), 20);
+			ensure_equals("dst counts come from src only", 3, dst["regions"][region1_handle_str]["get_other"]["resp_count"].asInteger());
+
+			ensure_approximately_equals("dst maximum with count 0 does not contribute to merged maximum",
+										dst["regions"][region1_handle_str]["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);
+
 		{
-			LLSD src = LLSD::emptyMap();
-			LLSD dst = LLSD::emptyMap();
+			s1.merge(s2);
+			
+			LLSD src = s2.asLLSD();
+			LLSD dst = s1.asLLSD();
 
-			src["regions"][reg1_name] = reg2_stats;
-			src["duration"] = 24;
-			dst["regions"][reg1_name] = reg1_stats;
-			dst["duration"] = 36;
+			// Remove time stamps, they're a problem
+			src.erase("duration");
+			src["regions"][region1_handle_str].erase("duration");
+			dst.erase("duration");
+			dst["regions"][region1_handle_str].erase("duration");
 
-			LLViewerAssetStats::mergeRegionsLLSD(src, dst);
-		
-			ensure_approximately_equals("src maximum with undefined count does not contribute to merged maximum",
-										dst["regions"][reg1_name]["get_other"]["resp_max"].asReal(), F64(-23.2892), 20);
+			ensure_equals("dst counts come from src only (flipped)", 3, dst["regions"][region1_handle_str]["get_other"]["resp_count"].asInteger());
+
+			ensure_approximately_equals("dst maximum with count 0 does not contribute to merged maximum (flipped)",
+										dst["regions"][region1_handle_str]["get_other"]["resp_max"].asReal(), F64(0.0), 20);
 		}
 	}
 
-    // resp_count unspecified is taken as 0 for minimum merges
+    // Minimum merges are interesting when one side contributes nothing
 	template<> template<>
-	void tst_viewerassetstats_index_object_t::test<13>()
+	void tst_viewerassetstats_index_object_t::test<12>()
 	{
-		LLSD::String reg1_name = region1.asString();
-		LLSD::String reg2_name = region2.asString();
-
-		LLSD reg1_stats = LLSD::emptyMap();
-		LLSD reg2_stats = LLSD::emptyMap();
-
-		LLSD & tmp_other1 = reg1_stats["get_other"];
-		tmp_other1["enqueued"] = 4;
-		tmp_other1["dequeued"] = 4;
-		tmp_other1["resp_count"] = 7;
-		tmp_other1["resp_max"] = F64(123.2892);
-		tmp_other1["resp_min"] = F64(23.2892);
-		tmp_other1["resp_mean"] = F64(58.28298);
-
-		LLSD & tmp_other2 = reg2_stats["get_other"];
-		tmp_other2["enqueued"] = 8;
-		tmp_other2["dequeued"] = 7;
-		// tmp_other2["resp_count"] = 0;
-		tmp_other2["resp_max"] = F64(0);
-		tmp_other2["resp_min"] = F64(0);
-		tmp_other2["resp_mean"] = F64(0);
-		
+		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);
+
 		{
-			LLSD src = LLSD::emptyMap();
-			LLSD dst = LLSD::emptyMap();
+			s2.merge(s1);
+			
+			LLSD src = s1.asLLSD();
+			LLSD dst = s2.asLLSD();
 
-			src["regions"][reg1_name] = reg1_stats;
-			src["duration"] = 24;
-			dst["regions"][reg1_name] = reg2_stats;
-			dst["duration"] = 36;
+			// Remove time stamps, they're a problem
+			src.erase("duration");
+			src["regions"][region1_handle_str].erase("duration");
+			dst.erase("duration");
+			dst["regions"][region1_handle_str].erase("duration");
 
-			LLViewerAssetStats::mergeRegionsLLSD(src, dst);
-		
-			ensure_approximately_equals("dst minimum with undefined count does not contribute to merged minimum",
-										dst["regions"][reg1_name]["get_other"]["resp_min"].asReal(), F64(23.2892), 20);
+			ensure_equals("dst counts come from src only", 3, dst["regions"][region1_handle_str]["get_other"]["resp_count"].asInteger());
+
+			ensure_approximately_equals("dst minimum with count 0 does not contribute to merged minimum",
+										dst["regions"][region1_handle_str]["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);
+
 		{
-			LLSD src = LLSD::emptyMap();
-			LLSD dst = LLSD::emptyMap();
+			s1.merge(s2);
+			
+			LLSD src = s2.asLLSD();
+			LLSD dst = s1.asLLSD();
 
-			src["regions"][reg1_name] = reg2_stats;
-			src["duration"] = 24;
-			dst["regions"][reg1_name] = reg1_stats;
-			dst["duration"] = 36;
+			// Remove time stamps, they're a problem
+			src.erase("duration");
+			src["regions"][region1_handle_str].erase("duration");
+			dst.erase("duration");
+			dst["regions"][region1_handle_str].erase("duration");
 
-			LLViewerAssetStats::mergeRegionsLLSD(src, dst);
-		
-			ensure_approximately_equals("src minimum with undefined count does not contribute to merged minimum",
-										dst["regions"][reg1_name]["get_other"]["resp_min"].asReal(), F64(23.2892), 20);
+			ensure_equals("dst counts come from src only (flipped)", 3, dst["regions"][region1_handle_str]["get_other"]["resp_count"].asInteger());
+
+			ensure_approximately_equals("dst minimum with count 0 does not contribute to merged minimum (flipped)",
+										dst["regions"][region1_handle_str]["get_other"]["resp_min"].asReal(), F64(2.7), 20);
 		}
 	}
+
 }