593 lines
15 KiB
C++
593 lines
15 KiB
C++
/**
|
|
* @file lltrace.h
|
|
* @brief Runtime statistics accumulation.
|
|
*
|
|
* $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_LLTRACE_H
|
|
#define LL_LLTRACE_H
|
|
|
|
#include "stdtypes.h"
|
|
#include "llpreprocessor.h"
|
|
|
|
#include "llmemory.h"
|
|
#include "llrefcount.h"
|
|
//#include "lltracethreadrecorder.h"
|
|
#include "llunit.h"
|
|
#include "llapr.h"
|
|
|
|
#include <list>
|
|
|
|
#define TOKEN_PASTE_ACTUAL(x, y) x##y
|
|
#define TOKEN_PASTE(x, y) TOKEN_PASTE_ACTUAL(x, y)
|
|
#define RECORD_BLOCK_TIME(block_timer) LLTrace::BlockTimer::Recorder TOKEN_PASTE(block_time_recorder, __COUNTER__)(block_timer);
|
|
|
|
namespace LLTrace
|
|
{
|
|
class Recording;
|
|
|
|
typedef LLUnit::Bytes<F64> Bytes;
|
|
typedef LLUnit::Kilobytes<F64> Kilobytes;
|
|
typedef LLUnit::Megabytes<F64> Megabytes;
|
|
typedef LLUnit::Gigabytes<F64> Gigabytes;
|
|
typedef LLUnit::Bits<F64> Bits;
|
|
typedef LLUnit::Kilobits<F64> Kilobits;
|
|
typedef LLUnit::Megabits<F64> Megabits;
|
|
typedef LLUnit::Gigabits<F64> Gigabits;
|
|
|
|
typedef LLUnit::Seconds<F64> Seconds;
|
|
typedef LLUnit::Milliseconds<F64> Milliseconds;
|
|
typedef LLUnit::Minutes<F64> Minutes;
|
|
typedef LLUnit::Hours<F64> Hours;
|
|
typedef LLUnit::Days<F64> Days;
|
|
typedef LLUnit::Weeks<F64> Weeks;
|
|
typedef LLUnit::Milliseconds<F64> Milliseconds;
|
|
typedef LLUnit::Microseconds<F64> Microseconds;
|
|
typedef LLUnit::Nanoseconds<F64> Nanoseconds;
|
|
|
|
typedef LLUnit::Meters<F64> Meters;
|
|
typedef LLUnit::Kilometers<F64> Kilometers;
|
|
typedef LLUnit::Centimeters<F64> Centimeters;
|
|
typedef LLUnit::Millimeters<F64> Millimeters;
|
|
|
|
void init();
|
|
void cleanup();
|
|
|
|
LLThreadLocalPointer<class ThreadRecorder>& get_thread_recorder();
|
|
|
|
class LL_COMMON_API MasterThreadRecorder& getMasterThreadRecorder();
|
|
|
|
// one per thread per type
|
|
template<typename ACCUMULATOR>
|
|
class LL_COMMON_API AccumulatorBuffer : public LLRefCount
|
|
{
|
|
static const U32 DEFAULT_ACCUMULATOR_BUFFER_SIZE = 64;
|
|
private:
|
|
enum StaticAllocationMarker { STATIC_ALLOC };
|
|
|
|
AccumulatorBuffer(StaticAllocationMarker m)
|
|
: mStorageSize(64),
|
|
mNextStorageSlot(0),
|
|
mStorage(new ACCUMULATOR[DEFAULT_ACCUMULATOR_BUFFER_SIZE])
|
|
{}
|
|
|
|
public:
|
|
|
|
// copying an accumulator buffer does not copy the actual contents, but simply initializes the buffer size
|
|
// to be identical to the other buffer
|
|
AccumulatorBuffer(const AccumulatorBuffer& other = getDefaultBuffer())
|
|
: mStorageSize(other.mStorageSize),
|
|
mStorage(new ACCUMULATOR[other.mStorageSize]),
|
|
mNextStorageSlot(other.mNextStorageSlot)
|
|
{}
|
|
|
|
~AccumulatorBuffer()
|
|
{
|
|
if (sPrimaryStorage == mStorage)
|
|
{
|
|
//TODO pick another primary?
|
|
sPrimaryStorage = NULL;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
llassert(mNextStorageSlot == other.mNextStorageSlot);
|
|
|
|
for (size_t i = 0; i < mNextStorageSlot; i++)
|
|
{
|
|
mStorage[i].addSamples(other.mStorage[i]);
|
|
}
|
|
}
|
|
|
|
void copyFrom(const AccumulatorBuffer<ACCUMULATOR>& other)
|
|
{
|
|
for (size_t i = 0; i < mNextStorageSlot; i++)
|
|
{
|
|
mStorage[i] = other.mStorage[i];
|
|
}
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
for (size_t i = 0; i < mNextStorageSlot; i++)
|
|
{
|
|
mStorage[i].reset();
|
|
}
|
|
}
|
|
|
|
void makePrimary()
|
|
{
|
|
sPrimaryStorage = mStorage;
|
|
}
|
|
|
|
bool isPrimary() const
|
|
{
|
|
return sPrimaryStorage == mStorage;
|
|
}
|
|
|
|
LL_FORCE_INLINE static ACCUMULATOR* getPrimaryStorage()
|
|
{
|
|
return sPrimaryStorage.get();
|
|
}
|
|
|
|
// 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 = mNextStorageSlot++;
|
|
if (next_slot >= mStorageSize)
|
|
{
|
|
resize(mStorageSize + (mStorageSize >> 2));
|
|
}
|
|
llassert(mStorage && next_slot < mStorageSize);
|
|
return next_slot;
|
|
}
|
|
|
|
void resize(size_t new_size)
|
|
{
|
|
ACCUMULATOR* old_storage = mStorage;
|
|
mStorage = new ACCUMULATOR[new_size];
|
|
for (S32 i = 0; i < mStorageSize; i++)
|
|
{
|
|
mStorage[i] = old_storage[i];
|
|
}
|
|
mStorageSize = new_size;
|
|
delete[] old_storage;
|
|
}
|
|
|
|
static AccumulatorBuffer<ACCUMULATOR>& getDefaultBuffer()
|
|
{
|
|
static AccumulatorBuffer sBuffer(STATIC_ALLOC);
|
|
return sBuffer;
|
|
}
|
|
|
|
private:
|
|
ACCUMULATOR* mStorage;
|
|
size_t mStorageSize;
|
|
size_t mNextStorageSlot;
|
|
static LLThreadLocalPointer<ACCUMULATOR> sPrimaryStorage;
|
|
};
|
|
template<typename ACCUMULATOR> LLThreadLocalPointer<ACCUMULATOR> AccumulatorBuffer<ACCUMULATOR>::sPrimaryStorage;
|
|
|
|
template<typename ACCUMULATOR>
|
|
class LL_COMMON_API TraceType
|
|
: public LLInstanceTracker<TraceType<ACCUMULATOR>, std::string>
|
|
{
|
|
public:
|
|
TraceType(const char* name, const char* description = NULL)
|
|
: LLInstanceTracker(name),
|
|
mName(name),
|
|
mDescription(description ? description : "")
|
|
{
|
|
mAccumulatorIndex = AccumulatorBuffer<ACCUMULATOR>::getDefaultBuffer().reserveSlot();
|
|
}
|
|
|
|
LL_FORCE_INLINE ACCUMULATOR& getPrimaryAccumulator()
|
|
{
|
|
return AccumulatorBuffer<ACCUMULATOR>::getPrimaryStorage()[mAccumulatorIndex];
|
|
}
|
|
|
|
ACCUMULATOR& getAccumulator(AccumulatorBuffer<ACCUMULATOR>* buffer) { return (*buffer)[mAccumulatorIndex]; }
|
|
const ACCUMULATOR& getAccumulator(const AccumulatorBuffer<ACCUMULATOR>* buffer) const { return (*buffer)[mAccumulatorIndex]; }
|
|
|
|
protected:
|
|
std::string mName;
|
|
std::string mDescription;
|
|
size_t mAccumulatorIndex;
|
|
};
|
|
|
|
|
|
template<typename T, typename IS_UNIT = void>
|
|
struct StorageType
|
|
{
|
|
typedef T type_t;
|
|
};
|
|
|
|
template<typename T>
|
|
struct StorageType<T, typename T::is_unit_tag_t>
|
|
{
|
|
typedef typename StorageType<typename T::storage_t>::type_t type_t;
|
|
};
|
|
|
|
template<> struct StorageType<F32> { typedef F64 type_t; };
|
|
template<> struct StorageType<S32> { typedef S64 type_t; };
|
|
template<> struct StorageType<U32> { typedef S64 type_t; };
|
|
template<> struct StorageType<S16> { typedef S64 type_t; };
|
|
template<> struct StorageType<U16> { typedef S64 type_t; };
|
|
template<> struct StorageType<S8> { typedef S64 type_t; };
|
|
template<> struct StorageType<U8> { typedef S64 type_t; };
|
|
|
|
template<typename T>
|
|
class LL_COMMON_API MeasurementAccumulator
|
|
{
|
|
public:
|
|
typedef T value_t;
|
|
typedef MeasurementAccumulator<T> self_t;
|
|
|
|
MeasurementAccumulator()
|
|
: mSum(0),
|
|
mMin(std::numeric_limits<T>::max()),
|
|
mMax(std::numeric_limits<T>::min()),
|
|
mMean(0),
|
|
mVarianceSum(0),
|
|
mNumSamples(0),
|
|
mLastValue(0)
|
|
{}
|
|
|
|
LL_FORCE_INLINE void sample(T value)
|
|
{
|
|
T storage_value(value);
|
|
mNumSamples++;
|
|
mSum += storage_value;
|
|
if (storage_value < mMin)
|
|
{
|
|
mMin = storage_value;
|
|
}
|
|
if (storage_value > mMax)
|
|
{
|
|
mMax = storage_value;
|
|
}
|
|
F64 old_mean = mMean;
|
|
mMean += ((F64)storage_value - old_mean) / (F64)mNumSamples;
|
|
mVarianceSum += ((F64)storage_value - old_mean) * ((F64)storage_value - mMean);
|
|
mLastValue = storage_value;
|
|
}
|
|
|
|
void addSamples(const self_t& other)
|
|
{
|
|
mSum += other.mSum;
|
|
if (other.mMin < mMin)
|
|
{
|
|
mMin = other.mMin;
|
|
}
|
|
if (other.mMax > mMax)
|
|
{
|
|
mMax = other.mMax;
|
|
}
|
|
mNumSamples += other.mNumSamples;
|
|
F64 weight = (F64)mNumSamples / (F64)(mNumSamples + other.mNumSamples);
|
|
mMean = mMean * weight + other.mMean * (1.f - weight);
|
|
|
|
F64 n_1 = (F64)mNumSamples,
|
|
n_2 = (F64)other.mNumSamples;
|
|
F64 m_1 = mMean,
|
|
m_2 = other.mMean;
|
|
F64 sd_1 = getStandardDeviation(),
|
|
sd_2 = other.getStandardDeviation();
|
|
// 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
|
|
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) * sd_1 * sd_1)
|
|
+ ((n_2 - 1.f) * sd_2 * sd_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));
|
|
}
|
|
mLastValue = other.mLastValue;
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
mNumSamples = 0;
|
|
mSum = 0;
|
|
mMin = 0;
|
|
mMax = 0;
|
|
}
|
|
|
|
T getSum() const { return (T)mSum; }
|
|
T getMin() const { return (T)mMin; }
|
|
T getMax() const { return (T)mMax; }
|
|
T getLastValue() const { return (T)mLastValue; }
|
|
F64 getMean() const { return mMean; }
|
|
F64 getStandardDeviation() const { return sqrtf(mVarianceSum / mNumSamples); }
|
|
U32 getSampleCount() const { return mNumSamples; }
|
|
|
|
private:
|
|
T mSum,
|
|
mMin,
|
|
mMax,
|
|
mLastValue;
|
|
|
|
F64 mMean,
|
|
mVarianceSum;
|
|
|
|
U32 mNumSamples;
|
|
};
|
|
|
|
template<typename T>
|
|
class LL_COMMON_API CountAccumulator
|
|
{
|
|
public:
|
|
typedef T value_t;
|
|
|
|
CountAccumulator()
|
|
: mSum(0),
|
|
mNumSamples(0)
|
|
{}
|
|
|
|
LL_FORCE_INLINE void add(T value)
|
|
{
|
|
mNumSamples++;
|
|
mSum += value;
|
|
}
|
|
|
|
void addSamples(const CountAccumulator<T>& other)
|
|
{
|
|
mSum += other.mSum;
|
|
mNumSamples += other.mNumSamples;
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
mNumSamples = 0;
|
|
mSum = 0;
|
|
}
|
|
|
|
T getSum() const { return (T)mSum; }
|
|
|
|
private:
|
|
T mSum;
|
|
|
|
U32 mNumSamples;
|
|
};
|
|
|
|
typedef TraceType<MeasurementAccumulator<F64> > measurement_common_float_t;
|
|
typedef TraceType<MeasurementAccumulator<S64> > measurement_common_int_t;
|
|
|
|
template <typename T = F64, typename IS_UNIT = void>
|
|
class LL_COMMON_API Measurement
|
|
: public TraceType<MeasurementAccumulator<typename StorageType<T>::type_t> >
|
|
{
|
|
public:
|
|
typedef typename StorageType<T>::type_t storage_t;
|
|
|
|
Measurement(const char* name, const char* description = NULL)
|
|
: TraceType(name, description)
|
|
{}
|
|
|
|
void sample(T value)
|
|
{
|
|
getPrimaryAccumulator().sample((storage_t)value);
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
class LL_COMMON_API Measurement <T, typename T::is_unit_tag_t>
|
|
: public TraceType<MeasurementAccumulator<typename StorageType<typename T::storage_t>::type_t> >
|
|
{
|
|
public:
|
|
typedef typename StorageType<typename T::storage_t>::type_t storage_t;
|
|
|
|
Measurement(const char* name, const char* description = NULL)
|
|
: TraceType(name, description)
|
|
{}
|
|
|
|
template<typename UNIT_T>
|
|
void sample(UNIT_T value)
|
|
{
|
|
T converted_value;
|
|
converted_value.assignFrom(value);
|
|
getPrimaryAccumulator().sample((storage_t)converted_value.value());
|
|
}
|
|
};
|
|
|
|
typedef TraceType<CountAccumulator<F64> > count_common_float_t;
|
|
typedef TraceType<CountAccumulator<S64> > count_common_int_t;
|
|
|
|
template <typename T = F64, typename IS_UNIT = void>
|
|
class LL_COMMON_API Count
|
|
: public TraceType<CountAccumulator<typename StorageType<T>::type_t> >
|
|
{
|
|
public:
|
|
typedef typename StorageType<T>::type_t storage_t;
|
|
|
|
Count(const char* name, const char* description = NULL)
|
|
: TraceType(name)
|
|
{}
|
|
|
|
void add(T value)
|
|
{
|
|
getPrimaryAccumulator().add((storage_t)value);
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
class LL_COMMON_API Count <T, typename T::is_unit_tag_t>
|
|
: public TraceType<CountAccumulator<typename StorageType<typename T::storage_t>::type_t> >
|
|
{
|
|
public:
|
|
typedef typename StorageType<typename T::storage_t>::type_t storage_t;
|
|
|
|
Count(const char* name, const char* description = NULL)
|
|
: TraceType(name)
|
|
{}
|
|
|
|
template<typename UNIT_T>
|
|
void add(UNIT_T value)
|
|
{
|
|
T converted_value;
|
|
converted_value.assignFrom(value);
|
|
getPrimaryAccumulator().add((storage_t)converted_value.value());
|
|
}
|
|
};
|
|
|
|
class LL_COMMON_API TimerAccumulator
|
|
{
|
|
public:
|
|
U32 mTotalTimeCounter,
|
|
mChildTimeCounter,
|
|
mCalls;
|
|
|
|
TimerAccumulator* mParent; // info for caller timer
|
|
TimerAccumulator* mLastCaller; // used to bootstrap tree construction
|
|
const class BlockTimer* mTimer; // points to block timer associated with this storage
|
|
U8 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
|
|
std::vector<TimerAccumulator*> mChildren; // currently assumed child timers
|
|
|
|
void addSamples(const TimerAccumulator& other)
|
|
{
|
|
mTotalTimeCounter += other.mTotalTimeCounter;
|
|
mChildTimeCounter += other.mChildTimeCounter;
|
|
mCalls += other.mCalls;
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
mTotalTimeCounter = 0;
|
|
mChildTimeCounter = 0;
|
|
mCalls = 0;
|
|
}
|
|
|
|
};
|
|
|
|
class LL_COMMON_API BlockTimer : public TraceType<TimerAccumulator>
|
|
{
|
|
public:
|
|
BlockTimer(const char* name)
|
|
: TraceType(name)
|
|
{}
|
|
|
|
struct Recorder
|
|
{
|
|
struct StackEntry
|
|
{
|
|
Recorder* mRecorder;
|
|
TimerAccumulator* mAccumulator;
|
|
U32 mChildTime;
|
|
};
|
|
|
|
LL_FORCE_INLINE Recorder(BlockTimer& block_timer)
|
|
: mLastRecorder(sCurRecorder)
|
|
{
|
|
mStartTime = getCPUClockCount32();
|
|
TimerAccumulator* accumulator = &block_timer.getPrimaryAccumulator(); // get per-thread accumulator
|
|
accumulator->mActiveCount++;
|
|
accumulator->mCalls++;
|
|
accumulator->mMoveUpTree |= (accumulator->mParent->mActiveCount == 0);
|
|
|
|
// push new timer on stack
|
|
sCurRecorder.mRecorder = this;
|
|
sCurRecorder.mAccumulator = accumulator;
|
|
sCurRecorder.mChildTime = 0;
|
|
}
|
|
|
|
LL_FORCE_INLINE ~Recorder()
|
|
{
|
|
U32 total_time = getCPUClockCount32() - mStartTime;
|
|
|
|
TimerAccumulator* accumulator = sCurRecorder.mAccumulator;
|
|
accumulator->mTotalTimeCounter += total_time;
|
|
accumulator->mChildTimeCounter += sCurRecorder.mChildTime;
|
|
accumulator->mActiveCount--;
|
|
|
|
accumulator->mLastCaller = mLastRecorder.mAccumulator;
|
|
mLastRecorder.mChildTime += total_time;
|
|
|
|
// pop stack
|
|
sCurRecorder = mLastRecorder;
|
|
}
|
|
|
|
StackEntry mLastRecorder;
|
|
U32 mStartTime;
|
|
};
|
|
|
|
private:
|
|
static U32 getCPUClockCount32()
|
|
{
|
|
U32 ret_val;
|
|
__asm
|
|
{
|
|
_emit 0x0f
|
|
_emit 0x31
|
|
shr eax,8
|
|
shl edx,24
|
|
or eax, edx
|
|
mov dword ptr [ret_val], eax
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
// return full timer value, *not* shifted by 8 bits
|
|
static U64 getCPUClockCount64()
|
|
{
|
|
U64 ret_val;
|
|
__asm
|
|
{
|
|
_emit 0x0f
|
|
_emit 0x31
|
|
mov eax,eax
|
|
mov edx,edx
|
|
mov dword ptr [ret_val+4], edx
|
|
mov dword ptr [ret_val], eax
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
static Recorder::StackEntry sCurRecorder;
|
|
};
|
|
}
|
|
|
|
#endif // LL_LLTRACE_H
|