diff options
| author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 21:25:21 +0200 |
|---|---|---|
| committer | Andrey Lihatskiy <alihatskiy@productengine.com> | 2024-05-22 22:40:26 +0300 |
| commit | e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 (patch) | |
| tree | 1bb897489ce524986f6196201c10ac0d8861aa5f /indra/newview/llfasttimerview.cpp | |
| parent | 069ea06848f766466f1a281144c82a0f2bd79f3a (diff) | |
Fix line endlings
Diffstat (limited to 'indra/newview/llfasttimerview.cpp')
| -rw-r--r-- | indra/newview/llfasttimerview.cpp | 3334 |
1 files changed, 1667 insertions, 1667 deletions
diff --git a/indra/newview/llfasttimerview.cpp b/indra/newview/llfasttimerview.cpp index 74345841cb..0658d84bb8 100644 --- a/indra/newview/llfasttimerview.cpp +++ b/indra/newview/llfasttimerview.cpp @@ -1,1667 +1,1667 @@ -/**
- * @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 "llcombobox.h"
-#include "llerror.h"
-#include "llgl.h"
-#include "llimagepng.h"
-#include "llrender.h"
-#include "llrendertarget.h"
-#include "lllocalcliprect.h"
-#include "lllayoutstack.h"
-#include "llmath.h"
-#include "llfontgl.h"
-#include "llsdserialize.h"
-#include "lltooltip.h"
-#include "llbutton.h"
-#include "llscrollbar.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 constexpr S32 MAX_VISIBLE_HISTORY = 12;
-static constexpr S32 LINE_GRAPH_HEIGHT = 240;
-static constexpr S32 MIN_BAR_HEIGHT = 3;
-static constexpr S32 RUNNING_AVERAGE_WIDTH = 100;
-static constexpr S32 NUM_FRAMES_HISTORY = 200;
-
-std::vector<BlockTimerStatHandle*> ft_display_idx; // line of table entry for display purposes (for collapse)
-
-bool LLFastTimerView::sAnalyzePerformance = false;
-
-S32 get_depth(const BlockTimerStatHandle* blockp)
-{
- S32 depth = 0;
- BlockTimerStatHandle* 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),
- mDisplayType(DISPLAY_TIME),
- mScrollIndex(0),
- mHoverID(NULL),
- mHoverBarIndex(-1),
- mStatsIndex(-1),
- mPauseHistory(false),
- mRecording(NUM_FRAMES_HISTORY)
-{
- mTimerBarRows.resize(NUM_FRAMES_HISTORY);
-}
-
-LLFastTimerView::~LLFastTimerView()
-{
-}
-
-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)
- {
-
- getChild<LLButton>("pause_btn")->setLabel(getString("pause"));
- }
- else
- {
- mScrollIndex = 0;
-
- getChild<LLButton>("pause_btn")->setLabel(getString("run"));
- }
-
- mPauseHistory = pause_state;
-}
-
-bool LLFastTimerView::postBuild()
-{
- LLButton& pause_btn = getChildRef<LLButton>("pause_btn");
- mScrollBar = getChild<LLScrollbar>("scroll_vert");
-
- 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->getTreeNode().mCollapsed)
- {
- mHoverTimer->getTreeNode().mCollapsed = true;
- }
- else if (mHoverTimer->getParent())
- {
- mHoverTimer->getParent()->getTreeNode().mCollapsed = 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);
- mStatsIndex = mScrollIndex + bar_idx;
- return true;
- }
- return LLFloater::handleRightMouseDown(x, y, mask);
-}
-
-BlockTimerStatHandle* LLFastTimerView::getLegendID(S32 y)
-{
- S32 idx = (mLegendRect.mTop - y) / (LLFontGL::getFontMonospace()->getLineHeight() + 2);
-
- 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(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME);
- it != LLTrace::end_block_timer_tree_df();
- ++it)
- {
- (*it)->getTreeNode().mCollapsed = false;
- }
- return true;
-}
-
-bool LLFastTimerView::handleMouseDown(S32 x, S32 y, MASK mask)
-{
- if (x < mScrollBar->getRect().mLeft)
- {
- BlockTimerStatHandle* idp = getLegendID(y);
- if (idp)
- {
- idp->getTreeNode().mCollapsed = !idp->getTreeNode().mCollapsed;
- }
- }
- else if (mHoverTimer)
- {
- //left click drills down by expanding timers
- mHoverTimer->getTreeNode().mCollapsed = false;
- }
- 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 = ll_round( lerp * (F32)(mRecording.getNumRecordedPeriods() - MAX_VISIBLE_HISTORY));
- mScrollIndex = llclamp( mScrollIndex, 0, (S32)mRecording.getNumRecordedPeriods());
- return true;
- }
- mHoverTimer = NULL;
- mHoverID = NULL;
-
- if(mPauseHistory && mBarRect.pointInRect(x, y))
- {
- //const S32 bars_top = mBarRect.mTop;
- const S32 bars_top = mBarRect.mTop - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4);
-
- mHoverBarIndex = llmin((bars_top - y) / (mBarRect.getHeight() / (MAX_VISIBLE_HISTORY + 2)) - 1,
- (S32)mRecording.getNumRecordedPeriods() - 1,
- MAX_VISIBLE_HISTORY);
- if (mHoverBarIndex == 0)
- {
- return true;
- }
- else if (mHoverBarIndex < 0)
- {
- mHoverBarIndex = 0;
- }
-
- TimerBarRow& row = mHoverBarIndex == 0 ? mAverageTimerRow : mTimerBarRows[mScrollIndex + mHoverBarIndex - 1];
-
- TimerBar* hover_bar = NULL;
- F32Seconds mouse_time_offset = ((F32)(x - mBarRect.mLeft) / (F32)mBarRect.getWidth()) * mTotalTimeDisplay;
- for (int bar_index = 0, end_index = LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount();
- bar_index < end_index;
- ++bar_index)
- {
- TimerBar& bar = row.mBars[bar_index];
- if (bar.mSelfStart > mouse_time_offset)
- {
- break;
- }
- if (bar.mSelfEnd > mouse_time_offset)
- {
- hover_bar = &bar;
- if (bar.mTimeBlock->getTreeNode().mCollapsed)
- {
- // stop on first collapsed BlockTimerStatHandle, since we can't select any children
- break;
- }
- }
- }
-
- if (hover_bar)
- {
- mHoverID = hover_bar->mTimeBlock;
- if (mHoverTimer != mHoverID)
- {
- // 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 = mHoverID;
- mToolTipRect.set(mBarRect.mLeft + (hover_bar->mSelfStart / mTotalTimeDisplay) * mBarRect.getWidth(),
- row.mTop,
- mBarRect.mLeft + (hover_bar->mSelfEnd / mTotalTimeDisplay) * mBarRect.getWidth(),
- row.mBottom);
- }
- }
- }
- else if (x < mScrollBar->getRect().mLeft)
- {
- BlockTimerStatHandle* timer_id = getLegendID(y);
- if (timer_id)
- {
- mHoverID = timer_id;
- }
- }
-
- return LLFloater::handleHover(x, y, mask);
-}
-
-
-static std::string get_tooltip(BlockTimerStatHandle& 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)F64Milliseconds(frame_recording.getPeriodMean (timer, RUNNING_AVERAGE_WIDTH)).value(), (S32)frame_recording.getPeriodMean(timer.callCount(), RUNNING_AVERAGE_WIDTH));
- }
- else
- {
- tooltip = llformat("%s (%d ms, %d calls)", timer.getName().c_str(), (S32)F64Milliseconds(frame_recording.getPrevRecording(history_index).getSum(timer)).value(), (S32)frame_recording.getPrevRecording(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 < mScrollBar->getRect().mLeft)
- {
- BlockTimerStatHandle* 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)
-{
- if (x < mBarRect.mLeft)
- {
- // Inside mScrollBar and list of timers
- mScrollBar->handleScrollWheel(x,y,clicks);
- }
- else
- {
- setPauseState(true);
- mScrollIndex = llclamp( mScrollIndex + clicks,
- 0,
- llmin((S32)mRecording.getNumRecordedPeriods(), (S32)mRecording.getNumRecordedPeriods() - MAX_VISIBLE_HISTORY));
- }
- return true;
-}
-
-static BlockTimerStatHandle FTM_RENDER_TIMER("Timers");
-static const S32 MARGIN = 10;
-
-static std::vector<LLColor4> sTimerColors;
-
-void LLFastTimerView::draw()
-{
- LL_RECORD_BLOCK_TIME(FTM_RENDER_TIMER);
-
- if (!mPauseHistory)
- {
- mRecording.appendRecording(LLTrace::get_frame_recording().getLastRecording());
- mTimerBarRows.pop_back();
- mTimerBarRows.push_front(TimerBarRow());
- }
-
- mDisplayMode = llclamp(getChild<LLComboBox>("time_scale_combo")->getCurrentIndex(), 0, 3);
- mDisplayType = (EDisplayType)llclamp(getChild<LLComboBox>("metric_combo")->getCurrentIndex(), 0, 2);
-
- generateUniqueColors();
-
- LLView::drawChildren();
- //getChild<LLLayoutStack>("timer_bars_stack")->updateLayout();
- //getChild<LLLayoutStack>("legend_stack")->updateLayout();
- LLView* bars_panel = getChildView("bars_panel");
- bars_panel->localRectToOtherView(bars_panel->getLocalRect(), &mBarRect, this);
-
- LLView* lines_panel = getChildView("lines_panel");
- lines_panel->localRectToOtherView(lines_panel->getLocalRect(), &mGraphRect, this);
-
- LLView* legend_panel = getChildView("legend");
- legend_panel->localRectToOtherView(legend_panel->getLocalRect(), &mLegendRect, this);
-
- // Draw the window background
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
- gl_rect_2d(getLocalRect(), LLColor4(0.f, 0.f, 0.f, 0.25f));
-
- drawHelp(getRect().getHeight() - MARGIN);
- drawLegend();
-
- //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.getLastRecording().getSum(FTM_FRAME));
- mHoverID = NULL;
- mHoverBarIndex = -1;
-}
-
-void LLFastTimerView::onOpen(const LLSD& key)
-{
- setPauseState(false);
- mRecording.reset();
- mRecording.appendPeriodicRecording(LLTrace::get_frame_recording());
- for(std::deque<TimerBarRow>::iterator it = mTimerBarRows.begin(), end_it = mTimerBarRows.end();
- it != end_it;
- ++it)
- {
- delete []it->mBars;
- it->mBars = NULL;
- }
-}
-
-void LLFastTimerView::onClose(bool app_quitting)
-{
- setVisible(false);
-}
-
-void saveChart(const std::string& label, const char* suffix, LLImageRaw* scratch)
-{
- // disable use of glReadPixels which messes up nVidia nSight graphics debugging
- if (!LLRender::sNsightDebugSupport)
- {
- LLImageDataSharedLock lock(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);
-
-
- LLSD cur;
-
- LLSD base_data;
-
- { //read base log into memory
- S32 i = 0;
- llifstream is(base.c_str());
- while (!is.eof() && LLSDParser::PARSE_FAILURE != 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;
- llifstream is(target.c_str());
- while (!is.eof() && LLSDParser::PARSE_FAILURE != 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();
- }
-
- //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() && LLSDParser::PARSE_FAILURE != 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].getSum();
- 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].getSum();
- 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
- llifstream base_is(baseline.c_str());
- llifstream target_is(target.c_str());
- if (!base_is.is_open() || !target_is.is_open())
- {
- LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL;
- 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 comparison
- llofstream 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(BlockTimer::sLog)
- {
- doAnalysisDefault(baseline, target, output) ;
- return ;
- }
-
- if(BlockTimer::sMetricLog)
- {
- LLMetricPerformanceTesterBasic::doAnalysisMetrics(baseline, target, output) ;
- return ;
- }
-}
-
-
-void LLFastTimerView::printLineStats()
-{
- // Output stats for clicked bar to log
- if (mStatsIndex >= 0)
- {
- std::string legend_stat;
- bool first = true;
- for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME);
- it != LLTrace::end_block_timer_tree_df();
- ++it)
- {
- BlockTimerStatHandle* idp = (*it);
-
- if (!first)
- {
- legend_stat += ", ";
- }
- first = false;
- legend_stat += idp->getName();
-
- //if (idp->getTreeNode().mCollapsed)
- //{
- // it.skipDescendants();
- //}
- }
- LL_INFOS() << legend_stat << LL_ENDL;
-
- std::string timer_stat;
- first = true;
- for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME);
- it != LLTrace::end_block_timer_tree_df();
- ++it)
- {
- BlockTimerStatHandle* idp = (*it);
-
- if (!first)
- {
- timer_stat += ", ";
- }
- first = false;
-
- F32Seconds ticks;
- if (mStatsIndex == 0)
- {
- ticks = mRecording.getPeriodMean(*idp, RUNNING_AVERAGE_WIDTH);
- }
- else
- {
- ticks = mRecording.getPrevRecording(mStatsIndex).getSum(*idp);
- }
- F32Milliseconds ms = ticks;
-
- timer_stat += llformat("%.1f",ms.value());
-
- //if (idp->getTreeNode().mCollapsed)
- //{
- // it.skipDescendants();
- //}
- }
- LL_INFOS() << timer_stat << LL_ENDL;
- mStatsIndex = -1;
- }
-}
-
-void LLFastTimerView::drawLineGraph()
-{
- LL_PROFILE_ZONE_SCOPED;
- //draw line graph history
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
- LLLocalClipRect clip(mGraphRect);
-
- //normalize based on last frame's maximum
- static F32Seconds max_time(0.000001);
- static U32 max_calls = 0;
- static F32 alpha_interp = 0.f;
-
- //highlight visible range
- {
- S32 first_frame = mRecording.getNumRecordedPeriods() - mScrollIndex;
- S32 last_frame = first_frame - MAX_VISIBLE_HISTORY;
-
- F32 frame_delta = ((F32) (mGraphRect.getWidth()))/(mRecording.getNumRecordedPeriods()-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 - (mScrollIndex + 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();
- }
- }
-
- F32Seconds cur_max(0);
- U32 cur_max_calls = 0;
-
- for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME);
- it != LLTrace::end_block_timer_tree_df();
- ++it)
- {
- BlockTimerStatHandle* idp = (*it);
-
- //fatten highlighted timer
- if (mHoverID == idp)
- {
- gGL.flush();
- glLineWidth(3);
- }
-
- llassert(idp->getIndex() < sTimerColors.size());
- const F32 * col = sTimerColors[idp->getIndex()].mV;// ft_display_table[idx].color->mV;
-
- F32 alpha = 1.f;
- bool is_hover_timer = true;
-
- if (mHoverID != NULL &&
- mHoverID != idp)
- { //fade out non-highlighted timers
- if (idp->getParent() != mHoverID)
- {
- alpha = alpha_interp;
- is_hover_timer = false;
- }
- }
-
- gGL.color4f(col[0], col[1], col[2], alpha);
- gGL.begin(LLRender::TRIANGLE_STRIP);
- F32 call_scale_factor = (F32)mGraphRect.getHeight() / (F32)max_calls;
- F32 time_scale_factor = (F32)mGraphRect.getHeight() / max_time.value();
- F32 hz_scale_factor = (F32) mGraphRect.getHeight() / (1.f / max_time.value());
-
- for (U32 j = mRecording.getNumRecordedPeriods();
- j > 0;
- j--)
- {
- LLTrace::Recording& recording = mRecording.getPrevRecording(j);
- F32Seconds time = llmax(recording.getSum(*idp), F64Seconds(0.000001));
- U32 calls = recording.getSum(idp->callCount());
-
- if (is_hover_timer)
- {
- //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.getNumRecordedPeriods()-1);
- F32 y;
- switch(mDisplayType)
-{
- case DISPLAY_TIME:
- y = mGraphRect.mBottom + time.value() * time_scale_factor;
- break;
- case DISPLAY_CALLS:
- y = mGraphRect.mBottom + (F32)calls * call_scale_factor;
- break;
- case DISPLAY_HZ:
- y = mGraphRect.mBottom + (1.f / time.value()) * hz_scale_factor;
- break;
- }
- gGL.vertex2f(x,y);
- gGL.vertex2f(x,mGraphRect.mBottom);
- }
- gGL.end();
-
- if (mHoverID == idp)
- {
- gGL.flush();
- glLineWidth(1);
- }
-
- if (idp->getTreeNode().mCollapsed)
- {
- //skip hidden timers
- it.skipDescendants();
- }
- }
-
- //interpolate towards new maximum
- max_time = (F32Seconds)lerp(max_time.value(), cur_max.value(), LLSmoothInterpolation::getInterpolant(0.1f));
- if (llabs((max_time - cur_max).value()) <= 1)
- {
- max_time = llmax(F32Microseconds(1.f), F32Microseconds(cur_max));
- }
-
- max_calls = ll_round(lerp((F32)max_calls, (F32) cur_max_calls, LLSmoothInterpolation::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, LLSmoothInterpolation::getInterpolant(0.1f));
-
- if (mHoverID != NULL)
- {
- S32 x = (mGraphRect.mRight + mGraphRect.mLeft)/2;
- S32 y = mGraphRect.mBottom + 8;
-
- LLFontGL::getFontMonospace()->renderUTF8(
- mHoverID->getName(),
- 0,
- x, y,
- LLColor4::white,
- LLFontGL::LEFT, LLFontGL::BOTTOM);
- }
-
- //display y-axis range
- std::string axis_label;
- switch(mDisplayType)
- {
- case DISPLAY_TIME:
- axis_label = llformat("%4.2f ms", F32Milliseconds(max_time).value());
- break;
- case DISPLAY_CALLS:
- axis_label = llformat("%d calls", (int)max_calls);
- break;
- case DISPLAY_HZ:
- axis_label = llformat("%4.2f Hz", max_time.value() ? 1.f / max_time.value() : 0.f);
- break;
- }
-
- LLFontGL* font = LLFontGL::getFontMonospace();
- S32 x = mGraphRect.mRight - font->getWidth(axis_label)-5;
- S32 y = mGraphRect.mTop - font->getLineHeight();;
-
- font->renderUTF8(axis_label, 0, x, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
-}
-
-void LLFastTimerView::drawLegend()
-{
- // draw legend
- S32 dx;
- S32 x = mLegendRect.mLeft;
- S32 y = mLegendRect.mTop;
- const S32 TEXT_HEIGHT = (S32)LLFontGL::getFontMonospace()->getLineHeight();
-
- {
- LLLocalClipRect clip(mLegendRect);
- S32 cur_line = 0;
- S32 scroll_offset = 0; // element's y offset from top of the inner scroll's rect
- ft_display_idx.clear();
- std::map<BlockTimerStatHandle*, S32> display_line;
- for (LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME);
- it != LLTrace::end_block_timer_tree_df();
- ++it)
- {
- BlockTimerStatHandle* idp = (*it);
- // Needed to figure out offsets and parenting
- display_line[idp] = cur_line;
- cur_line++;
- if (scroll_offset < mScrollBar->getDocPos())
- {
- // only offset for visible items
- scroll_offset += TEXT_HEIGHT + 2;
- if (idp->getTreeNode().mCollapsed)
- {
- it.skipDescendants();
- }
- continue;
- }
-
- // used for mouse clicks
- ft_display_idx.push_back(idp);
-
- // Actual draw, first bar (square), then text
- 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);
- llassert(idp->getIndex() < sTimerColors.size());
- gl_rect_2d(bar_rect, sTimerColors[idp->getIndex()]);
-
- F32Milliseconds ms(0);
- S32 calls = 0;
- if (mHoverBarIndex > 0 && mHoverID)
- {
- S32 hidx = mScrollIndex + mHoverBarIndex;
- ms = mRecording.getPrevRecording(hidx).getSum(*idp);
- calls = mRecording.getPrevRecording(hidx).getSum(idp->callCount());
- }
- else
- {
- ms = F64Seconds(mRecording.getPeriodMean(*idp, RUNNING_AVERAGE_WIDTH));
- calls = (S32)mRecording.getPeriodMean(idp->callCount(), RUNNING_AVERAGE_WIDTH);
- }
-
- std::string timer_label;
- switch(mDisplayType)
- {
- case DISPLAY_TIME:
- timer_label = llformat("%s [%.1f]",idp->getName().c_str(),ms.value());
- break;
- case DISPLAY_CALLS:
- timer_label = llformat("%s (%d)",idp->getName().c_str(),calls);
- break;
- case DISPLAY_HZ:
- timer_label = llformat("%.1f", ms.value() ? (1.f / ms.value()) : 0.f);
- break;
- }
- 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->getTreeNode().mCollapsed && !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);
- BlockTimerStatHandle* 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);
-
- scroll_offset += TEXT_HEIGHT + 2;
- if (idp->getTreeNode().mCollapsed)
- {
- it.skipDescendants();
- }
- }
- // Recalculate scroll size
- mScrollBar->setDocSize(scroll_offset - mLegendRect.getHeight());
- }
-}
-
-void LLFastTimerView::generateUniqueColors()
-{
- // generate unique colors
- {
- sTimerColors.resize(LLTrace::BlockTimerStatHandle::getNumIndices());
- sTimerColors[FTM_FRAME.getIndex()] = LLColor4::grey;
-
- F32 hue = 0.f;
-
- for (LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME);
- it != LLTrace::end_block_timer_tree_df();
- ++it)
- {
- BlockTimerStatHandle* 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);
-
- llassert(idp->getIndex() < sTimerColors.size());
- sTimerColors[idp->getIndex()] = child_color;
- }
- }
-}
-
-void LLFastTimerView::drawHelp( S32 y )
-{
- // Draw some help
- const S32 texth = (S32)LLFontGL::getFontMonospace()->getLineHeight();
-
- y -= (texth + 2);
- y -= (texth + 2);
-
- LLFontGL::getFontMonospace()->renderUTF8(std::string("[Right-Click log selected]"),
- 0, MARGIN, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
-}
-
-void LLFastTimerView::drawTicks()
-{
- // Draw MS ticks
- {
- U32Milliseconds 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-dy;
-
- //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, RUNNING_AVERAGE_WIDTH)*2;
- break;
- case 1:
- mTotalTimeDisplay = mRecording.getPeriodMax(FTM_FRAME);
- break;
- case 2:
- // Calculate the max total ticks for the current history
- mTotalTimeDisplay = mRecording.getPeriodMax(FTM_FRAME, 20);
- break;
- default:
- mTotalTimeDisplay = F64Milliseconds(100);
- break;
- }
-
- mTotalTimeDisplay = LLUnits::Milliseconds::fromValue(llceil(mTotalTimeDisplay.valueInUnits<LLUnits::Milliseconds>() / 20.f) * 20.f);
-}
-
-void LLFastTimerView::drawBars()
-{
- LL_PROFILE_ZONE_SCOPED;
- LLLocalClipRect clip(mBarRect);
-
- S32 bar_height = mBarRect.getHeight() / (MAX_VISIBLE_HISTORY + 2);
- const S32 vpad = llmax(1, bar_height / 4); // spacing between bars
- bar_height -= vpad;
-
- updateTotalTime();
- if (mTotalTimeDisplay <= (F32Seconds)0.0) return;
-
- drawTicks();
- const S32 bars_top = mBarRect.mTop - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4);
- drawBorders(bars_top, mBarRect.mLeft, bar_height, vpad);
-
- // Draw bars for each history entry
- // Special: 0 = show running average
- LLPointer<LLUIImage> bar_image = LLUI::getUIImage("Rounded_Square");
-
- const S32 image_width = bar_image->getTextureWidth();
- const S32 image_height = bar_image->getTextureHeight();
-
- gGL.getTexUnit(0)->bind(bar_image->getImage());
- {
- const S32 histmax = (S32)mRecording.getNumRecordedPeriods();
-
- // update widths
- if (!mPauseHistory)
- {
- U32 bar_index = 0;
- if (!mAverageTimerRow.mBars)
- {
- mAverageTimerRow.mBars = new TimerBar[LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount()];
- }
- updateTimerBarWidths(&FTM_FRAME, mAverageTimerRow, -1, bar_index);
- updateTimerBarOffsets(&FTM_FRAME, mAverageTimerRow);
-
- for (S32 history_index = 1; history_index <= histmax; history_index++)
- {
- llassert(history_index <= mTimerBarRows.size());
- TimerBarRow& row = mTimerBarRows[history_index - 1];
- bar_index = 0;
- if (!row.mBars)
- {
- row.mBars = new TimerBar[LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount()];
- updateTimerBarWidths(&FTM_FRAME, row, history_index, bar_index);
- updateTimerBarOffsets(&FTM_FRAME, row);
- }
- }
- }
-
- // draw bars
- LLRect frame_bar_rect;
- frame_bar_rect.setLeftTopAndSize(mBarRect.mLeft,
- bars_top,
- ll_round((mAverageTimerRow.mBars[0].mTotalTime / mTotalTimeDisplay) * mBarRect.getWidth()),
- bar_height);
- mAverageTimerRow.mTop = frame_bar_rect.mTop;
- mAverageTimerRow.mBottom = frame_bar_rect.mBottom;
- drawBar(frame_bar_rect, mAverageTimerRow, image_width, image_height);
- frame_bar_rect.translate(0, -(bar_height + vpad + bar_height));
-
- for(S32 bar_index = mScrollIndex; bar_index < llmin(histmax, mScrollIndex + MAX_VISIBLE_HISTORY); ++bar_index)
- {
- llassert(bar_index < mTimerBarRows.size());
- TimerBarRow& row = mTimerBarRows[bar_index];
- row.mTop = frame_bar_rect.mTop;
- row.mBottom = frame_bar_rect.mBottom;
- frame_bar_rect.mRight = frame_bar_rect.mLeft
- + ll_round((row.mBars[0].mTotalTime / mTotalTimeDisplay) * mBarRect.getWidth());
- drawBar(frame_bar_rect, row, image_width, image_height);
-
- frame_bar_rect.translate(0, -(bar_height + vpad));
- }
-
- }
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
-}
-
-F32Seconds LLFastTimerView::updateTimerBarWidths(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 history_index, U32& bar_index)
-{
- LL_PROFILE_ZONE_SCOPED;
- const F32Seconds self_time = history_index == -1
- ? mRecording.getPeriodMean(time_block->selfTime(), RUNNING_AVERAGE_WIDTH)
- : mRecording.getPrevRecording(history_index).getSum(time_block->selfTime());
-
- F32Seconds full_time = self_time;
-
- // reserve a spot for this bar to be rendered before its children
- // even though we don't know its size yet
- TimerBar& timer_bar = row.mBars[bar_index];
- bar_index++;
-
- for (BlockTimerStatHandle::child_iter it = time_block->beginChildren(), end_it = time_block->endChildren(); it != end_it; ++it)
- {
- full_time += updateTimerBarWidths(*it, row, history_index, bar_index);
- }
-
- timer_bar.mTotalTime = full_time;
- timer_bar.mSelfTime = self_time;
- timer_bar.mTimeBlock = time_block;
-
- return full_time;
-}
-
-S32 LLFastTimerView::updateTimerBarOffsets(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 timer_bar_index)
-{
- LL_PROFILE_ZONE_SCOPED;
-
- TimerBar& timer_bar = row.mBars[timer_bar_index];
- const F32Seconds bar_time = timer_bar.mTotalTime - timer_bar.mSelfTime;
- timer_bar.mChildrenStart = timer_bar.mSelfStart + timer_bar.mSelfTime / 2;
- timer_bar.mChildrenEnd = timer_bar.mChildrenStart + timer_bar.mTotalTime - timer_bar.mSelfTime;
-
- if (timer_bar_index == 0)
- {
- timer_bar.mSelfStart = F32Seconds(0.f);
- timer_bar.mSelfEnd = bar_time;
- }
-
- //now loop through children and figure out portion of bar image covered by each bar, now that we know the
- //sum of all children
- F32 bar_fraction_start = 0.f;
- TimerBar* last_child_timer_bar = NULL;
-
- bool first_child = true;
- for (BlockTimerStatHandle::child_iter it = time_block->beginChildren(), end_it = time_block->endChildren();
- it != end_it;
- ++it)
- {
- timer_bar_index++;
-
- TimerBar& child_timer_bar = row.mBars[timer_bar_index];
- BlockTimerStatHandle* child_time_block = *it;
-
- if (last_child_timer_bar)
- {
- last_child_timer_bar->mLastChild = false;
- }
- child_timer_bar.mLastChild = true;
- last_child_timer_bar = &child_timer_bar;
-
- child_timer_bar.mFirstChild = first_child;
- if (first_child)
- {
- first_child = false;
- }
-
- child_timer_bar.mStartFraction = bar_fraction_start;
- child_timer_bar.mEndFraction = bar_time > (S32Seconds)0
- ? bar_fraction_start + child_timer_bar.mTotalTime / bar_time
- : 1.f;
- child_timer_bar.mSelfStart = timer_bar.mChildrenStart
- + child_timer_bar.mStartFraction
- * (timer_bar.mChildrenEnd - timer_bar.mChildrenStart);
- child_timer_bar.mSelfEnd = timer_bar.mChildrenStart
- + child_timer_bar.mEndFraction
- * (timer_bar.mChildrenEnd - timer_bar.mChildrenStart);
-
- timer_bar_index = updateTimerBarOffsets(child_time_block, row, timer_bar_index);
-
- bar_fraction_start = child_timer_bar.mEndFraction;
- }
- return timer_bar_index;
-}
-
-S32 LLFastTimerView::drawBar(LLRect bar_rect, TimerBarRow& row, S32 image_width, S32 image_height, bool hovered, bool visible, S32 bar_index)
-{
- LL_PROFILE_ZONE_SCOPED;
- TimerBar& timer_bar = row.mBars[bar_index];
- LLTrace::BlockTimerStatHandle* time_block = timer_bar.mTimeBlock;
-
- hovered |= mHoverID == time_block;
-
- // animate scale of bar when hovering over that particular timer
- if (visible && (F32)bar_rect.getWidth() * (timer_bar.mEndFraction - timer_bar.mStartFraction) > 2.f)
- {
- LLRect render_rect(bar_rect);
- S32 scale_offset = 0;
- if (mHoverID == time_block)
- {
- scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 3.f);
- render_rect.mTop += scale_offset;
- render_rect.mBottom -= scale_offset;
- }
-
- llassert(time_block->getIndex() < sTimerColors.size());
- LLColor4 color = sTimerColors[time_block->getIndex()];
- if (!hovered) color = lerp(color, LLColor4::grey, 0.2f);
- gGL.color4fv(color.mV);
- gl_segmented_rect_2d_fragment_tex(render_rect,
- image_width, image_height,
- 16,
- timer_bar.mStartFraction, timer_bar.mEndFraction);
- }
-
- LLRect children_rect;
- children_rect.mLeft = ll_round(timer_bar.mChildrenStart / mTotalTimeDisplay * (F32)mBarRect.getWidth()) + mBarRect.mLeft;
- children_rect.mRight = ll_round(timer_bar.mChildrenEnd / mTotalTimeDisplay * (F32)mBarRect.getWidth()) + mBarRect.mLeft;
-
- if (bar_rect.getHeight() > MIN_BAR_HEIGHT)
- {
- // shrink as we go down a level
- children_rect.mTop = bar_rect.mTop - 1;
- children_rect.mBottom = bar_rect.mBottom + 1;
- }
- else
- {
- children_rect.mTop = bar_rect.mTop;
- children_rect.mBottom = bar_rect.mBottom;
- }
-
- bool children_visible = visible && !time_block->getTreeNode().mCollapsed;
-
- bar_index++;
- const U32 num_bars = LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount();
- if (bar_index < num_bars && row.mBars[bar_index].mFirstChild)
- {
- bool is_last = false;
- do
- {
- is_last = row.mBars[bar_index].mLastChild;
- bar_index = drawBar(children_rect, row, image_width, image_height, hovered, children_visible, bar_index);
- }
- while(!is_last && bar_index < num_bars);
- }
-
- return bar_index;
-}
+/** + * @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 "llcombobox.h" +#include "llerror.h" +#include "llgl.h" +#include "llimagepng.h" +#include "llrender.h" +#include "llrendertarget.h" +#include "lllocalcliprect.h" +#include "lllayoutstack.h" +#include "llmath.h" +#include "llfontgl.h" +#include "llsdserialize.h" +#include "lltooltip.h" +#include "llbutton.h" +#include "llscrollbar.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 constexpr S32 MAX_VISIBLE_HISTORY = 12; +static constexpr S32 LINE_GRAPH_HEIGHT = 240; +static constexpr S32 MIN_BAR_HEIGHT = 3; +static constexpr S32 RUNNING_AVERAGE_WIDTH = 100; +static constexpr S32 NUM_FRAMES_HISTORY = 200; + +std::vector<BlockTimerStatHandle*> ft_display_idx; // line of table entry for display purposes (for collapse) + +bool LLFastTimerView::sAnalyzePerformance = false; + +S32 get_depth(const BlockTimerStatHandle* blockp) +{ + S32 depth = 0; + BlockTimerStatHandle* 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), + mDisplayType(DISPLAY_TIME), + mScrollIndex(0), + mHoverID(NULL), + mHoverBarIndex(-1), + mStatsIndex(-1), + mPauseHistory(false), + mRecording(NUM_FRAMES_HISTORY) +{ + mTimerBarRows.resize(NUM_FRAMES_HISTORY); +} + +LLFastTimerView::~LLFastTimerView() +{ +} + +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) + { + + getChild<LLButton>("pause_btn")->setLabel(getString("pause")); + } + else + { + mScrollIndex = 0; + + getChild<LLButton>("pause_btn")->setLabel(getString("run")); + } + + mPauseHistory = pause_state; +} + +bool LLFastTimerView::postBuild() +{ + LLButton& pause_btn = getChildRef<LLButton>("pause_btn"); + mScrollBar = getChild<LLScrollbar>("scroll_vert"); + + 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->getTreeNode().mCollapsed) + { + mHoverTimer->getTreeNode().mCollapsed = true; + } + else if (mHoverTimer->getParent()) + { + mHoverTimer->getParent()->getTreeNode().mCollapsed = 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); + mStatsIndex = mScrollIndex + bar_idx; + return true; + } + return LLFloater::handleRightMouseDown(x, y, mask); +} + +BlockTimerStatHandle* LLFastTimerView::getLegendID(S32 y) +{ + S32 idx = (mLegendRect.mTop - y) / (LLFontGL::getFontMonospace()->getLineHeight() + 2); + + 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(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + (*it)->getTreeNode().mCollapsed = false; + } + return true; +} + +bool LLFastTimerView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (x < mScrollBar->getRect().mLeft) + { + BlockTimerStatHandle* idp = getLegendID(y); + if (idp) + { + idp->getTreeNode().mCollapsed = !idp->getTreeNode().mCollapsed; + } + } + else if (mHoverTimer) + { + //left click drills down by expanding timers + mHoverTimer->getTreeNode().mCollapsed = false; + } + 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 = ll_round( lerp * (F32)(mRecording.getNumRecordedPeriods() - MAX_VISIBLE_HISTORY)); + mScrollIndex = llclamp( mScrollIndex, 0, (S32)mRecording.getNumRecordedPeriods()); + return true; + } + mHoverTimer = NULL; + mHoverID = NULL; + + if(mPauseHistory && mBarRect.pointInRect(x, y)) + { + //const S32 bars_top = mBarRect.mTop; + const S32 bars_top = mBarRect.mTop - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4); + + mHoverBarIndex = llmin((bars_top - y) / (mBarRect.getHeight() / (MAX_VISIBLE_HISTORY + 2)) - 1, + (S32)mRecording.getNumRecordedPeriods() - 1, + MAX_VISIBLE_HISTORY); + if (mHoverBarIndex == 0) + { + return true; + } + else if (mHoverBarIndex < 0) + { + mHoverBarIndex = 0; + } + + TimerBarRow& row = mHoverBarIndex == 0 ? mAverageTimerRow : mTimerBarRows[mScrollIndex + mHoverBarIndex - 1]; + + TimerBar* hover_bar = NULL; + F32Seconds mouse_time_offset = ((F32)(x - mBarRect.mLeft) / (F32)mBarRect.getWidth()) * mTotalTimeDisplay; + for (int bar_index = 0, end_index = LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount(); + bar_index < end_index; + ++bar_index) + { + TimerBar& bar = row.mBars[bar_index]; + if (bar.mSelfStart > mouse_time_offset) + { + break; + } + if (bar.mSelfEnd > mouse_time_offset) + { + hover_bar = &bar; + if (bar.mTimeBlock->getTreeNode().mCollapsed) + { + // stop on first collapsed BlockTimerStatHandle, since we can't select any children + break; + } + } + } + + if (hover_bar) + { + mHoverID = hover_bar->mTimeBlock; + if (mHoverTimer != mHoverID) + { + // 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 = mHoverID; + mToolTipRect.set(mBarRect.mLeft + (hover_bar->mSelfStart / mTotalTimeDisplay) * mBarRect.getWidth(), + row.mTop, + mBarRect.mLeft + (hover_bar->mSelfEnd / mTotalTimeDisplay) * mBarRect.getWidth(), + row.mBottom); + } + } + } + else if (x < mScrollBar->getRect().mLeft) + { + BlockTimerStatHandle* timer_id = getLegendID(y); + if (timer_id) + { + mHoverID = timer_id; + } + } + + return LLFloater::handleHover(x, y, mask); +} + + +static std::string get_tooltip(BlockTimerStatHandle& 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)F64Milliseconds(frame_recording.getPeriodMean (timer, RUNNING_AVERAGE_WIDTH)).value(), (S32)frame_recording.getPeriodMean(timer.callCount(), RUNNING_AVERAGE_WIDTH)); + } + else + { + tooltip = llformat("%s (%d ms, %d calls)", timer.getName().c_str(), (S32)F64Milliseconds(frame_recording.getPrevRecording(history_index).getSum(timer)).value(), (S32)frame_recording.getPrevRecording(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 < mScrollBar->getRect().mLeft) + { + BlockTimerStatHandle* 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) +{ + if (x < mBarRect.mLeft) + { + // Inside mScrollBar and list of timers + mScrollBar->handleScrollWheel(x,y,clicks); + } + else + { + setPauseState(true); + mScrollIndex = llclamp( mScrollIndex + clicks, + 0, + llmin((S32)mRecording.getNumRecordedPeriods(), (S32)mRecording.getNumRecordedPeriods() - MAX_VISIBLE_HISTORY)); + } + return true; +} + +static BlockTimerStatHandle FTM_RENDER_TIMER("Timers"); +static const S32 MARGIN = 10; + +static std::vector<LLColor4> sTimerColors; + +void LLFastTimerView::draw() +{ + LL_RECORD_BLOCK_TIME(FTM_RENDER_TIMER); + + if (!mPauseHistory) + { + mRecording.appendRecording(LLTrace::get_frame_recording().getLastRecording()); + mTimerBarRows.pop_back(); + mTimerBarRows.push_front(TimerBarRow()); + } + + mDisplayMode = llclamp(getChild<LLComboBox>("time_scale_combo")->getCurrentIndex(), 0, 3); + mDisplayType = (EDisplayType)llclamp(getChild<LLComboBox>("metric_combo")->getCurrentIndex(), 0, 2); + + generateUniqueColors(); + + LLView::drawChildren(); + //getChild<LLLayoutStack>("timer_bars_stack")->updateLayout(); + //getChild<LLLayoutStack>("legend_stack")->updateLayout(); + LLView* bars_panel = getChildView("bars_panel"); + bars_panel->localRectToOtherView(bars_panel->getLocalRect(), &mBarRect, this); + + LLView* lines_panel = getChildView("lines_panel"); + lines_panel->localRectToOtherView(lines_panel->getLocalRect(), &mGraphRect, this); + + LLView* legend_panel = getChildView("legend"); + legend_panel->localRectToOtherView(legend_panel->getLocalRect(), &mLegendRect, this); + + // Draw the window background + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gl_rect_2d(getLocalRect(), LLColor4(0.f, 0.f, 0.f, 0.25f)); + + drawHelp(getRect().getHeight() - MARGIN); + drawLegend(); + + //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.getLastRecording().getSum(FTM_FRAME)); + mHoverID = NULL; + mHoverBarIndex = -1; +} + +void LLFastTimerView::onOpen(const LLSD& key) +{ + setPauseState(false); + mRecording.reset(); + mRecording.appendPeriodicRecording(LLTrace::get_frame_recording()); + for(std::deque<TimerBarRow>::iterator it = mTimerBarRows.begin(), end_it = mTimerBarRows.end(); + it != end_it; + ++it) + { + delete []it->mBars; + it->mBars = NULL; + } +} + +void LLFastTimerView::onClose(bool app_quitting) +{ + setVisible(false); +} + +void saveChart(const std::string& label, const char* suffix, LLImageRaw* scratch) +{ + // disable use of glReadPixels which messes up nVidia nSight graphics debugging + if (!LLRender::sNsightDebugSupport) + { + LLImageDataSharedLock lock(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); + + + LLSD cur; + + LLSD base_data; + + { //read base log into memory + S32 i = 0; + llifstream is(base.c_str()); + while (!is.eof() && LLSDParser::PARSE_FAILURE != 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; + llifstream is(target.c_str()); + while (!is.eof() && LLSDParser::PARSE_FAILURE != 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(); + } + + //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() && LLSDParser::PARSE_FAILURE != 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].getSum(); + 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].getSum(); + 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 + llifstream base_is(baseline.c_str()); + llifstream target_is(target.c_str()); + if (!base_is.is_open() || !target_is.is_open()) + { + LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL; + 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 comparison + llofstream 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(BlockTimer::sLog) + { + doAnalysisDefault(baseline, target, output) ; + return ; + } + + if(BlockTimer::sMetricLog) + { + LLMetricPerformanceTesterBasic::doAnalysisMetrics(baseline, target, output) ; + return ; + } +} + + +void LLFastTimerView::printLineStats() +{ + // Output stats for clicked bar to log + if (mStatsIndex >= 0) + { + std::string legend_stat; + bool first = true; + for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + BlockTimerStatHandle* idp = (*it); + + if (!first) + { + legend_stat += ", "; + } + first = false; + legend_stat += idp->getName(); + + //if (idp->getTreeNode().mCollapsed) + //{ + // it.skipDescendants(); + //} + } + LL_INFOS() << legend_stat << LL_ENDL; + + std::string timer_stat; + first = true; + for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + BlockTimerStatHandle* idp = (*it); + + if (!first) + { + timer_stat += ", "; + } + first = false; + + F32Seconds ticks; + if (mStatsIndex == 0) + { + ticks = mRecording.getPeriodMean(*idp, RUNNING_AVERAGE_WIDTH); + } + else + { + ticks = mRecording.getPrevRecording(mStatsIndex).getSum(*idp); + } + F32Milliseconds ms = ticks; + + timer_stat += llformat("%.1f",ms.value()); + + //if (idp->getTreeNode().mCollapsed) + //{ + // it.skipDescendants(); + //} + } + LL_INFOS() << timer_stat << LL_ENDL; + mStatsIndex = -1; + } +} + +void LLFastTimerView::drawLineGraph() +{ + LL_PROFILE_ZONE_SCOPED; + //draw line graph history + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLLocalClipRect clip(mGraphRect); + + //normalize based on last frame's maximum + static F32Seconds max_time(0.000001); + static U32 max_calls = 0; + static F32 alpha_interp = 0.f; + + //highlight visible range + { + S32 first_frame = mRecording.getNumRecordedPeriods() - mScrollIndex; + S32 last_frame = first_frame - MAX_VISIBLE_HISTORY; + + F32 frame_delta = ((F32) (mGraphRect.getWidth()))/(mRecording.getNumRecordedPeriods()-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 - (mScrollIndex + 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(); + } + } + + F32Seconds cur_max(0); + U32 cur_max_calls = 0; + + for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + BlockTimerStatHandle* idp = (*it); + + //fatten highlighted timer + if (mHoverID == idp) + { + gGL.flush(); + glLineWidth(3); + } + + llassert(idp->getIndex() < sTimerColors.size()); + const F32 * col = sTimerColors[idp->getIndex()].mV;// ft_display_table[idx].color->mV; + + F32 alpha = 1.f; + bool is_hover_timer = true; + + if (mHoverID != NULL && + mHoverID != idp) + { //fade out non-highlighted timers + if (idp->getParent() != mHoverID) + { + alpha = alpha_interp; + is_hover_timer = false; + } + } + + gGL.color4f(col[0], col[1], col[2], alpha); + gGL.begin(LLRender::TRIANGLE_STRIP); + F32 call_scale_factor = (F32)mGraphRect.getHeight() / (F32)max_calls; + F32 time_scale_factor = (F32)mGraphRect.getHeight() / max_time.value(); + F32 hz_scale_factor = (F32) mGraphRect.getHeight() / (1.f / max_time.value()); + + for (U32 j = mRecording.getNumRecordedPeriods(); + j > 0; + j--) + { + LLTrace::Recording& recording = mRecording.getPrevRecording(j); + F32Seconds time = llmax(recording.getSum(*idp), F64Seconds(0.000001)); + U32 calls = recording.getSum(idp->callCount()); + + if (is_hover_timer) + { + //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.getNumRecordedPeriods()-1); + F32 y; + switch(mDisplayType) +{ + case DISPLAY_TIME: + y = mGraphRect.mBottom + time.value() * time_scale_factor; + break; + case DISPLAY_CALLS: + y = mGraphRect.mBottom + (F32)calls * call_scale_factor; + break; + case DISPLAY_HZ: + y = mGraphRect.mBottom + (1.f / time.value()) * hz_scale_factor; + break; + } + gGL.vertex2f(x,y); + gGL.vertex2f(x,mGraphRect.mBottom); + } + gGL.end(); + + if (mHoverID == idp) + { + gGL.flush(); + glLineWidth(1); + } + + if (idp->getTreeNode().mCollapsed) + { + //skip hidden timers + it.skipDescendants(); + } + } + + //interpolate towards new maximum + max_time = (F32Seconds)lerp(max_time.value(), cur_max.value(), LLSmoothInterpolation::getInterpolant(0.1f)); + if (llabs((max_time - cur_max).value()) <= 1) + { + max_time = llmax(F32Microseconds(1.f), F32Microseconds(cur_max)); + } + + max_calls = ll_round(lerp((F32)max_calls, (F32) cur_max_calls, LLSmoothInterpolation::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, LLSmoothInterpolation::getInterpolant(0.1f)); + + if (mHoverID != NULL) + { + S32 x = (mGraphRect.mRight + mGraphRect.mLeft)/2; + S32 y = mGraphRect.mBottom + 8; + + LLFontGL::getFontMonospace()->renderUTF8( + mHoverID->getName(), + 0, + x, y, + LLColor4::white, + LLFontGL::LEFT, LLFontGL::BOTTOM); + } + + //display y-axis range + std::string axis_label; + switch(mDisplayType) + { + case DISPLAY_TIME: + axis_label = llformat("%4.2f ms", F32Milliseconds(max_time).value()); + break; + case DISPLAY_CALLS: + axis_label = llformat("%d calls", (int)max_calls); + break; + case DISPLAY_HZ: + axis_label = llformat("%4.2f Hz", max_time.value() ? 1.f / max_time.value() : 0.f); + break; + } + + LLFontGL* font = LLFontGL::getFontMonospace(); + S32 x = mGraphRect.mRight - font->getWidth(axis_label)-5; + S32 y = mGraphRect.mTop - font->getLineHeight();; + + font->renderUTF8(axis_label, 0, x, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); +} + +void LLFastTimerView::drawLegend() +{ + // draw legend + S32 dx; + S32 x = mLegendRect.mLeft; + S32 y = mLegendRect.mTop; + const S32 TEXT_HEIGHT = (S32)LLFontGL::getFontMonospace()->getLineHeight(); + + { + LLLocalClipRect clip(mLegendRect); + S32 cur_line = 0; + S32 scroll_offset = 0; // element's y offset from top of the inner scroll's rect + ft_display_idx.clear(); + std::map<BlockTimerStatHandle*, S32> display_line; + for (LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + BlockTimerStatHandle* idp = (*it); + // Needed to figure out offsets and parenting + display_line[idp] = cur_line; + cur_line++; + if (scroll_offset < mScrollBar->getDocPos()) + { + // only offset for visible items + scroll_offset += TEXT_HEIGHT + 2; + if (idp->getTreeNode().mCollapsed) + { + it.skipDescendants(); + } + continue; + } + + // used for mouse clicks + ft_display_idx.push_back(idp); + + // Actual draw, first bar (square), then text + 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); + llassert(idp->getIndex() < sTimerColors.size()); + gl_rect_2d(bar_rect, sTimerColors[idp->getIndex()]); + + F32Milliseconds ms(0); + S32 calls = 0; + if (mHoverBarIndex > 0 && mHoverID) + { + S32 hidx = mScrollIndex + mHoverBarIndex; + ms = mRecording.getPrevRecording(hidx).getSum(*idp); + calls = mRecording.getPrevRecording(hidx).getSum(idp->callCount()); + } + else + { + ms = F64Seconds(mRecording.getPeriodMean(*idp, RUNNING_AVERAGE_WIDTH)); + calls = (S32)mRecording.getPeriodMean(idp->callCount(), RUNNING_AVERAGE_WIDTH); + } + + std::string timer_label; + switch(mDisplayType) + { + case DISPLAY_TIME: + timer_label = llformat("%s [%.1f]",idp->getName().c_str(),ms.value()); + break; + case DISPLAY_CALLS: + timer_label = llformat("%s (%d)",idp->getName().c_str(),calls); + break; + case DISPLAY_HZ: + timer_label = llformat("%.1f", ms.value() ? (1.f / ms.value()) : 0.f); + break; + } + 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->getTreeNode().mCollapsed && !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); + BlockTimerStatHandle* 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); + + scroll_offset += TEXT_HEIGHT + 2; + if (idp->getTreeNode().mCollapsed) + { + it.skipDescendants(); + } + } + // Recalculate scroll size + mScrollBar->setDocSize(scroll_offset - mLegendRect.getHeight()); + } +} + +void LLFastTimerView::generateUniqueColors() +{ + // generate unique colors + { + sTimerColors.resize(LLTrace::BlockTimerStatHandle::getNumIndices()); + sTimerColors[FTM_FRAME.getIndex()] = LLColor4::grey; + + F32 hue = 0.f; + + for (LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + BlockTimerStatHandle* 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); + + llassert(idp->getIndex() < sTimerColors.size()); + sTimerColors[idp->getIndex()] = child_color; + } + } +} + +void LLFastTimerView::drawHelp( S32 y ) +{ + // Draw some help + const S32 texth = (S32)LLFontGL::getFontMonospace()->getLineHeight(); + + y -= (texth + 2); + y -= (texth + 2); + + LLFontGL::getFontMonospace()->renderUTF8(std::string("[Right-Click log selected]"), + 0, MARGIN, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); +} + +void LLFastTimerView::drawTicks() +{ + // Draw MS ticks + { + U32Milliseconds 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-dy; + + //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, RUNNING_AVERAGE_WIDTH)*2; + break; + case 1: + mTotalTimeDisplay = mRecording.getPeriodMax(FTM_FRAME); + break; + case 2: + // Calculate the max total ticks for the current history + mTotalTimeDisplay = mRecording.getPeriodMax(FTM_FRAME, 20); + break; + default: + mTotalTimeDisplay = F64Milliseconds(100); + break; + } + + mTotalTimeDisplay = LLUnits::Milliseconds::fromValue(llceil(mTotalTimeDisplay.valueInUnits<LLUnits::Milliseconds>() / 20.f) * 20.f); +} + +void LLFastTimerView::drawBars() +{ + LL_PROFILE_ZONE_SCOPED; + LLLocalClipRect clip(mBarRect); + + S32 bar_height = mBarRect.getHeight() / (MAX_VISIBLE_HISTORY + 2); + const S32 vpad = llmax(1, bar_height / 4); // spacing between bars + bar_height -= vpad; + + updateTotalTime(); + if (mTotalTimeDisplay <= (F32Seconds)0.0) return; + + drawTicks(); + const S32 bars_top = mBarRect.mTop - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4); + drawBorders(bars_top, mBarRect.mLeft, bar_height, vpad); + + // Draw bars for each history entry + // Special: 0 = show running average + LLPointer<LLUIImage> bar_image = LLUI::getUIImage("Rounded_Square"); + + const S32 image_width = bar_image->getTextureWidth(); + const S32 image_height = bar_image->getTextureHeight(); + + gGL.getTexUnit(0)->bind(bar_image->getImage()); + { + const S32 histmax = (S32)mRecording.getNumRecordedPeriods(); + + // update widths + if (!mPauseHistory) + { + U32 bar_index = 0; + if (!mAverageTimerRow.mBars) + { + mAverageTimerRow.mBars = new TimerBar[LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount()]; + } + updateTimerBarWidths(&FTM_FRAME, mAverageTimerRow, -1, bar_index); + updateTimerBarOffsets(&FTM_FRAME, mAverageTimerRow); + + for (S32 history_index = 1; history_index <= histmax; history_index++) + { + llassert(history_index <= mTimerBarRows.size()); + TimerBarRow& row = mTimerBarRows[history_index - 1]; + bar_index = 0; + if (!row.mBars) + { + row.mBars = new TimerBar[LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount()]; + updateTimerBarWidths(&FTM_FRAME, row, history_index, bar_index); + updateTimerBarOffsets(&FTM_FRAME, row); + } + } + } + + // draw bars + LLRect frame_bar_rect; + frame_bar_rect.setLeftTopAndSize(mBarRect.mLeft, + bars_top, + ll_round((mAverageTimerRow.mBars[0].mTotalTime / mTotalTimeDisplay) * mBarRect.getWidth()), + bar_height); + mAverageTimerRow.mTop = frame_bar_rect.mTop; + mAverageTimerRow.mBottom = frame_bar_rect.mBottom; + drawBar(frame_bar_rect, mAverageTimerRow, image_width, image_height); + frame_bar_rect.translate(0, -(bar_height + vpad + bar_height)); + + for(S32 bar_index = mScrollIndex; bar_index < llmin(histmax, mScrollIndex + MAX_VISIBLE_HISTORY); ++bar_index) + { + llassert(bar_index < mTimerBarRows.size()); + TimerBarRow& row = mTimerBarRows[bar_index]; + row.mTop = frame_bar_rect.mTop; + row.mBottom = frame_bar_rect.mBottom; + frame_bar_rect.mRight = frame_bar_rect.mLeft + + ll_round((row.mBars[0].mTotalTime / mTotalTimeDisplay) * mBarRect.getWidth()); + drawBar(frame_bar_rect, row, image_width, image_height); + + frame_bar_rect.translate(0, -(bar_height + vpad)); + } + + } + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); +} + +F32Seconds LLFastTimerView::updateTimerBarWidths(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 history_index, U32& bar_index) +{ + LL_PROFILE_ZONE_SCOPED; + const F32Seconds self_time = history_index == -1 + ? mRecording.getPeriodMean(time_block->selfTime(), RUNNING_AVERAGE_WIDTH) + : mRecording.getPrevRecording(history_index).getSum(time_block->selfTime()); + + F32Seconds full_time = self_time; + + // reserve a spot for this bar to be rendered before its children + // even though we don't know its size yet + TimerBar& timer_bar = row.mBars[bar_index]; + bar_index++; + + for (BlockTimerStatHandle::child_iter it = time_block->beginChildren(), end_it = time_block->endChildren(); it != end_it; ++it) + { + full_time += updateTimerBarWidths(*it, row, history_index, bar_index); + } + + timer_bar.mTotalTime = full_time; + timer_bar.mSelfTime = self_time; + timer_bar.mTimeBlock = time_block; + + return full_time; +} + +S32 LLFastTimerView::updateTimerBarOffsets(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 timer_bar_index) +{ + LL_PROFILE_ZONE_SCOPED; + + TimerBar& timer_bar = row.mBars[timer_bar_index]; + const F32Seconds bar_time = timer_bar.mTotalTime - timer_bar.mSelfTime; + timer_bar.mChildrenStart = timer_bar.mSelfStart + timer_bar.mSelfTime / 2; + timer_bar.mChildrenEnd = timer_bar.mChildrenStart + timer_bar.mTotalTime - timer_bar.mSelfTime; + + if (timer_bar_index == 0) + { + timer_bar.mSelfStart = F32Seconds(0.f); + timer_bar.mSelfEnd = bar_time; + } + + //now loop through children and figure out portion of bar image covered by each bar, now that we know the + //sum of all children + F32 bar_fraction_start = 0.f; + TimerBar* last_child_timer_bar = NULL; + + bool first_child = true; + for (BlockTimerStatHandle::child_iter it = time_block->beginChildren(), end_it = time_block->endChildren(); + it != end_it; + ++it) + { + timer_bar_index++; + + TimerBar& child_timer_bar = row.mBars[timer_bar_index]; + BlockTimerStatHandle* child_time_block = *it; + + if (last_child_timer_bar) + { + last_child_timer_bar->mLastChild = false; + } + child_timer_bar.mLastChild = true; + last_child_timer_bar = &child_timer_bar; + + child_timer_bar.mFirstChild = first_child; + if (first_child) + { + first_child = false; + } + + child_timer_bar.mStartFraction = bar_fraction_start; + child_timer_bar.mEndFraction = bar_time > (S32Seconds)0 + ? bar_fraction_start + child_timer_bar.mTotalTime / bar_time + : 1.f; + child_timer_bar.mSelfStart = timer_bar.mChildrenStart + + child_timer_bar.mStartFraction + * (timer_bar.mChildrenEnd - timer_bar.mChildrenStart); + child_timer_bar.mSelfEnd = timer_bar.mChildrenStart + + child_timer_bar.mEndFraction + * (timer_bar.mChildrenEnd - timer_bar.mChildrenStart); + + timer_bar_index = updateTimerBarOffsets(child_time_block, row, timer_bar_index); + + bar_fraction_start = child_timer_bar.mEndFraction; + } + return timer_bar_index; +} + +S32 LLFastTimerView::drawBar(LLRect bar_rect, TimerBarRow& row, S32 image_width, S32 image_height, bool hovered, bool visible, S32 bar_index) +{ + LL_PROFILE_ZONE_SCOPED; + TimerBar& timer_bar = row.mBars[bar_index]; + LLTrace::BlockTimerStatHandle* time_block = timer_bar.mTimeBlock; + + hovered |= mHoverID == time_block; + + // animate scale of bar when hovering over that particular timer + if (visible && (F32)bar_rect.getWidth() * (timer_bar.mEndFraction - timer_bar.mStartFraction) > 2.f) + { + LLRect render_rect(bar_rect); + S32 scale_offset = 0; + if (mHoverID == time_block) + { + scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 3.f); + render_rect.mTop += scale_offset; + render_rect.mBottom -= scale_offset; + } + + llassert(time_block->getIndex() < sTimerColors.size()); + LLColor4 color = sTimerColors[time_block->getIndex()]; + if (!hovered) color = lerp(color, LLColor4::grey, 0.2f); + gGL.color4fv(color.mV); + gl_segmented_rect_2d_fragment_tex(render_rect, + image_width, image_height, + 16, + timer_bar.mStartFraction, timer_bar.mEndFraction); + } + + LLRect children_rect; + children_rect.mLeft = ll_round(timer_bar.mChildrenStart / mTotalTimeDisplay * (F32)mBarRect.getWidth()) + mBarRect.mLeft; + children_rect.mRight = ll_round(timer_bar.mChildrenEnd / mTotalTimeDisplay * (F32)mBarRect.getWidth()) + mBarRect.mLeft; + + if (bar_rect.getHeight() > MIN_BAR_HEIGHT) + { + // shrink as we go down a level + children_rect.mTop = bar_rect.mTop - 1; + children_rect.mBottom = bar_rect.mBottom + 1; + } + else + { + children_rect.mTop = bar_rect.mTop; + children_rect.mBottom = bar_rect.mBottom; + } + + bool children_visible = visible && !time_block->getTreeNode().mCollapsed; + + bar_index++; + const U32 num_bars = LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount(); + if (bar_index < num_bars && row.mBars[bar_index].mFirstChild) + { + bool is_last = false; + do + { + is_last = row.mBars[bar_index].mLastChild; + bar_index = drawBar(children_rect, row, image_width, image_height, hovered, children_visible, bar_index); + } + while(!is_last && bar_index < num_bars); + } + + return bar_index; +} |
