781 lines
23 KiB
C++
781 lines
23 KiB
C++
/**********************************************************************
|
|
|
|
Filename : GStats.cpp
|
|
Content : Statistics tracking and reporting APIs
|
|
Created : May 20, 2008
|
|
Authors : Michael Antonov
|
|
|
|
Notes :
|
|
|
|
Copyright : (c) 2008 Scaleform Corp. All Rights Reserved.
|
|
|
|
Licensees may use this file in accordance with the valid Scaleform
|
|
Commercial License Agreement provided with the software.
|
|
|
|
This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
|
|
THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE.
|
|
|
|
**********************************************************************/
|
|
|
|
#include "GStats.h"
|
|
#include "GMemory.h"
|
|
|
|
#include "GAtomic.h"
|
|
|
|
|
|
#ifdef GFC_NO_STAT
|
|
|
|
#if __WIN32
|
|
namespace { char dummyStatsVar; }; // to disable warning LNK4221 on PC/Xbox
|
|
#endif
|
|
|
|
#else
|
|
|
|
// For root, we allow GStatGroup_Default.
|
|
GDECLARE_STAT_GROUP(GStatGroup_Default, "Default", GStatGroup_Default)
|
|
|
|
GDECLARE_MEMORY_STAT_AUTOSUM_GROUP(GStat_Mem, "Used Memory", GStatGroup_Default)
|
|
GDECLARE_MEMORY_STAT(GStat_Default_Mem, "General", GStat_Mem)
|
|
GDECLARE_MEMORY_STAT(GStat_Image_Mem, "Image", GStat_Mem)
|
|
#ifndef GFC_NO_SOUND
|
|
GDECLARE_MEMORY_STAT(GStat_Sound_Mem, "Sound", GStat_Mem)
|
|
#endif
|
|
GDECLARE_MEMORY_STAT(GStat_String_Mem, "String", GStat_Mem)
|
|
#ifndef GFC_NO_VIDEO
|
|
GDECLARE_MEMORY_STAT(GStat_Video_Mem, "Video", GStat_Mem)
|
|
#endif
|
|
|
|
GDECLARE_MEMORY_STAT_AUTOSUM_GROUP(GStat_Debug_Mem, "Debug Memory", GStat_Mem)
|
|
GDECLARE_MEMORY_STAT(GStat_DebugHUD_Mem, "Debug HUD", GStat_Debug_Mem)
|
|
GDECLARE_MEMORY_STAT(GStat_DebugTracker_Mem, "Debug Tracker", GStat_Debug_Mem)
|
|
GDECLARE_MEMORY_STAT(GStat_StatBag_Mem, "StatBag", GStat_Debug_Mem)
|
|
|
|
|
|
|
|
/*
|
|
Notes On Statistics
|
|
-------------------
|
|
|
|
These notes discuss some of concerns that can be addressed by the statistics
|
|
system in the future. It is good to understand these while making a general
|
|
stat tracking and display system.
|
|
|
|
Counter Types
|
|
|
|
The interpretation of the counter can have a significant effect on how it
|
|
is updated and displayed. There are several possible counter types:
|
|
|
|
1. Collector Count - This counter is reset to 0 before statistics report
|
|
is generated and then incremented as all the items
|
|
are "collected" for inclusion. Absolute Min/Max is
|
|
not known with this counter and recording it is not
|
|
useful within the statistic itself, although average
|
|
min/max can still be computed externally (subjects
|
|
to precision limits based on the sampling rate).
|
|
|
|
In GFx, it's planned to use such counters for memory
|
|
stat reporting, DP/Triangle counts, etc.
|
|
|
|
2. Running Count - This counter keeps the real-time state continuously,
|
|
and is thus incremented and decremented as objects/
|
|
memory/resources are created and destroyed. Min/Max
|
|
can be tracked correctly if necessary. Reset of counter
|
|
to 0 does not make sense as it would make the rest
|
|
of the results incorrect, but Min/Max could be reset
|
|
based on the sampling rate.
|
|
|
|
Heap memory sizes can be tracked that was so that
|
|
real Min/Max is known.
|
|
|
|
3. Absolute Count - Increment only version of the Running Count, which
|
|
can not be reset. Since the value is always incremented
|
|
tracking Min/Max does not make sense.
|
|
|
|
Could be applied to loaded data, such as the number
|
|
of shapes or fonts loaded from the SWF file, or the
|
|
total time it took for a resource to load.
|
|
|
|
|
|
Statistical Data Collection and Display
|
|
|
|
As statistics data is collected at specified intervals, there is a number
|
|
of data elements which can be usefully tracked and displayed for every
|
|
statistic. These include:
|
|
|
|
- Instantaneous (final) value.
|
|
- Average
|
|
- Sum
|
|
- Minimum
|
|
- Maximum
|
|
|
|
The last four statistics could be tracked and displayed based on the different
|
|
sampling windows; for example, we can display Average and Maximum values
|
|
over the period of one Frame, one Second, or program Lifetime. Unfortunately,
|
|
not all of this information is useful in all cases and displaying all of the
|
|
possible values may make the display busy and hard to interpret.
|
|
|
|
Here are a few examples:
|
|
|
|
a) Advance Ticks, - Average, Min, Max make sense over one second. Sum does
|
|
Advanced MCCount not since a second has many frames.
|
|
|
|
b) MovieDef Memory - Instantaneous value is more interesting, since as its
|
|
exact as opposed to averages (it is at least enough).
|
|
Min/Max not useful.
|
|
|
|
Load Time Ticks - Min/Max/Avg meaningless, since this is an absolute
|
|
count. Only the final value is useful.
|
|
|
|
c) MovieView Memory - Min, Max, Instantaneous Val all useful.
|
|
|
|
d) Tessellate Count, - Sum may be more interesting then Avg since tessellations
|
|
Triangles Gen only happen in certain frames and Avg would be small,
|
|
while knowing the total # of shapes/triangles processed
|
|
is useful.
|
|
|
|
|
|
So, ultimately while displaying all of the values may be ok but confusing. Allowing
|
|
developers select which value is displayed in the "Avg" column per stat may be
|
|
more useful {Average, Sum, Instance}.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// ***** Supported Interfaces
|
|
|
|
GStatInfo_InterfaceImpl<GMemoryStat> GStat_MemoryInterface;
|
|
GStatInfo_InterfaceImpl<GTimerStat> GStat_TimerInterface;
|
|
GStatInfo_InterfaceImpl<GCounterStat> GStat_CounterInterface;
|
|
|
|
GStatInfo::StatInterface* GStats_InterfaceTable[GStat::Stat_TypeCount] =
|
|
{
|
|
0,
|
|
&GStat_MemoryInterface,
|
|
&GStat_TimerInterface,
|
|
&GStat_CounterInterface
|
|
};
|
|
|
|
|
|
// ***** Descriptor Registration
|
|
|
|
struct GStatDescRegistry
|
|
{
|
|
enum
|
|
{
|
|
Desc_MemEntryCount = GStat_EntryCount * 2,
|
|
|
|
Desc_PageShift = 3,
|
|
Desc_PageSize = 1 << Desc_PageShift,
|
|
Desc_PageTableSize = GStat_MaxId / Desc_PageSize,
|
|
|
|
// Page table entries are set to this value if no memory or slot
|
|
// is allocated to them. We rely on default zero-initialization
|
|
// to assign Unused values.
|
|
Desc_IdUnused = 0
|
|
};
|
|
|
|
UInt DescAllocOffset;
|
|
|
|
// Id page table. All the contained values are biased by one (+1) so
|
|
// that a default value of '0' can represent the Unused value.
|
|
UInt16 IdPageTable[Desc_PageTableSize];
|
|
// Reserved memory for Ids.
|
|
GStatDesc* DescMem[Desc_MemEntryCount];
|
|
|
|
|
|
void RegisterDesc(GStatDesc* pdesc)
|
|
{
|
|
// Get offset of the page, and if page does not exit, allocate it.
|
|
UInt16 pageOffset = IdPageTable[pdesc->GetId() >> Desc_PageShift];
|
|
if (pageOffset == Desc_IdUnused)
|
|
{
|
|
if (Desc_MemEntryCount < (DescAllocOffset + Desc_PageSize))
|
|
{
|
|
GFC_DEBUG_WARNING1(1,
|
|
"GStatDescRegistry out of reserved memory on statId %d", pdesc->GetId());
|
|
return;
|
|
}
|
|
|
|
// Update the page index.
|
|
pageOffset = (UInt16)(DescAllocOffset) + 1;
|
|
IdPageTable[pdesc->GetId() >> Desc_PageShift] = pageOffset;
|
|
|
|
// And mark all offsets as un-initialized.
|
|
GStatDesc** p = DescMem + DescAllocOffset;
|
|
for(int i = 0; i <Desc_PageSize; i++, p++)
|
|
*p = 0;
|
|
DescAllocOffset += Desc_PageSize;
|
|
}
|
|
|
|
// Record the descriptor entry.
|
|
DescMem[pageOffset - 1 + (pdesc->GetId() & (Desc_PageSize-1))] = pdesc;
|
|
}
|
|
|
|
GStatDesc* GetDescImpl(UInt statId)
|
|
{
|
|
UInt pageOffset = IdPageTable[statId >> Desc_PageShift];
|
|
GStatDesc* pdesc;
|
|
|
|
// If this ASSERT gets hit, it means that StatId is not declared.
|
|
if (pageOffset == Desc_IdUnused)
|
|
return 0;
|
|
pdesc = DescMem[(pageOffset-1) + (statId & (Desc_PageSize-1))];
|
|
|
|
GASSERT(!pdesc || (pdesc->GetId() == statId));
|
|
return pdesc;
|
|
}
|
|
|
|
GStatDesc* GetDesc(UInt statId)
|
|
{
|
|
GStatDesc* pdesc = GetDescImpl(statId);
|
|
|
|
// Simple debugging logic to display a message with a previous name in case
|
|
// that the statId is use was not declared.
|
|
#ifdef GFC_BUILD_DEBUG
|
|
if (!pdesc)
|
|
{
|
|
UInt id = statId;
|
|
GStatDesc* pdescPrev;
|
|
|
|
while (id > 0)
|
|
{
|
|
id--;
|
|
pdescPrev = GetDescImpl(id);
|
|
if (pdescPrev)
|
|
{
|
|
GFC_DEBUG_ERROR3(1, "GStatId %d following after Id %d '%s' not registered.",
|
|
statId, id, pdescPrev->GetName());
|
|
break;
|
|
}
|
|
}
|
|
|
|
GASSERT(0);
|
|
}
|
|
|
|
#endif
|
|
return pdesc;
|
|
}
|
|
|
|
|
|
};
|
|
|
|
// We expect registry values to be zero-initialized. This is important
|
|
// because registration routines can run in any order, so we can not
|
|
// rely on the default constructor being available before RegisterDesc is called.
|
|
GStatDescRegistry GStatDescRegistryInstance = { 0, {0}, {0} };
|
|
|
|
|
|
/* Child Tree Initialization
|
|
|
|
We use two step delayed initialization of the descriptor child tree
|
|
to avoid problems with different translation units coming in unknown
|
|
order.
|
|
1) During static initialization, we register descriptors and
|
|
combine them into a linked list.
|
|
2) On first call to GetDesc, we traverse the list and convert
|
|
it into the tree. We use AtomicOps to make this singleton
|
|
initialization thread safe (in case GetDesc() is called from
|
|
different threads simultaneously on the first call).
|
|
*/
|
|
|
|
// Descriptor pointers used during static initialization.
|
|
static GStatDesc* GStats_pFirstDesc = 0;
|
|
static GStatDesc* GStats_pLastDesc = 0;
|
|
// Flags used to track atomic initialization.
|
|
static volatile UInt GStats_InitDone = 0;
|
|
static volatile UInt GStats_InitByUs = 0;
|
|
|
|
|
|
// Register descriptors so that they can be looked up by id.
|
|
void GStatDesc::RegisterDesc(GStatDesc* pdesc)
|
|
{
|
|
GStatDescRegistryInstance.RegisterDesc(pdesc);
|
|
|
|
// Link up all ids which can be children in a common list,
|
|
// so that we can later initialize a descriptor tree.
|
|
//if (pdesc->GroupId)
|
|
{
|
|
if (GStats_pLastDesc)
|
|
{
|
|
GStats_pLastDesc->pNextSibling = pdesc;
|
|
GStats_pLastDesc = pdesc;
|
|
}
|
|
else
|
|
{
|
|
GStats_pFirstDesc = pdesc;
|
|
GStats_pLastDesc = pdesc;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
const GStatDesc* GStatDesc::GetDesc(UInt id)
|
|
{
|
|
if (!GAtomicOps<UInt>::Load_Acquire(&GStats_InitDone))
|
|
InitChildTree();
|
|
|
|
return GStatDescRegistryInstance.GetDesc(id);
|
|
}
|
|
|
|
|
|
void GStatDesc::InitChildTree()
|
|
{
|
|
if (GAtomicOps<UInt>::Load_Acquire(&GStats_InitDone))
|
|
return;
|
|
|
|
// Make sure we are the only thread who is performing
|
|
// static tree initialization.
|
|
UInt oldInitByUs;
|
|
do {
|
|
oldInitByUs = GStats_InitByUs;
|
|
|
|
if (oldInitByUs == 1)
|
|
{
|
|
// Spin if someone else started initialization.
|
|
while (!GAtomicOps<UInt>::Load_Acquire(&GStats_InitDone))
|
|
;
|
|
return;
|
|
}
|
|
} while (!GAtomicOps<UInt>::CompareAndSet_Sync(&GStats_InitByUs, oldInitByUs, 1));
|
|
|
|
|
|
GStatDesc* pdesc, *pnext;
|
|
|
|
for(pdesc = GStats_pFirstDesc; pdesc != 0; pdesc = pnext)
|
|
{
|
|
pnext = pdesc->pNextSibling;
|
|
pdesc->pNextSibling = 0;
|
|
|
|
// Link up the parent.
|
|
GStatDesc* pparentDesc = GStatDescRegistryInstance.GetDesc(pdesc->GroupId);
|
|
|
|
if (pparentDesc != pdesc)
|
|
{
|
|
if (!pparentDesc->pChild)
|
|
pparentDesc->pChild = pdesc;
|
|
else
|
|
{
|
|
// Connect us in the end so that iteration order is preserved.
|
|
// This is safe to do here since all pNextSibling pointers modified
|
|
// here would have been traversed in the previous iteration of our
|
|
// containing for loop.
|
|
GStatDesc* psibling = pparentDesc->pChild;
|
|
while(psibling->pNextSibling)
|
|
psibling = psibling->pNextSibling;
|
|
psibling->pNextSibling = pdesc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GASSERT(pdesc->Id == pdesc->GroupId);
|
|
}
|
|
|
|
}
|
|
|
|
GStats_pFirstDesc = 0;
|
|
GStats_pLastDesc = 0;
|
|
|
|
GAtomicOps<UInt>::Store_Release(&GStats_InitDone, 1);
|
|
}
|
|
|
|
|
|
|
|
// ***** GStatBag implementation
|
|
|
|
// Create a stat bag with specified number of entries.
|
|
GStatBag::GStatBag(GMemoryHeap *pheap, UInt memReserve)
|
|
{
|
|
if (!pheap)
|
|
pheap = GMemory::GetGlobalHeap();
|
|
|
|
pMem = (UByte*) GHEAP_ALLOC(pheap, memReserve, GStat_StatBag_Mem);
|
|
MemSize = memReserve;
|
|
Clear();
|
|
}
|
|
|
|
GStatBag::GStatBag( const GStatBag& source )
|
|
{
|
|
pMem = (UByte*) GALLOC(GStat_EntryCount * 16, GStat_StatBag_Mem);
|
|
MemSize = GStat_EntryCount * 16;
|
|
*this = source;
|
|
}
|
|
|
|
GStatBag::~GStatBag()
|
|
{
|
|
GFREE(pMem);
|
|
}
|
|
|
|
|
|
// Clear out the bag, removing all states.
|
|
void GStatBag::Clear()
|
|
{
|
|
MemAllocOffset = 0;
|
|
for(int i = 0; i< StatBag_PageTableSize; i++)
|
|
IdPageTable[i] = StatBag_IdUnused;
|
|
}
|
|
|
|
// Reset stat values, usually causing peaks to be recorded.
|
|
void GStatBag::Reset()
|
|
{
|
|
for(int i = 0; i< StatBag_PageTableSize; i++)
|
|
{
|
|
UInt pageOffset = IdPageTable[i];
|
|
|
|
if (pageOffset != StatBag_IdUnused)
|
|
{
|
|
UInt16* ptable = (UInt16*) (pMem + (pageOffset * StatBag_MemGranularity));
|
|
|
|
for (int j=0; j < StatBag_PageSize; j++)
|
|
{
|
|
if (ptable[j] != StatBag_IdUnused)
|
|
{
|
|
UInt id = j | (i << StatBag_PageShift);
|
|
StatInterface* psi = GetInterface(id);
|
|
GStat* pstat = (GStat*)(pMem + ptable[j] * StatBag_MemGranularity);
|
|
psi->Reset(pstat);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GStatInfo::StatInterface* GStatBag::GetInterface(UInt id)
|
|
{
|
|
const GStatDesc* pdesc = GStatDesc::GetDesc(id);
|
|
GASSERT(pdesc->GetType() < GStat::Stat_TypeCount);
|
|
return GStats_InterfaceTable[pdesc->GetType()];
|
|
}
|
|
|
|
|
|
// Add a statistic value of a certain id.
|
|
bool GStatBag::Add(UInt statId, GStat* pstat)
|
|
{
|
|
// Check that stat is registered.
|
|
StatInterface* psi = GetInterface(statId);
|
|
GStat* pthisStat = GetStatRef(statId);
|
|
|
|
if (!pthisStat)
|
|
{
|
|
// Try to add stat
|
|
pthisStat = (GStat*)AllocStatData(statId, psi->GetStatDataSize());
|
|
if (!pthisStat)
|
|
return false;
|
|
psi->Init(pthisStat);
|
|
}
|
|
|
|
psi->Add(pthisStat, pstat);
|
|
return true;
|
|
}
|
|
|
|
bool GStatBag::AddMemoryStat(UInt statId, const GMemoryStat& stat)
|
|
{
|
|
GMemoryStat* pmemStat = (GMemoryStat*)GetStatRef(statId);
|
|
if (!pmemStat)
|
|
{
|
|
pmemStat = (GMemoryStat*)AllocStatData(statId, sizeof(GMemoryStat));
|
|
if (!pmemStat)
|
|
return false;
|
|
pmemStat->Init();
|
|
}
|
|
|
|
pmemStat->Add((GMemoryStat*)&stat);
|
|
return true;
|
|
}
|
|
|
|
bool GStatBag::IncrementMemoryStat(UInt statId, UPInt alloc, UPInt use)
|
|
{
|
|
GMemoryStat* pmemStat = (GMemoryStat*)GetStatRef(statId);
|
|
if (!pmemStat)
|
|
{
|
|
pmemStat = (GMemoryStat*)AllocStatData(statId, sizeof(GMemoryStat));
|
|
if (!pmemStat)
|
|
return false;
|
|
pmemStat->Init();
|
|
}
|
|
|
|
pmemStat->Increment(alloc, use);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
bool GStatBag::SetMin(UInt statId, GStat* pstat)
|
|
{
|
|
// Check that stat is registered.
|
|
StatInterface* psi = GetInterface(statId);
|
|
GStat* pthisStat = GetStatRef(statId);
|
|
|
|
if (!pthisStat)
|
|
{
|
|
// Try to add stat
|
|
pthisStat = (GStat*)AllocStatData(statId, psi->GetStatDataSize());
|
|
if (!pthisStat)
|
|
return false;
|
|
psi->Init(pthisStat);
|
|
psi->Add(pthisStat, pstat);
|
|
}
|
|
else
|
|
{
|
|
psi->SetMin(pthisStat, pstat);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GStatBag::SetMax(UInt statId, GStat* pstat)
|
|
{
|
|
StatInterface* psi = GetInterface(statId);
|
|
GStat* pthisStat = GetStatRef(statId);
|
|
|
|
if (!pthisStat)
|
|
{
|
|
// Try to add stat
|
|
pthisStat = (GStat*)AllocStatData(statId, psi->GetStatDataSize());
|
|
if (!pthisStat)
|
|
return false;
|
|
psi->Init(pthisStat);
|
|
psi->Add(pthisStat, pstat);
|
|
}
|
|
else
|
|
{
|
|
psi->SetMax(pthisStat, pstat);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Add values of a different stat bag to ours.
|
|
void GStatBag::CombineStatBags(const GStatBag& other,
|
|
bool (GStatBag::*combineFunc)(UInt, GStat*))
|
|
{
|
|
// Go through the items in the other bag, and add them one item at a time.
|
|
for(int i = 0; i< StatBag_PageTableSize; i++)
|
|
{
|
|
UInt pageOffset = other.IdPageTable[i];
|
|
if (pageOffset != StatBag_IdUnused)
|
|
{
|
|
UInt16* ptable = (UInt16*) (other.pMem + (pageOffset * StatBag_MemGranularity));
|
|
|
|
for (int j=0; j < StatBag_PageSize; j++)
|
|
{
|
|
if (ptable[j] != StatBag_IdUnused)
|
|
{
|
|
UInt id = j | (i << StatBag_PageShift);
|
|
GStat* pstat = (GStat*)(other.pMem + ptable[j] * StatBag_MemGranularity);
|
|
(this->*combineFunc)(id, pstat);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void GStatBag::RecursiveGroupUpdate(GStatDesc::Iterator it)
|
|
{
|
|
const GStatDesc* pdesc = *it;
|
|
|
|
if (pdesc)
|
|
{
|
|
GStatDesc::Iterator ichild = pdesc->GetChildIterator();
|
|
|
|
if (pdesc->IsAutoSumGroup())
|
|
{
|
|
// Add all children to us.
|
|
while(!ichild.IsEnd())
|
|
{
|
|
RecursiveGroupUpdate(ichild);
|
|
|
|
GStat* pstatData = GetStatRef(ichild.GetId());
|
|
if (pstatData)
|
|
Add(pdesc->GetId(), pstatData);
|
|
++ichild;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while(!ichild.IsEnd())
|
|
{
|
|
RecursiveGroupUpdate(ichild);
|
|
++ichild;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update all cumulative groups in the list.
|
|
void GStatBag::UpdateGroups()
|
|
{
|
|
if (!GAtomicOps<UInt>::Load_Acquire(&GStats_InitDone))
|
|
GStatDesc::InitChildTree();
|
|
|
|
// Traverse the StateDesc tree recursively and add the items.
|
|
RecursiveGroupUpdate(GStatDesc::GetGroupIterator(GStatGroup_Default));
|
|
}
|
|
|
|
|
|
GStat* GStatBag::GetStatRef(UInt statId) const
|
|
{
|
|
// GetStat size.
|
|
if (statId >= GStat_MaxId)
|
|
{
|
|
GFC_DEBUG_WARNING1(1,
|
|
"GStatBag::GetStat - statId value %d is out of supported range", statId);
|
|
return 0;
|
|
}
|
|
|
|
UInt pageOffset = IdPageTable[statId >> StatBag_PageShift];
|
|
if (pageOffset == StatBag_IdUnused)
|
|
return 0;
|
|
|
|
UInt16* ptable = (UInt16*) (pMem + (pageOffset * StatBag_MemGranularity));
|
|
|
|
UInt pageIndex = ptable[statId & (StatBag_PageSize-1)];
|
|
if (pageIndex == StatBag_IdUnused)
|
|
return 0;
|
|
|
|
return (GStat*)(pMem + (pageIndex * StatBag_MemGranularity));
|
|
}
|
|
|
|
|
|
UByte* GStatBag::AllocStatData(UInt statId, UPInt size)
|
|
{
|
|
GASSERT(statId != 0);
|
|
|
|
// Round up the size.
|
|
size = (size + StatBag_MemGranularity - 1) & ~(StatBag_MemGranularity-1);
|
|
|
|
UInt16 pageOffset = IdPageTable[statId >> StatBag_PageShift];
|
|
if (pageOffset == StatBag_IdUnused)
|
|
{
|
|
// If page does not exit, allocate it.
|
|
UPInt tableSize = ((StatBag_PageSize * sizeof(UInt16)) + StatBag_MemGranularity - 1) &
|
|
~(StatBag_MemGranularity-1);
|
|
|
|
if (MemSize < (MemAllocOffset + tableSize))
|
|
{
|
|
GFC_DEBUG_WARNING1(1, "GStatBag out of reserved memory on adding statId %d", statId);
|
|
return 0;
|
|
}
|
|
|
|
// Update the page index.
|
|
pageOffset = (UInt16)(MemAllocOffset / StatBag_MemGranularity);
|
|
IdPageTable[statId >> StatBag_PageShift] = pageOffset;
|
|
|
|
// And mark all offsets as un-initialized.
|
|
UInt16* p = (UInt16*) (pMem + MemAllocOffset);
|
|
for (int i = 0; i < StatBag_PageSize; i++, p++)
|
|
*p = (UInt16)StatBag_IdUnused;
|
|
|
|
MemAllocOffset += tableSize;
|
|
}
|
|
|
|
UInt16* ptable = (UInt16*) (pMem + (pageOffset * StatBag_MemGranularity));
|
|
UInt pageIndex = ptable[statId & (StatBag_PageSize-1)];
|
|
GASSERT(pageIndex == StatBag_IdUnused);
|
|
GUNUSED(pageIndex);
|
|
|
|
// Make sure there is enough memory.
|
|
if (MemSize < (MemAllocOffset + size))
|
|
{
|
|
GFC_DEBUG_WARNING1(1, "GStatBag out of reserved memory on adding statId %d", statId);
|
|
return 0;
|
|
}
|
|
|
|
ptable[statId & (StatBag_PageSize-1)] = (UInt16)(MemAllocOffset / StatBag_MemGranularity);
|
|
UByte *pdata = pMem + MemAllocOffset;
|
|
MemAllocOffset += size;
|
|
return pdata;
|
|
}
|
|
|
|
|
|
// Does this accumulate data or not?
|
|
bool GStatBag::GetStat(GStatInfo *pstat, UInt statId) const
|
|
{
|
|
GStat* pstatData = GetStatRef(statId);
|
|
if (!pstatData)
|
|
return false;
|
|
|
|
// Get type.
|
|
*pstat = GStatInfo(statId, GetInterface(statId), pstatData);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
// *** GStatBag::Iterator Logic
|
|
|
|
GStatBag::Iterator::Iterator(GStatBag* pbag, UInt id, UInt groupId)
|
|
: Id(id), GroupId(groupId), pBag(pbag)
|
|
{
|
|
// Potentially advance Iterator once so that we point to
|
|
// a valid item or end.
|
|
if (pbag)
|
|
{
|
|
AdvanceTillValid();
|
|
}
|
|
else
|
|
{
|
|
Id = StatBag_EndId;
|
|
}
|
|
}
|
|
|
|
// Advance to the next valid item or fail.
|
|
bool GStatBag::Iterator::AdvanceTillValid()
|
|
{
|
|
if (!GAtomicOps<UInt>::Load_Acquire(&GStats_InitDone))
|
|
GStatDesc::InitChildTree();
|
|
|
|
// Advance forward until we found a matching id or hit the end.
|
|
while (Id < StatBag_EndId)
|
|
{
|
|
// If this page is empty skip it.
|
|
if (pBag->IdPageTable[Id >> StatBag_PageShift] == StatBag_IdUnused)
|
|
{
|
|
Id = (Id + StatBag_PageSize) & ~(StatBag_PageSize-1);
|
|
}
|
|
else
|
|
{
|
|
if (pBag->GetStat(&Result, Id))
|
|
{
|
|
const GStatDesc* pdesc = GStatDescRegistryInstance.GetDesc(Id);
|
|
|
|
GASSERT(pdesc);
|
|
if ((GroupId == GStat_MaxId) ||
|
|
(pdesc->GetGroupId() == GroupId))
|
|
{
|
|
// Found id, we are done.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
Id++;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// Obtains an Iterator for the specified stat group. Default implementation
|
|
// will return all of the stats in the bag.
|
|
GStatBag::Iterator GStatBag::GetIterator(UInt groupId)
|
|
{
|
|
return Iterator (this, 0, groupId);
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wait for stats to be ready; useful if stat update
|
|
// request is queued up for update in a separate thread.
|
|
/*
|
|
void GStatBag::WaitForData()
|
|
{
|
|
}
|
|
*/
|
|
|
|
#endif
|