diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 86f1f7bbec2e8446fa034209ca72e0242813eb45..96d2fd05e5da94c73a23d4c4e4f9f117af6170e1 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -6716,7 +6716,18 @@
       <key>Value</key>
       <integer>1</integer>
     </map>
-
+    <key>RenderPerformanceTest</key>
+    <map>
+      <key>Comment</key>
+      <string>Disable rendering of everything but in-world content for 
+        performance testing</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>Boolean</string>
+      <key>Value</key>
+      <integer>0</integer>
+    </map>
       <key>RenderLightingDetail</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index d4621db32971115a432e8c9077c6f2347dfe5f9c..f140547497801d017d32ddda397064ee5a8b2294 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -1292,6 +1292,26 @@ bool LLAppViewer::cleanup()
 	// workaround for DEV-35406 crash on shutdown
 	LLEventPumps::instance().reset();
 
+	if (LLFastTimerView::sAnalyzePerformance)
+	{
+		llinfos << "Analyzing performance" << llendl;
+		
+		if(LLFastTimer::sLog)
+		{
+			LLFastTimerView::doAnalysis(
+				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "performance_baseline.slp"),
+				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "performance.slp"),
+				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "performance_report.csv"));
+		}
+		if(LLFastTimer::sMetricLog)
+		{
+			LLFastTimerView::doAnalysis(
+				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "metric_baseline.slp"),
+				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "metric.slp"),
+				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "metric_report.csv"));
+		}
+	}
+
 	// remove any old breakpad minidump files from the log directory
 	if (! isError())
 	{
@@ -1630,25 +1650,6 @@ bool LLAppViewer::cleanup()
 	delete mFastTimerLogThread;
 	mFastTimerLogThread = NULL;
 	
-	if (LLFastTimerView::sAnalyzePerformance)
-	{
-		llinfos << "Analyzing performance" << llendl;
-		
-		if(LLFastTimer::sLog)
-		{
-			LLFastTimerView::doAnalysis(
-				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "performance_baseline.slp"),
-				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "performance.slp"),
-				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "performance_report.csv"));
-		}
-		if(LLFastTimer::sMetricLog)
-		{
-			LLFastTimerView::doAnalysis(
-				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "metric_baseline.slp"),
-				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "metric.slp"),
-				gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "metric_report.csv"));
-		}
-	}
 	LLMetricPerformanceTester::cleanClass() ;
 
 	llinfos << "Cleaning up Media and Textures" << llendflush;
diff --git a/indra/newview/llfasttimerview.cpp b/indra/newview/llfasttimerview.cpp
index 8bc3b5a75f09c646152075991e4b6d1f07d90958..36aee733fd2f6b24ca6e0345c3e13e60cfac218f 100644
--- a/indra/newview/llfasttimerview.cpp
+++ b/indra/newview/llfasttimerview.cpp
@@ -38,7 +38,9 @@
 #include "llrect.h"
 #include "llerror.h"
 #include "llgl.h"
+#include "llimagepng.h"
 #include "llrender.h"
+#include "llrendertarget.h"
 #include "lllocalcliprect.h"
 #include "llmath.h"
 #include "llfontgl.h"
@@ -1016,6 +1018,364 @@ F64 LLFastTimerView::getTime(const std::string& name)
 	return 0.0;
 }
 
+void saveChart(const std::string& label, const char* suffix, LLImageRaw* scratch)
+{
+	//read result back into raw image
+	glReadPixels(0, 0, 1024, 512, GL_RGB, GL_UNSIGNED_BYTE, scratch->getData());
+
+	//write results to disk
+	LLPointer<LLImagePNG> result = new LLImagePNG();
+	result->encode(scratch, 0.f);
+
+	std::string ext = result->getExtension();
+	std::string filename = llformat("%s_%s.%s", label.c_str(), suffix, ext.c_str());
+	
+	std::string out_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, filename);
+	result->save(out_file);
+}
+
+template <class VEC_TYPE>
+void removeOutliers(std::vector<VEC_TYPE>& data, F32 k)
+{
+	if (data.size() < 100)
+	{ //not enough samples
+		return;
+	}
+
+	VEC_TYPE Q1 = data[data.size()/4];
+	VEC_TYPE Q3 = data[data.size()-data.size()/4-1];
+
+	VEC_TYPE min = Q1-k*(Q3-Q1);
+	VEC_TYPE max = Q3+k*(Q3-Q1);
+
+	U32 i = 0;
+	while (i < data.size() && data[i] < min)
+	{
+		i++;
+	}
+
+	S32 j = data.size()-1;
+	while (j > 0 && data[j] > max)
+	{
+		j--;
+	}
+
+	if (j < data.size()-1)
+	{
+		data.erase(data.begin()+j, data.end());
+	}
+
+	if (i > 0)
+	{
+		data.erase(data.begin(), data.begin()+i);
+	}
+}
+
+//static
+void LLFastTimerView::exportCharts(const std::string& base, const std::string& target)
+{
+	//allocate render target for drawing charts 
+	LLRenderTarget buffer;
+	buffer.allocate(1024,512, GL_RGB, FALSE, FALSE);
+	
+
+	LLSD cur;
+
+	LLSD base_data;
+
+	{ //read base log into memory
+		S32 i = 0;
+		std::ifstream is(base.c_str());
+		while (!is.eof() && LLSDSerialize::fromXML(cur, is))
+		{
+			base_data[i++] = cur;
+		}
+		is.close();
+	}
+
+	LLSD cur_data;
+	std::set<std::string> chart_names;
+
+	{ //read current log into memory
+		S32 i = 0;
+		std::ifstream is(target.c_str());
+		while (!is.eof() && LLSDSerialize::fromXML(cur, is))
+		{
+			cur_data[i++] = cur;
+
+			for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter)
+			{
+				std::string label = iter->first;
+				chart_names.insert(label);
+			}
+		}
+		is.close();
+	}
+
+	//get time domain
+	LLSD::Real cur_total_time = 0.0;
+
+	for (U32 i = 0; i < cur_data.size(); ++i)
+	{
+		cur_total_time += cur_data[i]["Total"]["Time"].asReal();
+	}
+
+	LLSD::Real base_total_time = 0.0;
+	for (U32 i = 0; i < base_data.size(); ++i)
+	{
+		base_total_time += base_data[i]["Total"]["Time"].asReal();
+	}
+
+	//allocate raw scratch space
+	LLPointer<LLImageRaw> scratch = new LLImageRaw(1024, 512, 3);
+
+	gGL.pushMatrix();
+	glLoadIdentity();
+	glMatrixMode(GL_PROJECTION);
+	glLoadIdentity();
+	glOrtho(-0.05, 1.05, -0.05, 1.05, -1.0, 1.0);
+
+	//render charts
+	gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+	
+	buffer.bindTarget();
+
+	for (std::set<std::string>::iterator iter = chart_names.begin(); iter != chart_names.end(); ++iter)
+	{
+		std::string label = *iter;
+	
+		LLSD::Real max_time = 0.0;
+		LLSD::Integer max_calls = 0;
+		LLSD::Real max_execution = 0.0;
+
+		std::vector<LLSD::Real> cur_execution;
+		std::vector<LLSD::Real> cur_times;
+		std::vector<LLSD::Integer> cur_calls;
+
+		std::vector<LLSD::Real> base_execution;
+		std::vector<LLSD::Real> base_times;
+		std::vector<LLSD::Integer> base_calls;
+
+		for (U32 i = 0; i < cur_data.size(); ++i)
+		{
+			LLSD::Real time = cur_data[i][label]["Time"].asReal();
+			LLSD::Integer calls = cur_data[i][label]["Calls"].asInteger();
+
+			LLSD::Real execution = 0.0;
+			if (calls > 0)
+			{
+				execution = time/calls;
+				cur_execution.push_back(execution);
+				cur_times.push_back(time);
+			}
+
+			cur_calls.push_back(calls);
+		}
+
+		for (U32 i = 0; i < base_data.size(); ++i)
+		{
+			LLSD::Real time = base_data[i][label]["Time"].asReal();
+			LLSD::Integer calls = base_data[i][label]["Calls"].asInteger();
+
+			LLSD::Real execution = 0.0;
+			if (calls > 0)
+			{
+				execution = time/calls;
+				base_execution.push_back(execution);
+				base_times.push_back(time);
+			}
+
+			base_calls.push_back(calls);
+		}
+
+		std::sort(base_calls.begin(), base_calls.end());
+		std::sort(base_times.begin(), base_times.end());
+		std::sort(base_execution.begin(), base_execution.end());
+
+		std::sort(cur_calls.begin(), cur_calls.end());
+		std::sort(cur_times.begin(), cur_times.end());
+		std::sort(cur_execution.begin(), cur_execution.end());
+
+		//remove outliers
+		const U32 OUTLIER_CUTOFF = 512;
+		if (base_times.size() > OUTLIER_CUTOFF)
+		{ 
+			removeOutliers(base_times, 1.f);
+		}
+
+		if (base_execution.size() > OUTLIER_CUTOFF)
+		{ 
+			removeOutliers(base_execution, 1.f);
+		}
+
+		if (cur_times.size() > OUTLIER_CUTOFF)
+		{ 
+			removeOutliers(cur_times, 1.f);
+		}
+
+		if (cur_execution.size() > OUTLIER_CUTOFF)
+		{ 
+			removeOutliers(cur_execution, 1.f);
+		}
+
+
+		max_time = llmax(base_times.empty() ? 0.0 : *base_times.rbegin(), cur_times.empty() ? 0.0 : *cur_times.rbegin());
+		max_calls = llmax(base_calls.empty() ? 0 : *base_calls.rbegin(), cur_calls.empty() ? 0 : *cur_calls.rbegin());
+		max_execution = llmax(base_execution.empty() ? 0.0 : *base_execution.rbegin(), cur_execution.empty() ? 0.0 : *cur_execution.rbegin());
+
+
+		LLVector3 last_p;
+
+		//====================================
+		// basic
+		//====================================
+		buffer.clear();
+
+		last_p.clear();
+
+		LLGLDisable cull(GL_CULL_FACE);
+
+		LLVector3 base_col(0, 0.7f, 0.f);
+		LLVector3 cur_col(1.f, 0.f, 0.f);
+
+		gGL.setSceneBlendType(LLRender::BT_ADD);
+
+		gGL.color3fv(base_col.mV);
+		for (U32 i = 0; i < base_times.size(); ++i)
+		{
+			gGL.begin(LLRender::TRIANGLE_STRIP);
+			gGL.vertex3fv(last_p.mV);
+			gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+			last_p.set((F32)i/(F32) base_times.size(), base_times[i]/max_time, 0.f);
+			gGL.vertex3fv(last_p.mV);
+			gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+			gGL.end();
+		}
+		
+		gGL.flush();
+
+		
+		last_p.clear();
+		{
+			LLGLEnable blend(GL_BLEND);
+						
+			gGL.color3fv(cur_col.mV);
+			for (U32 i = 0; i < cur_times.size(); ++i)
+			{
+				gGL.begin(LLRender::TRIANGLE_STRIP);
+				gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+				gGL.vertex3fv(last_p.mV);
+				last_p.set((F32) i / (F32) cur_times.size(), cur_times[i]/max_time, 0.f);
+				gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+				gGL.vertex3fv(last_p.mV);
+				gGL.end();
+			}
+			
+			gGL.flush();
+		}
+
+		saveChart(label, "time", scratch);
+		
+		//======================================
+		// calls
+		//======================================
+		buffer.clear();
+
+		last_p.clear();
+
+		gGL.color3fv(base_col.mV);
+		for (U32 i = 0; i < base_calls.size(); ++i)
+		{
+			gGL.begin(LLRender::TRIANGLE_STRIP);
+			gGL.vertex3fv(last_p.mV);
+			gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+			last_p.set((F32) i / (F32) base_calls.size(), (F32)base_calls[i]/max_calls, 0.f);
+			gGL.vertex3fv(last_p.mV);
+			gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+			gGL.end();
+		}
+		
+		gGL.flush();
+
+		{
+			LLGLEnable blend(GL_BLEND);
+			gGL.color3fv(cur_col.mV);
+			last_p.clear();
+
+			for (U32 i = 0; i < cur_calls.size(); ++i)
+			{
+				gGL.begin(LLRender::TRIANGLE_STRIP);
+				gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+				gGL.vertex3fv(last_p.mV);
+				last_p.set((F32) i / (F32) cur_calls.size(), (F32) cur_calls[i]/max_calls, 0.f);
+				gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+				gGL.vertex3fv(last_p.mV);
+				gGL.end();
+				
+			}
+			
+			gGL.flush();
+		}
+
+		saveChart(label, "calls", scratch);
+
+		//======================================
+		// execution
+		//======================================
+		buffer.clear();
+
+
+		gGL.color3fv(base_col.mV);
+		U32 count = 0;
+		U32 total_count = base_execution.size();
+
+		last_p.clear();
+
+		for (std::vector<LLSD::Real>::iterator iter = base_execution.begin(); iter != base_execution.end(); ++iter)
+		{
+			gGL.begin(LLRender::TRIANGLE_STRIP);
+			gGL.vertex3fv(last_p.mV);
+			gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+			last_p.set((F32)count/(F32)total_count, *iter/max_execution, 0.f);
+			gGL.vertex3fv(last_p.mV);
+			gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+			gGL.end();
+			count++;
+		}
+
+		last_p.clear();
+				
+		{
+			LLGLEnable blend(GL_BLEND);
+			gGL.color3fv(cur_col.mV);
+			count = 0;
+			total_count = cur_execution.size();
+
+			for (std::vector<LLSD::Real>::iterator iter = cur_execution.begin(); iter != cur_execution.end(); ++iter)
+			{
+				gGL.begin(LLRender::TRIANGLE_STRIP);
+				gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+				gGL.vertex3fv(last_p.mV);
+				last_p.set((F32)count/(F32)total_count, *iter/max_execution, 0.f);			
+				gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
+				gGL.vertex3fv(last_p.mV);
+				gGL.end();
+				count++;
+			}
+
+			gGL.flush();
+		}
+
+		saveChart(label, "execution", scratch);
+	}
+
+	buffer.flush();
+
+	gGL.popMatrix();
+	glMatrixMode(GL_MODELVIEW);
+	gGL.popMatrix();
+}
+
 //static
 LLSD LLFastTimerView::analyzePerformanceLogDefault(std::istream& is)
 {
@@ -1132,7 +1492,8 @@ void LLFastTimerView::doAnalysisDefault(std::string baseline, std::string target
 			base[label]["Samples"].asInteger());			
 	}
 
-	
+	exportCharts(baseline, target);
+
 	os.flush();
 	os.close();
 }
diff --git a/indra/newview/llfasttimerview.h b/indra/newview/llfasttimerview.h
index ac069909138121212776ce66a5c4dc85635cfd3d..e604a1f84df5639af4f6505938c39c82047297c9 100644
--- a/indra/newview/llfasttimerview.h
+++ b/indra/newview/llfasttimerview.h
@@ -50,6 +50,7 @@ class LLFastTimerView : public LLFloater
 	static void doAnalysisMetrics(std::string baseline, std::string target, std::string output) ;
 	static LLSD analyzeMetricPerformanceLog(std::istream& is) ;
 	static LLSD analyzePerformanceLogDefault(std::istream& is) ;
+	static void exportCharts(const std::string& base, const std::string& target);
 
 public:
 
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index 8eeffb84b054ccd8d248554dc0a023940868f3b7..ec88813b5b44a1331026769eacbaa9cc8d1845c8 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -127,6 +127,45 @@ static bool handleSetShaderChanged(const LLSD& newvalue)
 	return true;
 }
 
+static bool handleRenderPerfTestChanged(const LLSD& newvalue)
+{
+	bool status = !newvalue.asBoolean();
+	if (!status)
+	{
+		gPipeline.clearRenderTypeMask(LLPipeline::RENDER_TYPE_WL_SKY,
+									  LLPipeline::RENDER_TYPE_GROUND,
+									  LLPipeline::RENDER_TYPE_TERRAIN,
+									  LLPipeline::RENDER_TYPE_GRASS,
+									  LLPipeline::RENDER_TYPE_TREE,
+									  LLPipeline::RENDER_TYPE_WATER,
+									  LLPipeline::RENDER_TYPE_PASS_GRASS,
+									  LLPipeline::RENDER_TYPE_HUD,
+									  LLPipeline::RENDER_TYPE_PARTICLES,
+									  LLPipeline::RENDER_TYPE_CLOUDS,
+									  LLPipeline::RENDER_TYPE_HUD_PARTICLES,
+									  LLPipeline::END_RENDER_TYPES); 
+		gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_FEATURE_UI, false);
+	}
+	else 
+	{
+	gPipeline.andRenderTypeMask(LLPipeline::RENDER_TYPE_WL_SKY,
+									  LLPipeline::RENDER_TYPE_GROUND,
+									  LLPipeline::RENDER_TYPE_TERRAIN,
+									  LLPipeline::RENDER_TYPE_GRASS,
+									  LLPipeline::RENDER_TYPE_TREE,
+									  LLPipeline::RENDER_TYPE_WATER,
+									  LLPipeline::RENDER_TYPE_PASS_GRASS,
+									  LLPipeline::RENDER_TYPE_HUD,
+									  LLPipeline::RENDER_TYPE_PARTICLES,
+									  LLPipeline::RENDER_TYPE_CLOUDS,
+									  LLPipeline::RENDER_TYPE_HUD_PARTICLES,
+									  LLPipeline::END_RENDER_TYPES);
+		gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_FEATURE_UI, false);
+	}
+
+	return true;
+}
+
 static bool handleReleaseGLBufferChanged(const LLSD& newvalue)
 {
 	if (gPipeline.isInit())
@@ -500,6 +539,7 @@ void settings_setup_listeners()
 	gSavedSettings.getControl("RenderBakeSunlight")->getSignal()->connect(boost::bind(&handleResetVertexBuffersChanged, _2));
 	gSavedSettings.getControl("RenderNoAlpha")->getSignal()->connect(boost::bind(&handleResetVertexBuffersChanged, _2));
 	gSavedSettings.getControl("RenderAvatarVP")->getSignal()->connect(boost::bind(&handleSetShaderChanged, _2));
+	gSavedSettings.getControl("RenderPerformanceTest")->getSignal()->connect(boost::bind(&handleRenderPerfTestChanged, _2));
 	gSavedSettings.getControl("VertexShaderEnable")->getSignal()->connect(boost::bind(&handleSetShaderChanged, _2));
 	gSavedSettings.getControl("RenderUIBuffer")->getSignal()->connect(boost::bind(&handleReleaseGLBufferChanged, _2));
 	gSavedSettings.getControl("RenderSpecularResX")->getSignal()->connect(boost::bind(&handleReleaseGLBufferChanged, _2));
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index d4ee7e36e6c4f3792ceb6a364be8a32ce1c6dfc1..8b4feac3cdeb889a47046523fe753ea4625264e9 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -402,6 +402,14 @@ void LLPipeline::init()
 		toggleRenderType(RENDER_TYPE_GROUND);
 	}
 
+	// make sure RenderPerformanceTest persists (hackity hack hack)
+	// disables non-object rendering (UI, sky, water, etc)
+	if (gSavedSettings.getBOOL("RenderPerformanceTest"))
+	{
+		gSavedSettings.setBOOL("RenderPerformanceTest", FALSE);
+		gSavedSettings.setBOOL("RenderPerformanceTest", TRUE);
+	}
+
 	mOldRenderDebugMask = mRenderDebugMask;
 
 	mBackfaceCull = TRUE;
@@ -5358,6 +5366,18 @@ BOOL LLPipeline::toggleRenderDebugFeatureControl(void* data)
 	return gPipeline.hasRenderDebugFeatureMask(bit);
 }
 
+void LLPipeline::setRenderDebugFeatureControl(U32 bit, bool value)
+{
+	if (value)
+	{
+		gPipeline.mRenderDebugFeatureMask |= bit;
+	}
+	else
+	{
+		gPipeline.mRenderDebugFeatureMask &= !bit;
+	}
+}
+
 // static
 void LLPipeline::setRenderScriptedBeacons(BOOL val)
 {
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index 3beb8abc3cd79f9b491525c57758830e7e5573eb..bed4155d15170d081160057e6306c9c40307edbd 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -327,6 +327,7 @@ class LLPipeline
 	static BOOL toggleRenderTypeControlNegated(void* data);
 	static BOOL toggleRenderDebugControl(void* data);
 	static BOOL toggleRenderDebugFeatureControl(void* data);
+	static void setRenderDebugFeatureControl(U32 bit, bool value);
 
 	static void setRenderParticleBeacons(BOOL val);
 	static void toggleRenderParticleBeacons(void* data);