relocated and extended perfstats

Major changes in processing. 
More configuration options to control tweaks
tweak application migrated from performance floater to service etc.
master
Beq 2021-11-03 16:26:01 +00:00
parent ea4f6931ab
commit 0c999849cc
2 changed files with 783 additions and 0 deletions

View File

@ -0,0 +1,344 @@
/**
* @file fsperfstats.cpp
* @brief Stats collection to support perf floater and auto tune
*
* $LicenseInfo:firstyear=2021&license=fsviewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (C) 2021, The Phoenix Firestorm Project, 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
*
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
* http://www.firestormviewer.org
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "fsperfstats.h"
#include "llcontrol.h"
#include "pipeline.h"
extern LLControlGroup gSavedSettings;
namespace FSPerfStats
{
#ifdef USAGE_TRACKING
std::atomic<int64_t> inUse{0};
std::atomic<int64_t> inUseAvatar{0};
#endif
std::atomic<int64_t> tunedAvatars{0};
U32 targetFPS; // desired FPS
U32 renderAvatarMaxART{50000}; // highest render time we'll allow without culling features
U32 fpsTuningStrategy{0}; // linked to FSTuningFPSStrategy
U32 lastGlobalPrefChange{0};
std::mutex bufferToggleLock{};
bool autoTune{false};
U32 smoothingPeriods{1}; // number of frames to smooth over.
std::atomic<int> StatsRecorder::writeBuffer{0};
bool StatsRecorder::collectionEnabled{true};
LLUUID StatsRecorder::focusAv{LLUUID::null};
std::array<StatsRecorder::StatsTypeMatrix,2> StatsRecorder::statsDoubleBuffer{ {} };
std::array<StatsRecorder::StatsSummaryArray,2> StatsRecorder::max{ {} };
std::array<StatsRecorder::StatsSummaryArray,2> StatsRecorder::sum{ {} };
StatsRecorder::StatsRecorder():q(1024*16),t(&StatsRecorder::run)
{
// create a queue
// create a thread to consume from the queue
FSPerfStats::targetFPS = gSavedSettings.getU32("FSTargetFPS");
FSPerfStats::autoTune = gSavedSettings.getBOOL("FSAutoTuneFPS");
FSPerfStats::renderAvatarMaxART = gSavedSettings.getBOOL("FSRenderAvatarMaxART");
FSPerfStats::smoothingPeriods = gSavedSettings.getU32("FSPerfFloaterSmoothingPeriods");
t.detach();
}
// static
void StatsRecorder::toggleBuffer()
{
FSZone;
using ST = StatType_t;
bool unreliable{false};
auto& sceneStats = statsDoubleBuffer[writeBuffer][static_cast<size_t>(ObjType_t::OT_GENERAL)][LLUUID::null];
auto& lastStats = statsDoubleBuffer[writeBuffer ^ 1][static_cast<size_t>(ObjType_t::OT_GENERAL)][LLUUID::null];
static constexpr std::initializer_list<StatType_t> sceneStatsToAvg = {
StatType_t::RENDER_FRAME,
StatType_t::RENDER_DISPLAY,
StatType_t::RENDER_HUDS,
StatType_t::RENDER_UI,
StatType_t::RENDER_SWAP,
// RENDER_LFS,
// RENDER_MESHREPO,
StatType_t::RENDER_IDLE };
static constexpr std::initializer_list<StatType_t> avatarStatsToAvg = {
StatType_t::RENDER_GEOMETRY,
StatType_t::RENDER_SHADOWS,
StatType_t::RENDER_COMBINED,
StatType_t::RENDER_IDLE };
if( sceneStats[static_cast<size_t>(StatType_t::RENDER_FPSLIMIT)] != 0 || sceneStats[static_cast<size_t>(StatType_t::RENDER_SLEEP)] != 0 )
{
unreliable = true;
}
if(!unreliable)
{
// only use these stats when things are reliable.
for(auto & statEntry : sceneStatsToAvg)
{
auto avg = lastStats[static_cast<size_t>(statEntry)];
auto val = sceneStats[static_cast<size_t>(statEntry)];
sceneStats[static_cast<size_t>(statEntry)] = avg + (val/smoothingPeriods) - (avg/smoothingPeriods);
// LL_INFOS("scenestats") << "Scenestat: " << static_cast<size_t>(statEntry) << " before=" << avg << " new=" << val << " newavg=" << statsDoubleBuffer[writeBuffer][static_cast<size_t>(ObjType_t::OT_GENERAL)][LLUUID::null][static_cast<size_t>(statEntry)] << LL_ENDL;
}
auto& statsMap = statsDoubleBuffer[writeBuffer][static_cast<size_t>(ObjType_t::OT_ATTACHMENT)];
for(auto& stat_entry : statsMap)
{
auto val = stat_entry.second[static_cast<size_t>(ST::RENDER_COMBINED)];
if(val>smoothingPeriods){
auto avg = statsDoubleBuffer[writeBuffer ^ 1][static_cast<size_t>(ObjType_t::OT_ATTACHMENT)][stat_entry.first][static_cast<size_t>(ST::RENDER_COMBINED)];
stat_entry.second[static_cast<size_t>(ST::RENDER_COMBINED)] = avg + (val/smoothingPeriods) - (avg/smoothingPeriods);
}
}
auto& statsMapAv = statsDoubleBuffer[writeBuffer][static_cast<size_t>(ObjType_t::OT_AVATAR)];
for(auto& stat_entry : statsMapAv)
{
for(auto& stat : avatarStatsToAvg)
{
auto val = stat_entry.second[static_cast<size_t>(stat)];
if(val>smoothingPeriods)
{
auto avg = statsDoubleBuffer[writeBuffer ^ 1][static_cast<size_t>(ObjType_t::OT_AVATAR)][stat_entry.first][static_cast<size_t>(stat)];
stat_entry.second[static_cast<size_t>(stat)] = avg + (val/smoothingPeriods) - (avg/smoothingPeriods);
}
}
}
// swap the buffers
if(enabled())
{
std::lock_guard<std::mutex> lock{bufferToggleLock};
writeBuffer ^= 1;
}; // note we are relying on atomic updates here. The risk is low and would cause minor errors in the stats display.
}
// clean the write maps in all cases.
auto& statsTypeMatrix = statsDoubleBuffer[writeBuffer];
for(auto& statsMap : statsTypeMatrix)
{
FSZoneN("Clear stats maps");
for(auto& stat_entry : statsMap)
{
std::fill_n(stat_entry.second.begin() ,static_cast<size_t>(ST::STATS_COUNT),0);
}
statsMap.clear();
}
for(int i=0; i< static_cast<size_t>(ObjType_t::OT_COUNT); i++)
{
FSZoneN("clear max/sum");
max[writeBuffer][i].fill(0);
sum[writeBuffer][i].fill(0);
}
// and now adjust the visuals.
if(autoTune)
{
updateAvatarParams();
}
}
// clear buffers when we change region or need a hard reset.
// static
void StatsRecorder::clearStatsBuffers()
{
FSZone;
using ST = StatType_t;
auto& statsTypeMatrix = statsDoubleBuffer[writeBuffer];
for(auto& statsMap : statsTypeMatrix)
{
FSZoneN("Clear stats maps");
for(auto& stat_entry : statsMap)
{
std::fill_n(stat_entry.second.begin() ,static_cast<size_t>(ST::STATS_COUNT),0);
}
statsMap.clear();
}
for(int i=0; i< static_cast<size_t>(ObjType_t::OT_COUNT); i++)
{
FSZoneN("clear max/sum");
max[writeBuffer][i].fill(0);
sum[writeBuffer][i].fill(0);
}
// swap the clean buffers in
if(enabled())
{
std::lock_guard<std::mutex> lock{bufferToggleLock};
writeBuffer ^= 1;
};
// repeat before we start processing new stuff
for(auto& statsMap : statsTypeMatrix)
{
FSZoneN("Clear stats maps");
for(auto& stat_entry : statsMap)
{
std::fill_n(stat_entry.second.begin() ,static_cast<size_t>(ST::STATS_COUNT),0);
}
statsMap.clear();
}
for(int i=0; i< static_cast<size_t>(ObjType_t::OT_COUNT); i++)
{
FSZoneN("clear max/sum");
max[writeBuffer][i].fill(0);
sum[writeBuffer][i].fill(0);
}
}
// static
void StatsRecorder::updateAvatarParams()
{
LLCachedControl<F32> drawDistance(gSavedSettings, "RenderFarClip");
auto av_render_max_raw = FSPerfStats::StatsRecorder::getMax(ObjType_t::OT_AVATAR, FSPerfStats::StatType_t::RENDER_COMBINED);
// Is our target frame time lower than current? If so we need to take action to reduce draw overheads.
// cumulative avatar time (includes idle processing, attachments and base av)
auto tot_avatar_time_raw = FSPerfStats::StatsRecorder::getSum(ObjType_t::OT_AVATAR, FSPerfStats::StatType_t::RENDER_COMBINED);
// sleep time is basically forced sleep when window out of focus
auto tot_sleep_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_SLEEP);
// similar to sleep time, induced by FPS limit
auto tot_limit_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_FPSLIMIT);
// the time spent this frame on the "doFrame" call. Treated as "tot time for frame"
auto tot_frame_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_FRAME);
if(tot_sleep_time_raw != 0 || tot_limit_time_raw !=0 )
{
// Note: we do not average sleep and fpslimit, therefore we cannot reliably use them here.
// if at some point we need to, the averaging will need to take this into account or
// we forever think we're in the background due to residuals.
LL_DEBUGS() << "No tuning when not in focus" << LL_ENDL;
return;
}
// LL_INFOS() << "Effective FPS:" << (1000000/FSPerfStats::raw_to_us(tot_frame_time_raw)) << " Target:" << targetFPS << LL_ENDL;
// The frametime budget we have based on the target FPS selected
auto target_frame_time_raw = (U64)llround(get_timer_info().mClockFrequency/(targetFPS==0?1:targetFPS));
// LL_INFOS() << "Effective FPS(raw):" << tot_frame_time_raw << " Target:" << target_frame_time_raw << LL_ENDL;
// 1) Is the target frame tim lower than current?
if( target_frame_time_raw <= tot_frame_time_raw )
{
// if so we've got work to do
// how much of the frame was spent on non avatar related work?
U32 non_avatar_time_raw = tot_frame_time_raw - tot_avatar_time_raw;
// If the target frame time < non avatar frame time thne adjusting avatars is only goin gto get us so far.
U64 target_avatar_time_raw;
if(target_frame_time_raw < non_avatar_time_raw)
{
// we cannnot do this by avatar adjustment alone.
if((gFrameCount - FSPerfStats::lastGlobalPrefChange) > 10) // give changes a short time to take effect.
{
if(FSPerfStats::fpsTuningStrategy == 1)
{
// 1 - hack the water to opaque. all non opaque have a significan t hit, this is a big boost.
if(LLPipeline::RenderReflectionDetail != -2)
{
gSavedSettings.setS32("RenderReflectionDetail", -2);
FSPerfStats::lastGlobalPrefChange = gFrameCount;
}
else // deliberately "else" here so we only do these in steps
{
// step down the DD by 10m per update
auto new_dd = (drawDistance>42)?(drawDistance - 10) : 32;
gSavedSettings.setF32("RenderFarClip", new_dd);
FSPerfStats::lastGlobalPrefChange = gFrameCount;
}
}
}
// slam the avatar time to 0 "imposter all the things"
target_avatar_time_raw = 0;
}
else
{
// desired avatar budget.
target_avatar_time_raw = target_frame_time_raw - non_avatar_time_raw;
}
if( target_avatar_time_raw < tot_avatar_time_raw )
{
// we need to spend less time drawing avatars to meet our budget
// Note: working in usecs now cos reasons.
U32 new_render_limit_us {0};
// max render this frame may be higher than the last (cos new entrants and jitter) so make sure we are heading in the right direction
if(FSPerfStats::raw_to_us(av_render_max_raw) < renderAvatarMaxART)
{
new_render_limit_us = FSPerfStats::raw_to_us(av_render_max_raw);
}
else
{
new_render_limit_us = renderAvatarMaxART;
}
new_render_limit_us -= 100;
// bounce at the bottom to prevent "no limit"
if(new_render_limit_us <= 0 || new_render_limit_us >1000000)
{
new_render_limit_us = 100;
}
// assign the new value
renderAvatarMaxART = new_render_limit_us;
// LL_DEBUGS() << "AUTO_TUNE: avatar_budget adjusted to:" << new_render_limit_us << LL_ENDL;
}
// LL_DEBUGS() << "AUTO_TUNE: Target frame time:"<< FSPerfStats::raw_to_us(target_frame_time_raw) << "usecs (non_avatar is " << FSPerfStats::raw_to_us(non_avatar_time_raw) << "usecs) Max cost limited=" << renderAvatarMaxART << LL_ENDL;
}
else if( FSPerfStats::raw_to_us(target_frame_time_raw) > (FSPerfStats::raw_to_us(tot_frame_time_raw) + renderAvatarMaxART) )
{
if( FSPerfStats::tunedAvatars > 0 )
{
// if we have more time to spare let's shift up little in the hope we'll restore an avatar.
renderAvatarMaxART += 10;
}
if(drawDistance < 180.) // TODO(Beq) make this less arbitrary
{
gSavedSettings.setF32("RenderFarClip", drawDistance + 10.);
}
if( (FSPerfStats::raw_to_us(target_frame_time_raw) * 1.5) > FSPerfStats::raw_to_us(tot_frame_time_raw) &&
FSPerfStats::tunedAvatars == 0 &&
drawDistance >= 128. )
{
// if everything else is "max" and we have 50% headroom let's knock the water quality up a notch at a time.
auto water = gSavedSettings.getS32("RenderReflectionDetail");
gSavedSettings.setS32("RenderReflectionDetail", water+1);
}
}
}
}

439
indra/newview/fsperfstats.h Normal file
View File

@ -0,0 +1,439 @@
#pragma once
#ifndef FS_PERFSTATS_H_INCLUDED
#define FS_PERFSTATS_H_INCLUDED
/**
* @file fsperfstats.h
* @brief Statistics collection to support autotune and perf flaoter.
*
* $LicenseInfo:firstyear=2021&license=fsviewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (C) 2021, The Phoenix Firestorm Project, 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
*
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
* http://www.firestormviewer.org
* $/LicenseInfo$
*/
#include <atomic>
#include <chrono>
#include <array>
#include <unordered_map>
#include <mutex>
#include "lluuid.h"
#include "lltimer.h"
#include "blockingconcurrentqueue.h"
#include "llapp.h"
#include "fstelemetry.h"
// Additional logging options. These can skew inworld numbers so onyl use for debugging and tracking issues
#ifdef FS_HAS_TELEMETRY_SUPPORT
// USAGE_TRACKING - displays overlapping stats that may imply double counting.
// ATTACHMENT_TRACKING - displays detailed tracking info for Avatar and Attachment. very heavy overhead.
// #define USAGE_TRACKING
// #define ATTACHMENT_TRACKING
#else
#undef USAGE_TRACKING
#undef ATTACHMENT_TRACKING
#endif
extern U32 gFrameCount;
extern LLUUID gAgentID;
namespace FSPerfStats
{
#ifdef USAGE_TRACKING
extern std::atomic<int64_t> inUse;
extern std::atomic<int64_t> inUseAvatar;
#endif
extern std::atomic<int64_t> tunedAvatars;
extern U32 targetFPS; // desired FPS
extern U32 renderAvatarMaxART;
extern U32 fpsTuningStrategy;
extern U32 lastGlobalPrefChange;
extern std::mutex bufferToggleLock;
extern bool autoTune;
extern U32 smoothingPeriods; // number of frames to smooth over.
enum class ObjType_t{
OT_GENERAL=0, // Also Unknown. Used for n/a type stats such as scenery
OT_AVATAR,
OT_ATTACHMENT,
OT_HUD,
OT_COUNT
};
enum class StatType_t{
RENDER_GEOMETRY=0,
RENDER_SHADOWS,
RENDER_HUDS,
RENDER_UI,
RENDER_COMBINED,
RENDER_SWAP,
RENDER_FRAME,
RENDER_DISPLAY,
RENDER_SLEEP,
RENDER_LFS,
RENDER_MESHREPO,
RENDER_FPSLIMIT,
RENDER_FPS,
RENDER_IDLE,
RENDER_DONE, // toggle buffer & clearbuffer (see processUpdate for hackery)
STATS_COUNT
};
struct StatsRecord
{
StatType_t statType;
ObjType_t objType;
LLUUID avID;
LLUUID objID;
uint64_t time;
uint32_t count;
bool isRigged;
};
class StatsRecorder{
using Queue = moodycamel::BlockingConcurrentQueue<StatsRecord>;
public:
static inline StatsRecorder& getInstance()
{
static StatsRecorder instance;
// volatile int dummy{};
return instance;
}
static inline void setFocusAv(const LLUUID& avID){focusAv = avID;};
static inline const LLUUID& getFocusAv(){return (focusAv);};
static inline void send(StatsRecord&& u){StatsRecorder::getInstance().q.enqueue(u);};
static void endFrame(){StatsRecorder::getInstance().q.enqueue(StatsRecord{StatType_t::RENDER_DONE, ObjType_t::OT_GENERAL, LLUUID::null, LLUUID::null, 0});};
static void clearStats(){StatsRecorder::getInstance().q.enqueue(StatsRecord{StatType_t::RENDER_DONE, ObjType_t::OT_GENERAL, LLUUID::null, LLUUID::null, 1});};
static inline void setEnabled(bool on_or_off){collectionEnabled=on_or_off;};
static inline void enable() { collectionEnabled=true; };
static inline void disable() { collectionEnabled=false; };
static inline bool enabled() { return(collectionEnabled); };
static inline int getReadBufferIndex() { return (writeBuffer ^ 1); };
// static inline const StatsTypeMatrix& getCurrentStatsMatrix(){ return statsDoubleBuffer[getReadBufferIndex()];}
static inline uint64_t get(ObjType_t otype, LLUUID id, StatType_t type)
{
return statsDoubleBuffer[getReadBufferIndex()][static_cast<size_t>(otype)][id][static_cast<size_t>(type)];
}
static inline uint64_t getSceneStat(StatType_t type)
{
return statsDoubleBuffer[getReadBufferIndex()][static_cast<size_t>(ObjType_t::OT_GENERAL)][LLUUID::null][static_cast<size_t>(type)];
}
static inline uint64_t getSum(ObjType_t otype, StatType_t type)
{
return sum[getReadBufferIndex()][static_cast<size_t>(otype)][static_cast<size_t>(type)];
}
static inline uint64_t getMax(ObjType_t otype, StatType_t type)
{
return max[getReadBufferIndex()][static_cast<size_t>(otype)][static_cast<size_t>(type)];
}
static void StatsRecorder::updateAvatarParams();
private:
StatsRecorder();
// StatsArray is a uint64_t for each possible statistic type.
using StatsArray = std::array<uint64_t, static_cast<size_t>(FSPerfStats::StatType_t::STATS_COUNT)>;
using StatsMap = std::unordered_map<LLUUID, StatsArray, FSUUIDHash>;
using StatsTypeMatrix = std::array<StatsMap, static_cast<size_t>(FSPerfStats::ObjType_t::OT_COUNT)>;
using StatsSummaryArray = std::array<StatsArray, static_cast<size_t>(FSPerfStats::ObjType_t::OT_COUNT)>;
static std::atomic<int> writeBuffer;
static LLUUID focusAv;
static std::array<StatsTypeMatrix,2> statsDoubleBuffer;
static std::array<StatsSummaryArray,2> max;
static std::array<StatsSummaryArray,2> sum;
static bool collectionEnabled;
void processUpdate(const StatsRecord& upd)
{
FSZone;
// LL_INFOS("perfstats") << "processing update:" << LL_ENDL;
using ST = StatType_t;
// Note: nullptr is used as the key for global stats
static char avstr[36];
static char obstr[36];
if(upd.statType == StatType_t::RENDER_DONE && upd.objType == ObjType_t::OT_GENERAL && upd.time == 0)
{
// LL_INFOS("perfstats") << "End of Frame Toggle Buffer:" << gFrameCount << LL_ENDL;
toggleBuffer();
return;
}
if(upd.statType == StatType_t::RENDER_DONE && upd.objType == ObjType_t::OT_GENERAL && upd.time == 1)
{
// LL_INFOS("perfstats") << "New region - clear buffers:" << gFrameCount << LL_ENDL;
clearStatsBuffers();
return;
}
auto ot{upd.objType};
auto& key{upd.objID};
auto& avKey{upd.avID};
auto type {upd.statType};
auto val {upd.time};
FSZoneText(key.toStringFast(obstr),36);
FSZoneText(avKey.toStringFast(avstr),36);
FSZoneValue(val);
if(ot == ObjType_t::OT_GENERAL)
{
// LL_INFOS("perfstats") << "General update:" << LL_ENDL;
doUpd(key, ot, type,val);
return;
}
if(ot == ObjType_t::OT_AVATAR)
{
// LL_INFOS("perfstats") << "Avatar update:" << LL_ENDL;
doUpd(avKey, ot, type, val);
return;
}
if(ot == ObjType_t::OT_ATTACHMENT)
{
if( !upd.isRigged )
{
// For all attachments that are not rigged we add them to the avatar (for all avatars) cost.
doUpd(avKey, ObjType_t::OT_AVATAR, type, val);
}
if( avKey == focusAv )
{
// For attachments that are for the focusAv (self for now) we record them for the attachment/complexity view
// LL_INFOS("perfstats") << "frame: " << gFrameCount << " Attachment update("<< (type==StatType_t::RENDER_GEOMETRY?"GEOMETRY":"SHADOW") << ": " << key.asString() << " = " << val << LL_ENDL;
doUpd(key, ot, type, val);
}
else
{
// LL_INFOS("perfstats") << "frame: " << gFrameCount << " non-self Att update("<< (type==StatType_t::RENDER_GEOMETRY?"GEOMETRY":"SHADOW") << ": " << key.asString() << " = " << val << " for av " << avKey.asString() << LL_ENDL;
}
}
}
static inline void doUpd(const LLUUID& key, ObjType_t ot, StatType_t type, uint64_t val)
{
FSZone;
using ST = StatType_t;
StatsMap& stm {statsDoubleBuffer[writeBuffer][static_cast<size_t>(ot)]};
auto& thisAsset = stm[key];
thisAsset[static_cast<size_t>(type)] += val;
thisAsset[static_cast<size_t>(ST::RENDER_COMBINED)] += val;
sum[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(type)] += val;
sum[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(ST::RENDER_COMBINED)] += val;
if(max[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(type)] < thisAsset[static_cast<size_t>(type)])
{
max[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(type)] = thisAsset[static_cast<size_t>(type)];
}
if(max[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(ST::RENDER_COMBINED)] < thisAsset[static_cast<size_t>(ST::RENDER_COMBINED)])
{
max[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(ST::RENDER_COMBINED)] = thisAsset[static_cast<size_t>(ST::RENDER_COMBINED)];
}
}
static void toggleBuffer();
static void clearStatsBuffers();
// thread entry
static void run()
{
StatsRecord upd[10];
auto& instance {StatsRecorder::getInstance()};
FSThreadName( "PerfStats" );
while( !LLApp::isExiting() )
{
FSZone("perf batch");
auto count = instance.q.wait_dequeue_bulk_timed(upd, 10, std::chrono::milliseconds(5));
if(count)
{
// LL_INFOS("perfstats") << "processing " << count << " updates." << LL_ENDL;
for(auto i =0; i < count; i++)
{
instance.processUpdate(upd[i]);
}
}
}
}
Queue q;
std::thread t;
~StatsRecorder() = default;
StatsRecorder(const StatsRecorder&) = delete;
StatsRecorder& operator=(const StatsRecorder&) = delete;
};
template <enum ObjType_t ObjTypeDiscriminator>
class RecordTime
{
private:
RecordTime(const RecordTime&) = delete;
RecordTime() = delete;
U64 start;
public:
StatsRecord stat;
RecordTime( const LLUUID& av, const LLUUID& id, StatType_t type, bool isRiggedAtt=false):start{LLTimer::getCurrentClockCount()},
stat{type, ObjTypeDiscriminator, std::move(av), std::move(id), 0, isRiggedAtt}{
FSZoneC(tracy::Color::Orange);
#ifdef USAGE_TRACKING
if(stat.objType == FSPerfStats::ObjType_t::OT_ATTACHMENT)
{
if(FSPerfStats::inUse){FSZoneText("OVERLAP ATT",11);}
if(!stat.isRigged && FSPerfStats::inUseAvatar){FSZoneText("OVERLAP AVATAR",14);}
FSPlot("InUse", (int64_t)FSPerfStats::inUse);
FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar);
FSPerfStats::inUse++;
if( !stat.isRigged ) {FSPerfStats::inUseAvatar++;};
FSPlot("InUse", (int64_t)FSPerfStats::inUse);
FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar);
}
#endif
};
template < typename = std::enable_if_t<ObjTypeDiscriminator == ObjType_t::OT_GENERAL> >
RecordTime( StatType_t type ):RecordTime<ObjTypeDiscriminator>(LLUUID::null, LLUUID::null, type )
{
FSZone;
};
template < typename = std::enable_if_t<ObjTypeDiscriminator == ObjType_t::OT_AVATAR> >
RecordTime( const LLUUID & av, StatType_t type ):RecordTime<ObjTypeDiscriminator>(std::move(av), LLUUID::null, type)
{
FSZoneC(tracy::Color::Purple);
#ifdef USAGE_TRACKING
if(FSPerfStats::inUseAvatar){FSZoneText("OVERLAP AVATAR",14);}
FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar);
FSPerfStats::inUseAvatar++;
FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar);
#endif
};
~RecordTime()
{
if(!FSPerfStats::StatsRecorder::enabled())
{
return;
}
FSZoneC(tracy::Color::Red);
#ifdef USAGE_TRACKING
if (stat.objType == FSPerfStats::ObjType_t::OT_ATTACHMENT)
{
FSPlot("InUse", (int64_t)FSPerfStats::inUse);
--FSPerfStats::inUse;
FSPlot("InUse", (int64_t)FSPerfStats::inUse);
}
if( ( stat.objType == FSPerfStats::ObjType_t::OT_ATTACHMENT && !stat.isRigged ) || stat.objType == FSPerfStats::ObjType_t::OT_AVATAR )
{
FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar);
--FSPerfStats::inUseAvatar;
FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar);
}
#endif
stat.time = LLTimer::getCurrentClockCount() - start;
#ifdef ATTACHMENT_TRACKING
static char obstr[36];
static char avstr[36];
FSZoneValue(static_cast<U64>(stat.objType));
FSZoneText(stat.avID.toStringFast(avstr), 36);
FSZoneText(stat.objID.toStringFast(obstr), 36);
FSZoneValue(stat.time);
#endif
StatsRecorder::send(std::move(stat));
};
};
inline double raw_to_ns(U64 raw) { return (static_cast<double>(raw) * 1000000000.0) * get_timer_info().mClockFrequencyInv; };
inline double raw_to_us(U64 raw) { return (static_cast<double>(raw) * 1000000.0) * get_timer_info().mClockFrequencyInv; };
inline double raw_to_ms(U64 raw) { return (static_cast<double>(raw) * 1000.0) * get_timer_info().mClockFrequencyInv; };
using RecordSceneTime = RecordTime<ObjType_t::OT_GENERAL>;
using RecordAvatarTime = RecordTime<ObjType_t::OT_AVATAR>;
using RecordAttachmentTime = RecordTime<ObjType_t::OT_ATTACHMENT>;
};// namespace FSPerfStats
// helper functions
using RATptr = std::unique_ptr<FSPerfStats::RecordAttachmentTime>;
template <typename T>
static inline void trackAttachments(const T * vobj, bool isRigged, RATptr* ratPtrp)
{
if( !vobj ){ ratPtrp->reset(); return;};
const T* rootAtt{vobj};
if( rootAtt->isAttachment() )
{
FSZone;
while( !rootAtt->isRootEdit() )
{
rootAtt = (T*)(rootAtt->getParent());
}
auto avPtr = (T*)(rootAtt->getParent());
if(!avPtr){ratPtrp->reset(); return;}
auto& av = avPtr->getID();
auto& obj = rootAtt->getAttachmentItemID();
if(!*ratPtrp || (*ratPtrp)->stat.objID != obj || (*ratPtrp)->stat.avID != av )
{
#if TRACY_ENABLE && defined(ATTACHMENT_TRACKING)
FSZoneNC( "trackAttachments:new", tracy::Color::Red );
auto& str = rootAtt->getAttachmentItemName();
FSZoneText(str.c_str(), str.size());
static char avStr[36];
av.toStringFast(avStr);
static char obStr[4];
obj.toShortString(obStr);
FSZoneText( avStr, 36);
FSZoneText( obStr, 4);
#endif
if(*ratPtrp){ratPtrp->reset();}; // deliberately reset to ensure destruction before construction of replacement.
*ratPtrp = std::make_unique<FSPerfStats::RecordAttachmentTime>( av, obj, ( (LLPipeline::sShadowRender)?FSPerfStats::StatType_t::RENDER_SHADOWS : FSPerfStats::StatType_t::RENDER_GEOMETRY ), isRigged );
}
(*ratPtrp)->stat.count++;
}
return;
};
#endif