summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon')
-rwxr-xr-xindra/llcommon/CMakeLists.txt2
-rwxr-xr-xindra/llcommon/llcommon.cpp14
-rwxr-xr-xindra/llcommon/llfasttimer.cpp15
-rwxr-xr-xindra/llcommon/llfasttimer.h30
-rwxr-xr-xindra/llcommon/llpointer.h18
-rwxr-xr-xindra/llcommon/llqueuedthread.cpp4
-rwxr-xr-xindra/llcommon/llthread.cpp4
-rw-r--r--indra/llcommon/llthreadlocalstorage.cpp2
-rw-r--r--indra/llcommon/llthreadlocalstorage.h172
-rw-r--r--indra/llcommon/lltrace.cpp50
-rw-r--r--indra/llcommon/lltrace.h626
-rw-r--r--indra/llcommon/lltraceaccumulators.cpp123
-rw-r--r--indra/llcommon/lltraceaccumulators.h656
-rw-r--r--indra/llcommon/lltracerecording.cpp118
-rw-r--r--indra/llcommon/lltracerecording.h45
-rw-r--r--indra/llcommon/lltracethreadrecorder.cpp196
-rw-r--r--indra/llcommon/lltracethreadrecorder.h94
-rw-r--r--indra/llcommon/llunit.h68
18 files changed, 1072 insertions, 1165 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index bf99a4c3a0..0c76fd46c0 100755
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -103,6 +103,7 @@ set(llcommon_SOURCE_FILES
llthreadsafequeue.cpp
lltimer.cpp
lltrace.cpp
+ lltraceaccumulators.cpp
lltracerecording.cpp
lltracethreadrecorder.cpp
lluri.cpp
@@ -231,6 +232,7 @@ set(llcommon_HEADER_FILES
llthreadsafequeue.h
lltimer.h
lltrace.h
+ lltraceaccumulators.h
lltracerecording.h
lltracethreadrecorder.h
lltreeiterators.h
diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp
index c720df7555..96ec0cdefe 100755
--- a/indra/llcommon/llcommon.cpp
+++ b/indra/llcommon/llcommon.cpp
@@ -30,10 +30,13 @@
#include "llmemory.h"
#include "llthread.h"
#include "lltrace.h"
+#include "lltracethreadrecorder.h"
//static
BOOL LLCommon::sAprInitialized = FALSE;
+static LLTrace::ThreadRecorder* sMasterThreadRecorder = NULL;
+
//static
void LLCommon::initClass()
{
@@ -45,13 +48,20 @@ void LLCommon::initClass()
}
LLTimer::initClass();
LLThreadSafeRefCount::initThreadSafeRefCount();
- LLTrace::init();
+
+ if (!sMasterThreadRecorder)
+ {
+ sMasterThreadRecorder = new LLTrace::ThreadRecorder();
+ LLTrace::set_master_thread_recorder(sMasterThreadRecorder);
+ }
}
//static
void LLCommon::cleanupClass()
{
- LLTrace::cleanup();
+ delete sMasterThreadRecorder;
+ sMasterThreadRecorder = NULL;
+ LLTrace::set_master_thread_recorder(NULL);
LLThreadSafeRefCount::cleanupThreadSafeRefCount();
LLTimer::cleanupClass();
if (sAprInitialized)
diff --git a/indra/llcommon/llfasttimer.cpp b/indra/llcommon/llfasttimer.cpp
index 60c451b137..a72f16d385 100755
--- a/indra/llcommon/llfasttimer.cpp
+++ b/indra/llcommon/llfasttimer.cpp
@@ -247,17 +247,18 @@ void TimeBlock::incrementalUpdateTimerTree()
void TimeBlock::updateTimes()
- {
- U64 cur_time = getCPUClockCount64();
-
+{
// walk up stack of active timers and accumulate current time while leaving timing structures active
- BlockTimerStackRecord* stack_record = ThreadTimerStack::getInstance();
+ BlockTimerStackRecord* stack_record = LLThreadLocalSingletonPointer<BlockTimerStackRecord>::getInstance();
+ if (!stack_record) return;
+
+ U64 cur_time = getCPUClockCount64();
BlockTimer* cur_timer = stack_record->mActiveTimer;
TimeBlockAccumulator* accumulator = stack_record->mTimeBlock->getPrimaryAccumulator();
while(cur_timer
&& cur_timer->mParentTimerData.mActiveTimer != cur_timer) // root defined by parent pointing to self
- {
+ {
U64 cumulative_time_delta = cur_time - cur_timer->mStartTime;
accumulator->mTotalTimeCounter += cumulative_time_delta
- (accumulator->mTotalTimeCounter
@@ -386,7 +387,7 @@ void TimeBlock::dumpCurTimes()
U32 num_calls = last_frame_recording.getSum(timerp->callCount());
// Don't bother with really brief times, keep output concise
- if (total_time < LLUnit<F32, LLUnits::Milliseconds>(0.1)) continue;
+ if (total_time < LLUnits::Milliseconds::fromValue(0.1f)) continue;
std::ostringstream out_str;
TimeBlock* parent_timerp = timerp;
@@ -397,7 +398,7 @@ void TimeBlock::dumpCurTimes()
}
out_str << timerp->getName() << " "
- << std::setprecision(3) << total_time.getAs<LLUnits::Milliseconds>() << " ms, "
+ << std::setprecision(3) << total_time.valueAs<LLUnits::Milliseconds>() << " ms, "
<< num_calls << " calls";
llinfos << out_str.str() << llendl;
diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h
index 642c99ccce..73c40749ed 100755
--- a/indra/llcommon/llfasttimer.h
+++ b/indra/llcommon/llfasttimer.h
@@ -38,29 +38,6 @@ class LLMutex;
namespace LLTrace
{
-struct BlockTimerStackRecord
-{
- class BlockTimer* mActiveTimer;
- class TimeBlock* mTimeBlock;
- U64 mChildTime;
-};
-
-class ThreadTimerStack
-: public BlockTimerStackRecord,
- public LLThreadLocalSingleton<ThreadTimerStack>
-{
- friend class LLThreadLocalSingleton<ThreadTimerStack>;
- ThreadTimerStack()
- {}
-
-public:
- ThreadTimerStack& operator=(const BlockTimerStackRecord& other)
- {
- BlockTimerStackRecord::operator=(other);
- return *this;
- }
-};
-
class BlockTimer
{
public:
@@ -278,7 +255,8 @@ public:
LL_FORCE_INLINE BlockTimer::BlockTimer(TimeBlock& timer)
{
#if FAST_TIMER_ON
- BlockTimerStackRecord* cur_timer_data = ThreadTimerStack::getIfExists();
+ BlockTimerStackRecord* cur_timer_data = LLThreadLocalSingletonPointer<BlockTimerStackRecord>::getInstance();
+ if (!cur_timer_data) return;
TimeBlockAccumulator* accumulator = timer.getPrimaryAccumulator();
accumulator->mActiveCount++;
mBlockStartTotalTimeCounter = accumulator->mTotalTimeCounter;
@@ -300,7 +278,9 @@ LL_FORCE_INLINE BlockTimer::~BlockTimer()
{
#if FAST_TIMER_ON
U64 total_time = TimeBlock::getCPUClockCount64() - mStartTime;
- BlockTimerStackRecord* cur_timer_data = ThreadTimerStack::getIfExists();
+ BlockTimerStackRecord* cur_timer_data = LLThreadLocalSingletonPointer<BlockTimerStackRecord>::getInstance();
+ if (!cur_timer_data) return;
+
TimeBlockAccumulator* accumulator = cur_timer_data->mTimeBlock->getPrimaryAccumulator();
accumulator->mCalls++;
diff --git a/indra/llcommon/llpointer.h b/indra/llcommon/llpointer.h
index 6a0a8fcb0d..c827996db1 100755
--- a/indra/llcommon/llpointer.h
+++ b/indra/llcommon/llpointer.h
@@ -173,15 +173,23 @@ public:
typedef LLPointer<Type> pointer_t;
LLCopyOnWritePointer()
+ : mStayUnique(false)
{}
LLCopyOnWritePointer(Type* ptr)
- : LLPointer<Type>(ptr)
+ : LLPointer<Type>(ptr),
+ mStayUnique(false)
{}
LLCopyOnWritePointer(LLPointer<Type>& ptr)
- : LLPointer<Type>(ptr)
- {}
+ : LLPointer<Type>(ptr),
+ mStayUnique(false)
+ {
+ if (ptr.mForceUnique)
+ {
+ makeUnique();
+ }
+ }
Type* write()
{
@@ -199,6 +207,10 @@ public:
const Type* operator->() const { return pointer_t::mPointer; }
const Type& operator*() const { return *pointer_t::mPointer; }
+
+ void setStayUnique(bool stay) { makeUnique(); mStayUnique = stay; }
+private:
+ bool mStayUnique;
};
#endif
diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp
index 4339f203db..3689c4728e 100755
--- a/indra/llcommon/llqueuedthread.cpp
+++ b/indra/llcommon/llqueuedthread.cpp
@@ -470,7 +470,7 @@ S32 LLQueuedThread::processNextRequest()
}
}
- LLTrace::get_thread_recorder()->pushToMaster();
+ LLTrace::get_thread_recorder()->pushToParent();
}
S32 pending = getPending();
@@ -502,7 +502,7 @@ void LLQueuedThread::run()
if (isQuitting())
{
- LLTrace::get_thread_recorder()->pushToMaster();
+ LLTrace::get_thread_recorder()->pushToParent();
endThread();
break;
}
diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp
index 118568d5ef..166a4eb26d 100755
--- a/indra/llcommon/llthread.cpp
+++ b/indra/llcommon/llthread.cpp
@@ -93,7 +93,7 @@ void *APR_THREAD_FUNC LLThread::staticRun(apr_thread_t *apr_threadp, void *datap
{
LLThread *threadp = (LLThread *)datap;
- LLTrace::ThreadRecorder* thread_recorder = new LLTrace::SlaveThreadRecorder(LLTrace::getUIThreadRecorder());
+ LLTrace::ThreadRecorder thread_recorder(*LLTrace::get_master_thread_recorder());
#if !LL_DARWIN
sThreadID = threadp->mID;
@@ -107,8 +107,6 @@ void *APR_THREAD_FUNC LLThread::staticRun(apr_thread_t *apr_threadp, void *datap
// We're done with the run function, this thread is done executing now.
threadp->mStatus = STOPPED;
- delete thread_recorder;
-
return NULL;
}
diff --git a/indra/llcommon/llthreadlocalstorage.cpp b/indra/llcommon/llthreadlocalstorage.cpp
index 32d94331a6..03c306cc7f 100644
--- a/indra/llcommon/llthreadlocalstorage.cpp
+++ b/indra/llcommon/llthreadlocalstorage.cpp
@@ -88,6 +88,7 @@ void LLThreadLocalPointerBase::destroyStorage()
}
}
+//static
void LLThreadLocalPointerBase::initAllThreadLocalStorage()
{
if (!sInitialized)
@@ -102,6 +103,7 @@ void LLThreadLocalPointerBase::initAllThreadLocalStorage()
}
}
+//static
void LLThreadLocalPointerBase::destroyAllThreadLocalStorage()
{
if (sInitialized)
diff --git a/indra/llcommon/llthreadlocalstorage.h b/indra/llcommon/llthreadlocalstorage.h
index a15f9185b1..3b2f5f4193 100644
--- a/indra/llcommon/llthreadlocalstorage.h
+++ b/indra/llcommon/llthreadlocalstorage.h
@@ -125,178 +125,6 @@ public:
};
template<typename DERIVED_TYPE>
-class LLThreadLocalSingleton
-{
- typedef enum e_init_state
- {
- UNINITIALIZED = 0,
- CONSTRUCTING,
- INITIALIZING,
- INITIALIZED,
- DELETED
- } EInitState;
-
-public:
- LLThreadLocalSingleton()
- {}
-
- virtual ~LLThreadLocalSingleton()
- {
-#if LL_DARWIN
- pthread_setspecific(sInstanceKey, NULL);
-#else
- sInstance = NULL;
-#endif
- setInitState(DELETED);
- }
-
- static void deleteSingleton()
- {
- delete getIfExists();
- }
-
- static DERIVED_TYPE* getInstance()
- {
- EInitState init_state = getInitState();
- if (init_state == CONSTRUCTING)
- {
- llerrs << "Tried to access singleton " << typeid(DERIVED_TYPE).name() << " from singleton constructor!" << llendl;
- }
-
- if (init_state == DELETED)
- {
- llwarns << "Trying to access deleted singleton " << typeid(DERIVED_TYPE).name() << " creating new instance" << llendl;
- }
-
-#if LL_DARWIN
- createTLSInstance();
-#endif
- if (!getIfExists())
- {
- setInitState(CONSTRUCTING);
- DERIVED_TYPE* instancep = new DERIVED_TYPE();
-#if LL_DARWIN
- S32 result = pthread_setspecific(sInstanceKey, (void*)instancep);
- if (result != 0)
- {
- llerrs << "Could not set thread local storage" << llendl;
- }
-#else
- sInstance = instancep;
-#endif
- setInitState(INITIALIZING);
- instancep->initSingleton();
- setInitState(INITIALIZED);
- }
-
- return getIfExists();
- }
-
- static DERIVED_TYPE* getIfExists()
- {
-#if LL_DARWIN
- return (DERIVED_TYPE*)pthread_getspecific(sInstanceKey);
-#else
- return sInstance;
-#endif
- }
-
- // Reference version of getInstance()
- // Preferred over getInstance() as it disallows checking for NULL
- static DERIVED_TYPE& instance()
- {
- return *getInstance();
- }
-
- // Has this singleton been created uet?
- // Use this to avoid accessing singletons before the can safely be constructed
- static bool instanceExists()
- {
- return getInitState() == INITIALIZED;
- }
-
- // Has this singleton already been deleted?
- // Use this to avoid accessing singletons from a static object's destructor
- static bool destroyed()
- {
- return getInitState() == DELETED;
- }
-private:
-#if LL_DARWIN
- static void createTLSInitState()
- {
- static S32 key_created = pthread_key_create(&sInitStateKey, NULL);
- if (key_created != 0)
- {
- llerrs << "Could not create thread local storage" << llendl;
- }
- }
-
- static void createTLSInstance()
- {
- static S32 key_created = pthread_key_create(&sInstanceKey, NULL);
- if (key_created != 0)
- {
- llerrs << "Could not create thread local storage" << llendl;
- }
- }
-#endif
- static EInitState getInitState()
- {
-#if LL_DARWIN
- createTLSInitState();
- return (EInitState)(int)pthread_getspecific(sInitStateKey);
-#else
- return sInitState;
-#endif
- }
-
- static void setInitState(EInitState state)
- {
-#if LL_DARWIN
- createTLSInitState();
- pthread_setspecific(sInitStateKey, (void*)state);
-#else
- sInitState = state;
-#endif
- }
- LLThreadLocalSingleton(const LLThreadLocalSingleton& other);
- virtual void initSingleton() {}
-
-#ifdef LL_WINDOWS
- static __declspec(thread) DERIVED_TYPE* sInstance;
- static __declspec(thread) EInitState sInitState;
-#elif LL_LINUX
- static __thread DERIVED_TYPE* sInstance;
- static __thread EInitState sInitState;
-#elif LL_DARWIN
- static pthread_key_t sInstanceKey;
- static pthread_key_t sInitStateKey;
-#endif
-};
-
-#if LL_WINDOWS
-template<typename DERIVED_TYPE>
-__declspec(thread) DERIVED_TYPE* LLThreadLocalSingleton<DERIVED_TYPE>::sInstance = NULL;
-
-template<typename DERIVED_TYPE>
-__declspec(thread) typename LLThreadLocalSingleton<DERIVED_TYPE>::EInitState LLThreadLocalSingleton<DERIVED_TYPE>::sInitState = LLThreadLocalSingleton<DERIVED_TYPE>::UNINITIALIZED;
-#elif LL_LINUX
-template<typename DERIVED_TYPE>
-__thread DERIVED_TYPE* LLThreadLocalSingleton<DERIVED_TYPE>::sInstance = NULL;
-
-template<typename DERIVED_TYPE>
-__thread typename LLThreadLocalSingleton<DERIVED_TYPE>::EInitState LLThreadLocalSingleton<DERIVED_TYPE>::sInitState = LLThreadLocalSingleton<DERIVED_TYPE>::UNINITIALIZED;
-#elif LL_DARWIN
-template<typename DERIVED_TYPE>
-pthread_key_t LLThreadLocalSingleton<DERIVED_TYPE>::sInstanceKey;
-
-template<typename DERIVED_TYPE>
-pthread_key_t LLThreadLocalSingleton<DERIVED_TYPE>::sInitStateKey;
-
-#endif
-
-template<typename DERIVED_TYPE>
class LLThreadLocalSingletonPointer
{
public:
diff --git a/indra/llcommon/lltrace.cpp b/indra/llcommon/lltrace.cpp
index 59a4b42c97..3dffbe6d4a 100644
--- a/indra/llcommon/lltrace.cpp
+++ b/indra/llcommon/lltrace.cpp
@@ -30,58 +30,26 @@
#include "lltracethreadrecorder.h"
#include "llfasttimer.h"
-static S32 sInitializationCount = 0;
-
namespace LLTrace
{
-static MasterThreadRecorder* gUIThreadRecorder = NULL;
-
-void init()
-{
- if (sInitializationCount++ == 0)
- {
- gUIThreadRecorder = new MasterThreadRecorder();
- }
-}
-
-bool isInitialized()
-{
- return sInitializationCount > 0;
-}
-
-void cleanup()
+TraceBase::TraceBase( const char* name, const char* description )
+: mName(name),
+ mDescription(description ? description : "")
{
- if (--sInitializationCount == 0)
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ if (LLTrace::get_master_thread_recorder() != NULL)
{
- delete gUIThreadRecorder;
- gUIThreadRecorder = NULL;
+ llerrs << "Attempting to declare trace object after program initialization. Trace objects should be statically initialized." << llendl;
}
+#endif
}
-MasterThreadRecorder& getUIThreadRecorder()
+const char* TraceBase::getUnitLabel()
{
- llassert(gUIThreadRecorder != NULL);
- return *gUIThreadRecorder;
+ return "";
}
-LLThreadLocalPointer<ThreadRecorder>& get_thread_recorder_ptr()
-{
- static LLThreadLocalPointer<ThreadRecorder> s_thread_recorder;
- return s_thread_recorder;
-}
-
-const LLThreadLocalPointer<ThreadRecorder>& get_thread_recorder()
-{
- return get_thread_recorder_ptr();
-}
-
-void set_thread_recorder(ThreadRecorder* recorder)
-{
- get_thread_recorder_ptr() = recorder;
-}
-
-
TimeBlockTreeNode::TimeBlockTreeNode()
: mBlock(NULL),
mParent(NULL),
diff --git a/indra/llcommon/lltrace.h b/indra/llcommon/lltrace.h
index 884a316a3b..2c45923aac 100644
--- a/indra/llcommon/lltrace.h
+++ b/indra/llcommon/lltrace.h
@@ -32,7 +32,7 @@
#include "llmemory.h"
#include "llrefcount.h"
-#include "llunit.h"
+#include "lltraceaccumulators.h"
#include "llthreadlocalstorage.h"
#include "lltimer.h"
@@ -53,198 +53,29 @@ STORAGE_TYPE storage_value(LLUnit<STORAGE_TYPE, UNIT_TYPE> val) { return val.val
template<typename UNIT_TYPE, typename STORAGE_TYPE>
STORAGE_TYPE storage_value(LLUnitImplicit<STORAGE_TYPE, UNIT_TYPE> val) { return val.value(); }
-void init();
-void cleanup();
-bool isInitialized();
-
-const LLThreadLocalPointer<class ThreadRecorder>& get_thread_recorder();
-void set_thread_recorder(class ThreadRecorder*);
-
-class MasterThreadRecorder& getUIThreadRecorder();
-
-template<typename ACCUMULATOR>
-class AccumulatorBuffer : public LLRefCount
+class TraceBase
{
- typedef AccumulatorBuffer<ACCUMULATOR> self_t;
- static const U32 DEFAULT_ACCUMULATOR_BUFFER_SIZE = 64;
-private:
- struct StaticAllocationMarker { };
-
- AccumulatorBuffer(StaticAllocationMarker m)
- : mStorageSize(0),
- mStorage(NULL)
- {}
-
public:
+ TraceBase(const char* name, const char* description);
+ virtual ~TraceBase() {};
+ virtual const char* getUnitLabel();
- AccumulatorBuffer(const AccumulatorBuffer& other = *getDefaultBuffer())
- : mStorageSize(0),
- mStorage(NULL)
- {
- resize(other.mStorageSize);
- for (S32 i = 0; i < sNextStorageSlot; i++)
- {
- mStorage[i] = other.mStorage[i];
- }
- }
-
- ~AccumulatorBuffer()
- {
- if (isPrimary())
- {
- LLThreadLocalSingletonPointer<ACCUMULATOR>::setInstance(NULL);
- }
- delete[] mStorage;
- }
-
- LL_FORCE_INLINE ACCUMULATOR& operator[](size_t index)
- {
- return mStorage[index];
- }
-
- LL_FORCE_INLINE const ACCUMULATOR& operator[](size_t index) const
- {
- return mStorage[index];
- }
-
- void addSamples(const AccumulatorBuffer<ACCUMULATOR>& other, bool append = true)
- {
- llassert(mStorageSize >= sNextStorageSlot && other.mStorageSize > sNextStorageSlot);
- for (size_t i = 0; i < sNextStorageSlot; i++)
- {
- mStorage[i].addSamples(other.mStorage[i], append);
- }
- }
-
- void copyFrom(const AccumulatorBuffer<ACCUMULATOR>& other)
- {
- llassert(mStorageSize >= sNextStorageSlot && other.mStorageSize > sNextStorageSlot);
- for (size_t i = 0; i < sNextStorageSlot; i++)
- {
- mStorage[i] = other.mStorage[i];
- }
- }
-
- void reset(const AccumulatorBuffer<ACCUMULATOR>* other = NULL)
- {
- llassert(mStorageSize >= sNextStorageSlot);
- for (size_t i = 0; i < sNextStorageSlot; i++)
- {
- mStorage[i].reset(other ? &other->mStorage[i] : NULL);
- }
- }
-
- void flush(LLUnitImplicit<F64, LLUnits::Seconds> time_stamp)
- {
- llassert(mStorageSize >= sNextStorageSlot);
- for (size_t i = 0; i < sNextStorageSlot; i++)
- {
- mStorage[i].flush(time_stamp);
- }
- }
-
- void makePrimary()
- {
- LLThreadLocalSingletonPointer<ACCUMULATOR>::setInstance(mStorage);
- }
-
- bool isPrimary() const
- {
- return LLThreadLocalSingletonPointer<ACCUMULATOR>::getInstance() == mStorage;
- }
-
- LL_FORCE_INLINE static ACCUMULATOR* getPrimaryStorage()
- {
- ACCUMULATOR* accumulator = LLThreadLocalSingletonPointer<ACCUMULATOR>::getInstance();
- return accumulator ? accumulator : getDefaultBuffer()->mStorage;
- }
-
- // NOTE: this is not thread-safe. We assume that slots are reserved in the main thread before any child threads are spawned
- size_t reserveSlot()
- {
-#ifndef LL_RELEASE_FOR_DOWNLOAD
- if (LLTrace::isInitialized())
- {
- llerrs << "Attempting to declare trace object after program initialization. Trace objects should be statically initialized." << llendl;
- }
-#endif
- size_t next_slot = sNextStorageSlot++;
- if (next_slot >= mStorageSize)
- {
- resize(mStorageSize + (mStorageSize >> 2));
- }
- llassert(mStorage && next_slot < mStorageSize);
- return next_slot;
- }
-
- void resize(size_t new_size)
- {
- if (new_size <= mStorageSize) return;
-
- ACCUMULATOR* old_storage = mStorage;
- mStorage = new ACCUMULATOR[new_size];
- if (old_storage)
- {
- for (S32 i = 0; i < mStorageSize; i++)
- {
- mStorage[i] = old_storage[i];
- }
- }
- mStorageSize = new_size;
- delete[] old_storage;
-
- self_t* default_buffer = getDefaultBuffer();
- if (this != default_buffer
- && new_size > default_buffer->size())
- {
- //NB: this is not thread safe, but we assume that all resizing occurs during static initialization
- default_buffer->resize(new_size);
- }
- }
-
- size_t size() const
- {
- return getNumIndices();
- }
-
- static size_t getNumIndices()
- {
- return sNextStorageSlot;
- }
-
- static self_t* getDefaultBuffer()
- {
- static bool sInitialized = false;
- if (!sInitialized)
- {
- // this buffer is allowed to leak so that trace calls from global destructors have somewhere to put their data
- // so as not to trigger an access violation
- sDefaultBuffer = new AccumulatorBuffer(StaticAllocationMarker());
- sInitialized = true;
- sDefaultBuffer->resize(DEFAULT_ACCUMULATOR_BUFFER_SIZE);
- }
- return sDefaultBuffer;
- }
+ const std::string& getName() const { return mName; }
-private:
- ACCUMULATOR* mStorage;
- size_t mStorageSize;
- static size_t sNextStorageSlot;
- static self_t* sDefaultBuffer;
+protected:
+ const std::string mName;
+ const std::string mDescription;
};
-template<typename ACCUMULATOR> size_t AccumulatorBuffer<ACCUMULATOR>::sNextStorageSlot = 0;
-template<typename ACCUMULATOR> AccumulatorBuffer<ACCUMULATOR>* AccumulatorBuffer<ACCUMULATOR>::sDefaultBuffer = NULL;
-
template<typename ACCUMULATOR>
class TraceType
-: public LLInstanceTracker<TraceType<ACCUMULATOR>, std::string>
+: public TraceBase,
+ public LLInstanceTracker<TraceType<ACCUMULATOR>, std::string>
{
public:
TraceType(const char* name, const char* description = NULL)
: LLInstanceTracker<TraceType<ACCUMULATOR>, std::string>(name),
- mName(name),
- mDescription(description ? description : ""),
+ TraceBase(name, description),
mAccumulatorIndex(AccumulatorBuffer<ACCUMULATOR>::getDefaultBuffer()->reserveSlot())
{}
@@ -257,354 +88,10 @@ public:
size_t getIndex() const { return mAccumulatorIndex; }
static size_t getNumIndices() { return AccumulatorBuffer<ACCUMULATOR>::getNumIndices(); }
- virtual const char* getUnitLabel() { return ""; }
-
- const std::string& getName() const { return mName; }
-
-protected:
- const std::string mName;
- const std::string mDescription;
- const size_t mAccumulatorIndex;
-};
-
-class EventAccumulator
-{
-public:
- typedef F64 value_t;
- typedef F64 mean_t;
-
- EventAccumulator()
- : mSum(0),
- mMin((std::numeric_limits<F64>::max)()),
- mMax((std::numeric_limits<F64>::min)()),
- mMean(0),
- mVarianceSum(0),
- mNumSamples(0),
- mLastValue(0)
- {}
-
- void record(F64 value)
- {
- mNumSamples++;
- mSum += value;
- // NOTE: both conditions will hold on first pass through
- if (value < mMin)
- {
- mMin = value;
- }
- if (value > mMax)
- {
- mMax = value;
- }
- F64 old_mean = mMean;
- mMean += (value - old_mean) / (F64)mNumSamples;
- mVarianceSum += (value - old_mean) * (value - mMean);
- mLastValue = value;
- }
-
- void addSamples(const EventAccumulator& other, bool append)
- {
- if (other.mNumSamples)
- {
- mSum += other.mSum;
-
- // NOTE: both conditions will hold first time through
- if (other.mMin < mMin) { mMin = other.mMin; }
- if (other.mMax > mMax) { mMax = other.mMax; }
-
- // combine variance (and hence standard deviation) of 2 different sized sample groups using
- // the following formula: http://www.mrc-bsu.cam.ac.uk/cochrane/handbook/chapter_7/7_7_3_8_combining_groups.htm
- F64 n_1 = (F64)mNumSamples,
- n_2 = (F64)other.mNumSamples;
- F64 m_1 = mMean,
- m_2 = other.mMean;
- F64 v_1 = mVarianceSum / mNumSamples,
- v_2 = other.mVarianceSum / other.mNumSamples;
- if (n_1 == 0)
- {
- mVarianceSum = other.mVarianceSum;
- }
- else if (n_2 == 0)
- {
- // don't touch variance
- // mVarianceSum = mVarianceSum;
- }
- else
- {
- mVarianceSum = (F64)mNumSamples
- * ((((n_1 - 1.f) * v_1)
- + ((n_2 - 1.f) * v_2)
- + (((n_1 * n_2) / (n_1 + n_2))
- * ((m_1 * m_1) + (m_2 * m_2) - (2.f * m_1 * m_2))))
- / (n_1 + n_2 - 1.f));
- }
-
- F64 weight = (F64)mNumSamples / (F64)(mNumSamples + other.mNumSamples);
- mNumSamples += other.mNumSamples;
- mMean = mMean * weight + other.mMean * (1.f - weight);
- if (append) mLastValue = other.mLastValue;
- }
- }
-
- void reset(const EventAccumulator* other)
- {
- mNumSamples = 0;
- mSum = 0;
- mMin = std::numeric_limits<F64>::max();
- mMax = std::numeric_limits<F64>::min();
- mMean = 0;
- mVarianceSum = 0;
- mLastValue = other ? other->mLastValue : 0;
- }
-
- void flush(LLUnitImplicit<F64, LLUnits::Seconds>) {}
-
- F64 getSum() const { return mSum; }
- F64 getMin() const { return mMin; }
- F64 getMax() const { return mMax; }
- F64 getLastValue() const { return mLastValue; }
- F64 getMean() const { return mMean; }
- F64 getStandardDeviation() const { return sqrtf(mVarianceSum / mNumSamples); }
- U32 getSampleCount() const { return mNumSamples; }
-
-private:
- F64 mSum,
- mMin,
- mMax,
- mLastValue;
-
- F64 mMean,
- mVarianceSum;
-
- U32 mNumSamples;
-};
-
-
-class SampleAccumulator
-{
-public:
- typedef F64 value_t;
- typedef F64 mean_t;
-
- SampleAccumulator()
- : mSum(0),
- mMin((std::numeric_limits<F64>::max)()),
- mMax((std::numeric_limits<F64>::min)()),
- mMean(0),
- mVarianceSum(0),
- mLastSampleTimeStamp(LLTimer::getTotalSeconds()),
- mTotalSamplingTime(0),
- mNumSamples(0),
- mLastValue(0),
- mHasValue(false)
- {}
-
- void sample(F64 value)
- {
- LLUnitImplicit<F64, LLUnits::Seconds> time_stamp = LLTimer::getTotalSeconds();
- LLUnitImplicit<F64, LLUnits::Seconds> delta_time = time_stamp - mLastSampleTimeStamp;
- mLastSampleTimeStamp = time_stamp;
-
- if (mHasValue)
- {
- mTotalSamplingTime += delta_time;
- mSum += mLastValue * delta_time;
-
- // NOTE: both conditions will hold first time through
- if (value < mMin) { mMin = value; }
- if (value > mMax) { mMax = value; }
-
- F64 old_mean = mMean;
- mMean += (delta_time / mTotalSamplingTime) * (mLastValue - old_mean);
- mVarianceSum += delta_time * (mLastValue - old_mean) * (mLastValue - mMean);
- }
-
- mLastValue = value;
- mNumSamples++;
- mHasValue = true;
- }
-
- void addSamples(const SampleAccumulator& other, bool append)
- {
- if (other.mTotalSamplingTime)
- {
- mSum += other.mSum;
-
- // NOTE: both conditions will hold first time through
- if (other.mMin < mMin) { mMin = other.mMin; }
- if (other.mMax > mMax) { mMax = other.mMax; }
-
- // combine variance (and hence standard deviation) of 2 different sized sample groups using
- // the following formula: http://www.mrc-bsu.cam.ac.uk/cochrane/handbook/chapter_7/7_7_3_8_combining_groups.htm
- F64 n_1 = mTotalSamplingTime,
- n_2 = other.mTotalSamplingTime;
- F64 m_1 = mMean,
- m_2 = other.mMean;
- F64 v_1 = mVarianceSum / mTotalSamplingTime,
- v_2 = other.mVarianceSum / other.mTotalSamplingTime;
- if (n_1 == 0)
- {
- mVarianceSum = other.mVarianceSum;
- }
- else if (n_2 == 0)
- {
- // variance is unchanged
- // mVarianceSum = mVarianceSum;
- }
- else
- {
- mVarianceSum = mTotalSamplingTime
- * ((((n_1 - 1.f) * v_1)
- + ((n_2 - 1.f) * v_2)
- + (((n_1 * n_2) / (n_1 + n_2))
- * ((m_1 * m_1) + (m_2 * m_2) - (2.f * m_1 * m_2))))
- / (n_1 + n_2 - 1.f));
- }
-
- llassert(other.mTotalSamplingTime > 0);
- F64 weight = mTotalSamplingTime / (mTotalSamplingTime + other.mTotalSamplingTime);
- mNumSamples += other.mNumSamples;
- mTotalSamplingTime += other.mTotalSamplingTime;
- mMean = (mMean * weight) + (other.mMean * (1.0 - weight));
- if (append)
- {
- mLastValue = other.mLastValue;
- mLastSampleTimeStamp = other.mLastSampleTimeStamp;
- mHasValue |= other.mHasValue;
- }
- }
- }
-
- void reset(const SampleAccumulator* other)
- {
- mNumSamples = 0;
- mSum = 0;
- mMin = std::numeric_limits<F64>::max();
- mMax = std::numeric_limits<F64>::min();
- mMean = other ? other->mLastValue : 0;
- mVarianceSum = 0;
- mLastSampleTimeStamp = LLTimer::getTotalSeconds();
- mTotalSamplingTime = 0;
- mLastValue = other ? other->mLastValue : 0;
- mHasValue = other ? other->mHasValue : false;
- }
-
- void flush(LLUnitImplicit<F64, LLUnits::Seconds> time_stamp)
- {
- LLUnitImplicit<F64, LLUnits::Seconds> delta_time = time_stamp - mLastSampleTimeStamp;
-
- if (mHasValue)
- {
- mSum += mLastValue * delta_time;
- mTotalSamplingTime += delta_time;
- }
- mLastSampleTimeStamp = time_stamp;
- }
-
- F64 getSum() const { return mSum; }
- F64 getMin() const { return mMin; }
- F64 getMax() const { return mMax; }
- F64 getLastValue() const { return mLastValue; }
- F64 getMean() const { return mMean; }
- F64 getStandardDeviation() const { return sqrtf(mVarianceSum / mTotalSamplingTime); }
- U32 getSampleCount() const { return mNumSamples; }
-
-private:
- F64 mSum,
- mMin,
- mMax,
- mLastValue;
-
- bool mHasValue;
-
- F64 mMean,
- mVarianceSum;
-
- LLUnitImplicit<F64, LLUnits::Seconds> mLastSampleTimeStamp,
- mTotalSamplingTime;
-
- U32 mNumSamples;
-};
-
-class CountAccumulator
-{
-public:
- typedef F64 value_t;
- typedef F64 mean_t;
-
- CountAccumulator()
- : mSum(0),
- mNumSamples(0)
- {}
-
- void add(F64 value)
- {
- mNumSamples++;
- mSum += value;
- }
-
- void addSamples(const CountAccumulator& other, bool /*append*/)
- {
- mSum += other.mSum;
- mNumSamples += other.mNumSamples;
- }
-
- void reset(const CountAccumulator* other)
- {
- mNumSamples = 0;
- mSum = 0;
- }
-
- void flush(LLUnitImplicit<F64, LLUnits::Seconds>) {}
-
- F64 getSum() const { return mSum; }
-
- U32 getSampleCount() const { return mNumSamples; }
-
private:
- F64 mSum;
-
- U32 mNumSamples;
+ const size_t mAccumulatorIndex;
};
-class TimeBlockAccumulator
-{
-public:
- typedef LLUnit<F64, LLUnits::Seconds> value_t;
- typedef LLUnit<F64, LLUnits::Seconds> mean_t;
- typedef TimeBlockAccumulator self_t;
-
- // fake classes that allows us to view different facets of underlying statistic
- struct CallCountFacet
- {
- typedef U32 value_t;
- typedef F32 mean_t;
- };
-
- struct SelfTimeFacet
- {
- typedef LLUnit<F64, LLUnits::Seconds> value_t;
- typedef LLUnit<F64, LLUnits::Seconds> mean_t;
- };
-
- TimeBlockAccumulator();
- void addSamples(const self_t& other, bool /*append*/);
- void reset(const self_t* other);
- void flush(LLUnitImplicit<F64, LLUnits::Seconds>) {}
-
- //
- // members
- //
- U64 mStartTotalTimeCounter,
- mTotalTimeCounter,
- mSelfTimeCounter;
- U32 mCalls;
- class TimeBlock* mParent; // last acknowledged parent of this time block
- class TimeBlock* mLastCaller; // used to bootstrap tree construction
- U16 mActiveCount; // number of timers with this ID active on stack
- bool mMoveUpTree; // needs to be moved up the tree of timers at the end of frame
-
-};
template<>
class TraceType<TimeBlockAccumulator::CallCountFacet>
@@ -628,23 +115,6 @@ public:
{}
};
-class TimeBlock;
-class TimeBlockTreeNode
-{
-public:
- TimeBlockTreeNode();
-
- void setParent(TimeBlock* parent);
- TimeBlock* getParent() { return mParent; }
-
- TimeBlock* mBlock;
- TimeBlock* mParent;
- std::vector<TimeBlock*> mChildren;
- bool mCollapsed;
- bool mNeedsSorting;
-};
-
-
template <typename T = F64>
class EventStatHandle
: public TraceType<EventAccumulator>
@@ -712,64 +182,6 @@ void add(CountStatHandle<T>& count, VALUE_T value)
count.getPrimaryAccumulator()->add(storage_value(converted_value));
}
-
-struct MemStatAccumulator
-{
- typedef MemStatAccumulator self_t;
-
- // fake classes that allows us to view different facets of underlying statistic
- struct AllocationCountFacet
- {
- typedef U32 value_t;
- typedef F32 mean_t;
- };
-
- struct DeallocationCountFacet
- {
- typedef U32 value_t;
- typedef F32 mean_t;
- };
-
- struct ChildMemFacet
- {
- typedef LLUnit<F64, LLUnits::Bytes> value_t;
- typedef LLUnit<F64, LLUnits::Bytes> mean_t;
- };
-
- MemStatAccumulator()
- : mAllocatedCount(0),
- mDeallocatedCount(0)
- {}
-
- void addSamples(const MemStatAccumulator& other, bool append)
- {
- mSize.addSamples(other.mSize, append);
- mChildSize.addSamples(other.mChildSize, append);
- mAllocatedCount += other.mAllocatedCount;
- mDeallocatedCount += other.mDeallocatedCount;
- }
-
- void reset(const MemStatAccumulator* other)
- {
- mSize.reset(other ? &other->mSize : NULL);
- mChildSize.reset(other ? &other->mChildSize : NULL);
- mAllocatedCount = 0;
- mDeallocatedCount = 0;
- }
-
- void flush(LLUnitImplicit<F64, LLUnits::Seconds> time_stamp)
- {
- mSize.flush(time_stamp);
- mChildSize.flush(time_stamp);
- }
-
- SampleAccumulator mSize,
- mChildSize;
- int mAllocatedCount,
- mDeallocatedCount;
-};
-
-
template<>
class TraceType<MemStatAccumulator::AllocationCountFacet>
: public TraceType<MemStatAccumulator>
@@ -912,7 +324,7 @@ class MemTrackable
template<typename TRACKED, typename TRACKED_IS_TRACKER>
struct TrackMemImpl;
- typedef MemTrackable<DERIVED> mem_trackable_t;
+ typedef MemTrackable<DERIVED, ALIGNMENT> mem_trackable_t;
public:
typedef void mem_trackable_tag_t;
@@ -986,14 +398,16 @@ public:
}
- void memClaimAmount(size_t size)
+ template<typename AMOUNT_T>
+ AMOUNT_T& memClaimAmount(AMOUNT_T& size)
{
MemStatAccumulator* accumulator = DERIVED::sMemStat.getPrimaryAccumulator();
- mMemFootprint += size;
+ mMemFootprint += (size_t)size;
if (accumulator)
{
accumulator->mSize.sample(accumulator->mSize.getLastValue() + (F64)size);
}
+ return size;
}
// remove memory we had claimed from our calculated footprint
@@ -1011,13 +425,15 @@ public:
return value;
}
- void memDisclaimAmount(size_t size)
+ template<typename AMOUNT_T>
+ AMOUNT_T& memDisclaimAmount(AMOUNT_T& size)
{
MemStatAccumulator* accumulator = DERIVED::sMemStat.getPrimaryAccumulator();
if (accumulator)
{
accumulator->mSize.sample(accumulator->mSize.getLastValue() - (F64)size);
}
+ return size;
}
private:
diff --git a/indra/llcommon/lltraceaccumulators.cpp b/indra/llcommon/lltraceaccumulators.cpp
new file mode 100644
index 0000000000..950c1d97d1
--- /dev/null
+++ b/indra/llcommon/lltraceaccumulators.cpp
@@ -0,0 +1,123 @@
+/**
+ * @file lltracesampler.cpp
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2012, 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 "linden_common.h"
+
+#include "lltraceaccumulators.h"
+#include "lltracethreadrecorder.h"
+
+namespace LLTrace
+{
+
+
+///////////////////////////////////////////////////////////////////////
+// AccumulatorBufferGroup
+///////////////////////////////////////////////////////////////////////
+
+AccumulatorBufferGroup::AccumulatorBufferGroup()
+{}
+
+void AccumulatorBufferGroup::handOffTo(AccumulatorBufferGroup& other)
+{
+ other.mCounts.reset(&mCounts);
+ other.mSamples.reset(&mSamples);
+ other.mEvents.reset(&mEvents);
+ other.mStackTimers.reset(&mStackTimers);
+ other.mMemStats.reset(&mMemStats);
+}
+
+void AccumulatorBufferGroup::makePrimary()
+{
+ mCounts.makePrimary();
+ mSamples.makePrimary();
+ mEvents.makePrimary();
+ mStackTimers.makePrimary();
+ mMemStats.makePrimary();
+
+ ThreadRecorder* thread_recorder = get_thread_recorder().get();
+ AccumulatorBuffer<TimeBlockAccumulator>& timer_accumulator_buffer = mStackTimers;
+ // update stacktimer parent pointers
+ for (S32 i = 0, end_i = mStackTimers.size(); i < end_i; i++)
+ {
+ TimeBlockTreeNode* tree_node = thread_recorder->getTimeBlockTreeNode(i);
+ if (tree_node)
+ {
+ timer_accumulator_buffer[i].mParent = tree_node->mParent;
+ }
+ }
+}
+
+//static
+void AccumulatorBufferGroup::clearPrimary()
+{
+ AccumulatorBuffer<CountAccumulator>::clearPrimary();
+ AccumulatorBuffer<SampleAccumulator>::clearPrimary();
+ AccumulatorBuffer<EventAccumulator>::clearPrimary();
+ AccumulatorBuffer<TimeBlockAccumulator>::clearPrimary();
+ AccumulatorBuffer<MemStatAccumulator>::clearPrimary();
+}
+
+bool AccumulatorBufferGroup::isPrimary() const
+{
+ return mCounts.isPrimary();
+}
+
+void AccumulatorBufferGroup::append( const AccumulatorBufferGroup& other )
+{
+ mCounts.addSamples(other.mCounts);
+ mSamples.addSamples(other.mSamples);
+ mEvents.addSamples(other.mEvents);
+ mMemStats.addSamples(other.mMemStats);
+ mStackTimers.addSamples(other.mStackTimers);
+}
+
+void AccumulatorBufferGroup::merge( const AccumulatorBufferGroup& other)
+{
+ mCounts.addSamples(other.mCounts, false);
+ mSamples.addSamples(other.mSamples, false);
+ mEvents.addSamples(other.mEvents, false);
+ mMemStats.addSamples(other.mMemStats, false);
+ // for now, hold out timers from merge, need to be displayed per thread
+ //mStackTimers.addSamples(other.mStackTimers, false);
+}
+
+void AccumulatorBufferGroup::reset(AccumulatorBufferGroup* other)
+{
+ mCounts.reset(other ? &other->mCounts : NULL);
+ mSamples.reset(other ? &other->mSamples : NULL);
+ mEvents.reset(other ? &other->mEvents : NULL);
+ mStackTimers.reset(other ? &other->mStackTimers : NULL);
+ mMemStats.reset(other ? &other->mMemStats : NULL);
+}
+
+void AccumulatorBufferGroup::sync()
+{
+ LLUnitImplicit<F64, LLUnits::Seconds> time_stamp = LLTimer::getTotalSeconds();
+
+ mSamples.sync(time_stamp);
+ mMemStats.sync(time_stamp);
+}
+
+}
diff --git a/indra/llcommon/lltraceaccumulators.h b/indra/llcommon/lltraceaccumulators.h
new file mode 100644
index 0000000000..fac6347ff9
--- /dev/null
+++ b/indra/llcommon/lltraceaccumulators.h
@@ -0,0 +1,656 @@
+/**
+ * @file lltraceaccumulators.h
+ * @brief Storage for accumulating statistics
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2012, 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$
+ */
+
+#ifndef LL_LLTRACEACCUMULATORS_H
+#define LL_LLTRACEACCUMULATORS_H
+
+
+#include "stdtypes.h"
+#include "llpreprocessor.h"
+#include "llunit.h"
+#include "lltimer.h"
+#include "llrefcount.h"
+#include "llthreadlocalstorage.h"
+
+namespace LLTrace
+{
+
+ template<typename ACCUMULATOR>
+ class AccumulatorBuffer : public LLRefCount
+ {
+ typedef AccumulatorBuffer<ACCUMULATOR> self_t;
+ static const U32 DEFAULT_ACCUMULATOR_BUFFER_SIZE = 64;
+ private:
+ struct StaticAllocationMarker { };
+
+ AccumulatorBuffer(StaticAllocationMarker m)
+ : mStorageSize(0),
+ mStorage(NULL)
+ {}
+
+ public:
+
+ AccumulatorBuffer(const AccumulatorBuffer& other = *getDefaultBuffer())
+ : mStorageSize(0),
+ mStorage(NULL)
+ {
+ resize(other.mStorageSize);
+ for (S32 i = 0; i < sNextStorageSlot; i++)
+ {
+ mStorage[i] = other.mStorage[i];
+ }
+ }
+
+ ~AccumulatorBuffer()
+ {
+ if (isPrimary())
+ {
+ LLThreadLocalSingletonPointer<ACCUMULATOR>::setInstance(NULL);
+ }
+ delete[] mStorage;
+ }
+
+ LL_FORCE_INLINE ACCUMULATOR& operator[](size_t index)
+ {
+ return mStorage[index];
+ }
+
+ LL_FORCE_INLINE const ACCUMULATOR& operator[](size_t index) const
+ {
+ return mStorage[index];
+ }
+
+ void addSamples(const AccumulatorBuffer<ACCUMULATOR>& other, bool append = true)
+ {
+ llassert(mStorageSize >= sNextStorageSlot && other.mStorageSize > sNextStorageSlot);
+ for (size_t i = 0; i < sNextStorageSlot; i++)
+ {
+ mStorage[i].addSamples(other.mStorage[i], append);
+ }
+ }
+
+ void copyFrom(const AccumulatorBuffer<ACCUMULATOR>& other)
+ {
+ llassert(mStorageSize >= sNextStorageSlot && other.mStorageSize > sNextStorageSlot);
+ for (size_t i = 0; i < sNextStorageSlot; i++)
+ {
+ mStorage[i] = other.mStorage[i];
+ }
+ }
+
+ void reset(const AccumulatorBuffer<ACCUMULATOR>* other = NULL)
+ {
+ llassert(mStorageSize >= sNextStorageSlot);
+ for (size_t i = 0; i < sNextStorageSlot; i++)
+ {
+ mStorage[i].reset(other ? &other->mStorage[i] : NULL);
+ }
+ }
+
+ void sync(LLUnitImplicit<F64, LLUnits::Seconds> time_stamp)
+ {
+ llassert(mStorageSize >= sNextStorageSlot);
+ for (size_t i = 0; i < sNextStorageSlot; i++)
+ {
+ mStorage[i].sync(time_stamp);
+ }
+ }
+
+ void makePrimary()
+ {
+ LLThreadLocalSingletonPointer<ACCUMULATOR>::setInstance(mStorage);
+ }
+
+ bool isPrimary() const
+ {
+ return LLThreadLocalSingletonPointer<ACCUMULATOR>::getInstance() == mStorage;
+ }
+
+ static void clearPrimary()
+ {
+ LLThreadLocalSingletonPointer<ACCUMULATOR>::setInstance(NULL);
+ }
+
+ LL_FORCE_INLINE static ACCUMULATOR* getPrimaryStorage()
+ {
+ ACCUMULATOR* accumulator = LLThreadLocalSingletonPointer<ACCUMULATOR>::getInstance();
+ return accumulator ? accumulator : getDefaultBuffer()->mStorage;
+ }
+
+ // NOTE: this is not thread-safe. We assume that slots are reserved in the main thread before any child threads are spawned
+ size_t reserveSlot()
+ {
+ size_t next_slot = sNextStorageSlot++;
+ if (next_slot >= mStorageSize)
+ {
+ resize(mStorageSize + (mStorageSize >> 2));
+ }
+ llassert(mStorage && next_slot < mStorageSize);
+ return next_slot;
+ }
+
+ void resize(size_t new_size)
+ {
+ if (new_size <= mStorageSize) return;
+
+ ACCUMULATOR* old_storage = mStorage;
+ mStorage = new ACCUMULATOR[new_size];
+ if (old_storage)
+ {
+ for (S32 i = 0; i < mStorageSize; i++)
+ {
+ mStorage[i] = old_storage[i];
+ }
+ }
+ mStorageSize = new_size;
+ delete[] old_storage;
+
+ self_t* default_buffer = getDefaultBuffer();
+ if (this != default_buffer
+ && new_size > default_buffer->size())
+ {
+ //NB: this is not thread safe, but we assume that all resizing occurs during static initialization
+ default_buffer->resize(new_size);
+ }
+ }
+
+ size_t size() const
+ {
+ return getNumIndices();
+ }
+
+ static size_t getNumIndices()
+ {
+ return sNextStorageSlot;
+ }
+
+ static self_t* getDefaultBuffer()
+ {
+ static bool sInitialized = false;
+ if (!sInitialized)
+ {
+ // this buffer is allowed to leak so that trace calls from global destructors have somewhere to put their data
+ // so as not to trigger an access violation
+ sDefaultBuffer = new AccumulatorBuffer(StaticAllocationMarker());
+ sInitialized = true;
+ sDefaultBuffer->resize(DEFAULT_ACCUMULATOR_BUFFER_SIZE);
+ }
+ return sDefaultBuffer;
+ }
+
+ private:
+ ACCUMULATOR* mStorage;
+ size_t mStorageSize;
+ static size_t sNextStorageSlot;
+ static self_t* sDefaultBuffer;
+ };
+
+ template<typename ACCUMULATOR> size_t AccumulatorBuffer<ACCUMULATOR>::sNextStorageSlot = 0;
+ template<typename ACCUMULATOR> AccumulatorBuffer<ACCUMULATOR>* AccumulatorBuffer<ACCUMULATOR>::sDefaultBuffer = NULL;
+
+
+ class EventAccumulator
+ {
+ public:
+ typedef F64 value_t;
+ typedef F64 mean_t;
+
+ EventAccumulator()
+ : mSum(0),
+ mMin((std::numeric_limits<F64>::max)()),
+ mMax((std::numeric_limits<F64>::min)()),
+ mMean(0),
+ mVarianceSum(0),
+ mNumSamples(0),
+ mLastValue(0)
+ {}
+
+ void record(F64 value)
+ {
+ mNumSamples++;
+ mSum += value;
+ // NOTE: both conditions will hold on first pass through
+ if (value < mMin)
+ {
+ mMin = value;
+ }
+ if (value > mMax)
+ {
+ mMax = value;
+ }
+ F64 old_mean = mMean;
+ mMean += (value - old_mean) / (F64)mNumSamples;
+ mVarianceSum += (value - old_mean) * (value - mMean);
+ mLastValue = value;
+ }
+
+ void addSamples(const EventAccumulator& other, bool append)
+ {
+ if (other.mNumSamples)
+ {
+ mSum += other.mSum;
+
+ // NOTE: both conditions will hold first time through
+ if (other.mMin < mMin) { mMin = other.mMin; }
+ if (other.mMax > mMax) { mMax = other.mMax; }
+
+ // combine variance (and hence standard deviation) of 2 different sized sample groups using
+ // the following formula: http://www.mrc-bsu.cam.ac.uk/cochrane/handbook/chapter_7/7_7_3_8_combining_groups.htm
+ F64 n_1 = (F64)mNumSamples,
+ n_2 = (F64)other.mNumSamples;
+ F64 m_1 = mMean,
+ m_2 = other.mMean;
+ F64 v_1 = mVarianceSum / mNumSamples,
+ v_2 = other.mVarianceSum / other.mNumSamples;
+ if (n_1 == 0)
+ {
+ mVarianceSum = other.mVarianceSum;
+ }
+ else if (n_2 == 0)
+ {
+ // don't touch variance
+ // mVarianceSum = mVarianceSum;
+ }
+ else
+ {
+ mVarianceSum = (F64)mNumSamples
+ * ((((n_1 - 1.f) * v_1)
+ + ((n_2 - 1.f) * v_2)
+ + (((n_1 * n_2) / (n_1 + n_2))
+ * ((m_1 * m_1) + (m_2 * m_2) - (2.f * m_1 * m_2))))
+ / (n_1 + n_2 - 1.f));
+ }
+
+ F64 weight = (F64)mNumSamples / (F64)(mNumSamples + other.mNumSamples);
+ mNumSamples += other.mNumSamples;
+ mMean = mMean * weight + other.mMean * (1.f - weight);
+ if (append) mLastValue = other.mLastValue;
+ }
+ }
+
+ void reset(const EventAccumulator* other)
+ {
+ mNumSamples = 0;
+ mSum = 0;
+ mMin = std::numeric_limits<F64>::max();
+ mMax = std::numeric_limits<F64>::min();
+ mMean = 0;
+ mVarianceSum = 0;
+ mLastValue = other ? other->mLastValue : 0;
+ }
+
+ void sync(LLUnitImplicit<F64, LLUnits::Seconds>) {}
+
+ F64 getSum() const { return mSum; }
+ F64 getMin() const { return mMin; }
+ F64 getMax() const { return mMax; }
+ F64 getLastValue() const { return mLastValue; }
+ F64 getMean() const { return mMean; }
+ F64 getStandardDeviation() const { return sqrtf(mVarianceSum / mNumSamples); }
+ U32 getSampleCount() const { return mNumSamples; }
+
+ private:
+ F64 mSum,
+ mMin,
+ mMax,
+ mLastValue;
+
+ F64 mMean,
+ mVarianceSum;
+
+ U32 mNumSamples;
+ };
+
+
+ class SampleAccumulator
+ {
+ public:
+ typedef F64 value_t;
+ typedef F64 mean_t;
+
+ SampleAccumulator()
+ : mSum(0),
+ mMin((std::numeric_limits<F64>::max)()),
+ mMax((std::numeric_limits<F64>::min)()),
+ mMean(0),
+ mVarianceSum(0),
+ mLastSampleTimeStamp(LLTimer::getTotalSeconds()),
+ mTotalSamplingTime(0),
+ mNumSamples(0),
+ mLastValue(0),
+ mHasValue(false)
+ {}
+
+ void sample(F64 value)
+ {
+ LLUnitImplicit<F64, LLUnits::Seconds> time_stamp = LLTimer::getTotalSeconds();
+ LLUnitImplicit<F64, LLUnits::Seconds> delta_time = time_stamp - mLastSampleTimeStamp;
+ mLastSampleTimeStamp = time_stamp;
+
+ if (mHasValue)
+ {
+ mTotalSamplingTime += delta_time;
+ mSum += mLastValue * delta_time;
+
+ // NOTE: both conditions will hold first time through
+ if (value < mMin) { mMin = value; }
+ if (value > mMax) { mMax = value; }
+
+ F64 old_mean = mMean;
+ mMean += (delta_time / mTotalSamplingTime) * (mLastValue - old_mean);
+ mVarianceSum += delta_time * (mLastValue - old_mean) * (mLastValue - mMean);
+ }
+
+ mLastValue = value;
+ mNumSamples++;
+ mHasValue = true;
+ }
+
+ void addSamples(const SampleAccumulator& other, bool append)
+ {
+ if (other.mTotalSamplingTime)
+ {
+ mSum += other.mSum;
+
+ // NOTE: both conditions will hold first time through
+ if (other.mMin < mMin) { mMin = other.mMin; }
+ if (other.mMax > mMax) { mMax = other.mMax; }
+
+ // combine variance (and hence standard deviation) of 2 different sized sample groups using
+ // the following formula: http://www.mrc-bsu.cam.ac.uk/cochrane/handbook/chapter_7/7_7_3_8_combining_groups.htm
+ F64 n_1 = mTotalSamplingTime,
+ n_2 = other.mTotalSamplingTime;
+ F64 m_1 = mMean,
+ m_2 = other.mMean;
+ F64 v_1 = mVarianceSum / mTotalSamplingTime,
+ v_2 = other.mVarianceSum / other.mTotalSamplingTime;
+ if (n_1 == 0)
+ {
+ mVarianceSum = other.mVarianceSum;
+ }
+ else if (n_2 == 0)
+ {
+ // variance is unchanged
+ // mVarianceSum = mVarianceSum;
+ }
+ else
+ {
+ mVarianceSum = mTotalSamplingTime
+ * ((((n_1 - 1.f) * v_1)
+ + ((n_2 - 1.f) * v_2)
+ + (((n_1 * n_2) / (n_1 + n_2))
+ * ((m_1 * m_1) + (m_2 * m_2) - (2.f * m_1 * m_2))))
+ / (n_1 + n_2 - 1.f));
+ }
+
+ llassert(other.mTotalSamplingTime > 0);
+ F64 weight = mTotalSamplingTime / (mTotalSamplingTime + other.mTotalSamplingTime);
+ mNumSamples += other.mNumSamples;
+ mTotalSamplingTime += other.mTotalSamplingTime;
+ mMean = (mMean * weight) + (other.mMean * (1.0 - weight));
+ if (append)
+ {
+ mLastValue = other.mLastValue;
+ mLastSampleTimeStamp = other.mLastSampleTimeStamp;
+ mHasValue |= other.mHasValue;
+ }
+ }
+ }
+
+ void reset(const SampleAccumulator* other)
+ {
+ mNumSamples = 0;
+ mSum = 0;
+ mMin = std::numeric_limits<F64>::max();
+ mMax = std::numeric_limits<F64>::min();
+ mMean = other ? other->mLastValue : 0;
+ mVarianceSum = 0;
+ mLastSampleTimeStamp = LLTimer::getTotalSeconds();
+ mTotalSamplingTime = 0;
+ mLastValue = other ? other->mLastValue : 0;
+ mHasValue = other ? other->mHasValue : false;
+ }
+
+ void sync(LLUnitImplicit<F64, LLUnits::Seconds> time_stamp)
+ {
+ LLUnitImplicit<F64, LLUnits::Seconds> delta_time = time_stamp - mLastSampleTimeStamp;
+
+ if (mHasValue)
+ {
+ mSum += mLastValue * delta_time;
+ mTotalSamplingTime += delta_time;
+ }
+ mLastSampleTimeStamp = time_stamp;
+ }
+
+ F64 getSum() const { return mSum; }
+ F64 getMin() const { return mMin; }
+ F64 getMax() const { return mMax; }
+ F64 getLastValue() const { return mLastValue; }
+ F64 getMean() const { return mMean; }
+ F64 getStandardDeviation() const { return sqrtf(mVarianceSum / mTotalSamplingTime); }
+ U32 getSampleCount() const { return mNumSamples; }
+
+ private:
+ F64 mSum,
+ mMin,
+ mMax,
+ mLastValue;
+
+ bool mHasValue;
+
+ F64 mMean,
+ mVarianceSum;
+
+ LLUnitImplicit<F64, LLUnits::Seconds> mLastSampleTimeStamp,
+ mTotalSamplingTime;
+
+ U32 mNumSamples;
+ };
+
+ class CountAccumulator
+ {
+ public:
+ typedef F64 value_t;
+ typedef F64 mean_t;
+
+ CountAccumulator()
+ : mSum(0),
+ mNumSamples(0)
+ {}
+
+ void add(F64 value)
+ {
+ mNumSamples++;
+ mSum += value;
+ }
+
+ void addSamples(const CountAccumulator& other, bool /*append*/)
+ {
+ mSum += other.mSum;
+ mNumSamples += other.mNumSamples;
+ }
+
+ void reset(const CountAccumulator* other)
+ {
+ mNumSamples = 0;
+ mSum = 0;
+ }
+
+ void sync(LLUnitImplicit<F64, LLUnits::Seconds>) {}
+
+ F64 getSum() const { return mSum; }
+
+ U32 getSampleCount() const { return mNumSamples; }
+
+ private:
+ F64 mSum;
+
+ U32 mNumSamples;
+ };
+
+ class TimeBlockAccumulator
+ {
+ public:
+ typedef LLUnit<F64, LLUnits::Seconds> value_t;
+ typedef LLUnit<F64, LLUnits::Seconds> mean_t;
+ typedef TimeBlockAccumulator self_t;
+
+ // fake classes that allows us to view different facets of underlying statistic
+ struct CallCountFacet
+ {
+ typedef U32 value_t;
+ typedef F32 mean_t;
+ };
+
+ struct SelfTimeFacet
+ {
+ typedef LLUnit<F64, LLUnits::Seconds> value_t;
+ typedef LLUnit<F64, LLUnits::Seconds> mean_t;
+ };
+
+ TimeBlockAccumulator();
+ void addSamples(const self_t& other, bool /*append*/);
+ void reset(const self_t* other);
+ void sync(LLUnitImplicit<F64, LLUnits::Seconds>) {}
+
+ //
+ // members
+ //
+ U64 mStartTotalTimeCounter,
+ mTotalTimeCounter,
+ mSelfTimeCounter;
+ U32 mCalls;
+ class TimeBlock* mParent; // last acknowledged parent of this time block
+ class TimeBlock* mLastCaller; // used to bootstrap tree construction
+ U16 mActiveCount; // number of timers with this ID active on stack
+ bool mMoveUpTree; // needs to be moved up the tree of timers at the end of frame
+
+ };
+
+ class TimeBlock;
+ class TimeBlockTreeNode
+ {
+ public:
+ TimeBlockTreeNode();
+
+ void setParent(TimeBlock* parent);
+ TimeBlock* getParent() { return mParent; }
+
+ TimeBlock* mBlock;
+ TimeBlock* mParent;
+ std::vector<TimeBlock*> mChildren;
+ bool mCollapsed;
+ bool mNeedsSorting;
+ };
+
+ struct BlockTimerStackRecord
+ {
+ class BlockTimer* mActiveTimer;
+ class TimeBlock* mTimeBlock;
+ U64 mChildTime;
+ };
+
+ struct MemStatAccumulator
+ {
+ typedef MemStatAccumulator self_t;
+
+ // fake classes that allows us to view different facets of underlying statistic
+ struct AllocationCountFacet
+ {
+ typedef U32 value_t;
+ typedef F32 mean_t;
+ };
+
+ struct DeallocationCountFacet
+ {
+ typedef U32 value_t;
+ typedef F32 mean_t;
+ };
+
+ struct ChildMemFacet
+ {
+ typedef LLUnit<F64, LLUnits::Bytes> value_t;
+ typedef LLUnit<F64, LLUnits::Bytes> mean_t;
+ };
+
+ MemStatAccumulator()
+ : mAllocatedCount(0),
+ mDeallocatedCount(0)
+ {}
+
+ void addSamples(const MemStatAccumulator& other, bool append)
+ {
+ mSize.addSamples(other.mSize, append);
+ mChildSize.addSamples(other.mChildSize, append);
+ mAllocatedCount += other.mAllocatedCount;
+ mDeallocatedCount += other.mDeallocatedCount;
+ }
+
+ void reset(const MemStatAccumulator* other)
+ {
+ mSize.reset(other ? &other->mSize : NULL);
+ mChildSize.reset(other ? &other->mChildSize : NULL);
+ mAllocatedCount = 0;
+ mDeallocatedCount = 0;
+ }
+
+ void sync(LLUnitImplicit<F64, LLUnits::Seconds> time_stamp)
+ {
+ mSize.sync(time_stamp);
+ mChildSize.sync(time_stamp);
+ }
+
+ SampleAccumulator mSize,
+ mChildSize;
+ int mAllocatedCount,
+ mDeallocatedCount;
+ };
+
+ struct AccumulatorBufferGroup : public LLRefCount
+ {
+ AccumulatorBufferGroup();
+
+ void handOffTo(AccumulatorBufferGroup& other);
+ void makePrimary();
+ bool isPrimary() const;
+ static void clearPrimary();
+
+ void append(const AccumulatorBufferGroup& other);
+ void merge(const AccumulatorBufferGroup& other);
+ void reset(AccumulatorBufferGroup* other = NULL);
+ void sync();
+
+ AccumulatorBuffer<CountAccumulator> mCounts;
+ AccumulatorBuffer<SampleAccumulator> mSamples;
+ AccumulatorBuffer<EventAccumulator> mEvents;
+ AccumulatorBuffer<TimeBlockAccumulator> mStackTimers;
+ AccumulatorBuffer<MemStatAccumulator> mMemStats;
+ };
+}
+
+#endif // LL_LLTRACEACCUMULATORS_H
+
diff --git a/indra/llcommon/lltracerecording.cpp b/indra/llcommon/lltracerecording.cpp
index d34434f161..f1388e7935 100644
--- a/indra/llcommon/lltracerecording.cpp
+++ b/indra/llcommon/lltracerecording.cpp
@@ -33,85 +33,7 @@
namespace LLTrace
{
-
-
-///////////////////////////////////////////////////////////////////////
-// RecordingBuffers
-///////////////////////////////////////////////////////////////////////
-
-RecordingBuffers::RecordingBuffers()
-{}
-
-void RecordingBuffers::handOffTo(RecordingBuffers& other)
-{
- other.mCounts.reset(&mCounts);
- other.mSamples.reset(&mSamples);
- other.mEvents.reset(&mEvents);
- other.mStackTimers.reset(&mStackTimers);
- other.mMemStats.reset(&mMemStats);
-}
-
-void RecordingBuffers::makePrimary()
-{
- mCounts.makePrimary();
- mSamples.makePrimary();
- mEvents.makePrimary();
- mStackTimers.makePrimary();
- mMemStats.makePrimary();
-
- ThreadRecorder* thread_recorder = get_thread_recorder().get();
- AccumulatorBuffer<TimeBlockAccumulator>& timer_accumulator_buffer = mStackTimers;
- // update stacktimer parent pointers
- for (S32 i = 0, end_i = mStackTimers.size(); i < end_i; i++)
- {
- TimeBlockTreeNode* tree_node = thread_recorder->getTimeBlockTreeNode(i);
- if (tree_node)
- {
- timer_accumulator_buffer[i].mParent = tree_node->mParent;
- }
- }
-}
-
-bool RecordingBuffers::isPrimary() const
-{
- return mCounts.isPrimary();
-}
-
-void RecordingBuffers::append( const RecordingBuffers& other )
-{
- mCounts.addSamples(other.mCounts);
- mSamples.addSamples(other.mSamples);
- mEvents.addSamples(other.mEvents);
- mMemStats.addSamples(other.mMemStats);
- mStackTimers.addSamples(other.mStackTimers);
-}
-
-void RecordingBuffers::merge( const RecordingBuffers& other)
-{
- mCounts.addSamples(other.mCounts, false);
- mSamples.addSamples(other.mSamples, false);
- mEvents.addSamples(other.mEvents, false);
- mMemStats.addSamples(other.mMemStats, false);
- // for now, hold out timers from merge, need to be displayed per thread
- //mStackTimers.addSamples(other.mStackTimers, false);
-}
-
-void RecordingBuffers::reset(RecordingBuffers* other)
-{
- mCounts.reset(other ? &other->mCounts : NULL);
- mSamples.reset(other ? &other->mSamples : NULL);
- mEvents.reset(other ? &other->mEvents : NULL);
- mStackTimers.reset(other ? &other->mStackTimers : NULL);
- mMemStats.reset(other ? &other->mMemStats : NULL);
-}
-
-void RecordingBuffers::flush()
-{
- LLUnitImplicit<F64, LLUnits::Seconds> time_stamp = LLTimer::getTotalSeconds();
-
- mSamples.flush(time_stamp);
-}
-
+
///////////////////////////////////////////////////////////////////////
// Recording
///////////////////////////////////////////////////////////////////////
@@ -119,7 +41,7 @@ void RecordingBuffers::flush()
Recording::Recording()
: mElapsedSeconds(0)
{
- mBuffers = new RecordingBuffers();
+ mBuffers = new AccumulatorBufferGroup();
}
Recording::Recording( const Recording& other )
@@ -132,17 +54,17 @@ Recording& Recording::operator = (const Recording& other)
// this will allow us to seamlessly start without affecting any data we've acquired from other
setPlayState(PAUSED);
- Recording& mutable_other = const_cast<Recording&>(other);
- mutable_other.update();
+ const_cast<Recording&>(other).update();
EPlayState other_play_state = other.getPlayState();
- mBuffers = mutable_other.mBuffers;
-
- LLStopWatchControlsMixin<Recording>::setPlayState(other_play_state);
+ mBuffers = other.mBuffers;
// above call will clear mElapsedSeconds as a side effect, so copy it here
mElapsedSeconds = other.mElapsedSeconds;
mSamplingTimer = other.mSamplingTimer;
+
+ setPlayState(other_play_state);
+
return *this;
}
@@ -151,7 +73,7 @@ Recording::~Recording()
{
if (isStarted() && LLTrace::get_thread_recorder().notNull())
{
- LLTrace::get_thread_recorder()->deactivate(this);
+ LLTrace::get_thread_recorder()->deactivate(mBuffers.write());
}
}
@@ -159,8 +81,10 @@ void Recording::update()
{
if (isStarted())
{
- mBuffers.write()->flush();
- LLTrace::get_thread_recorder()->bringUpToDate(this);
+ mElapsedSeconds += mSamplingTimer.getElapsedTimeF64();
+ AccumulatorBufferGroup* buffers = mBuffers.write();
+ LLTrace::get_thread_recorder()->bringUpToDate(buffers);
+
mSamplingTimer.reset();
}
}
@@ -176,14 +100,15 @@ void Recording::handleReset()
void Recording::handleStart()
{
mSamplingTimer.reset();
- LLTrace::get_thread_recorder()->activate(this);
+ mBuffers.setStayUnique(true);
+ LLTrace::get_thread_recorder()->activate(mBuffers.write());
}
void Recording::handleStop()
{
mElapsedSeconds += mSamplingTimer.getElapsedTimeF64();
- mBuffers.write()->flush();
- LLTrace::get_thread_recorder()->deactivate(this);
+ LLTrace::get_thread_recorder()->deactivate(mBuffers.write());
+ mBuffers.setStayUnique(false);
}
void Recording::handleSplitTo(Recording& other)
@@ -191,19 +116,14 @@ void Recording::handleSplitTo(Recording& other)
mBuffers.write()->handOffTo(*other.mBuffers.write());
}
-void Recording::appendRecording( const Recording& other )
+void Recording::appendRecording( Recording& other )
{
update();
+ other.update();
mBuffers.write()->append(*other.mBuffers);
mElapsedSeconds += other.mElapsedSeconds;
}
-void Recording::mergeRecording( const Recording& other)
-{
- update();
- mBuffers.write()->merge(*other.mBuffers);
-}
-
LLUnit<F64, LLUnits::Seconds> Recording::getSum(const TraceType<TimeBlockAccumulator>& stat)
{
const TimeBlockAccumulator& accumulator = mBuffers->mStackTimers[stat.getIndex()];
@@ -710,8 +630,6 @@ F64 PeriodicRecording::getPeriodMean( const TraceType<SampleAccumulator>& stat,
void ExtendableRecording::extend()
{
- // stop recording to get latest data
- mPotentialRecording.update();
// push the data back to accepted recording
mAcceptedRecording.appendRecording(mPotentialRecording);
// flush data, so we can start from scratch
diff --git a/indra/llcommon/lltracerecording.h b/indra/llcommon/lltracerecording.h
index b839e85de0..7b0970ffdf 100644
--- a/indra/llcommon/lltracerecording.h
+++ b/indra/llcommon/lltracerecording.h
@@ -32,7 +32,7 @@
#include "llpointer.h"
#include "lltimer.h"
-#include "lltrace.h"
+#include "lltraceaccumulators.h"
class LLStopWatchControlsMixinCommon
{
@@ -81,6 +81,7 @@ class LLStopWatchControlsMixin
: public LLStopWatchControlsMixinCommon
{
public:
+
typedef LLStopWatchControlsMixin<DERIVED> self_t;
virtual void splitTo(DERIVED& other)
{
@@ -98,6 +99,11 @@ public:
static_cast<self_t&>(other).handleSplitTo(*static_cast<DERIVED*>(this));
}
private:
+ self_t& operator = (const self_t& other)
+ {
+ // don't do anything, derived class must implement logic
+ }
+
// atomically stop this object while starting the other
// no data can be missed in between stop and start
virtual void handleSplitTo(DERIVED& other) {};
@@ -106,25 +112,17 @@ private:
namespace LLTrace
{
- struct RecordingBuffers : public LLRefCount
- {
- RecordingBuffers();
-
- void handOffTo(RecordingBuffers& other);
- void makePrimary();
- bool isPrimary() const;
-
- void append(const RecordingBuffers& other);
- void merge(const RecordingBuffers& other);
- void reset(RecordingBuffers* other = NULL);
- void flush();
-
- AccumulatorBuffer<CountAccumulator> mCounts;
- AccumulatorBuffer<SampleAccumulator> mSamples;
- AccumulatorBuffer<EventAccumulator> mEvents;
- AccumulatorBuffer<TimeBlockAccumulator> mStackTimers;
- AccumulatorBuffer<MemStatAccumulator> mMemStats;
- };
+ template<typename T>
+ class TraceType;
+
+ template<typename T>
+ class CountStatHandle;
+
+ template<typename T>
+ class SampleStatHandle;
+
+ template<typename T>
+ class EventStatHandle;
class Recording
: public LLStopWatchControlsMixin<Recording>
@@ -138,10 +136,7 @@ namespace LLTrace
Recording& operator = (const Recording& other);
// accumulate data from subsequent, non-overlapping recording
- void appendRecording(const Recording& other);
-
- // gather data from recording, ignoring time relationship (for example, pulling data from slave threads)
- void mergeRecording(const Recording& other);
+ void appendRecording(Recording& other);
// grab latest recorded data
void update();
@@ -291,7 +286,7 @@ namespace LLTrace
LLTimer mSamplingTimer;
LLUnit<F64, LLUnits::Seconds> mElapsedSeconds;
- LLCopyOnWritePointer<RecordingBuffers> mBuffers;
+ LLCopyOnWritePointer<AccumulatorBufferGroup> mBuffers;
};
class LL_COMMON_API PeriodicRecording
diff --git a/indra/llcommon/lltracethreadrecorder.cpp b/indra/llcommon/lltracethreadrecorder.cpp
index 54006f4e5b..7ac0e75154 100644
--- a/indra/llcommon/lltracethreadrecorder.cpp
+++ b/indra/llcommon/lltracethreadrecorder.cpp
@@ -31,25 +31,33 @@
namespace LLTrace
{
+static ThreadRecorder* sMasterThreadRecorder = NULL;
///////////////////////////////////////////////////////////////////////
// ThreadRecorder
///////////////////////////////////////////////////////////////////////
ThreadRecorder::ThreadRecorder()
+: mMasterRecorder(NULL)
{
+ init();
+}
+
+void ThreadRecorder::init()
+{
+ LLThreadLocalSingletonPointer<BlockTimerStackRecord>::setInstance(&mBlockTimerStackRecord);
//NB: the ordering of initialization in this function is very fragile due to a large number of implicit dependencies
set_thread_recorder(this);
TimeBlock& root_time_block = TimeBlock::getRootTimeBlock();
- ThreadTimerStack* timer_stack = ThreadTimerStack::getInstance();
+ BlockTimerStackRecord* timer_stack = LLThreadLocalSingletonPointer<BlockTimerStackRecord>::getInstance();
timer_stack->mTimeBlock = &root_time_block;
timer_stack->mActiveTimer = NULL;
mNumTimeBlockTreeNodes = AccumulatorBuffer<TimeBlockAccumulator>::getDefaultBuffer()->size();
mTimeBlockTreeNodes = new TimeBlockTreeNode[mNumTimeBlockTreeNodes];
- mThreadRecording.start();
+ activate(&mThreadRecordingBuffers);
// initialize time block parent pointers
for (LLInstanceTracker<TimeBlock>::instance_iter it = LLInstanceTracker<TimeBlock>::beginInstances(), end_it = LLInstanceTracker<TimeBlock>::endInstances();
@@ -70,8 +78,21 @@ ThreadRecorder::ThreadRecorder()
TimeBlock::getRootTimeBlock().getPrimaryAccumulator()->mActiveCount = 1;
}
+
+ThreadRecorder::ThreadRecorder(ThreadRecorder& master)
+: mMasterRecorder(&master)
+{
+ init();
+ mMasterRecorder->addChildRecorder(this);
+}
+
+
ThreadRecorder::~ThreadRecorder()
{
+ LLThreadLocalSingletonPointer<BlockTimerStackRecord>::setInstance(NULL);
+
+ deactivate(&mThreadRecordingBuffers);
+
delete mRootTimer;
if (!mActiveRecordings.empty())
@@ -82,9 +103,14 @@ ThreadRecorder::~ThreadRecorder()
set_thread_recorder(NULL);
delete[] mTimeBlockTreeNodes;
+
+ if (mMasterRecorder)
+ {
+ mMasterRecorder->removeChildRecorder(this);
+ }
}
-TimeBlockTreeNode* ThreadRecorder::getTimeBlockTreeNode(S32 index)
+TimeBlockTreeNode* ThreadRecorder::getTimeBlockTreeNode( S32 index )
{
if (0 <= index && index < mNumTimeBlockTreeNodes)
{
@@ -94,23 +120,25 @@ TimeBlockTreeNode* ThreadRecorder::getTimeBlockTreeNode(S32 index)
}
-void ThreadRecorder::activate( Recording* recording )
+void ThreadRecorder::activate( AccumulatorBufferGroup* recording )
{
ActiveRecording* active_recording = new ActiveRecording(recording);
if (!mActiveRecordings.empty())
{
- mActiveRecordings.back()->mPartialRecording.handOffTo(active_recording->mPartialRecording);
+ AccumulatorBufferGroup& prev_active_recording = mActiveRecordings.back()->mPartialRecording;
+ prev_active_recording.sync();
+ prev_active_recording.handOffTo(active_recording->mPartialRecording);
}
mActiveRecordings.push_back(active_recording);
mActiveRecordings.back()->mPartialRecording.makePrimary();
}
-ThreadRecorder::active_recording_list_t::reverse_iterator ThreadRecorder::bringUpToDate( Recording* recording )
+ThreadRecorder::active_recording_list_t::reverse_iterator ThreadRecorder::bringUpToDate( AccumulatorBufferGroup* recording )
{
if (mActiveRecordings.empty()) return mActiveRecordings.rend();
- mActiveRecordings.back()->mPartialRecording.flush();
+ mActiveRecordings.back()->mPartialRecording.sync();
TimeBlock::updateTimes();
active_recording_list_t::reverse_iterator it, end_it;
@@ -148,145 +176,125 @@ ThreadRecorder::active_recording_list_t::reverse_iterator ThreadRecorder::bringU
return it;
}
-void ThreadRecorder::deactivate( Recording* recording )
+void ThreadRecorder::deactivate( AccumulatorBufferGroup* recording )
{
active_recording_list_t::reverse_iterator it = bringUpToDate(recording);
if (it != mActiveRecordings.rend())
{
- // and if we've found the recording we wanted to update
- active_recording_list_t::reverse_iterator next_it = it;
- ++next_it;
- if (next_it != mActiveRecordings.rend())
- {
- (*next_it)->mPartialRecording.makePrimary();
- }
-
active_recording_list_t::iterator recording_to_remove = (++it).base();
+ bool was_primary = (*recording_to_remove)->mPartialRecording.isPrimary();
llassert((*recording_to_remove)->mTargetRecording == recording);
delete *recording_to_remove;
mActiveRecordings.erase(recording_to_remove);
+ if (was_primary)
+ {
+ if (mActiveRecordings.empty())
+ {
+ AccumulatorBufferGroup::clearPrimary();
+ }
+ else
+ {
+ mActiveRecordings.back()->mPartialRecording.makePrimary();
+ }
+ }
}
}
-ThreadRecorder::ActiveRecording::ActiveRecording( Recording* target )
+ThreadRecorder::ActiveRecording::ActiveRecording( AccumulatorBufferGroup* target )
: mTargetRecording(target)
{
}
void ThreadRecorder::ActiveRecording::movePartialToTarget()
{
- mTargetRecording->mBuffers.write()->append(mPartialRecording);
+ mTargetRecording->append(mPartialRecording);
// reset based on self to keep history
mPartialRecording.reset(&mPartialRecording);
}
-///////////////////////////////////////////////////////////////////////
-// SlaveThreadRecorder
-///////////////////////////////////////////////////////////////////////
-
-SlaveThreadRecorder::SlaveThreadRecorder(MasterThreadRecorder& master)
-: mMasterRecorder(master)
-{
- mMasterRecorder.addSlaveThread(this);
+// called by child thread
+void ThreadRecorder::addChildRecorder( class ThreadRecorder* child )
+{ LLMutexLock lock(&mChildListMutex);
+ mChildThreadRecorders.push_back(child);
}
-SlaveThreadRecorder::~SlaveThreadRecorder()
-{
- mMasterRecorder.removeSlaveThread(this);
-}
+// called by child thread
+void ThreadRecorder::removeChildRecorder( class ThreadRecorder* child )
+{ LLMutexLock lock(&mChildListMutex);
-void SlaveThreadRecorder::pushToMaster()
+for (child_thread_recorder_list_t::iterator it = mChildThreadRecorders.begin(), end_it = mChildThreadRecorders.end();
+ it != end_it;
+ ++it)
{
- mThreadRecording.stop();
+ if ((*it) == child)
{
- LLMutexLock(mMasterRecorder.getSlaveListMutex());
- mSharedData.appendFrom(mThreadRecording);
+ mChildThreadRecorders.erase(it);
+ break;
}
- mThreadRecording.start();
-}
-
-void SlaveThreadRecorder::SharedData::appendFrom( const Recording& source )
-{
- LLMutexLock lock(&mRecordingMutex);
- appendRecording(source);
}
-
-void SlaveThreadRecorder::SharedData::appendTo( Recording& sink )
-{
- LLMutexLock lock(&mRecordingMutex);
- sink.appendRecording(*this);
}
-void SlaveThreadRecorder::SharedData::mergeFrom( const RecordingBuffers& source )
+void ThreadRecorder::pushToParent()
{
- LLMutexLock lock(&mRecordingMutex);
- mBuffers.write()->merge(source);
+ { LLMutexLock lock(&mSharedRecordingMutex);
+ LLTrace::get_thread_recorder()->bringUpToDate(&mThreadRecordingBuffers);
+ mSharedRecordingBuffers.append(mThreadRecordingBuffers);
+ }
}
+
-void SlaveThreadRecorder::SharedData::mergeTo( RecordingBuffers& sink )
-{
- LLMutexLock lock(&mRecordingMutex);
- sink.merge(*mBuffers);
-}
-void SlaveThreadRecorder::SharedData::reset()
-{
- LLMutexLock lock(&mRecordingMutex);
- Recording::reset();
-}
+static LLFastTimer::DeclareTimer FTM_PULL_TRACE_DATA_FROM_CHILDREN("Pull child thread trace data");
-///////////////////////////////////////////////////////////////////////
-// MasterThreadRecorder
-///////////////////////////////////////////////////////////////////////
-
-static LLFastTimer::DeclareTimer FTM_PULL_TRACE_DATA_FROM_SLAVES("Pull slave trace data");
-void MasterThreadRecorder::pullFromSlaveThreads()
+void ThreadRecorder::pullFromChildren()
{
- LLFastTimer _(FTM_PULL_TRACE_DATA_FROM_SLAVES);
+ LLFastTimer _(FTM_PULL_TRACE_DATA_FROM_CHILDREN);
if (mActiveRecordings.empty()) return;
- LLMutexLock lock(&mSlaveListMutex);
+ { LLMutexLock lock(&mChildListMutex);
- RecordingBuffers& target_recording_buffers = mActiveRecordings.back()->mPartialRecording;
- for (slave_thread_recorder_list_t::iterator it = mSlaveThreadRecorders.begin(), end_it = mSlaveThreadRecorders.end();
- it != end_it;
- ++it)
- {
- // ignore block timing info for now
- (*it)->mSharedData.mergeTo(target_recording_buffers);
- (*it)->mSharedData.reset();
+ AccumulatorBufferGroup& target_recording_buffers = mActiveRecordings.back()->mPartialRecording;
+ target_recording_buffers.sync();
+ for (child_thread_recorder_list_t::iterator it = mChildThreadRecorders.begin(), end_it = mChildThreadRecorders.end();
+ it != end_it;
+ ++it)
+ { LLMutexLock lock(&(*it)->mSharedRecordingMutex);
+
+ target_recording_buffers.merge((*it)->mSharedRecordingBuffers);
+ (*it)->mSharedRecordingBuffers.reset();
+ }
}
}
-void MasterThreadRecorder::addSlaveThread( class SlaveThreadRecorder* child )
+
+void set_master_thread_recorder(ThreadRecorder* recorder)
{
- LLMutexLock lock(&mSlaveListMutex);
+ sMasterThreadRecorder = recorder;
+}
- mSlaveThreadRecorders.push_back(child);
+
+ThreadRecorder* get_master_thread_recorder()
+{
+ return sMasterThreadRecorder;
}
-void MasterThreadRecorder::removeSlaveThread( class SlaveThreadRecorder* child )
+LLThreadLocalPointer<ThreadRecorder>& get_thread_recorder_ptr()
{
- LLMutexLock lock(&mSlaveListMutex);
+ static LLThreadLocalPointer<ThreadRecorder> s_thread_recorder;
+ return s_thread_recorder;
+}
- for (slave_thread_recorder_list_t::iterator it = mSlaveThreadRecorders.begin(), end_it = mSlaveThreadRecorders.end();
- it != end_it;
- ++it)
- {
- if ((*it) == child)
- {
- mSlaveThreadRecorders.erase(it);
- break;
- }
- }
+const LLThreadLocalPointer<ThreadRecorder>& get_thread_recorder()
+{
+ return get_thread_recorder_ptr();
}
-void MasterThreadRecorder::pushToMaster()
-{}
+void set_thread_recorder(ThreadRecorder* recorder)
+{
+ get_thread_recorder_ptr() = recorder;
+}
-MasterThreadRecorder::MasterThreadRecorder()
-{}
}
diff --git a/indra/llcommon/lltracethreadrecorder.h b/indra/llcommon/lltracethreadrecorder.h
index bf3701304f..535f855200 100644
--- a/indra/llcommon/lltracethreadrecorder.h
+++ b/indra/llcommon/lltracethreadrecorder.h
@@ -31,7 +31,8 @@
#include "llpreprocessor.h"
#include "llmutex.h"
-#include "lltracerecording.h"
+#include "lltraceaccumulators.h"
+#include "llthreadlocalstorage.h"
namespace LLTrace
{
@@ -42,85 +43,60 @@ namespace LLTrace
typedef std::vector<ActiveRecording*> active_recording_list_t;
public:
ThreadRecorder();
+ explicit ThreadRecorder(ThreadRecorder& master);
- virtual ~ThreadRecorder();
+ ~ThreadRecorder();
- void activate(Recording* recording);
- void deactivate(Recording* recording);
- active_recording_list_t::reverse_iterator bringUpToDate(Recording* recording);
+ void activate(AccumulatorBufferGroup* recording);
+ void deactivate(AccumulatorBufferGroup* recording);
+ active_recording_list_t::reverse_iterator bringUpToDate(AccumulatorBufferGroup* recording);
- virtual void pushToMaster() = 0;
+ void addChildRecorder(class ThreadRecorder* child);
+ void removeChildRecorder(class ThreadRecorder* child);
+
+ // call this periodically to gather stats data from child threads
+ void pullFromChildren();
+ void pushToParent();
TimeBlockTreeNode* getTimeBlockTreeNode(S32 index);
protected:
+ void init();
+
+ protected:
struct ActiveRecording
{
- ActiveRecording(Recording* target);
+ ActiveRecording(AccumulatorBufferGroup* target);
- Recording* mTargetRecording;
- RecordingBuffers mPartialRecording;
+ AccumulatorBufferGroup* mTargetRecording;
+ AccumulatorBufferGroup mPartialRecording;
void movePartialToTarget();
};
- Recording mThreadRecording;
-
- active_recording_list_t mActiveRecordings;
-
- class BlockTimer* mRootTimer;
- TimeBlockTreeNode* mTimeBlockTreeNodes;
- size_t mNumTimeBlockTreeNodes;
- };
-
- class LL_COMMON_API MasterThreadRecorder : public ThreadRecorder
- {
- public:
- MasterThreadRecorder();
-
- void addSlaveThread(class SlaveThreadRecorder* child);
- void removeSlaveThread(class SlaveThreadRecorder* child);
- /*virtual */ void pushToMaster();
+ AccumulatorBufferGroup mThreadRecordingBuffers;
- // call this periodically to gather stats data from slave threads
- void pullFromSlaveThreads();
+ BlockTimerStackRecord mBlockTimerStackRecord;
+ active_recording_list_t mActiveRecordings;
- LLMutex* getSlaveListMutex() { return &mSlaveListMutex; }
+ class BlockTimer* mRootTimer;
+ TimeBlockTreeNode* mTimeBlockTreeNodes;
+ size_t mNumTimeBlockTreeNodes;
+ typedef std::list<class ThreadRecorder*> child_thread_recorder_list_t;
+ child_thread_recorder_list_t mChildThreadRecorders; // list of child thread recorders associated with this master
+ LLMutex mChildListMutex; // protects access to child list
+ LLMutex mSharedRecordingMutex;
+ AccumulatorBufferGroup mSharedRecordingBuffers;
+ ThreadRecorder* mMasterRecorder;
- private:
-
- typedef std::list<class SlaveThreadRecorder*> slave_thread_recorder_list_t;
-
- slave_thread_recorder_list_t mSlaveThreadRecorders; // list of slave thread recorders associated with this master
- LLMutex mSlaveListMutex; // protects access to slave list
};
- class LL_COMMON_API SlaveThreadRecorder : public ThreadRecorder
- {
- public:
- SlaveThreadRecorder(MasterThreadRecorder& master);
- ~SlaveThreadRecorder();
-
- // call this periodically to gather stats data for master thread to consume
- /*virtual*/ void pushToMaster();
+ const LLThreadLocalPointer<ThreadRecorder>& get_thread_recorder();
+ void set_thread_recorder(ThreadRecorder*);
- MasterThreadRecorder* mMaster;
-
- class SharedData : public Recording
- {
- public:
- void appendFrom(const Recording& source);
- void appendTo(Recording& sink);
- void mergeFrom(const RecordingBuffers& source);
- void mergeTo(RecordingBuffers& sink);
- void reset();
- private:
- LLMutex mRecordingMutex;
- };
- SharedData mSharedData;
- MasterThreadRecorder& mMasterRecorder;
- };
+ void set_master_thread_recorder(ThreadRecorder*);
+ ThreadRecorder* get_master_thread_recorder();
}
#endif // LL_LLTRACETHREADRECORDER_H
diff --git a/indra/llcommon/llunit.h b/indra/llcommon/llunit.h
index 2402cdbb95..c9bbed5574 100644
--- a/indra/llcommon/llunit.h
+++ b/indra/llcommon/llunit.h
@@ -75,17 +75,11 @@ struct LLUnit
}
template<typename NEW_UNIT_TYPE>
- STORAGE_TYPE getAs()
+ STORAGE_TYPE valueAs()
{
return LLUnit<STORAGE_TYPE, NEW_UNIT_TYPE>(*this).value();
}
- template<typename NEW_UNIT_TYPE>
- STORAGE_TYPE setAs(STORAGE_TYPE val)
- {
- *this = LLUnit<STORAGE_TYPE, NEW_UNIT_TYPE>(val);
- }
-
void operator += (storage_t value)
{
mValue += value;
@@ -181,6 +175,7 @@ LL_FORCE_INLINE void ll_convert_units(LLUnit<S1, T1> in, LLUnit<S2, T2>& out, ..
if (boost::is_same<T1, typename T1::base_unit_t>::value)
{
if (boost::is_same<T2, typename T2::base_unit_t>::value)
+
{
// T1 and T2 fully reduced and equal...just copy
out = (S2)in.value();
@@ -493,26 +488,45 @@ struct LLUnitInverseLinearOps
}
};
-#define LL_DECLARE_BASE_UNIT(base_unit_name, unit_label) \
-struct base_unit_name { typedef base_unit_name base_unit_t; static const char* getUnitLabel() { return unit_label; }}
-
-#define LL_DECLARE_DERIVED_UNIT(unit_name, unit_label, base_unit_name, conversion_operation) \
-struct unit_name \
-{ \
- typedef base_unit_name base_unit_t; \
- static const char* getUnitLabel() { return unit_label; } \
-}; \
- \
-template<typename S1, typename S2> \
-void ll_convert_units(LLUnit<S1, unit_name> in, LLUnit<S2, base_unit_name>& out) \
-{ \
- out = (S2)(LLUnitLinearOps<S1>(in.value()) conversion_operation).mValue; \
-} \
- \
-template<typename S1, typename S2> \
-void ll_convert_units(LLUnit<S1, base_unit_name> in, LLUnit<S2, unit_name>& out) \
-{ \
- out = (S2)(LLUnitInverseLinearOps<S1>(in.value()) conversion_operation).mValue; \
+#define LL_DECLARE_BASE_UNIT(base_unit_name, unit_label) \
+struct base_unit_name \
+{ \
+ typedef base_unit_name base_unit_t; \
+ static const char* getUnitLabel() { return unit_label; } \
+ template<typename T> \
+ static LLUnit<T, base_unit_name> fromValue(T value) { return LLUnit<T, base_unit_name>(value); } \
+ template<typename STORAGE_T, typename UNIT_T> \
+ static LLUnit<STORAGE_T, base_unit_name> fromValue(LLUnit<STORAGE_T, UNIT_T> value) \
+ { return LLUnit<STORAGE_T, base_unit_name>(value); } \
+}; \
+template<typename T> std::ostream& operator<<(std::ostream& s, const LLUnit<T, base_unit_name>& val) \
+{ s << val.value() << base_unit_name::getUnitLabel; return s; }
+
+
+#define LL_DECLARE_DERIVED_UNIT(unit_name, unit_label, base_unit_name, conversion_operation) \
+struct unit_name \
+{ \
+ typedef base_unit_name base_unit_t; \
+ static const char* getUnitLabel() { return unit_label; } \
+ template<typename T> \
+ static LLUnit<T, unit_name> fromValue(T value) { return LLUnit<T, unit_name>(value); } \
+ template<typename STORAGE_T, typename UNIT_T> \
+ static LLUnit<STORAGE_T, unit_name> fromValue(LLUnit<STORAGE_T, UNIT_T> value) \
+ { return LLUnit<STORAGE_T, unit_name>(value); } \
+}; \
+template<typename T> std::ostream& operator<<(std::ostream& s, const LLUnit<T, unit_name>& val) \
+{ s << val.value() << unit_name::getUnitLabel; return s; } \
+ \
+template<typename S1, typename S2> \
+void ll_convert_units(LLUnit<S1, unit_name> in, LLUnit<S2, base_unit_name>& out) \
+{ \
+ out = (S2)(LLUnitLinearOps<S1>(in.value()) conversion_operation).mValue; \
+} \
+ \
+template<typename S1, typename S2> \
+void ll_convert_units(LLUnit<S1, base_unit_name> in, LLUnit<S2, unit_name>& out) \
+{ \
+ out = (S2)(LLUnitInverseLinearOps<S1>(in.value()) conversion_operation).mValue; \
}
//