554 lines
13 KiB
C++
554 lines
13 KiB
C++
|
|
/**
|
|
* @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 "llunits.h"
|
|
#include "lltimer.h"
|
|
#include "llrefcount.h"
|
|
#include "llthreadlocalstorage.h"
|
|
#include "llmemory.h"
|
|
#include <limits>
|
|
|
|
namespace LLTrace
|
|
{
|
|
const F64 NaN = std::numeric_limits<double>::quiet_NaN();
|
|
|
|
enum EBufferAppendType
|
|
{
|
|
SEQUENTIAL,
|
|
NON_SEQUENTIAL
|
|
};
|
|
|
|
template<typename ACCUMULATOR>
|
|
class AccumulatorBuffer : public LLRefCount
|
|
{
|
|
typedef AccumulatorBuffer<ACCUMULATOR> self_t;
|
|
static const S32 DEFAULT_ACCUMULATOR_BUFFER_SIZE = 32;
|
|
private:
|
|
struct StaticAllocationMarker { };
|
|
|
|
AccumulatorBuffer(StaticAllocationMarker m)
|
|
: mStorageSize(0),
|
|
mStorage(NULL)
|
|
{}
|
|
|
|
public:
|
|
AccumulatorBuffer()
|
|
: mStorageSize(0),
|
|
mStorage(NULL)
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
|
|
const AccumulatorBuffer& other = *getDefaultBuffer();
|
|
resize(sNextStorageSlot);
|
|
for (S32 i = 0; i < sNextStorageSlot; i++)
|
|
{
|
|
mStorage[i] = other.mStorage[i];
|
|
}
|
|
}
|
|
|
|
~AccumulatorBuffer()
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
|
|
if (isCurrent())
|
|
{
|
|
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];
|
|
}
|
|
|
|
|
|
AccumulatorBuffer(const AccumulatorBuffer& other)
|
|
: mStorageSize(0),
|
|
mStorage(NULL)
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
|
|
resize(sNextStorageSlot);
|
|
for (S32 i = 0; i < sNextStorageSlot; i++)
|
|
{
|
|
mStorage[i] = other.mStorage[i];
|
|
}
|
|
}
|
|
|
|
void addSamples(const AccumulatorBuffer<ACCUMULATOR>& other, EBufferAppendType append_type)
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
|
|
llassert(mStorageSize >= sNextStorageSlot && other.mStorageSize >= sNextStorageSlot);
|
|
for (size_t i = 0; i < sNextStorageSlot; i++)
|
|
{
|
|
mStorage[i].addSamples(other.mStorage[i], append_type);
|
|
}
|
|
}
|
|
|
|
void copyFrom(const AccumulatorBuffer<ACCUMULATOR>& other)
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
|
|
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)
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
|
|
llassert(mStorageSize >= sNextStorageSlot);
|
|
for (size_t i = 0; i < sNextStorageSlot; i++)
|
|
{
|
|
mStorage[i].reset(other ? &other->mStorage[i] : NULL);
|
|
}
|
|
}
|
|
|
|
void sync(F64SecondsImplicit time_stamp)
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
|
|
llassert(mStorageSize >= sNextStorageSlot);
|
|
for (size_t i = 0; i < sNextStorageSlot; i++)
|
|
{
|
|
mStorage[i].sync(time_stamp);
|
|
}
|
|
}
|
|
|
|
void makeCurrent()
|
|
{
|
|
LLThreadLocalSingletonPointer<ACCUMULATOR>::setInstance(mStorage);
|
|
}
|
|
|
|
bool isCurrent() const
|
|
{
|
|
return LLThreadLocalSingletonPointer<ACCUMULATOR>::getInstance() == mStorage;
|
|
}
|
|
|
|
static void clearCurrent()
|
|
{
|
|
LLThreadLocalSingletonPointer<ACCUMULATOR>::setInstance(NULL);
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
|
|
size_t next_slot = sNextStorageSlot++;
|
|
if (next_slot >= mStorageSize)
|
|
{
|
|
// don't perform doubling, as this should only happen during startup
|
|
// want to keep a tight bounds as we will have a lot of these buffers
|
|
resize(mStorageSize + mStorageSize / 2);
|
|
}
|
|
llassert(mStorage && next_slot < mStorageSize);
|
|
return next_slot;
|
|
}
|
|
|
|
void resize(size_t new_size)
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
|
|
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();
|
|
}
|
|
|
|
size_t capacity() const
|
|
{
|
|
return mStorageSize;
|
|
}
|
|
|
|
static size_t getNumIndices()
|
|
{
|
|
return sNextStorageSlot;
|
|
}
|
|
|
|
static self_t* getDefaultBuffer()
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
|
|
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;
|
|
static F64 getDefaultValue() { return NaN; }
|
|
|
|
EventAccumulator()
|
|
: mSum(0),
|
|
mMin(F32(NaN)),
|
|
mMax(F32(NaN)),
|
|
mMean(NaN),
|
|
mSumOfSquares(0),
|
|
mNumSamples(0),
|
|
mLastValue(NaN)
|
|
{}
|
|
|
|
void record(F64 value)
|
|
{
|
|
if (mNumSamples == 0)
|
|
{
|
|
mSum = value;
|
|
mMean = value;
|
|
mMin = value;
|
|
mMax = value;
|
|
}
|
|
else
|
|
{
|
|
mSum += value;
|
|
F64 old_mean = mMean;
|
|
mMean += (value - old_mean) / (F64)mNumSamples;
|
|
mSumOfSquares += (value - old_mean) * (value - mMean);
|
|
|
|
if (value < mMin) { mMin = value; }
|
|
else if (value > mMax) { mMax = value; }
|
|
}
|
|
|
|
mNumSamples++;
|
|
mLastValue = value;
|
|
}
|
|
|
|
void addSamples(const EventAccumulator& other, EBufferAppendType append_type);
|
|
void reset(const EventAccumulator* other);
|
|
void sync(F64SecondsImplicit) {}
|
|
|
|
F64 getSum() const { return mSum; }
|
|
F32 getMin() const { return mMin; }
|
|
F32 getMax() const { return mMax; }
|
|
F64 getLastValue() const { return mLastValue; }
|
|
F64 getMean() const { return mMean; }
|
|
F64 getStandardDeviation() const { return sqrtf(mSumOfSquares / mNumSamples); }
|
|
F64 getSumOfSquares() const { return mSumOfSquares; }
|
|
S32 getSampleCount() const { return mNumSamples; }
|
|
bool hasValue() const { return mNumSamples > 0; }
|
|
|
|
// helper utility to calculate combined sumofsquares total
|
|
static F64 mergeSumsOfSquares(const EventAccumulator& a, const EventAccumulator& b);
|
|
|
|
private:
|
|
F64 mSum,
|
|
mLastValue;
|
|
|
|
F64 mMean,
|
|
mSumOfSquares;
|
|
|
|
F32 mMin,
|
|
mMax;
|
|
|
|
S32 mNumSamples;
|
|
};
|
|
|
|
|
|
class SampleAccumulator
|
|
{
|
|
public:
|
|
typedef F64 value_t;
|
|
static F64 getDefaultValue() { return NaN; }
|
|
|
|
SampleAccumulator()
|
|
: mSum(0),
|
|
mMin(F32(NaN)),
|
|
mMax(F32(NaN)),
|
|
mMean(NaN),
|
|
mSumOfSquares(0),
|
|
mLastSampleTimeStamp(0),
|
|
mTotalSamplingTime(0),
|
|
mNumSamples(0),
|
|
mLastValue(NaN),
|
|
mHasValue(false)
|
|
{}
|
|
|
|
void sample(F64 value)
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
|
|
F64SecondsImplicit time_stamp = LLTimer::getTotalSeconds();
|
|
|
|
// store effect of last value
|
|
sync(time_stamp);
|
|
|
|
if (!mHasValue)
|
|
{
|
|
mHasValue = true;
|
|
|
|
mMin = value;
|
|
mMax = value;
|
|
mMean = value;
|
|
mLastSampleTimeStamp = time_stamp;
|
|
}
|
|
else
|
|
{
|
|
if (value < mMin) { mMin = value; }
|
|
else if (value > mMax) { mMax = value; }
|
|
}
|
|
|
|
mLastValue = value;
|
|
mNumSamples++;
|
|
}
|
|
|
|
void addSamples(const SampleAccumulator& other, EBufferAppendType append_type);
|
|
void reset(const SampleAccumulator* other);
|
|
|
|
void sync(F64SecondsImplicit time_stamp)
|
|
{
|
|
if (mHasValue && time_stamp != mLastSampleTimeStamp)
|
|
{
|
|
F64SecondsImplicit delta_time = time_stamp - mLastSampleTimeStamp;
|
|
mSum += mLastValue * delta_time;
|
|
mTotalSamplingTime += delta_time;
|
|
F64 old_mean = mMean;
|
|
mMean += (delta_time / mTotalSamplingTime) * (mLastValue - old_mean);
|
|
mSumOfSquares += delta_time * (mLastValue - old_mean) * (mLastValue - mMean);
|
|
}
|
|
mLastSampleTimeStamp = time_stamp;
|
|
}
|
|
|
|
F64 getSum() const { return mSum; }
|
|
F32 getMin() const { return mMin; }
|
|
F32 getMax() const { return mMax; }
|
|
F64 getLastValue() const { return mLastValue; }
|
|
F64 getMean() const { return mMean; }
|
|
F64 getStandardDeviation() const { return sqrtf(mSumOfSquares / mTotalSamplingTime); }
|
|
F64 getSumOfSquares() const { return mSumOfSquares; }
|
|
F64SecondsImplicit getSamplingTime() const { return mTotalSamplingTime; }
|
|
S32 getSampleCount() const { return mNumSamples; }
|
|
bool hasValue() const { return mHasValue; }
|
|
|
|
// helper utility to calculate combined sumofsquares total
|
|
static F64 mergeSumsOfSquares(const SampleAccumulator& a, const SampleAccumulator& b);
|
|
|
|
private:
|
|
F64 mSum,
|
|
mLastValue;
|
|
|
|
F64 mMean,
|
|
mSumOfSquares;
|
|
|
|
F64SecondsImplicit
|
|
mLastSampleTimeStamp,
|
|
mTotalSamplingTime;
|
|
|
|
F32 mMin,
|
|
mMax;
|
|
|
|
S32 mNumSamples;
|
|
// distinct from mNumSamples, since we might have inherited a last value from
|
|
// a previous sampling period
|
|
bool mHasValue;
|
|
};
|
|
|
|
class CountAccumulator
|
|
{
|
|
public:
|
|
typedef F64 value_t;
|
|
static F64 getDefaultValue() { return 0; }
|
|
|
|
CountAccumulator()
|
|
: mSum(0),
|
|
mNumSamples(0)
|
|
{}
|
|
|
|
void add(F64 value)
|
|
{
|
|
mNumSamples++;
|
|
mSum += value;
|
|
}
|
|
|
|
void addSamples(const CountAccumulator& other, EBufferAppendType /*type*/)
|
|
{
|
|
mSum += other.mSum;
|
|
mNumSamples += other.mNumSamples;
|
|
}
|
|
|
|
void reset(const CountAccumulator* other)
|
|
{
|
|
mNumSamples = 0;
|
|
mSum = 0;
|
|
}
|
|
|
|
void sync(F64SecondsImplicit) {}
|
|
|
|
F64 getSum() const { return mSum; }
|
|
|
|
S32 getSampleCount() const { return mNumSamples; }
|
|
|
|
bool hasValue() const { return true; }
|
|
|
|
private:
|
|
F64 mSum;
|
|
|
|
S32 mNumSamples;
|
|
};
|
|
|
|
class alignas(32) TimeBlockAccumulator
|
|
{
|
|
public:
|
|
typedef F64Seconds value_t;
|
|
static F64Seconds getDefaultValue() { return F64Seconds(0); }
|
|
|
|
typedef TimeBlockAccumulator self_t;
|
|
|
|
// fake classes that allows us to view different facets of underlying statistic
|
|
struct CallCountFacet
|
|
{
|
|
typedef S32 value_t;
|
|
};
|
|
|
|
struct SelfTimeFacet
|
|
{
|
|
typedef F64Seconds value_t;
|
|
};
|
|
|
|
// arrays are allocated with 32 byte alignment
|
|
void *operator new [](size_t size)
|
|
{
|
|
return ll_aligned_malloc<32>(size);
|
|
}
|
|
|
|
void operator delete[](void* ptr, size_t size)
|
|
{
|
|
ll_aligned_free<32>(ptr);
|
|
}
|
|
|
|
TimeBlockAccumulator();
|
|
void addSamples(const self_t& other, EBufferAppendType append_type);
|
|
void reset(const self_t* other);
|
|
void sync(F64SecondsImplicit) {}
|
|
bool hasValue() const { return true; }
|
|
|
|
//
|
|
// members
|
|
//
|
|
U64 mTotalTimeCounter,
|
|
mSelfTimeCounter;
|
|
S32 mCalls;
|
|
class BlockTimerStatHandle* mParent; // last acknowledged parent of this time block
|
|
class BlockTimerStatHandle* 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 BlockTimerStatHandle;
|
|
|
|
class TimeBlockTreeNode
|
|
{
|
|
public:
|
|
TimeBlockTreeNode();
|
|
|
|
void setParent(BlockTimerStatHandle* parent);
|
|
BlockTimerStatHandle* getParent() { return mParent; }
|
|
|
|
BlockTimerStatHandle* mBlock;
|
|
BlockTimerStatHandle* mParent;
|
|
std::vector<BlockTimerStatHandle*> mChildren;
|
|
bool mCollapsed;
|
|
bool mNeedsSorting;
|
|
};
|
|
|
|
struct BlockTimerStackRecord
|
|
{
|
|
class BlockTimer* mActiveTimer;
|
|
class BlockTimerStatHandle* mTimeBlock;
|
|
U64 mChildTime;
|
|
};
|
|
|
|
struct AccumulatorBufferGroup : public LLRefCount
|
|
{
|
|
AccumulatorBufferGroup();
|
|
AccumulatorBufferGroup(const AccumulatorBufferGroup&);
|
|
~AccumulatorBufferGroup();
|
|
|
|
void handOffTo(AccumulatorBufferGroup& other);
|
|
void makeCurrent();
|
|
bool isCurrent() const;
|
|
static void clearCurrent();
|
|
|
|
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;
|
|
};
|
|
}
|
|
|
|
#endif // LL_LLTRACEACCUMULATORS_H
|