phoenix-firestorm/indra/llcommon/lltrace.h

562 lines
13 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 "llmutex.h"
#include "llmemory.h"
#include "lltimer.h"
#include "llrefcount.h"
#include "lltracerecording.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;
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];
}
void mergeSamples(const AccumulatorBuffer<ACCUMULATOR>& other)
{
llassert(mNextStorageSlot == other.mNextStorageSlot);
for (size_t i = 0; i < mNextStorageSlot; i++)
{
mStorage[i].mergeSamples(other.mStorage[i]);
}
}
void mergeDeltas(const AccumulatorBuffer<ACCUMULATOR>& start, const AccumulatorBuffer<ACCUMULATOR>& finish)
{
llassert(mNextStorageSlot == start.mNextStorageSlot && mNextStorageSlot == finish.mNextStorageSlot);
for (size_t i = 0; i < mNextStorageSlot; i++)
{
mStorage[i].mergeDeltas(start.mStorage[i], finish.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)
{
size_t new_size = mStorageSize + (mStorageSize >> 2);
delete [] mStorage;
mStorage = new ACCUMULATOR[new_size];
mStorageSize = new_size;
}
llassert(next_slot < mStorageSize);
return next_slot;
}
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:
TraceType(const std::string& name)
: mName(name)
{
mAccumulatorIndex = AccumulatorBuffer<ACCUMULATOR>::getDefaultBuffer().reserveSlot();
}
LL_FORCE_INLINE ACCUMULATOR& getPrimaryAccumulator()
{
return AccumulatorBuffer<ACCUMULATOR>::getPrimaryStorage()[mAccumulatorIndex];
}
ACCUMULATOR& getAccumulator(AccumulatorBuffer<ACCUMULATOR>* buffer) { return (*buffer)[mAccumulatorIndex]; }
protected:
std::string mName;
size_t mAccumulatorIndex;
};
template<typename T>
class LL_COMMON_API MeasurementAccumulator
{
public:
MeasurementAccumulator()
: mSum(0),
mMin(0),
mMax(0),
mNumSamples(0)
{}
LL_FORCE_INLINE void sample(T value)
{
mNumSamples++;
mSum += value;
if (value < mMin)
{
mMin = value;
}
else if (value > mMax)
{
mMax = value;
}
F32 old_mean = mMean;
mMean += ((F32)value - old_mean) / (F32)mNumSamples;
mStandardDeviation += ((F32)value - old_mean) * ((F32)value - mMean);
}
void mergeSamples(const MeasurementAccumulator<T>& other)
{
mSum += other.mSum;
if (other.mMin < mMin)
{
mMin = other.mMin;
}
if (other.mMax > mMax)
{
mMax = other.mMax;
}
mNumSamples += other.mNumSamples;
F32 weight = (F32)mNumSamples / (F32)(mNumSamples + other.mNumSamples);
mMean = mMean * weight + other.mMean * (1.f - weight);
F32 n_1 = (F32)mNumSamples,
n_2 = (F32)other.mNumSamples;
F32 m_1 = mMean,
m_2 = other.mMean;
F32 sd_1 = mStandardDeviation,
sd_2 = other.mStandardDeviation;
// 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
F32 variance = ((((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));
mStandardDeviation = sqrtf(variance);
}
void mergeDeltas(const MeasurementAccumulator<T>& start, const MeasurementAccumulator<T>& finish)
{
llerrs << "Delta merge invalid for measurement accumulators" << llendl;
}
void reset()
{
mNumSamples = 0;
mSum = 0;
mMin = 0;
mMax = 0;
}
T getSum() { return mSum; }
T getMin() { return mMin; }
T getMax() { return mMax; }
F32 getMean() { return mMean; }
F32 getStandardDeviation() { return mStandardDeviation; }
private:
T mSum,
mMin,
mMax;
F32 mMean,
mStandardDeviation;
U32 mNumSamples;
};
template<typename T>
class LL_COMMON_API RateAccumulator
{
public:
RateAccumulator()
: mSum(0),
mNumSamples(0)
{}
LL_FORCE_INLINE void add(T value)
{
mNumSamples++;
mSum += value;
}
void mergeSamples(const RateAccumulator<T>& other)
{
mSum += other.mSum;
mNumSamples += other.mNumSamples;
}
void mergeDeltas(const RateAccumulator<T>& start, const RateAccumulator<T>& finish)
{
mSum += finish.mSum - start.mSum;
mNumSamples += finish.mNumSamples - start.mNumSamples;
}
void reset()
{
mNumSamples = 0;
mSum = 0;
}
T getSum() { return mSum; }
private:
T mSum;
U32 mNumSamples;
};
template <typename T>
class LL_COMMON_API Measurement
: public TraceType<MeasurementAccumulator<T> >,
public LLInstanceTracker<Measurement<T>, std::string>
{
public:
Measurement(const std::string& name)
: TraceType(name),
LLInstanceTracker(name)
{}
void sample(T value)
{
getPrimaryAccumulator().sample(value);
}
};
template <typename T>
class LL_COMMON_API Rate
: public TraceType<RateAccumulator<T> >,
public LLInstanceTracker<Rate<T>, std::string>
{
public:
Rate(const std::string& name)
: TraceType(name),
LLInstanceTracker(name)
{}
void add(T value)
{
getPrimaryAccumulator().add(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 mergeSamples(const TimerAccumulator& other)
{
mTotalTimeCounter += other.mTotalTimeCounter;
mChildTimeCounter += other.mChildTimeCounter;
mCalls += other.mCalls;
}
void mergeDeltas(const TimerAccumulator& start, const TimerAccumulator& finish)
{
mTotalTimeCounter += finish.mTotalTimeCounter - start.mTotalTimeCounter;
mChildTimeCounter += finish.mChildTimeCounter - start.mChildTimeCounter;
mCalls += finish.mCalls - start.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;
};
class LL_COMMON_API ThreadRecorder
{
public:
ThreadRecorder();
ThreadRecorder(const ThreadRecorder& other);
virtual ~ThreadRecorder();
void activate(Recording* recording);
void deactivate(Recording* recording);
virtual void pushToMaster() = 0;
Recording* getPrimaryRecording();
protected:
Recording mPrimaryRecording;
Recording mFullRecording;
std::list<Recording*> mActiveRecordings;
};
class LL_COMMON_API MasterThreadRecorder : public ThreadRecorder
{
public:
MasterThreadRecorder();
void addSlaveThread(class SlaveThreadRecorder* child);
void removeSlaveThread(class SlaveThreadRecorder* child);
/*virtual */ void pushToMaster();
// call this periodically to gather stats data from slave threads
void pullFromSlaveThreads();
LLMutex* getSlaveListMutex() { return &mSlaveListMutex; }
private:
struct SlaveThreadRecorderProxy
{
SlaveThreadRecorderProxy(class SlaveThreadRecorder* recorder);
class SlaveThreadRecorder* mRecorder;
Recording mSlaveRecording;
private:
//no need to copy these and then have to duplicate the storage
SlaveThreadRecorderProxy(const SlaveThreadRecorderProxy& other) {}
};
typedef std::list<SlaveThreadRecorderProxy*> slave_thread_recorder_list_t;
slave_thread_recorder_list_t mSlaveThreadRecorders;
LLMutex mSlaveListMutex;
};
class LL_COMMON_API SlaveThreadRecorder : public ThreadRecorder
{
public:
SlaveThreadRecorder();
~SlaveThreadRecorder();
// call this periodically to gather stats data for master thread to consume
/*virtual*/ void pushToMaster();
MasterThreadRecorder* mMaster;
class SharedData
{
public:
void copyFrom(const Recording& source);
void copyTo(Recording& sink);
private:
LLMutex mRecorderMutex;
Recording mRecorder;
};
SharedData mSharedData;
};
}
#endif // LL_LLTRACE_H