phoenix-firestorm/indra/llcommon/lltraceaccumulators.h

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