-
Richard Linden authored
fix for inaccurate optimization of full block time calculations
Richard Linden authoredfix for inaccurate optimization of full block time calculations
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
llfasttimerview.cpp 41.79 KiB
/**
* @file llfasttimerview.cpp
* @brief LLFastTimerView class implementation
*
* $LicenseInfo:firstyear=2004&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llfasttimerview.h"
#include "llviewerwindow.h"
#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"
#include "llsdserialize.h"
#include "lltooltip.h"
#include "llbutton.h"
#include "llappviewer.h"
#include "llviewertexturelist.h"
#include "llui.h"
#include "llviewercontrol.h"
#include "llfasttimer.h"
#include "lltreeiterators.h"
#include "llmetricperformancetester.h"
#include "llviewerstats.h"
//////////////////////////////////////////////////////////////////////////////
using namespace LLTrace;
static const S32 MAX_VISIBLE_HISTORY = 10;
static const S32 LINE_GRAPH_HEIGHT = 240;
static const S32 MIN_BAR_HEIGHT = 3;
std::vector<TimeBlock*> ft_display_idx; // line of table entry for display purposes (for collapse)
typedef LLTreeDFSIter<TimeBlock, TimeBlock::child_const_iter> timer_tree_iterator_t;
BOOL LLFastTimerView::sAnalyzePerformance = FALSE;
static timer_tree_iterator_t begin_timer_tree(TimeBlock& id)
{
return timer_tree_iterator_t(&id,
boost::bind(boost::mem_fn(&TimeBlock::beginChildren), _1),
boost::bind(boost::mem_fn(&TimeBlock::endChildren), _1));
}
static timer_tree_iterator_t end_timer_tree()
{
return timer_tree_iterator_t();
}
S32 get_depth(const TimeBlock* blockp)
{
S32 depth = 0;
TimeBlock* timerp = blockp->getParent();
while(timerp)
{
depth++;
if (timerp->getParent() == timerp) break;
timerp = timerp->getParent();
}
return depth;
}
LLFastTimerView::LLFastTimerView(const LLSD& key)
: LLFloater(key),
mHoverTimer(NULL),
mDisplayMode(0),
mDisplayCenter(ALIGN_CENTER),
mDisplayCalls(false),
mDisplayHz(false),
mScrollIndex(0),
mHoverID(NULL),
mHoverBarIndex(-1),
mPrintStats(-1),
mRecording(&get_frame_recording()),
mPauseHistory(false)
{
mTimerBars = new std::vector<TimerBar>[MAX_VISIBLE_HISTORY + 1];
}
LLFastTimerView::~LLFastTimerView()
{
if (mRecording != &get_frame_recording())
{
delete mRecording;
}
mRecording = NULL;
delete [] mTimerBars;
}
void LLFastTimerView::onPause()
{
setPauseState(!mPauseHistory);
}
void LLFastTimerView::setPauseState(bool pause_state)
{
if (pause_state == mPauseHistory) return;
// reset scroll to bottom when unpausing
if (!pause_state)
{
if (mRecording != &get_frame_recording())
{
delete mRecording;
}
mRecording = &get_frame_recording();
getChild<LLButton>("pause_btn")->setLabel(getString("pause"));
}
else
{
mRecording = new PeriodicRecording(get_frame_recording());
mScrollIndex = 0;
getChild<LLButton>("pause_btn")->setLabel(getString("run"));
}
mPauseHistory = pause_state;
}
BOOL LLFastTimerView::postBuild()
{
LLButton& pause_btn = getChildRef<LLButton>("pause_btn");
pause_btn.setCommitCallback(boost::bind(&LLFastTimerView::onPause, this));
return TRUE;
}
BOOL LLFastTimerView::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
if (mHoverTimer )
{
// right click collapses timers
if (!mHoverTimer->getCollapsed())
{
mHoverTimer->setCollapsed(true);
}
else if (mHoverTimer->getParent())
{
mHoverTimer->getParent()->setCollapsed(true);
}
return TRUE;
}
else if (mBarRect.pointInRect(x, y))
{
S32 bar_idx = MAX_VISIBLE_HISTORY - ((y - mBarRect.mBottom) * (MAX_VISIBLE_HISTORY + 2) / mBarRect.getHeight());
bar_idx = llclamp(bar_idx, 0, MAX_VISIBLE_HISTORY);
mPrintStats = mScrollIndex + bar_idx;
return TRUE;
}
return LLFloater::handleRightMouseDown(x, y, mask);
}
TimeBlock* LLFastTimerView::getLegendID(S32 y)
{
S32 idx = (mBarRect.mTop - y) / (LLFontGL::getFontMonospace()->getLineHeight()+2) - 1;
if (idx >= 0 && idx < (S32)ft_display_idx.size())
{
return ft_display_idx[idx];
}
return NULL;
}
BOOL LLFastTimerView::handleDoubleClick(S32 x, S32 y, MASK mask)
{
for(timer_tree_iterator_t it = begin_timer_tree(FTM_FRAME);
it != end_timer_tree();
++it)
{
(*it)->setCollapsed(false);
}
return TRUE;
}
BOOL LLFastTimerView::handleMouseDown(S32 x, S32 y, MASK mask)
{
if (x < mBarRect.mLeft)
{
TimeBlock* idp = getLegendID(y);
if (idp)
{
idp->setCollapsed(!idp->getCollapsed());
}
}
else if (mHoverTimer)
{
//left click drills down by expanding timers
mHoverTimer->setCollapsed(false);
}
else if (mask & MASK_ALT)
{
if (mask & MASK_CONTROL)
{
mDisplayHz = !mDisplayHz;
}
else
{
mDisplayCalls = !mDisplayCalls;
}
}
else if (mask & MASK_SHIFT)
{
if (++mDisplayMode > 3)
mDisplayMode = 0;
}
else if (mask & MASK_CONTROL)
{
mDisplayCenter = (ChildAlignment)((mDisplayCenter + 1) % ALIGN_COUNT);
}
else if (mGraphRect.pointInRect(x, y))
{
gFocusMgr.setMouseCapture(this);
return TRUE;
}
return LLFloater::handleMouseDown(x, y, mask);
}
BOOL LLFastTimerView::handleMouseUp(S32 x, S32 y, MASK mask)
{
if (hasMouseCapture())
{
gFocusMgr.setMouseCapture(NULL);
}
return LLFloater::handleMouseUp(x, y, mask);;
}
BOOL LLFastTimerView::handleHover(S32 x, S32 y, MASK mask)
{
if (hasMouseCapture())
{
F32 lerp = llclamp(1.f - (F32) (x - mGraphRect.mLeft) / (F32) mGraphRect.getWidth(), 0.f, 1.f);
mScrollIndex = llround( lerp * (F32)(mRecording->getNumPeriods() - MAX_VISIBLE_HISTORY));
mScrollIndex = llclamp( mScrollIndex, 0, mRecording->getNumPeriods());
return TRUE;
}
mHoverTimer = NULL;
mHoverID = NULL;
if(mPauseHistory && mBarRect.pointInRect(x, y))
{
mHoverBarIndex = llmin((mBarRect.mTop - y) / (mBarRect.getHeight() / (MAX_VISIBLE_HISTORY + 2)) - 1,
mRecording->getNumPeriods() - 1,
MAX_VISIBLE_HISTORY);
if (mHoverBarIndex == 0)
{
return TRUE;
}
else if (mHoverBarIndex == -1)
{
mHoverBarIndex = 0;
}
S32 i = 0;
for(timer_tree_iterator_t it = begin_timer_tree(FTM_FRAME);
it != end_timer_tree();
++it, ++i)
{
// is mouse over bar for this timer?
if (mTimerBars[mHoverBarIndex][i].mVisibleRect.pointInRect(x, y))
{
mHoverID = (*it);
if (mHoverTimer != *it)
{
// could be that existing tooltip is for a parent and is thus
// covering region for this new timer, go ahead and unblock
// so we can create a new tooltip
LLToolTipMgr::instance().unblockToolTips();
mHoverTimer = (*it);
}
mToolTipRect = mTimerBars[mHoverBarIndex][i].mVisibleRect;
}
if ((*it)->getCollapsed())
{
it.skipDescendants();
}
}
}
else if (x < mBarRect.mLeft)
{
TimeBlock* timer_id = getLegendID(y);
if (timer_id)
{
mHoverID = timer_id;
}
}
return LLFloater::handleHover(x, y, mask);
}
static std::string get_tooltip(TimeBlock& timer, S32 history_index, PeriodicRecording& frame_recording)
{
std::string tooltip;
if (history_index == 0)
{
// by default, show average number of call
tooltip = llformat("%s (%d ms, %d calls)", timer.getName().c_str(), (S32)LLUnit<LLUnits::Milliseconds, F64>(frame_recording.getPeriodMean(timer)).value(), (S32)frame_recording.getPeriodMean(timer.callCount()));
}
else
{
tooltip = llformat("%s (%d ms, %d calls)", timer.getName().c_str(), (S32)LLUnit<LLUnits::Milliseconds, F64>(frame_recording.getPrevRecordingPeriod(history_index).getSum(timer)).value(), (S32)frame_recording.getPrevRecordingPeriod(history_index).getSum(timer.callCount()));
}
return tooltip;
}
BOOL LLFastTimerView::handleToolTip(S32 x, S32 y, MASK mask)
{
if(mPauseHistory && mBarRect.pointInRect(x, y))
{
// tooltips for timer bars
if (mHoverTimer)
{
LLRect screen_rect;
localRectToScreen(mToolTipRect, &screen_rect);
std::string tooltip = get_tooltip(*mHoverTimer, mHoverBarIndex > 0 ? mScrollIndex + mHoverBarIndex : 0, *mRecording);
LLToolTipMgr::instance().show(LLToolTip::Params()
.message(tooltip)
.sticky_rect(screen_rect)
.delay_time(0.f));
return TRUE;
}
}
else
{
// tooltips for timer legend
if (x < mBarRect.mLeft)
{
TimeBlock* idp = getLegendID(y);
if (idp)
{
LLToolTipMgr::instance().show(get_tooltip(*idp, 0, *mRecording));
return TRUE;
}
}
}
return LLFloater::handleToolTip(x, y, mask);
}
BOOL LLFastTimerView::handleScrollWheel(S32 x, S32 y, S32 clicks)
{
setPauseState(true);
mScrollIndex = llclamp( mScrollIndex + clicks,
0,
llmin(mRecording->getNumPeriods(), (S32)mRecording->getNumPeriods() - 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;
void LLFastTimerView::draw()
{
LLFastTimer t(FTM_RENDER_TIMER);
generateUniqueColors();
// Draw the window background
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
gl_rect_2d(getLocalRect(), LLColor4(0.f, 0.f, 0.f, 0.25f));
S32 y = drawHelp(getRect().getHeight() - MARGIN);
drawLegend(y - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 2));
// update rectangle that includes timer bars
const S32 LEGEND_WIDTH = 220;
mBarRect.mLeft = MARGIN + LEGEND_WIDTH + 8;
mBarRect.mTop = y;
mBarRect.mRight = getRect().getWidth() - MARGIN;
mBarRect.mBottom = MARGIN + LINE_GRAPH_HEIGHT;
drawBars();
drawLineGraph();
printLineStats();
LLView::draw();
mAllTimeMax = llmax(mAllTimeMax, mRecording->getLastRecordingPeriod().getSum(FTM_FRAME));
mHoverID = NULL;
mHoverBarIndex = -1;
}
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)
{
//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
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)
{
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();
}
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();
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);
}
}
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";
for (LLSD::map_iterator iter = base.beginMap(); iter != base.endMap(); ++iter)
{
LLSD::String label = iter->first;
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);
}
void LLFastTimerView::printLineStats()
{
// Output stats for clicked bar to log
if (mPrintStats >= 0)
{
std::string legend_stat;
bool first = true;
for(timer_tree_iterator_t it = begin_timer_tree(FTM_FRAME);
it != end_timer_tree();
++it)
{
TimeBlock* idp = (*it);
if (!first)
{
legend_stat += ", ";
}
first = false;
legend_stat += idp->getName();
if (idp->getCollapsed())
{
it.skipDescendants();
}
}
llinfos << legend_stat << llendl;
std::string timer_stat;
first = true;
for(timer_tree_iterator_t it = begin_timer_tree(FTM_FRAME);
it != end_timer_tree();
++it)
{
TimeBlock* idp = (*it);
if (!first)
{
timer_stat += ", ";
}
first = false;
LLUnit<LLUnits::Seconds, F32> ticks;
if (mPrintStats > 0)
{
ticks = mRecording->getPrevRecordingPeriod(mPrintStats).getSum(*idp);
}
else
{
ticks = mRecording->getPeriodMean(*idp);
}
LLUnit<LLUnits::Milliseconds, F32> ms = ticks;
timer_stat += llformat("%.1f",ms.value());
if (idp->getCollapsed())
{
it.skipDescendants();
}
}
llinfos << timer_stat << llendl;
mPrintStats = -1;
}
}
static LLFastTimer::DeclareTimer FTM_DRAW_LINE_GRAPH("Draw line graph");
void LLFastTimerView::drawLineGraph()
{
LLFastTimer _(FTM_DRAW_LINE_GRAPH);
//draw line graph history
S32 x = mBarRect.mLeft;
S32 y = LINE_GRAPH_HEIGHT;
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 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", LLUnit<LLUnits::Milliseconds, F32>(max_time).value());
x = mGraphRect.mRight - LLFontGL::getFontMonospace()->getWidth(axis_label)-5;
y = mGraphRect.mTop - LLFontGL::getFontMonospace()->getLineHeight();
LLFontGL::getFontMonospace()->renderUTF8(axis_label, 0, x, y, LLColor4::white,
LLFontGL::LEFT, LLFontGL::TOP);
//highlight visible range
{
S32 first_frame = mRecording->getNumPeriods() - mScrollIndex;
S32 last_frame = first_frame - MAX_VISIBLE_HISTORY;
F32 frame_delta = ((F32) (mGraphRect.getWidth()))/(mRecording->getNumPeriods()-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 - 1;
F32 bar = (F32) mGraphRect.mLeft + frame_delta*bar_frame;
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(FTM_FRAME);
it != end_timer_tree();
++it)
{
TimeBlock* idp = (*it);
//fatten highlighted timer
if (mHoverID == idp)
{
gGL.flush();
glLineWidth(3);
}
const F32 * col = sTimerColors[idp].mV;// ft_display_table[idx].color->mV;
F32 alpha = 1.f;
if (mHoverID != NULL &&
mHoverID != idp)
{ //fade out non-highlighted timers
if (idp->getParent() != mHoverID)
{
alpha = alpha_interp;
}
}
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())/(mRecording->getNumPeriods()-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);
}
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_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));
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);
}
}
void LLFastTimerView::drawLegend( S32 y )
{
// draw legend
S32 dx;
S32 x = MARGIN;
const S32 TEXT_HEIGHT = (S32)LLFontGL::getFontMonospace()->getLineHeight();
{
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(FTM_FRAME);
it != timer_tree_iterator_t();
++it)
{
TimeBlock* idp = (*it);
display_line[idp] = cur_line;
ft_display_idx.push_back(idp);
cur_line++;
x = MARGIN;
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]);
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());
}
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;
LLColor4 color = LLColor4::white;
if (get_depth(idp) > 0)
{
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);
}
}
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();
}
LLFontGL::getFontMonospace()->renderUTF8(timer_label, 0,
x, y,
color,
LLFontGL::LEFT, LLFontGL::TOP,
is_child_of_hover_item ? LLFontGL::BOLD : LLFontGL::NORMAL);
y -= (TEXT_HEIGHT + 2);
if (idp->getCollapsed())
{
it.skipDescendants();
}
}
}
}
void LLFastTimerView::generateUniqueColors()
{
// generate unique colors
{
sTimerColors[&FTM_FRAME] = LLColor4::grey;
F32 hue = 0.f;
for (timer_tree_iterator_t it = begin_timer_tree(FTM_FRAME);
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);
sTimerColors[idp] = child_color;
}
}
}
S32 LLFastTimerView::drawHelp( S32 y )
{
// Draw some help
const S32 texth = (S32)LLFontGL::getFontMonospace()->getLineHeight();
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()
{
// Draw MS ticks
{
LLUnit<LLUnits::Milliseconds, U32> ms = mTotalTimeDisplay;
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);
by = y + 5;
//average bar
gl_rect_2d(x_start-5, by, getRect().getWidth()-5, by-bar_height-dy-5, LLColor4::grey, FALSE);
by -= bar_height*2+dy;
//current frame bar
gl_rect_2d(x_start-5, by, getRect().getWidth()-5, by-bar_height-dy-2, LLColor4::grey, FALSE);
by -= bar_height+dy+1;
//history bars
gl_rect_2d(x_start-5, by, getRect().getWidth()-5, LINE_GRAPH_HEIGHT-bar_height-dy-2, LLColor4::grey, FALSE);
by = LINE_GRAPH_HEIGHT-bar_height-dy-7;
//line graph
mGraphRect = LLRect(x_start-5, by, getRect().getWidth()-5, 5);
gl_rect_2d(mGraphRect, FALSE);
}
}
void LLFastTimerView::updateTotalTime()
{
switch(mDisplayMode)
{
case 0:
mTotalTimeDisplay = mRecording->getPeriodMean(FTM_FRAME)*2;
break;
case 1:
mTotalTimeDisplay = mAllTimeMax;
break;
case 2:
// Calculate the max total ticks for the current history
mTotalTimeDisplay = mRecording->getPeriodMax(FTM_FRAME);
break;
default:
mTotalTimeDisplay = LLUnit<LLUnits::Milliseconds, F32>(100);
break;
}
mTotalTimeDisplay = LLUnit<LLUnits::Milliseconds, F32>(llceil(mTotalTimeDisplay.as<LLUnits::Milliseconds>().value() / 20.f) * 20.f);
}
void LLFastTimerView::drawBars()
{
updateTotalTime();
if (mTotalTimeDisplay <= 0.0) return;
LLLocalClipRect clip(mBarRect);
S32 bar_height = mBarRect.getHeight() / (MAX_VISIBLE_HISTORY + 2);
S32 vpad = llmax(1, bar_height / 4); // spacing between bars
bar_height -= vpad;
drawTicks();
S32 y = mBarRect.mTop - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4);
drawBorders(y, mBarRect.mLeft, bar_height, vpad);
// Draw bars for each history entry
// Special: -1 = show running average
LLPointer<LLUIImage> bar_image = LLUI::getUIImage("Rounded_Square");
gGL.getTexUnit(0)->bind(bar_image->getImage());
const S32 histmax = llmin(mRecording->getNumPeriods(), MAX_VISIBLE_HISTORY) + 1;
for (S32 bar_index = 0; bar_index < histmax && y > LINE_GRAPH_HEIGHT; bar_index++)
{
S32 history_index = (bar_index > 0)
? bar_index + mScrollIndex
: -1;
mTimerBars[bar_index].clear();
mTimerBars[bar_index].reserve(LLInstanceTracker<LLTrace::TimeBlock>::instanceCount());
updateTimerBarWidths(&FTM_FRAME, mTimerBars[bar_index], history_index, true);
LLRect frame_bar_rect(mBarRect.mLeft, y, mBarRect.mLeft + mTimerBars[bar_index][0].mWidth, y-bar_height);
mTimerBars[bar_index][0].mVisibleRect = frame_bar_rect;
updateTimerBarFractions(&FTM_FRAME, 0, mTimerBars[bar_index]);
drawBar(&FTM_FRAME, frame_bar_rect, mTimerBars[bar_index], 0, bar_image);
y -= (bar_height + vpad);
if (bar_index == 0)
y -= bar_height;
}
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
}
static LLFastTimer::DeclareTimer FTM_UPDATE_TIMER_BAR_WIDTHS("Update timer bar widths");
S32 LLFastTimerView::updateTimerBarWidths(LLTrace::TimeBlock* time_block, std::vector<TimerBar>& bars, S32 history_index, bool visible)
{
LLFastTimer _(FTM_UPDATE_TIMER_BAR_WIDTHS);
F32 self_time_frame_fraction = history_index == -1
? (mRecording->getPeriodMean(time_block->selfTime()) / mTotalTimeDisplay)
: (mRecording->getPrevRecordingPeriod(history_index).getSum(time_block->selfTime()) / mTotalTimeDisplay);
S32 self_time_width = llround(self_time_frame_fraction * (F32)mBarRect.getWidth());
S32 full_width = self_time_width;
bool children_visible = visible;
// reserve a spot for this bar to be rendered before its children
// even though we don't know its size yet
S32 bar_rect_index = bars.size();
if (visible)
{
bars.push_back(TimerBar());
}
if (time_block->getCollapsed())
{
children_visible = false;
}
for (TimeBlock::child_iter it = time_block->beginChildren(), end_it = time_block->endChildren(); it != end_it; ++it)
{
full_width += updateTimerBarWidths(*it, bars, history_index, children_visible);
}
if (visible)
{
TimerBar& timer_bar = bars[bar_rect_index];
timer_bar.mWidth = full_width;
timer_bar.mSelfWidth = self_time_width;
timer_bar.mColor = sTimerColors[time_block];
BOOL is_child_of_hover_item = (time_block == mHoverID);
TimeBlock* next_parent = time_block->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 (mHoverID != NULL
&& time_block != mHoverID
&& !is_child_of_hover_item)
{
timer_bar.mColor = lerp(timer_bar.mColor, LLColor4::grey, 0.8f);
}
}
return full_width;
}
static LLFastTimer::DeclareTimer FTM_UPDATE_TIMER_BAR_FRACTIONS("Update timer bar fractions");
S32 LLFastTimerView::updateTimerBarFractions(LLTrace::TimeBlock* time_block, S32 timer_bar_index, std::vector<TimerBar>& bars)
{
LLFastTimer _(FTM_UPDATE_TIMER_BAR_FRACTIONS);
TimerBar& timer_bar = bars[timer_bar_index];
S32 child_time_width = timer_bar.mWidth - timer_bar.mSelfWidth;
LLRect children_rect = timer_bar.mVisibleRect;
if (mDisplayCenter == ALIGN_CENTER)
{
children_rect.mLeft += timer_bar.mSelfWidth / 2;
}
else if (mDisplayCenter == ALIGN_RIGHT)
{
children_rect.mLeft += timer_bar.mSelfWidth;
}
children_rect.mRight = children_rect.mLeft + timer_bar.mWidth - timer_bar.mSelfWidth;
if (children_rect.getHeight() > MIN_BAR_HEIGHT)
{
children_rect.mTop -= 1;
children_rect.mBottom += 1;
}
timer_bar.mChildrenRect = children_rect;
//now loop through children and figure out portion of bar image covered by each bar, now that we know the
//sum of all children
if (!time_block->getCollapsed())
{
F32 bar_fraction_start = 0.f;
for (TimeBlock::child_iter it = time_block->beginChildren(), end_it = time_block->endChildren();
it != end_it;
++it)
{
timer_bar_index++;
TimerBar& child_timer_bar = bars[timer_bar_index];
TimeBlock* child_time_block = *it;
child_timer_bar.mStartFraction = bar_fraction_start;
child_timer_bar.mEndFraction = child_time_width > 0
? bar_fraction_start + (F32)child_timer_bar.mWidth / child_time_width
: 1.f;
child_timer_bar.mVisibleRect.set(children_rect.mLeft + llround(child_timer_bar.mStartFraction * children_rect.getWidth()),
children_rect.mTop,
children_rect.mLeft + llround(child_timer_bar.mEndFraction * children_rect.getWidth()),
children_rect.mBottom);
timer_bar_index = updateTimerBarFractions(child_time_block, timer_bar_index, bars);
bar_fraction_start = child_timer_bar.mEndFraction;
}
}
return timer_bar_index;
}
S32 LLFastTimerView::drawBar(LLTrace::TimeBlock* time_block, LLRect bar_rect, std::vector<TimerBar>& bars, S32 bar_index, LLPointer<LLUIImage>& bar_image)
{
TimerBar& timer_bar = bars[bar_index];
// animate scale of bar when hovering over that particular timer
if (bar_rect.getWidth() > 0)
{
LLRect render_rect(bar_rect);
S32 scale_offset = 0;
if (time_block == mHoverID)
{
scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 3.f);
render_rect.mTop += scale_offset;
render_rect.mBottom -= scale_offset;
}
gGL.color4fv(timer_bar.mColor.mV);
gl_segmented_rect_2d_fragment_tex(render_rect,
bar_image->getTextureWidth(), bar_image->getTextureHeight(),
16,
timer_bar.mStartFraction, timer_bar.mEndFraction);
}
if (!time_block->getCollapsed())
{
for (TimeBlock::child_iter it = time_block->beginChildren(), end_it = time_block->endChildren(); it != end_it; ++it)
{
++bar_index;
bar_index = drawBar(*it, timer_bar.mChildrenRect, bars, bar_index, bar_image);
}
}
return bar_index;
}