diff --git a/indra/newview/llfasttimerview.cpp b/indra/newview/llfasttimerview.cpp
index ba298ed819c87c64f4f5ae22730be75e8e28ee6b..59a52fdaea187de6313e04bcc3e83b9155330bed 100644
--- a/indra/newview/llfasttimerview.cpp
+++ b/indra/newview/llfasttimerview.cpp
@@ -106,7 +106,9 @@ LLFastTimerView::LLFastTimerView(const LLSD& key)
 	mPrintStats(-1),
 	mRecording(&get_frame_recording()),
 	mPauseHistory(false)
-{}
+{
+	mBarRects = new std::vector<LLRect>[MAX_VISIBLE_HISTORY];
+}
 
 LLFastTimerView::~LLFastTimerView()
 {
@@ -115,6 +117,7 @@ LLFastTimerView::~LLFastTimerView()
 		delete mRecording;
 	}
 	mRecording = NULL;
+	delete [] mBarRects;
 }
 
 void LLFastTimerView::onPause()
@@ -250,13 +253,11 @@ BOOL LLFastTimerView::handleMouseUp(S32 x, S32 y, MASK mask)
 
 BOOL LLFastTimerView::handleHover(S32 x, S32 y, MASK mask)
 {
-	PeriodicRecording& frame_recording = *mRecording;
-
 	if (hasMouseCapture())
 	{
 		F32 lerp = llclamp(1.f - (F32) (x - mGraphRect.mLeft) / (F32) mGraphRect.getWidth(), 0.f, 1.f);
 		mScrollIndex = llround( lerp * (F32)(HISTORY_NUM - MAX_VISIBLE_HISTORY));
-		mScrollIndex = llclamp(	mScrollIndex, 0, frame_recording.getNumPeriods());
+		mScrollIndex = llclamp(	mScrollIndex, 0, mRecording->getNumPeriods());
 		return TRUE;
 	}
 	mHoverTimer = NULL;
@@ -281,8 +282,7 @@ BOOL LLFastTimerView::handleHover(S32 x, S32 y, MASK mask)
 			++it, ++i)
 		{
 			// is mouse over bar for this timer?
-			if (x > mBarStart[mHoverBarIndex][i] &&
-				x < mBarEnd[mHoverBarIndex][i])
+			if (mBarRects[mHoverBarIndex][i].pointInRect(x, y))
 			{
 				mHoverID = (*it);
 				if (mHoverTimer != *it)
@@ -294,10 +294,7 @@ BOOL LLFastTimerView::handleHover(S32 x, S32 y, MASK mask)
 					mHoverTimer = (*it);
 				}
 
-				mToolTipRect.set(mBarStart[mHoverBarIndex][i], 
-					mBarRect.mBottom + llround(((F32)(MAX_VISIBLE_HISTORY - mHoverBarIndex + 1)) * ((F32)mBarRect.getHeight() / ((F32)MAX_VISIBLE_HISTORY + 2.f))),
-					mBarEnd[mHoverBarIndex][i],
-					mBarRect.mBottom + llround((F32)(MAX_VISIBLE_HISTORY - mHoverBarIndex) * ((F32)mBarRect.getHeight() / ((F32)MAX_VISIBLE_HISTORY + 2.f))));
+				mToolTipRect = mBarRects[mHoverBarIndex][i];
 			}
 
 			if ((*it)->getCollapsed())
@@ -376,16 +373,16 @@ BOOL LLFastTimerView::handleToolTip(S32 x, S32 y, MASK mask)
 
 BOOL LLFastTimerView::handleScrollWheel(S32 x, S32 y, S32 clicks)
 {
-	PeriodicRecording& frame_recording = *mRecording;
-
 	mPauseHistory = true;
 	mScrollIndex = llclamp(	mScrollIndex + clicks,
 							0,
-							llmin(frame_recording.getNumPeriods(), (S32)HISTORY_NUM - MAX_VISIBLE_HISTORY));
+							llmin(mRecording->getNumPeriods(), (S32)HISTORY_NUM - MAX_VISIBLE_HISTORY));
 	return TRUE;
 }
 
 static TimeBlock FTM_RENDER_TIMER("Timers", true);
+static const S32 MARGIN = 10;
+static const S32 LEGEND_WIDTH = 220;
 
 static std::map<TimeBlock*, LLColor4> sTimerColors;
 
@@ -393,574 +390,558 @@ void LLFastTimerView::draw()
 {
 	LLFastTimer t(FTM_RENDER_TIMER);
 
-	PeriodicRecording& frame_recording = *mRecording;
-
-	std::string tdesc;
-
-	const S32 margin = 10;
-	const S32 height = getRect().getHeight();
-	const S32 width = getRect().getWidth();
-	
-	LLRect new_rect;
-	new_rect.setLeftTopAndSize(getRect().mLeft, getRect().mTop, width, height);
-	setRect(new_rect);
-
-	S32 left, top, right, bottom;
-	S32 x, y, barw, barh, dx, dy;
-	const S32 texth = (S32)LLFontGL::getFontMonospace()->getLineHeight();
-	LLPointer<LLUIImage> box_imagep = LLUI::getUIImage("Rounded_Square");
+	generateUniqueColors();
 
 	// Draw the window background
 	gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
-	gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4(0.f, 0.f, 0.f, 0.25f));
-	
-	// Draw some help
-	{
-		x = margin;
-		y = height - margin;
-
-		char modedesc[][32] = {
-			"2 x Average ",
-			"Max         ",
-			"Recent Max  ",
-			"100 ms      "
-		};
-		char centerdesc[][32] = {
-			"Left      ",
-			"Centered  ",
-			"Ordered   "
-		};
+	gl_rect_2d(getLocalRect(), LLColor4(0.f, 0.f, 0.f, 0.25f));
 
-		tdesc = llformat("Full bar = %s [Click to pause/reset] [SHIFT-Click to toggle]",modedesc[mDisplayMode]);
-		LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
+	S32 y = drawHelp(getRect().getHeight() - MARGIN);
+	drawLegend(y - ((S32)LLFontGL::getFontMonospace()->getLineHeight() - 2));
 
-		x = margin, y -= (texth + 2);
-		tdesc = llformat("Justification = %s [CTRL-Click to toggle]",centerdesc[mDisplayCenter]);
-		LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
-		y -= (texth + 2);
+	// update rectangle that includes timer bars
+	const S32 LEGEND_WIDTH = 220;
 
-		LLFontGL::getFontMonospace()->renderUTF8(std::string("[Right-Click log selected] [ALT-Click toggle counts] [ALT-SHIFT-Click sub hidden]"),
-										 0, x, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
-		y -= (texth + 2);
-	}
+	mBarRect.mLeft = MARGIN + LEGEND_WIDTH + 8;
+	mBarRect.mTop = y;
+	mBarRect.mRight = getRect().getWidth() - MARGIN;
+	mBarRect.mBottom = MARGIN + LINE_GRAPH_HEIGHT;
 
-	S32 histmax = llmin(frame_recording.getNumPeriods()+1, MAX_VISIBLE_HISTORY);
+	drawBars();
+	drawLineGraph();
+	printLineStats();
+	LLView::draw();
 		
-	const S32 ytop = y;
-	y -= (texth + 2);
-
-	// generate unique colors
-	{
-		sTimerColors[&getFrameTimer()] = LLColor4::grey;
-
-		F32 hue = 0.f;
-
-		for (timer_tree_iterator_t it = begin_timer_tree(getFrameTimer());
-			it != timer_tree_iterator_t();
-			++it)
-		{
-			TimeBlock* idp = (*it);
-
-			const F32 HUE_INCREMENT = 0.23f;
-			hue = fmodf(hue + HUE_INCREMENT, 1.f);
-			// saturation increases with depth
-			F32 saturation = clamp_rescale((F32)get_depth(idp), 0.f, 3.f, 0.f, 1.f);
-			// lightness alternates with depth
-			F32 lightness = get_depth(idp) % 2 ? 0.5f : 0.6f;
-
-			LLColor4 child_color;
-			child_color.setHSL(hue, saturation, lightness);
+	mAllTimeMax = llmax(mAllTimeMax, mRecording->getLastRecordingPeriod().getSum(getFrameTimer()));
+	mHoverID = NULL;
+	mHoverBarIndex = -1;
+}
 
-			sTimerColors[idp] = child_color;
-		}
-	}
+F64 LLFastTimerView::getTime(const std::string& name)
+{
+	//TODO: replace calls to this with use of timer object directly
+	//llstatic_assert(false, "TODO: implement");
+	return 0.0;
+}
 
-	// draw legend
-	const S32 LEGEND_WIDTH = 220;
-	const S32 x_start = margin + LEGEND_WIDTH + 8;
-	{
-		LLLocalClipRect clip(LLRect(margin, y, LEGEND_WIDTH, margin));
-		S32 cur_line = 0;
-		ft_display_idx.clear();
-		std::map<TimeBlock*, S32> display_line;
-		for (timer_tree_iterator_t it = begin_timer_tree(getFrameTimer());
-			it != timer_tree_iterator_t();
-			++it)
-		{
-			TimeBlock* idp = (*it);
-			display_line[idp] = cur_line;
-			ft_display_idx.push_back(idp);
-			cur_line++;
+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());
 
-			x = margin;
+	//write results to disk
+	LLPointer<LLImagePNG> result = new LLImagePNG();
+	result->encode(scratch, 0.f);
 
-			left = x; right = x + texth;
-			top = y; bottom = y - texth;
-			S32 scale_offset = 0;
-			if (idp == mHoverID)
-			{
-				scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 2.f);
-			}
-			gl_rect_2d(left - scale_offset, top + scale_offset, right + scale_offset, bottom - scale_offset, sTimerColors[idp]);
+	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);
+}
 
-			LLUnit<LLUnits::Milliseconds, F32> ms = 0;
-			S32 calls = 0;
-			if (mHoverBarIndex > 0 && mHoverID)
-			{
-				S32 hidx = mScrollIndex + mHoverBarIndex;
-				ms = frame_recording.getPrevRecordingPeriod(hidx).getSum(*idp);
-				calls = frame_recording.getPrevRecordingPeriod(hidx).getSum(idp->callCount());
-			}
-			else
-			{
-				ms = LLUnit<LLUnits::Seconds, F64>(frame_recording.getPeriodMean(*idp));
-				calls = (S32)frame_recording.getPeriodMean(idp->callCount());
-			}
+//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);
+	
 
-			if (mDisplayCalls)
-			{
-				tdesc = llformat("%s (%d)",idp->getName().c_str(),calls);
-			}
-			else
-			{
-				tdesc = llformat("%s [%.1f]",idp->getName().c_str(),ms.value());
-			}
-			dx = (texth+4) + get_depth(idp)*8;
+	LLSD cur;
 
-			LLColor4 color = LLColor4::white;
-			if (get_depth(idp) > 0)
-			{
-				S32 line_start_y = (top + bottom) / 2;
-				S32 line_end_y = line_start_y + ((texth + 2) * (cur_line - display_line[idp->getParent()])) - texth;
-				gl_line_2d(x + dx - 8, line_start_y, x + dx, line_start_y, color);
-				S32 line_x = x + (texth + 4) + ((get_depth(idp) - 1) * 8);
-				gl_line_2d(line_x, line_start_y, line_x, line_end_y, color);
-				if (idp->getCollapsed() && !idp->getChildren().empty())
-				{
-					gl_line_2d(line_x+4, line_start_y-3, line_x+4, line_start_y+4, color);
-				}
-			}
+	LLSD base_data;
 
-			x += dx;
-			BOOL is_child_of_hover_item = (idp == mHoverID);
-			TimeBlock* next_parent = idp->getParent();
-			while(!is_child_of_hover_item && next_parent)
-			{
-				is_child_of_hover_item = (mHoverID == next_parent);
-				if (next_parent->getParent() == next_parent) break;
-				next_parent = next_parent->getParent();
-			}
+	{ //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();
+	}
 
-			LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, 
-											x, y, 
-											color, 
-											LLFontGL::LEFT, LLFontGL::TOP, 
-											is_child_of_hover_item ? LLFontGL::BOLD : LLFontGL::NORMAL);
+	LLSD cur_data;
+	std::set<std::string> chart_names;
 
-			y -= (texth + 2);
+	{ //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;
 
-			if (idp->getCollapsed()) 
+			for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter)
 			{
-				it.skipDescendants();
+				std::string label = iter->first;
+				chart_names.insert(label);
 			}
 		}
+		is.close();
 	}
 
-	// update rectangle that includes timer bars
-	mBarRect.mLeft = x_start;
-	mBarRect.mRight = getRect().getWidth();
-	mBarRect.mTop = ytop - (LLFontGL::getFontMonospace()->getLineHeight() + 4);
-	mBarRect.mBottom = margin + LINE_GRAPH_HEIGHT;
-
-	y = ytop;
-	barh = (ytop - margin - LINE_GRAPH_HEIGHT) / (MAX_VISIBLE_HISTORY + 2);
-	dy = barh>>2; // spacing between bars
-	if (dy < 1) dy = 1;
-	barh -= dy;
-	barw = width - x_start - margin;
-
-	// Draw the history bars
-	LLUnit<LLUnits::Seconds, F64> total_time;
-	switch(mDisplayMode)
+	//get time domain
+	LLSD::Real cur_total_time = 0.0;
+
+	for (U32 i = 0; i < cur_data.size(); ++i)
 	{
-	case 0:
-		total_time = frame_recording.getPeriodMean(getFrameTimer())*2;
-		break;
-	case 1:
-		total_time = mAllTimeMax;
-		break;
-	case 2:
-		// Calculate the max total ticks for the current history
-		total_time = frame_recording.getPeriodMax(getFrameTimer());
-		break;
-	default:
-		total_time = LLUnit<LLUnits::Milliseconds, F32>(100);
-		break;
+		cur_total_time += cur_data[i]["Total"]["Time"].asReal();
 	}
 
-	if (total_time > 0)
+	LLSD::Real base_total_time = 0.0;
+	for (U32 i = 0; i < base_data.size(); ++i)
 	{
-		LLLocalClipRect clip(LLRect(x_start, ytop, getRect().getWidth() - margin, margin));
-
-		mAllTimeMax = llmax(mAllTimeMax, frame_recording.getLastRecordingPeriod().getSum(getFrameTimer()));
-		
-		// Draw MS ticks
-		{
-			LLUnit<LLUnits::Milliseconds, U32> ms = total_time;
+		base_total_time += base_data[i]["Total"]["Time"].asReal();
+	}
 
-			tdesc = llformat("%.1f ms |", (F32)ms.value()*.25f);
-			x = x_start + barw/4 - LLFontGL::getFontMonospace()->getWidth(tdesc);
-			LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white,
-											LLFontGL::LEFT, LLFontGL::TOP);
-			
-			tdesc = llformat("%.1f ms |", (F32)ms.value()*.50f);
-			x = x_start + barw/2 - LLFontGL::getFontMonospace()->getWidth(tdesc);
-			LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white,
-											LLFontGL::LEFT, LLFontGL::TOP);
-			
-			tdesc = llformat("%.1f ms |", (F32)ms.value()*.75f);
-			x = x_start + (barw*3)/4 - LLFontGL::getFontMonospace()->getWidth(tdesc);
-			LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white,
-											LLFontGL::LEFT, LLFontGL::TOP);
-			
-			tdesc = llformat( "%d ms |", (U32)ms.value());
-			x = x_start + barw - LLFontGL::getFontMonospace()->getWidth(tdesc);
-			LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white,
-											LLFontGL::LEFT, LLFontGL::TOP);
-		}
+	//allocate raw scratch space
+	LLPointer<LLImageRaw> scratch = new LLImageRaw(1024, 512, 3);
 
-		// Draw borders
-		{
-			gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
-			gGL.color4f(0.5f,0.5f,0.5f,0.5f);
+	gGL.pushMatrix();
+	gGL.loadIdentity();
+	gGL.matrixMode(LLRender::MM_PROJECTION);
+	gGL.loadIdentity();
+	gGL.ortho(-0.05f, 1.05f, -0.05f, 1.05f, -1.0f, 1.0f);
 
-			S32 by = y + 2;
-			
-			y -= ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4);
+	//render charts
+	gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+	
+	buffer.bindTarget();
 
-			//heading
-			gl_rect_2d(x_start-5, by, getRect().getWidth()-5, y+5, FALSE);
+	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;
 
-			//tree view
-			gl_rect_2d(5, by, x_start-10, 5, FALSE);
+		std::vector<LLSD::Real> cur_execution;
+		std::vector<LLSD::Real> cur_times;
+		std::vector<LLSD::Integer> cur_calls;
 
-			by = y + 5;
-			//average bar
-			gl_rect_2d(x_start-5, by, getRect().getWidth()-5, by-barh-dy-5, FALSE);
-			
-			by -= barh*2+dy;
-			
-			//current frame bar
-			gl_rect_2d(x_start-5, by, getRect().getWidth()-5, by-barh-dy-2, FALSE);
-			
-			by -= barh+dy+1;
-			
-			//history bars
-			gl_rect_2d(x_start-5, by, getRect().getWidth()-5, LINE_GRAPH_HEIGHT-barh-dy-2, FALSE);			
-			
-			by = LINE_GRAPH_HEIGHT-barh-dy-7;
-			
-			//line graph
-			mGraphRect = LLRect(x_start-5, by, getRect().getWidth()-5, 5);
-			
-			gl_rect_2d(mGraphRect, FALSE);
-		}
-		
-		mBarStart.clear();
-		mBarEnd.clear();
+		std::vector<LLSD::Real> base_execution;
+		std::vector<LLSD::Real> base_times;
+		std::vector<LLSD::Integer> base_calls;
 
-		// Draw bars for each history entry
-		// Special: -1 = show running average
-		gGL.getTexUnit(0)->bind(box_imagep->getImage());
-		for (S32 j=-1; j<histmax && y > LINE_GRAPH_HEIGHT; j++)
+		for (U32 i = 0; i < cur_data.size(); ++i)
 		{
-			mBarStart.push_back(std::vector<S32>());
-			mBarEnd.push_back(std::vector<S32>());
-			int sublevel_dx[FTV_MAX_DEPTH];
-			int sublevel_left[FTV_MAX_DEPTH];
-			int sublevel_right[FTV_MAX_DEPTH];
-			S32 tidx;
-			if (j >= 0)
+			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)
 			{
-				tidx = j + 1 + mScrollIndex;
+				execution = time/calls;
+				cur_execution.push_back(execution);
+				cur_times.push_back(time);
 			}
-			else
+
+			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)
 			{
-				tidx = -1;
+				execution = time/calls;
+				base_execution.push_back(execution);
+				base_times.push_back(time);
 			}
-			
-			x = x_start;
-			
-			// draw the bars for each stat
-			std::vector<S32> xpos;
-			std::vector<S32> deltax;
-			xpos.push_back(x_start);
-			
-			TimeBlock* prev_id = NULL;
 
-			S32 i = 0;
-			for(timer_tree_iterator_t it = begin_timer_tree(getFrameTimer());
-				it != end_timer_tree();
-				++it, ++i)
-			{
-				TimeBlock* idp = (*it);
-				F32 frac = tidx == -1
-					? (frame_recording.getPeriodMean(*idp) / total_time) 
-					: (frame_recording.getPrevRecordingPeriod(tidx).getSum(*idp).value() / total_time.value());
+			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)
+		{ 
+			ll_remove_outliers(base_times, 1.f);
+		}
+
+		if (base_execution.size() > OUTLIER_CUTOFF)
+		{ 
+			ll_remove_outliers(base_execution, 1.f);
+		}
+
+		if (cur_times.size() > OUTLIER_CUTOFF)
+		{ 
+			ll_remove_outliers(cur_times, 1.f);
+		}
+
+		if (cur_execution.size() > OUTLIER_CUTOFF)
+		{ 
+			ll_remove_outliers(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();
+		}
 		
-				dx = llround(frac * (F32)barw);
-				S32 prev_delta_x = deltax.empty() ? 0 : deltax.back();
-				deltax.push_back(dx);
-				
-				int level = get_depth(idp) - 1;
-				
-				while ((S32)xpos.size() > level + 1)
-				{
-					xpos.pop_back();
-				}
-				left = xpos.back();
+		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();
 				
-				if (level == 0)
-				{
-					sublevel_left[level] = x_start;
-					sublevel_dx[level] = dx;
-					sublevel_right[level] = sublevel_left[level] + sublevel_dx[level];
-				}
-				else if (prev_id && get_depth(prev_id) < get_depth(idp))
-				{
-					F64 sublevelticks = 0;
-
-					for (TimeBlock::child_const_iter it = prev_id->beginChildren();
-						it != prev_id->endChildren();
-						++it)
-					{
-						sublevelticks += (tidx == -1)
-							? frame_recording.getPeriodMean(**it).value()
-							: frame_recording.getPrevRecordingPeriod(tidx).getSum(**it).value();
-					}
-
-					F32 subfrac = (F32)sublevelticks / (F32)total_time.value();
-					sublevel_dx[level] = (int)(subfrac * (F32)barw + .5f);
-
-					if (mDisplayCenter == ALIGN_CENTER)
-					{
-						left += (prev_delta_x - sublevel_dx[level])/2;
-					}
-					else if (mDisplayCenter == ALIGN_RIGHT)
-					{
-						left += (prev_delta_x - sublevel_dx[level]);
-					}
-
-					sublevel_left[level] = left;
-					sublevel_right[level] = sublevel_left[level] + sublevel_dx[level];
-				}				
-
-				right = left + dx;
-				xpos.back() = right;
-				xpos.push_back(left);
+			}
+			
+			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();
 				
-				mBarStart.back().push_back(left);
-				mBarEnd.back().push_back(right);
+		{
+			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++;
+			}
 
-				top = y;
-				bottom = y - barh;
+			gGL.flush();
+		}
 
-				if (right > left)
-				{
-					//U32 rounded_edges = 0;
-					LLColor4 color = sTimerColors[idp];//*ft_display_table[i].color;
-					S32 scale_offset = 0;
-
-					BOOL is_child_of_hover_item = (idp == mHoverID);
-					TimeBlock* next_parent = idp->getParent();
-					while(!is_child_of_hover_item && next_parent)
-					{
-						is_child_of_hover_item = (mHoverID == next_parent);
-						if (next_parent->getParent() == next_parent) break;
-						next_parent = next_parent->getParent();
-					}
-
-					if (idp == mHoverID)
-					{
-						scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 3.f);
-						//color = lerp(color, LLColor4::black, -0.4f);
-					}
-					else if (mHoverID != NULL && !is_child_of_hover_item)
-					{
-						color = lerp(color, LLColor4::grey, 0.8f);
-					}
-
-					gGL.color4fv(color.mV);
-					F32 start_fragment = llclamp((F32)(left - sublevel_left[level]) / (F32)sublevel_dx[level], 0.f, 1.f);
-					F32 end_fragment = llclamp((F32)(right - sublevel_left[level]) / (F32)sublevel_dx[level], 0.f, 1.f);
-					gl_segmented_rect_2d_fragment_tex(sublevel_left[level], top - level + scale_offset, sublevel_right[level], bottom + level - scale_offset, box_imagep->getTextureWidth(), box_imagep->getTextureHeight(), 16, start_fragment, end_fragment);
+		saveChart(label, "execution", scratch);
+	}
 
-				}
+	buffer.flush();
 
-				if ((*it)->getCollapsed())
-				{
-					it.skipDescendants();
-				}
-		
-				prev_id = idp;
+	gGL.popMatrix();
+	gGL.matrixMode(LLRender::MM_MODELVIEW);
+	gGL.popMatrix();
+}
+
+//static
+LLSD LLFastTimerView::analyzePerformanceLogDefault(std::istream& is)
+{
+	LLSD ret;
+
+	LLSD cur;
+
+	LLSD::Real total_time = 0.0;
+	LLSD::Integer total_frames = 0;
+
+	typedef std::map<std::string,LLViewerStats::StatsAccumulator> stats_map_t;
+	stats_map_t time_stats;
+	stats_map_t sample_stats;
+
+	while (!is.eof() && LLSDSerialize::fromXML(cur, is))
+	{
+		for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter)
+		{
+			std::string label = iter->first;
+
+			F64 time = iter->second["Time"].asReal();
+
+			// Skip the total figure
+			if(label.compare("Total") != 0)
+			{
+				total_time += time;
+			}			
+
+			if (time > 0.0)
+			{
+				LLSD::Integer samples = iter->second["Calls"].asInteger();
+
+				time_stats[label].push(time);
+				sample_stats[label].push(samples);
 			}
-			y -= (barh + dy);
-			if (j < 0)
-				y -= barh;
 		}
+		total_frames++;
+	}
+
+	for(stats_map_t::iterator it = time_stats.begin(); it != time_stats.end(); ++it)
+	{
+		std::string label = it->first;
+		ret[label]["TotalTime"] = time_stats[label].mSum;
+		ret[label]["MeanTime"] = time_stats[label].getMean();
+		ret[label]["MaxTime"] = time_stats[label].getMaxValue();
+		ret[label]["MinTime"] = time_stats[label].getMinValue();
+		ret[label]["StdDevTime"] = time_stats[label].getStdDev();
+		
+		ret[label]["Samples"] = sample_stats[label].mSum;
+		ret[label]["MaxSamples"] = sample_stats[label].getMaxValue();
+		ret[label]["MinSamples"] = sample_stats[label].getMinValue();
+		ret[label]["StdDevSamples"] = sample_stats[label].getStdDev();
+
+		ret[label]["Frames"] = (LLSD::Integer)time_stats[label].getCount();
 	}
+		
+	ret["SessionTime"] = total_time;
+	ret["FrameCount"] = total_frames;
+
+	return ret;
+
+}
+
+//static
+void LLFastTimerView::doAnalysisDefault(std::string baseline, std::string target, std::string output)
+{
+	// Open baseline and current target, exit if one is inexistent
+	std::ifstream base_is(baseline.c_str());
+	std::ifstream target_is(target.c_str());
+	if (!base_is.is_open() || !target_is.is_open())
+	{
+		llwarns << "'-analyzeperformance' error : baseline or current target file inexistent" << llendl;
+		base_is.close();
+		target_is.close();
+		return;
+	}
+
+	//analyze baseline
+	LLSD base = analyzePerformanceLogDefault(base_is);
+	base_is.close();
+
+	//analyze current
+	LLSD current = analyzePerformanceLogDefault(target_is);
+	target_is.close();
+
+	//output comparision
+	std::ofstream os(output.c_str());
+
+	LLSD::Real session_time = current["SessionTime"].asReal();
+	os <<
+		"Label, "
+		"% Change, "
+		"% of Session, "
+		"Cur Min, "
+		"Cur Max, "
+		"Cur Mean/sample, "
+		"Cur Mean/frame, "
+		"Cur StdDev/frame, "
+		"Cur Total, "
+		"Cur Frames, "
+		"Cur Samples, "
+		"Base Min, "
+		"Base Max, "
+		"Base Mean/sample, "
+		"Base Mean/frame, "
+		"Base StdDev/frame, "
+		"Base Total, "
+		"Base Frames, "
+		"Base Samples\n"; 
 
-	//draw line graph history
+	for (LLSD::map_iterator iter = base.beginMap();  iter != base.endMap(); ++iter)
 	{
-		gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
-		LLLocalClipRect clip(mGraphRect);
-			
-		//normalize based on last frame's maximum
-		static LLUnit<LLUnits::Seconds, F32> max_time = 0.000001;
-		static U32 max_calls = 0;
-		static F32 alpha_interp = 0.f;
-			
-		//display y-axis range
-		std::string tdesc;
-		if (mDisplayCalls)
-			tdesc = llformat("%d calls", (int)max_calls);
-		else if (mDisplayHz)
-			tdesc = llformat("%d Hz", (int)(1.f / max_time.value()));
-		else
-			tdesc = llformat("%4.2f ms", max_time.value());
-							
-		x = mGraphRect.mRight - LLFontGL::getFontMonospace()->getWidth(tdesc)-5;
-		y = mGraphRect.mTop - LLFontGL::getFontMonospace()->getLineHeight();
- 
-		LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white,
-										LLFontGL::LEFT, LLFontGL::TOP);
-
-		//highlight visible range
-		{
-			S32 first_frame = HISTORY_NUM - mScrollIndex;
-			S32 last_frame = first_frame - MAX_VISIBLE_HISTORY;
-				
-			F32 frame_delta = ((F32) (mGraphRect.getWidth()))/(HISTORY_NUM-1);
-				
-			F32 right = (F32) mGraphRect.mLeft + frame_delta*first_frame;
-			F32 left = (F32) mGraphRect.mLeft + frame_delta*last_frame;
-				
-			gGL.color4f(0.5f,0.5f,0.5f,0.3f);
-			gl_rect_2d((S32) left, mGraphRect.mTop, (S32) right, mGraphRect.mBottom);
-				
-			if (mHoverBarIndex >= 0)
-			{
-				S32 bar_frame = first_frame - mHoverBarIndex;
-				F32 bar = (F32) mGraphRect.mLeft + frame_delta*bar_frame;
+		LLSD::String label = iter->first;
 
-				gGL.color4f(0.5f,0.5f,0.5f,1);
-				
-				gGL.begin(LLRender::LINES);
-				gGL.vertex2i((S32)bar, mGraphRect.mBottom);
-				gGL.vertex2i((S32)bar, mGraphRect.mTop);
-				gGL.end();
-			}
-		}
-			
-		LLUnit<LLUnits::Seconds, F32> cur_max = 0;
-		U32 cur_max_calls = 0;
-		for(timer_tree_iterator_t it = begin_timer_tree(getFrameTimer());
-			it != end_timer_tree();
-			++it)
+		if (current[label]["Samples"].asInteger() == 0 ||
+			base[label]["Samples"].asInteger() == 0)
 		{
-			TimeBlock* idp = (*it);
-				
-			//fatten highlighted timer
-			if (mHoverID == idp)
-			{
-				gGL.flush();
-				glLineWidth(3);
-			}
+			//cannot compare
+			continue;
+		}	
+		LLSD::Real a = base[label]["TotalTime"].asReal() / base[label]["Samples"].asReal();
+		LLSD::Real b = current[label]["TotalTime"].asReal() / current[label]["Samples"].asReal();
 			
-			const F32 * col = sTimerColors[idp].mV;// ft_display_table[idx].color->mV;
-				
-			F32 alpha = 1.f;
-				
-			if (mHoverID != NULL &&
-				idp != mHoverID)
-			{	//fade out non-highlighted timers
-				if (idp->getParent() != mHoverID)
-				{
-					alpha = alpha_interp;
-				}
-			}
+		LLSD::Real diff = b-a;
 
-			gGL.color4f(col[0], col[1], col[2], alpha);				
-			gGL.begin(LLRender::TRIANGLE_STRIP);
-			for (U32 j = frame_recording.getNumPeriods();
-				j > 0;
-				j--)
-			{
-				LLUnit<LLUnits::Seconds, F32> time = llmax(frame_recording.getPrevRecordingPeriod(j).getSum(*idp), LLUnit<LLUnits::Seconds, F64>(0.000001));
-				U32 calls = frame_recording.getPrevRecordingPeriod(j).getSum(idp->callCount());
-
-				if (alpha == 1.f)
-				{ 
-					//normalize to highlighted timer
-					cur_max = llmax(cur_max, time);
-					cur_max_calls = llmax(cur_max_calls, calls);
-				}
-				F32 x = mGraphRect.mRight - j * (F32)(mGraphRect.getWidth())/(HISTORY_NUM-1);
-				F32 y = mDisplayHz 
-					? mGraphRect.mBottom + (1.f / time.value()) * ((F32) mGraphRect.getHeight() / (1.f / max_time.value()))
-					: mGraphRect.mBottom + time / max_time * (F32)mGraphRect.getHeight();
-				gGL.vertex2f(x,y);
-				gGL.vertex2f(x,mGraphRect.mBottom);
-			}
-			gGL.end();
-				
-			if (mHoverID == idp)
-			{
-				gGL.flush();
-				glLineWidth(1);
-			}
+		LLSD::Real perc = diff/a * 100;
 
-			if (idp->getCollapsed())
-			{	
-				//skip hidden timers
-				it.skipDescendants();
-			}
-		}
-			
-		//interpolate towards new maximum
-		max_time = lerp(max_time.value(), cur_max.value(), LLCriticalDamp::getInterpolant(0.1f));
-		if (max_time - cur_max <= 1 ||  cur_max - max_time  <= 1)
-		{
-			max_time = llmax(LLUnit<LLUnits::Microseconds, F32>(1), LLUnit<LLUnits::Microseconds, F32>(cur_max));
-		}
+		os << llformat("%s, %.2f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %d, %d, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %d, %d\n",
+			label.c_str(), 
+			(F32) perc, 
+			(F32) (current[label]["TotalTime"].asReal()/session_time * 100.0), 
 
-		max_calls = llround(lerp((F32)max_calls, (F32) cur_max_calls, LLCriticalDamp::getInterpolant(0.1f)));
-		if (llabs((S32)(max_calls - cur_max_calls)) <= 1)
-		{
-			max_calls = cur_max_calls;
-		}
+			(F32) current[label]["MinTime"].asReal(), 
+			(F32) current[label]["MaxTime"].asReal(), 
+			(F32) b, 
+			(F32) current[label]["MeanTime"].asReal(), 
+			(F32) current[label]["StdDevTime"].asReal(),
+			(F32) current[label]["TotalTime"].asReal(), 
+			current[label]["Frames"].asInteger(),
+			current[label]["Samples"].asInteger(),
+			(F32) base[label]["MinTime"].asReal(), 
+			(F32) base[label]["MaxTime"].asReal(), 
+			(F32) a, 
+			(F32) base[label]["MeanTime"].asReal(), 
+			(F32) base[label]["StdDevTime"].asReal(),
+			(F32) base[label]["TotalTime"].asReal(), 
+			base[label]["Frames"].asInteger(),
+			base[label]["Samples"].asInteger());			
+	}
 
-		// TODO: make sure alpha is correct in DisplayHz mode
-		F32 alpha_target = (max_time > cur_max)
-			? llmin(max_time / cur_max - 1.f,1.f) 
-			: llmin(cur_max/ max_time - 1.f,1.f);
-		alpha_interp = lerp(alpha_interp, alpha_target, LLCriticalDamp::getInterpolant(0.1f));
+	exportCharts(baseline, target);
+
+	os.flush();
+	os.close();
+}
 
-		if (mHoverID != NULL)
+//static
+void LLFastTimerView::outputAllMetrics()
+{
+	if (LLMetricPerformanceTesterBasic::hasMetricPerformanceTesters())
+	{
+		for (LLMetricPerformanceTesterBasic::name_tester_map_t::iterator iter = LLMetricPerformanceTesterBasic::sTesterMap.begin(); 
+			iter != LLMetricPerformanceTesterBasic::sTesterMap.end(); ++iter)
 		{
-			x = (mGraphRect.mRight + mGraphRect.mLeft)/2;
-			y = mGraphRect.mBottom + 8;
+			LLMetricPerformanceTesterBasic* tester = ((LLMetricPerformanceTesterBasic*)iter->second);	
+			tester->outputTestResults();
+		}
+	}
+}
 
-			LLFontGL::getFontMonospace()->renderUTF8(
-				mHoverID->getName(), 
-				0, 
-				x, y, 
-				LLColor4::white,
-				LLFontGL::LEFT, LLFontGL::BOTTOM);
-		}					
+//static
+void LLFastTimerView::doAnalysis(std::string baseline, std::string target, std::string output)
+{
+	if(TimeBlock::sLog)
+	{
+		doAnalysisDefault(baseline, target, output) ;
+		return ;
+	}
+
+	if(TimeBlock::sMetricLog)
+	{
+		LLMetricPerformanceTesterBasic::doAnalysisMetrics(baseline, target, output) ;
+		return ;
 	}
+}
+void	LLFastTimerView::onClickCloseBtn()
+{
+	setVisible(false);
+}
 
+TimeBlock& LLFastTimerView::getFrameTimer()
+{
+	return FTM_FRAME;
+}
 
+void LLFastTimerView::printLineStats()
+{
 	// Output stats for clicked bar to log
 	if (mPrintStats >= 0)
 	{
@@ -1003,11 +984,11 @@ void LLFastTimerView::draw()
 			LLUnit<LLUnits::Seconds, F32> ticks;
 			if (mPrintStats > 0)
 			{
-				ticks = frame_recording.getPrevRecordingPeriod(mPrintStats).getSum(*idp);
+				ticks = mRecording->getPrevRecordingPeriod(mPrintStats).getSum(*idp);
 			}
 			else
 			{
-				ticks = frame_recording.getPeriodMean(*idp);
+				ticks = mRecording->getPeriodMean(*idp);
 			}
 			LLUnit<LLUnits::Milliseconds, F32> ms = ticks;
 
@@ -1021,534 +1002,567 @@ void LLFastTimerView::draw()
 		llinfos << timer_stat << llendl;
 		mPrintStats = -1;
 	}
-		
-	mHoverID = NULL;
-	mHoverBarIndex = -1;
-
-	LLView::draw();
-}
-
-F64 LLFastTimerView::getTime(const std::string& name)
-{
-	//TODO: replace calls to this with use of timer object directly
-	//llstatic_assert(false, "TODO: implement");
-	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);
 }
 
-//static
-void LLFastTimerView::exportCharts(const std::string& base, const std::string& target)
+void LLFastTimerView::drawLineGraph()
 {
-	//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();
-	gGL.loadIdentity();
-	gGL.matrixMode(LLRender::MM_PROJECTION);
-	gGL.loadIdentity();
-	gGL.ortho(-0.05f, 1.05f, -0.05f, 1.05f, -1.0f, 1.0f);
-
-	//render charts
+	//draw line graph history
+	S32 x = mBarRect.mLeft;
+	S32 y = LINE_GRAPH_HEIGHT;
 	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;
+	LLLocalClipRect clip(mGraphRect);
+
+	//normalize based on last frame's maximum
+	static LLUnit<LLUnits::Seconds, F32> max_time = 0.000001;
+	static U32 max_calls = 0;
+	static F32 alpha_interp = 0.f;
+
+	//display y-axis range
+	std::string axis_label;
+	if (mDisplayCalls)
+		axis_label = llformat("%d calls", (int)max_calls);
+	else if (mDisplayHz)
+		axis_label = llformat("%d Hz", (int)(1.f / max_time.value()));
+	else
+		axis_label = llformat("%4.2f ms", max_time.value());
 
-		std::vector<LLSD::Real> base_execution;
-		std::vector<LLSD::Real> base_times;
-		std::vector<LLSD::Integer> base_calls;
+	x = mGraphRect.mRight - LLFontGL::getFontMonospace()->getWidth(axis_label)-5;
+	y = mGraphRect.mTop - LLFontGL::getFontMonospace()->getLineHeight();
 
-		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();
+	LLFontGL::getFontMonospace()->renderUTF8(axis_label, 0, x, y, LLColor4::white,
+		LLFontGL::LEFT, LLFontGL::TOP);
 
-			LLSD::Real execution = 0.0;
-			if (calls > 0)
-			{
-				execution = time/calls;
-				cur_execution.push_back(execution);
-				cur_times.push_back(time);
-			}
+	//highlight visible range
+	{
+		S32 first_frame = HISTORY_NUM - mScrollIndex;
+		S32 last_frame = first_frame - MAX_VISIBLE_HISTORY;
 
-			cur_calls.push_back(calls);
-		}
+		F32 frame_delta = ((F32) (mGraphRect.getWidth()))/(HISTORY_NUM-1);
 
-		for (U32 i = 0; i < base_data.size(); ++i)
+		F32 right = (F32) mGraphRect.mLeft + frame_delta*first_frame;
+		F32 left = (F32) mGraphRect.mLeft + frame_delta*last_frame;
+
+		gGL.color4f(0.5f,0.5f,0.5f,0.3f);
+		gl_rect_2d((S32) left, mGraphRect.mTop, (S32) right, mGraphRect.mBottom);
+
+		if (mHoverBarIndex >= 0)
 		{
-			LLSD::Real time = base_data[i][label]["Time"].asReal();
-			LLSD::Integer calls = base_data[i][label]["Calls"].asInteger();
+			S32 bar_frame = first_frame - mHoverBarIndex;
+			F32 bar = (F32) mGraphRect.mLeft + frame_delta*bar_frame;
 
-			LLSD::Real execution = 0.0;
-			if (calls > 0)
-			{
-				execution = time/calls;
-				base_execution.push_back(execution);
-				base_times.push_back(time);
-			}
+			gGL.color4f(0.5f,0.5f,0.5f,1);
 
-			base_calls.push_back(calls);
+			gGL.begin(LLRender::LINES);
+			gGL.vertex2i((S32)bar, mGraphRect.mBottom);
+			gGL.vertex2i((S32)bar, mGraphRect.mTop);
+			gGL.end();
 		}
+	}
 
-		std::sort(base_calls.begin(), base_calls.end());
-		std::sort(base_times.begin(), base_times.end());
-		std::sort(base_execution.begin(), base_execution.end());
+	LLUnit<LLUnits::Seconds, F32> cur_max = 0;
+	U32 cur_max_calls = 0;
+	for(timer_tree_iterator_t it = begin_timer_tree(getFrameTimer());
+		it != end_timer_tree();
+		++it)
+	{
+		TimeBlock* idp = (*it);
 
-		std::sort(cur_calls.begin(), cur_calls.end());
-		std::sort(cur_times.begin(), cur_times.end());
-		std::sort(cur_execution.begin(), cur_execution.end());
+		//fatten highlighted timer
+		if (mHoverID == idp)
+		{
+			gGL.flush();
+			glLineWidth(3);
+		}
 
-		//remove outliers
-		const U32 OUTLIER_CUTOFF = 512;
-		if (base_times.size() > OUTLIER_CUTOFF)
-		{ 
-			ll_remove_outliers(base_times, 1.f);
+		const F32 * col = sTimerColors[idp].mV;// ft_display_table[idx].color->mV;
+
+		F32 alpha = 1.f;
+
+		if (mHoverID != NULL &&
+			idp != mHoverID)
+		{	//fade out non-highlighted timers
+			if (idp->getParent() != mHoverID)
+			{
+				alpha = alpha_interp;
+			}
 		}
 
-		if (base_execution.size() > OUTLIER_CUTOFF)
-		{ 
-			ll_remove_outliers(base_execution, 1.f);
+		gGL.color4f(col[0], col[1], col[2], alpha);				
+		gGL.begin(LLRender::TRIANGLE_STRIP);
+		for (U32 j = mRecording->getNumPeriods();
+			j > 0;
+			j--)
+		{
+			LLUnit<LLUnits::Seconds, F32> time = llmax(mRecording->getPrevRecordingPeriod(j).getSum(*idp), LLUnit<LLUnits::Seconds, F64>(0.000001));
+			U32 calls = mRecording->getPrevRecordingPeriod(j).getSum(idp->callCount());
+
+			if (alpha == 1.f)
+			{ 
+				//normalize to highlighted timer
+				cur_max = llmax(cur_max, time);
+				cur_max_calls = llmax(cur_max_calls, calls);
+			}
+			F32 x = mGraphRect.mRight - j * (F32)(mGraphRect.getWidth())/(HISTORY_NUM-1);
+			F32 y = mDisplayHz 
+				? mGraphRect.mBottom + (1.f / time.value()) * ((F32) mGraphRect.getHeight() / (1.f / max_time.value()))
+				: mGraphRect.mBottom + time / max_time * (F32)mGraphRect.getHeight();
+			gGL.vertex2f(x,y);
+			gGL.vertex2f(x,mGraphRect.mBottom);
 		}
+		gGL.end();
 
-		if (cur_times.size() > OUTLIER_CUTOFF)
-		{ 
-			ll_remove_outliers(cur_times, 1.f);
+		if (mHoverID == idp)
+		{
+			gGL.flush();
+			glLineWidth(1);
 		}
 
-		if (cur_execution.size() > OUTLIER_CUTOFF)
-		{ 
-			ll_remove_outliers(cur_execution, 1.f);
+		if (idp->getCollapsed())
+		{	
+			//skip hidden timers
+			it.skipDescendants();
 		}
+	}
 
+	//interpolate towards new maximum
+	max_time = lerp(max_time.value(), cur_max.value(), LLCriticalDamp::getInterpolant(0.1f));
+	if (max_time - cur_max <= 1 ||  cur_max - max_time  <= 1)
+	{
+		max_time = llmax(LLUnit<LLUnits::Microseconds, F32>(1), LLUnit<LLUnits::Microseconds, F32>(cur_max));
+	}
 
-		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());
+	max_calls = llround(lerp((F32)max_calls, (F32) cur_max_calls, LLCriticalDamp::getInterpolant(0.1f)));
+	if (llabs((S32)(max_calls - cur_max_calls)) <= 1)
+	{
+		max_calls = cur_max_calls;
+	}
 
+	// TODO: make sure alpha is correct in DisplayHz mode
+	F32 alpha_target = (max_time > cur_max)
+		? llmin(max_time / cur_max - 1.f,1.f) 
+		: llmin(cur_max/ max_time - 1.f,1.f);
+	alpha_interp = lerp(alpha_interp, alpha_target, LLCriticalDamp::getInterpolant(0.1f));
 
-		LLVector3 last_p;
+	if (mHoverID != NULL)
+	{
+		x = (mGraphRect.mRight + mGraphRect.mLeft)/2;
+		y = mGraphRect.mBottom + 8;
+
+		LLFontGL::getFontMonospace()->renderUTF8(
+			mHoverID->getName(), 
+			0, 
+			x, y, 
+			LLColor4::white,
+			LLFontGL::LEFT, LLFontGL::BOTTOM);
+	}					
+}
 
-		//====================================
-		// basic
-		//====================================
-		buffer.clear();
+void LLFastTimerView::drawLegend( S32 y )
+{
+	// draw legend
+	S32 dx;
+	S32 x = MARGIN;
+	const S32 TEXT_HEIGHT = (S32)LLFontGL::getFontMonospace()->getLineHeight();
 
-		last_p.clear();
+	{
+		LLLocalClipRect clip(LLRect(MARGIN, y, LEGEND_WIDTH, MARGIN));
+		S32 cur_line = 0;
+		ft_display_idx.clear();
+		std::map<TimeBlock*, S32> display_line;
+		for (timer_tree_iterator_t it = begin_timer_tree(getFrameTimer());
+			it != timer_tree_iterator_t();
+			++it)
+		{
+			TimeBlock* idp = (*it);
+			display_line[idp] = cur_line;
+			ft_display_idx.push_back(idp);
+			cur_line++;
 
-		LLGLDisable cull(GL_CULL_FACE);
+			x = MARGIN;
 
-		LLVector3 base_col(0, 0.7f, 0.f);
-		LLVector3 cur_col(1.f, 0.f, 0.f);
+			LLRect bar_rect(x, y, x + TEXT_HEIGHT, y - TEXT_HEIGHT);
+			S32 scale_offset = 0;
+			if (idp == mHoverID)
+			{
+				scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 2.f);
+			}
+			bar_rect.stretch(scale_offset);
+			gl_rect_2d(bar_rect, sTimerColors[idp]);
 
-		gGL.setSceneBlendType(LLRender::BT_ADD);
+			LLUnit<LLUnits::Milliseconds, F32> ms = 0;
+			S32 calls = 0;
+			if (mHoverBarIndex > 0 && mHoverID)
+			{
+				S32 hidx = mScrollIndex + mHoverBarIndex;
+				ms = mRecording->getPrevRecordingPeriod(hidx).getSum(*idp);
+				calls = mRecording->getPrevRecordingPeriod(hidx).getSum(idp->callCount());
+			}
+			else
+			{
+				ms = LLUnit<LLUnits::Seconds, F64>(mRecording->getPeriodMean(*idp));
+				calls = (S32)mRecording->getPeriodMean(idp->callCount());
+			}
 
-		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();
+			std::string timer_label;
+			if (mDisplayCalls)
+			{
+				timer_label = llformat("%s (%d)",idp->getName().c_str(),calls);
+			}
+			else
+			{
+				timer_label = llformat("%s [%.1f]",idp->getName().c_str(),ms.value());
+			}
+			dx = (TEXT_HEIGHT+4) + get_depth(idp)*8;
 
-		
-		last_p.clear();
-		{
-			LLGLEnable blend(GL_BLEND);
-						
-			gGL.color3fv(cur_col.mV);
-			for (U32 i = 0; i < cur_times.size(); ++i)
+			LLColor4 color = LLColor4::white;
+			if (get_depth(idp) > 0)
 			{
-				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();
+				S32 line_start_y = bar_rect.getCenterY();
+				S32 line_end_y = line_start_y + ((TEXT_HEIGHT + 2) * (cur_line - display_line[idp->getParent()])) - TEXT_HEIGHT;
+				gl_line_2d(x + dx - 8, line_start_y, x + dx, line_start_y, color);
+				S32 line_x = x + (TEXT_HEIGHT + 4) + ((get_depth(idp) - 1) * 8);
+				gl_line_2d(line_x, line_start_y, line_x, line_end_y, color);
+				if (idp->getCollapsed() && !idp->getChildren().empty())
+				{
+					gl_line_2d(line_x+4, line_start_y-3, line_x+4, line_start_y+4, color);
+				}
 			}
-			
-			gGL.flush();
-		}
 
-		saveChart(label, "time", scratch);
-		
-		//======================================
-		// calls
-		//======================================
-		buffer.clear();
+			x += dx;
+			BOOL is_child_of_hover_item = (idp == mHoverID);
+			TimeBlock* next_parent = idp->getParent();
+			while(!is_child_of_hover_item && next_parent)
+			{
+				is_child_of_hover_item = (mHoverID == next_parent);
+				if (next_parent->getParent() == next_parent) break;
+				next_parent = next_parent->getParent();
+			}
 
-		last_p.clear();
+			LLFontGL::getFontMonospace()->renderUTF8(timer_label, 0, 
+				x, y, 
+				color, 
+				LLFontGL::LEFT, LLFontGL::TOP, 
+				is_child_of_hover_item ? LLFontGL::BOLD : LLFontGL::NORMAL);
 
-		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();
+			y -= (TEXT_HEIGHT + 2);
+
+			if (idp->getCollapsed()) 
+			{
+				it.skipDescendants();
+			}
 		}
-		
-		gGL.flush();
+	}
+}
+
+void LLFastTimerView::generateUniqueColors()
+{
+	// generate unique colors
+	{
+		sTimerColors[&getFrameTimer()] = LLColor4::grey;
+
+		F32 hue = 0.f;
 
+		for (timer_tree_iterator_t it = begin_timer_tree(getFrameTimer());
+			it != timer_tree_iterator_t();
+			++it)
 		{
-			LLGLEnable blend(GL_BLEND);
-			gGL.color3fv(cur_col.mV);
-			last_p.clear();
+			TimeBlock* idp = (*it);
 
-			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();
+			const F32 HUE_INCREMENT = 0.23f;
+			hue = fmodf(hue + HUE_INCREMENT, 1.f);
+			// saturation increases with depth
+			F32 saturation = clamp_rescale((F32)get_depth(idp), 0.f, 3.f, 0.f, 1.f);
+			// lightness alternates with depth
+			F32 lightness = get_depth(idp) % 2 ? 0.5f : 0.6f;
+
+			LLColor4 child_color;
+			child_color.setHSL(hue, saturation, lightness);
+
+			sTimerColors[idp] = child_color;
 		}
+	}
+}
 
-		saveChart(label, "calls", scratch);
+S32 LLFastTimerView::drawHelp( S32 y )
+{
+	// Draw some help
+	{
+		const S32 texth = (S32)LLFontGL::getFontMonospace()->getLineHeight();
 
-		//======================================
-		// execution
-		//======================================
-		buffer.clear();
+		char modedesc[][32] = {
+			"2 x Average ",
+			"Max         ",
+			"Recent Max  ",
+			"100 ms      "
+		};
+		char centerdesc[][32] = {
+			"Left      ",
+			"Centered  ",
+			"Ordered   "
+		};
+
+		std::string text;
+		text = llformat("Full bar = %s [Click to pause/reset] [SHIFT-Click to toggle]",modedesc[mDisplayMode]);
+		LLFontGL::getFontMonospace()->renderUTF8(text, 0, MARGIN, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
+
+		y -= (texth + 2);
+		text = llformat("Justification = %s [CTRL-Click to toggle]",centerdesc[mDisplayCenter]);
+		LLFontGL::getFontMonospace()->renderUTF8(text, 0, MARGIN, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
+		y -= (texth + 2);
+
+		LLFontGL::getFontMonospace()->renderUTF8(std::string("[Right-Click log selected] [ALT-Click toggle counts] [ALT-SHIFT-Click sub hidden]"),
+			0, MARGIN, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
+		y -= (texth + 2);
+	}	return y;
+}
+
+void LLFastTimerView::drawTicks( LLUnit<LLUnits::Seconds, F64> total_time )
+{
+	// Draw MS ticks
+	{
+		LLUnit<LLUnits::Milliseconds, U32> ms = total_time;
+		std::string tick_label;
+		S32 x;
+		S32 barw = mBarRect.getWidth();
+
+		tick_label = llformat("%.1f ms |", (F32)ms.value()*.25f);
+		x = mBarRect.mLeft + barw/4 - LLFontGL::getFontMonospace()->getWidth(tick_label);
+		LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white,
+			LLFontGL::LEFT, LLFontGL::TOP);
+
+		tick_label = llformat("%.1f ms |", (F32)ms.value()*.50f);
+		x = mBarRect.mLeft + barw/2 - LLFontGL::getFontMonospace()->getWidth(tick_label);
+		LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white,
+			LLFontGL::LEFT, LLFontGL::TOP);
+
+		tick_label = llformat("%.1f ms |", (F32)ms.value()*.75f);
+		x = mBarRect.mLeft + (barw*3)/4 - LLFontGL::getFontMonospace()->getWidth(tick_label);
+		LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white,
+			LLFontGL::LEFT, LLFontGL::TOP);
+
+		tick_label = llformat( "%d ms |", (U32)ms.value());
+		x = mBarRect.mLeft + barw - LLFontGL::getFontMonospace()->getWidth(tick_label);
+		LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white,
+			LLFontGL::LEFT, LLFontGL::TOP);
+	}
+}
+
+void LLFastTimerView::drawBorders( S32 y, const S32 x_start, S32 bar_height, S32 dy )
+{
+	// Draw borders
+	{
+		S32 by = y + 6 + (S32)LLFontGL::getFontMonospace()->getLineHeight();	
+
+		//heading
+		gl_rect_2d(x_start-5, by, getRect().getWidth()-5, y+5, LLColor4::grey, FALSE);
 
+		//tree view
+		gl_rect_2d(5, by, x_start-10, 5, LLColor4::grey, FALSE);
 
-		gGL.color3fv(base_col.mV);
-		U32 count = 0;
-		U32 total_count = base_execution.size();
+		by = y + 5;
+		//average bar
+		gl_rect_2d(x_start-5, by, getRect().getWidth()-5, by-bar_height-dy-5, LLColor4::grey, FALSE);
 
-		last_p.clear();
+		by -= bar_height*2+dy;
 
-		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++;
-		}
+		//current frame bar
+		gl_rect_2d(x_start-5, by, getRect().getWidth()-5, by-bar_height-dy-2, LLColor4::grey, FALSE);
 
-		last_p.clear();
-				
-		{
-			LLGLEnable blend(GL_BLEND);
-			gGL.color3fv(cur_col.mV);
-			count = 0;
-			total_count = cur_execution.size();
+		by -= bar_height+dy+1;
 
-			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++;
-			}
+		//history bars
+		gl_rect_2d(x_start-5, by, getRect().getWidth()-5, LINE_GRAPH_HEIGHT-bar_height-dy-2, LLColor4::grey, FALSE);			
 
-			gGL.flush();
-		}
+		by = LINE_GRAPH_HEIGHT-bar_height-dy-7;
 
-		saveChart(label, "execution", scratch);
-	}
+		//line graph
+		mGraphRect = LLRect(x_start-5, by, getRect().getWidth()-5, 5);
 
-	buffer.flush();
+		gl_rect_2d(mGraphRect, FALSE);
+	}
+}
 
-	gGL.popMatrix();
-	gGL.matrixMode(LLRender::MM_MODELVIEW);
-	gGL.popMatrix();
+LLUnit<LLUnits::Seconds, F64> LLFastTimerView::getTotalTime()
+{
+	LLUnit<LLUnits::Seconds, F64> total_time;
+	switch(mDisplayMode)
+	{
+	case 0:
+		total_time = mRecording->getPeriodMean(getFrameTimer())*2;
+		break;
+	case 1:
+		total_time = mAllTimeMax;
+		break;
+	case 2:
+		// Calculate the max total ticks for the current history
+		total_time = mRecording->getPeriodMax(getFrameTimer());
+		break;
+	default:
+		total_time = LLUnit<LLUnits::Milliseconds, F32>(100);
+		break;
+	}
+	return total_time;
 }
 
-//static
-LLSD LLFastTimerView::analyzePerformanceLogDefault(std::istream& is)
+void LLFastTimerView::drawBars()
 {
-	LLSD ret;
+	LLUnit<LLUnits::Seconds, F64> total_time = getTotalTime();
+	if (total_time <= 0.0) return;
 
-	LLSD cur;
+	LLPointer<LLUIImage> box_imagep = LLUI::getUIImage("Rounded_Square");
+	LLLocalClipRect clip(mBarRect);
 
-	LLSD::Real total_time = 0.0;
-	LLSD::Integer total_frames = 0;
+	S32 bar_height = (mBarRect.mTop - MARGIN - LINE_GRAPH_HEIGHT) / (MAX_VISIBLE_HISTORY + 2);
+	S32 vpad = llmax(1, bar_height / 4); // spacing between bars
+	bar_height -= vpad;
 
-	typedef std::map<std::string,LLViewerStats::StatsAccumulator> stats_map_t;
-	stats_map_t time_stats;
-	stats_map_t sample_stats;
+	drawTicks(total_time);
+	S32 y = mBarRect.mTop - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4);
+	drawBorders(y, mBarRect.mLeft, bar_height, vpad);
 
-	while (!is.eof() && LLSDSerialize::fromXML(cur, is))
+	// Draw bars for each history entry
+	// Special: -1 = show running average
+	gGL.getTexUnit(0)->bind(box_imagep->getImage());
+	const S32 histmax = llmin(mRecording->getNumPeriods()+1, MAX_VISIBLE_HISTORY);
+
+	for (S32 j = -1; j < histmax && y > LINE_GRAPH_HEIGHT; j++)
 	{
-		for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter)
+		mBarRects[llmax(j, 0)].clear();
+		int sublevel_dx[FTV_MAX_DEPTH];
+		int sublevel_left[FTV_MAX_DEPTH];
+		int sublevel_right[FTV_MAX_DEPTH];
+		S32 tidx = (j >= 0)
+			? j + 1 + mScrollIndex
+			: -1;
+
+		// draw the bars for each stat
+		std::vector<S32> xpos;
+		S32 deltax = 0;
+		xpos.push_back(mBarRect.mLeft);
+
+		TimeBlock* prev_id = NULL;
+
+		S32 i = 0;
+		for(timer_tree_iterator_t it = begin_timer_tree(getFrameTimer());
+			it != end_timer_tree();
+			++it, ++i)
 		{
-			std::string label = iter->first;
+			TimeBlock* idp = (*it);
+			F32 frac = tidx == -1
+				? (mRecording->getPeriodMean(*idp) / total_time) 
+				: (mRecording->getPrevRecordingPeriod(tidx).getSum(*idp).value() / total_time.value());
 
-			F64 time = iter->second["Time"].asReal();
+			S32 dx = llround(frac * (F32)mBarRect.getWidth());
+			S32 prev_delta_x = deltax;
+			deltax = dx;
 
-			// Skip the total figure
-			if(label.compare("Total") != 0)
+			const int level = get_depth(idp) - 1;
+			while ((S32)xpos.size() > level + 1)
 			{
-				total_time += time;
-			}			
+				xpos.pop_back();
+			}
 
-			if (time > 0.0)
-			{
-				LLSD::Integer samples = iter->second["Calls"].asInteger();
+			LLRect bar_rect;
+			bar_rect.setLeftTopAndSize(xpos.back(), y, dx, bar_height);
+			mBarRects[llmax(j, 0)].push_back(bar_rect);
 
-				time_stats[label].push(time);
-				sample_stats[label].push(samples);
+			if (level == 0)
+			{
+				sublevel_left[level] = mBarRect.mLeft;
+				sublevel_dx[level] = dx;
+				sublevel_right[level] = sublevel_left[level] + sublevel_dx[level];
 			}
-		}
-		total_frames++;
-	}
+			else if (prev_id && get_depth(prev_id) < get_depth(idp))
+			{
+				F64 sublevelticks = 0;
 
-	for(stats_map_t::iterator it = time_stats.begin(); it != time_stats.end(); ++it)
-	{
-		std::string label = it->first;
-		ret[label]["TotalTime"] = time_stats[label].mSum;
-		ret[label]["MeanTime"] = time_stats[label].getMean();
-		ret[label]["MaxTime"] = time_stats[label].getMaxValue();
-		ret[label]["MinTime"] = time_stats[label].getMinValue();
-		ret[label]["StdDevTime"] = time_stats[label].getStdDev();
-		
-		ret[label]["Samples"] = sample_stats[label].mSum;
-		ret[label]["MaxSamples"] = sample_stats[label].getMaxValue();
-		ret[label]["MinSamples"] = sample_stats[label].getMinValue();
-		ret[label]["StdDevSamples"] = sample_stats[label].getStdDev();
+				for (TimeBlock::child_const_iter it = prev_id->beginChildren();
+					it != prev_id->endChildren();
+					++it)
+				{
+					sublevelticks += (tidx == -1)
+						? mRecording->getPeriodMean(**it).value()
+						: mRecording->getPrevRecordingPeriod(tidx).getSum(**it).value();
+				}
 
-		ret[label]["Frames"] = (LLSD::Integer)time_stats[label].getCount();
-	}
-		
-	ret["SessionTime"] = total_time;
-	ret["FrameCount"] = total_frames;
+				F32 subfrac = (F32)sublevelticks / (F32)total_time.value();
+				sublevel_dx[level] = (int)(subfrac * (F32)mBarRect.getWidth() + .5f);
 
-	return ret;
+				if (mDisplayCenter == ALIGN_CENTER)
+				{
+					bar_rect.mLeft += (prev_delta_x - sublevel_dx[level])/2;
+				}
+				else if (mDisplayCenter == ALIGN_RIGHT)
+				{
+					bar_rect.mLeft += (prev_delta_x - sublevel_dx[level]);
+				}
 
-}
+				sublevel_left[level] = bar_rect.mLeft;
+				sublevel_right[level] = sublevel_left[level] + sublevel_dx[level];
+			}				
 
-//static
-void LLFastTimerView::doAnalysisDefault(std::string baseline, std::string target, std::string output)
-{
-	// Open baseline and current target, exit if one is inexistent
-	std::ifstream base_is(baseline.c_str());
-	std::ifstream target_is(target.c_str());
-	if (!base_is.is_open() || !target_is.is_open())
-	{
-		llwarns << "'-analyzeperformance' error : baseline or current target file inexistent" << llendl;
-		base_is.close();
-		target_is.close();
-		return;
-	}
+			xpos.back() = bar_rect.mRight;
+			xpos.push_back(bar_rect.mLeft);
 
-	//analyze baseline
-	LLSD base = analyzePerformanceLogDefault(base_is);
-	base_is.close();
+			if (bar_rect.getWidth() > 0)
+			{
+				LLColor4 color = sTimerColors[idp];
+				S32 scale_offset = 0;
 
-	//analyze current
-	LLSD current = analyzePerformanceLogDefault(target_is);
-	target_is.close();
+				BOOL is_child_of_hover_item = (idp == mHoverID);
+				TimeBlock* next_parent = idp->getParent();
+				while(!is_child_of_hover_item && next_parent)
+				{
+					is_child_of_hover_item = (mHoverID == next_parent);
+					if (next_parent->getParent() == next_parent) break;
+					next_parent = next_parent->getParent();
+				}
 
-	//output comparision
-	std::ofstream os(output.c_str());
+				if (idp == mHoverID)
+				{
+					scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 3.f);
+					//color = lerp(color, LLColor4::black, -0.4f);
+				}
+				else if (mHoverID != NULL && !is_child_of_hover_item)
+				{
+					color = lerp(color, LLColor4::grey, 0.8f);
+				}
 
-	LLSD::Real session_time = current["SessionTime"].asReal();
-	os <<
-		"Label, "
-		"% Change, "
-		"% of Session, "
-		"Cur Min, "
-		"Cur Max, "
-		"Cur Mean/sample, "
-		"Cur Mean/frame, "
-		"Cur StdDev/frame, "
-		"Cur Total, "
-		"Cur Frames, "
-		"Cur Samples, "
-		"Base Min, "
-		"Base Max, "
-		"Base Mean/sample, "
-		"Base Mean/frame, "
-		"Base StdDev/frame, "
-		"Base Total, "
-		"Base Frames, "
-		"Base Samples\n"; 
+				gGL.color4fv(color.mV);
+				F32 start_fragment = llclamp((F32)(bar_rect.mLeft - sublevel_left[level]) / (F32)sublevel_dx[level], 0.f, 1.f);
+				F32 end_fragment = llclamp((F32)(bar_rect.mRight - sublevel_left[level]) / (F32)sublevel_dx[level], 0.f, 1.f);
+				gl_segmented_rect_2d_fragment_tex(
+					sublevel_left[level], 
+					bar_rect.mTop - level + scale_offset, 
+					sublevel_right[level], 
+					bar_rect.mBottom + level - scale_offset, 
+					box_imagep->getTextureWidth(), box_imagep->getTextureHeight(), 
+					16, 
+					start_fragment, end_fragment);
+			}
 
-	for (LLSD::map_iterator iter = base.beginMap();  iter != base.endMap(); ++iter)
-	{
-		LLSD::String label = iter->first;
+			if ((*it)->getCollapsed())
+			{
+				it.skipDescendants();
+			}
+
+			prev_id = idp;
+		}
+		y -= (bar_height + vpad);
+		if (j < 0)
+			y -= bar_height;
+	}
+	gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+}
 
-		if (current[label]["Samples"].asInteger() == 0 ||
-			base[label]["Samples"].asInteger() == 0)
-		{
-			//cannot compare
-			continue;
-		}	
-		LLSD::Real a = base[label]["TotalTime"].asReal() / base[label]["Samples"].asReal();
-		LLSD::Real b = current[label]["TotalTime"].asReal() / current[label]["Samples"].asReal();
-			
-		LLSD::Real diff = b-a;
 
-		LLSD::Real perc = diff/a * 100;
 
-		os << llformat("%s, %.2f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %d, %d, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %d, %d\n",
-			label.c_str(), 
-			(F32) perc, 
-			(F32) (current[label]["TotalTime"].asReal()/session_time * 100.0), 
 
-			(F32) current[label]["MinTime"].asReal(), 
-			(F32) current[label]["MaxTime"].asReal(), 
-			(F32) b, 
-			(F32) current[label]["MeanTime"].asReal(), 
-			(F32) current[label]["StdDevTime"].asReal(),
-			(F32) current[label]["TotalTime"].asReal(), 
-			current[label]["Frames"].asInteger(),
-			current[label]["Samples"].asInteger(),
-			(F32) base[label]["MinTime"].asReal(), 
-			(F32) base[label]["MaxTime"].asReal(), 
-			(F32) a, 
-			(F32) base[label]["MeanTime"].asReal(), 
-			(F32) base[label]["StdDevTime"].asReal(),
-			(F32) base[label]["TotalTime"].asReal(), 
-			base[label]["Frames"].asInteger(),
-			base[label]["Samples"].asInteger());			
-	}
 
-	exportCharts(baseline, target);
 
-	os.flush();
-	os.close();
-}
 
-//static
-void LLFastTimerView::outputAllMetrics()
-{
-	if (LLMetricPerformanceTesterBasic::hasMetricPerformanceTesters())
-	{
-		for (LLMetricPerformanceTesterBasic::name_tester_map_t::iterator iter = LLMetricPerformanceTesterBasic::sTesterMap.begin(); 
-			iter != LLMetricPerformanceTesterBasic::sTesterMap.end(); ++iter)
-		{
-			LLMetricPerformanceTesterBasic* tester = ((LLMetricPerformanceTesterBasic*)iter->second);	
-			tester->outputTestResults();
-		}
-	}
-}
 
-//static
-void LLFastTimerView::doAnalysis(std::string baseline, std::string target, std::string output)
-{
-	if(TimeBlock::sLog)
-	{
-		doAnalysisDefault(baseline, target, output) ;
-		return ;
-	}
 
-	if(TimeBlock::sMetricLog)
-	{
-		LLMetricPerformanceTesterBasic::doAnalysisMetrics(baseline, target, output) ;
-		return ;
-	}
-}
-void	LLFastTimerView::onClickCloseBtn()
-{
-	setVisible(false);
-}
 
-TimeBlock& LLFastTimerView::getFrameTimer()
-{
-	return FTM_FRAME;
-}
 
 
diff --git a/indra/newview/llfasttimerview.h b/indra/newview/llfasttimerview.h
index 07662bb795ef64f5a2e8bb7168b2fb18f2f6f05a..09be1370272356b3318112255364e0f4b61cf795 100644
--- a/indra/newview/llfasttimerview.h
+++ b/indra/newview/llfasttimerview.h
@@ -62,15 +62,28 @@ class LLFastTimerView : public LLFloater
 	virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks);
 	virtual void draw();
 
+
+
 	LLTrace::TimeBlock* getLegendID(S32 y);
 	F64 getTime(const std::string& name);
 
 protected:
 	virtual	void	onClickCloseBtn();
+
 private:	
-	typedef std::vector<std::vector<S32> > bar_positions_t;
-	bar_positions_t mBarStart;
-	bar_positions_t mBarEnd;
+	void drawTicks(LLUnit<LLUnits::Seconds, F64> total_time);
+	void drawLineGraph();
+	void drawLegend(S32 y);
+	S32 drawHelp(S32 y);
+	void drawBorders( S32 y, const S32 x_start, S32 barh, S32 dy);
+	void drawBars();
+
+	void printLineStats();
+	void generateUniqueColors();
+	LLUnit<LLUnits::Seconds, F64> getTotalTime();
+
+
+	std::vector<LLRect>* mBarRects;
 	S32 mDisplayMode;
 
 	typedef enum child_alignment