Files
UnrealEngine/Engine/Source/Developer/Profiler/Private/ProfilerDataSource.h
2025-05-18 13:04:45 +08:00

2268 lines
67 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#if STATS
#include "HAL/ThreadSingleton.h"
#include "Containers/ChunkedArray.h"
#include "Misc/Guid.h"
#include "ProfilerCommon.h"
#include "ProfilerSample.h"
class FEventGraphData;
class FEventGraphSample;
class FProfilerAggregatedStat;
class FProfilerSession;
class IDataProvider;
/** Type definition for shared pointers to instances of FEventGraphSample. */
typedef TSharedPtr<class FEventGraphSample> FEventGraphSamplePtr;
/** Type definition for shared references to instances of FEventGraphSample. */
typedef TSharedRef<class FEventGraphSample> FEventGraphSampleRef;
/** Type definition for weak references to instances of FEventGraphSample. */
typedef TWeakPtr<class FEventGraphSample> FEventGraphSampleWeak;
/** Type definition for shared pointers to instances of FEventGraphData. */
typedef TSharedPtr<class FEventGraphData, ESPMode::ThreadSafe> FEventGraphDataPtr;
/** Type definition for shared references to instances of FEventGraphData. */
typedef TSharedRef<class FEventGraphData, ESPMode::ThreadSafe> FEventGraphDataRef;
/** Scratch buffers for multithreaded usage. */
struct FProfilerScratchArea : public TThreadSingleton<FProfilerScratchArea>
{
TArray<FEventGraphSample*> ExecuteOperationArray;
};
/*-----------------------------------------------------------------------------
TimeAccuracy
-----------------------------------------------------------------------------*/
struct FTimeAccuracy
{
enum Type
{
FPS008,
FPS015,
FPS030,
FPS060,
FPS120,
InvalidOrMax
};
static const float AsFrameTime( Type InTimeAccuracy )
{
static const float FrameTimeTable[InvalidOrMax] = {1000.0f/_FPS008,1000.0f/_FPS015,1000.0f/_FPS030,1000.0f/_FPS060,1000.0f/_FPS120};
return FrameTimeTable[InTimeAccuracy];
}
static const float AsInvFrameTime( Type InTimeAccuracy )
{
static const float FPSInvTable[InvalidOrMax] = {0.001f*_FPS008,0.001f*_FPS015,0.001f*_FPS030,0.001f*_FPS060,0.001f*_FPS120};
return FPSInvTable[InTimeAccuracy];
}
static const int32 AsFPSCounter( Type InTimeAccuracy )
{
static const int32 FPSTable[InvalidOrMax] = {_FPS008,_FPS015,_FPS030,_FPS060,_FPS120};
return FPSTable[InTimeAccuracy];
}
private:
static const int32 _FPS008;
static const int32 _FPS015;
static const int32 _FPS030;
static const int32 _FPS060;
static const int32 _FPS120;
};
// Private header and implementation
/*-----------------------------------------------------------------------------
FGraphDataSourceDescription
-----------------------------------------------------------------------------*/
class FGraphDataSourceDescription
{
public:
FGraphDataSourceDescription( const uint32 InStatID )
: StatID( InStatID )
, SampleType( EProfilerSampleTypes::InvalidOrMax )
, CreationTime( -1 )
{}
void Initialize( const FString InStatName, const FString InGroupName, const EProfilerSampleTypes::Type InSampleType, const FDateTime InCreationTime )
{
StatName = InStatName;
GroupName = InGroupName;
SampleType = InSampleType;
CreationTime = InCreationTime;
}
/**
* @return the ID of the stat owned by this data source.
*/
const uint32 GetStatID() const
{
return StatID;
}
/**
* @return name of the stat owned by this data source.
*/
const FString& GetStatName() const
{
return StatName;
}
/**
* @return name of the stat group owned by this data source.
*/
const FString& GetGroupName() const
{
return GroupName;
}
/**
* @return the sample type of the stat owned by this data source.
*/
const EProfilerSampleTypes::Type GetSampleType() const
{
return SampleType;
}
const FDateTime& GetCreationTime() const
{
return CreationTime;
}
/**
* @return number of bytes allocated by class instance.
*/
const SIZE_T GetMemoryUsage() const
{
SIZE_T MemoryUsage = sizeof(*this) + StatName.GetAllocatedSize() + GroupName.GetAllocatedSize();
return MemoryUsage;
}
protected:
/** The ID of the stat owned by this data source. */
const uint32 StatID;
/** The name of the stat owned by this data source. */
FString StatName;
/** The name of the stat group owned by this data source. */
FString GroupName;
/** The sample type of the stat owned by this data source. */
EProfilerSampleTypes::Type SampleType;
/** The time when this profiler session was created ( time of the connection to the client, time when a profiler capture was created ). */
FDateTime CreationTime;
};
/*-----------------------------------------------------------------------------
TCacheDataContainer
-----------------------------------------------------------------------------*/
/**
* Base class used for caching data.
* Type - type of data needed to be cached
*/
template< typename Type >
class TCacheDataContainer : public FNoncopyable
{
protected:
TCacheDataContainer()
: CachedValues( 0 )
{}
~TCacheDataContainer()
{}
/** Clears all cached values and reserves the same amount of memory that was allocated before. */
void ClearCache()
{
CachedChunks.Empty( CachedChunks.Num() );
CachedValues.Empty( CachedValues.Num() );
}
/**
* @return number of bytes allocated by class instance.
*/
const SIZE_T GetMemoryUsage() const
{
SIZE_T MemoryUsage = CachedValues.GetAllocatedSize() + CachedChunks.GetAllocatedSize();
return MemoryUsage;
}
enum
{
/** Number of cached values per chunk. */
NumElementsPerChunk = 64,
/** Number of bytes per chunk. */
NumBytesPerChunk = NumElementsPerChunk * sizeof(Type)
};
/** Cached values, one chunk contains 256 cached values. */
mutable TChunkedArray<Type,NumBytesPerChunk> CachedValues;
/** Each bit in this bit array indicates whether a chunk is fully cached or not. */
mutable TBitArray<> CachedChunks;
};
/*-----------------------------------------------------------------------------
TCachedDataByTime
-----------------------------------------------------------------------------*/
/**
* Simple class that provides mechanism for caching data by time with predefined time accuracy.
* Type - type of data needed to be cached
* ManagerClass - class that inherits from this class, must implement methods GetUncachedValueFromTimeRange(),GetTotalTimeMS()
*/
template< typename Type, typename ManagerClass >
class TCachedDataByTime : private TCacheDataContainer<Type>
{
public:
/** Typedef for template< typename Type, typename ManagerClass > class TCachedDataByTime. */
typedef TCachedDataByTime<Type, ManagerClass> ThisCachedDataByTime;
typedef TCacheDataContainer<Type> ThisCacheDataContainer;
TCachedDataByTime( const FTimeAccuracy::Type InTimeAccuracy )
: TCacheDataContainer<Type>()
, TimeAccuracyMS( FTimeAccuracy::AsFrameTime(InTimeAccuracy) )
, InvTimeAccuracyMS( FTimeAccuracy::AsInvFrameTime(InTimeAccuracy) )
{}
~TCachedDataByTime()
{}
void SetTimeAccuracy( const FTimeAccuracy::Type InTimeAccuracy )
{
ClearCache();
TimeAccuracyMS = FTimeAccuracy::AsFrameTime(InTimeAccuracy);
InvTimeAccuracyMS = FTimeAccuracy::AsInvFrameTime(InTimeAccuracy);
}
/** Clears all cached values and reserves the same amount of memory that was allocated before. */
void ClearCache()
{
TCacheDataContainer<Type>::ClearCache();
}
/**
* Calculates start index for the specified time range.
*
* @param StartTimeMS - the start of the time range
* @param EndTimeMS - the end of the time range
*
* @return an index of the frame for the specified time range
*/
FORCEINLINE const uint32 GetStartIndexFromTimeRange( const float StartTimeMS, const float EndTimeMS ) const
{
CheckInvariants( StartTimeMS, EndTimeMS );
const uint32 Index = FMath::TruncToInt( StartTimeMS * InvTimeAccuracyMS );
return Index;
}
/**
* Calculates value for the specified time range.
* This method looks for frames that meet the requirements.
* Then read the value(s) and calculate the average value.
*
* This is only a basic implementation and may change in future. Works only with constant time range.
*
* @param StartTimeMS - the start of the time range
* @param EndTimeMS - the end of the time range
*
* @return a value for the specified time range
*/
const Type GetValueFromTimeRange( const float StartTimeMS, const float EndTimeMS ) const
{
Type Result = (Type)0;
const uint32 Index = GetStartIndexFromTimeRange( StartTimeMS, EndTimeMS );
const uint32 CurrentChunkIndex = Index / ThisCacheDataContainer::NumElementsPerChunk;
const uint32 TotalNumFrames = FMath::TruncToInt( static_cast<const ManagerClass*>(this)->GetTotalTimeMS() * InvTimeAccuracyMS );
const uint32 NumNeededChunks = (TotalNumFrames + ThisCacheDataContainer::NumElementsPerChunk - 1) / ThisCacheDataContainer::NumElementsPerChunk;
const uint32 NumMissingValues = TotalNumFrames - ThisCacheDataContainer::CachedValues.Num();
// Add missing elements to the cached values.
ThisCacheDataContainer::CachedValues.Add( NumMissingValues );
// Add missing chunks and initialize to false.
for( uint32 NewChunkIndex = ThisCacheDataContainer::CachedChunks.Num(); NewChunkIndex < NumNeededChunks; NewChunkIndex++ )
{
ThisCacheDataContainer::CachedChunks.Add( false );
}
const bool bIsChunkFullyCached = ThisCacheDataContainer::CachedChunks[CurrentChunkIndex];
const bool bCanBeCached = CurrentChunkIndex < NumNeededChunks-1;
// Check if this stat for the specified frame index is included in the cached values.
if( bIsChunkFullyCached )
{
Result = ThisCacheDataContainer::CachedValues(Index);
}
// If value is not cached and if value can be cached, initialize the whole chunk.
else if( !bIsChunkFullyCached && bCanBeCached )
{
const uint32 ChunkStartIndex = CurrentChunkIndex * ThisCacheDataContainer::NumElementsPerChunk;
const uint32 ChunkEndIndex = ChunkStartIndex + ThisCacheDataContainer::NumElementsPerChunk;
for( uint32 NewValueIndex = ChunkStartIndex; NewValueIndex < ChunkEndIndex; NewValueIndex++ )
{
const float SampleStartTimeMS = (float)NewValueIndex * TimeAccuracyMS;
ThisCacheDataContainer::CachedValues(NewValueIndex) = (Type)static_cast<const ManagerClass*>(this)->GetUncachedValueFromTimeRange( SampleStartTimeMS, SampleStartTimeMS+TimeAccuracyMS );
}
ThisCacheDataContainer::CachedChunks[CurrentChunkIndex] = true;
Result = ThisCacheDataContainer::CachedValues(Index);
}
else
{
Result = (Type)static_cast<const ManagerClass*>(this)->GetUncachedValueFromTimeRange( StartTimeMS, EndTimeMS );
}
return Result;
}
/**
* @return number of bytes allocated by class instance.
*/
const SIZE_T GetMemoryUsage() const
{
SIZE_T MemoryUsage = TCacheDataContainer<Type>::GetMemoryUsage();
return MemoryUsage;
}
protected:
FORCEINLINE void CheckInvariants( const float StartTimeMS, const float EndTimeMS ) const
{
check( EndTimeMS > StartTimeMS );
const float TimeRange = EndTimeMS - StartTimeMS;
const float AbsDiff = FMath::Abs<float>( TimeRange-TimeAccuracyMS );
bool bIsNearlyEqual = FMath::IsNearlyZero( AbsDiff, 0.1f );
check( bIsNearlyEqual && TEXT("Time accuracy doesn't match") );
}
protected:
/** Time accuracy of the cached data, in milliseconds. */
float TimeAccuracyMS;
/** Inverted time accuracy of the cached data, in milliseconds. */
float InvTimeAccuracyMS;
};
/*-----------------------------------------------------------------------------
TCachedDataByIndex
-----------------------------------------------------------------------------*/
/**
* Simple class that provides mechanism for caching data by index.
* Type - type of data needed to be cached
* ManagerClass - class that inherits from this class, must implement methods GetUncachedValueFromIndex() and GetNumFrames()
*/
template< typename Type, typename ManagerClass >
class TCachedDataByIndex : private TCacheDataContainer<Type>
{
public:
/** Typedef for template< typename Type, typename ManagerClass > class TCachedDataByIndex. */
typedef TCachedDataByIndex<Type, ManagerClass> ThisCachedDataByIndex;
typedef TCacheDataContainer<Type> ThisCacheDataContainer;
TCachedDataByIndex()
: TCacheDataContainer<Type>()
{}
/** Destructor. */
~TCachedDataByIndex()
{}
/**
* @param Index - index of the value that is being retrieved
*
* @return a value for the specified index, the value is cached on demand and stored in cache for instant access.
*/
const Type GetValueFromIndex( const uint32 Index ) const
{
Type Result = (Type)0;
const uint32 CurrentChunkIndex = Index / ThisCacheDataContainer::NumElementsPerChunk;
const uint32 TotalNumFrames = static_cast<const ManagerClass*>(this)->GetNumFrames();
const uint32 NumNeededChunks = (TotalNumFrames + ThisCacheDataContainer::NumElementsPerChunk - 1) / ThisCacheDataContainer::NumElementsPerChunk;
const uint32 NumMissingValues = TotalNumFrames - ThisCacheDataContainer::CachedValues.Num();
// Add missing elements to the cached values.
ThisCacheDataContainer::CachedValues.Add( NumMissingValues );
// Add missing chunks and initialize to false.
for( uint32 NewChunkIndex = ThisCacheDataContainer::CachedChunks.Num(); NewChunkIndex < NumNeededChunks; NewChunkIndex++ )
{
ThisCacheDataContainer::CachedChunks.Add( false );
}
const bool bIsChunkFullyCached = ThisCacheDataContainer::CachedChunks[CurrentChunkIndex];
const bool bCanBeCached = CurrentChunkIndex < NumNeededChunks-1;
// Check if this stat for the specified frame index is included in the cached values.
if( bIsChunkFullyCached )
{
Result = ThisCacheDataContainer::CachedValues(Index);
}
// If value is not cached and if value can be cached, initialize the whole chunk.
else if( !bIsChunkFullyCached && bCanBeCached )
{
const uint32 ChunkStartIndex = CurrentChunkIndex * ThisCacheDataContainer::NumElementsPerChunk;
const uint32 ChunkEndIndex = ChunkStartIndex + ThisCacheDataContainer::NumElementsPerChunk;
for( uint32 NewValueIndex = ChunkStartIndex; NewValueIndex < ChunkEndIndex; NewValueIndex++ )
{
ThisCacheDataContainer::CachedValues(NewValueIndex) = (Type)static_cast<const ManagerClass*>(this)->GetUncachedValueFromIndex( NewValueIndex );
}
ThisCacheDataContainer::CachedChunks[CurrentChunkIndex] = true;
Result = ThisCacheDataContainer::CachedValues(Index);
}
else
{
Result = (Type)static_cast<const ManagerClass*>(this)->GetUncachedValueFromIndex( Index );
}
return Result;
}
/**
* @return number of bytes allocated by class instance.
*/
const SIZE_T GetMemoryUsage() const
{
SIZE_T MemoryUsage = TCacheDataContainer<Type>::GetMemoryUsage();
return MemoryUsage;
}
};
/*-----------------------------------------------------------------------------
FGraphDataSource
-----------------------------------------------------------------------------*/
/** Type definition for type of the cached values. */
typedef float TGraphDataType;
/**
* A specialized view of the a data provider. Provides access only to the specified group of data.
* This class allows accessing data in linear way which may be used to draw a line graph.
*/
class FGraphDataSource
: public FGraphDataSourceDescription
, public TCachedDataByIndex< TGraphDataType, FGraphDataSource >
, public TCachedDataByTime< TGraphDataType, FGraphDataSource >
{
friend class TCachedDataByIndex< TGraphDataType, FGraphDataSource >;
friend class TCachedDataByTime< TGraphDataType, FGraphDataSource >;
friend class FProfilerSession;
protected:
/**
* Initialization constructor, hidden on purpose.
*
* @param InProfilerSession - a reference to the profiler session that owns this stat
* @param InStatID - the ID of the stat that this graph data source will be created for
*
*/
FGraphDataSource( const TSharedRef<FProfilerSession>& InProfilerSession, const uint32 InStatID );
public:
/** Virtual destructor. */
virtual ~FGraphDataSource()
{
const SIZE_T MemoryUsage = GetMemoryUsage();
}
public:
/**
* @return number of bytes allocated by this graph data source.
*/
const SIZE_T GetMemoryUsage() const
{
SIZE_T MemoryUsage = 0;
MemoryUsage += ThisCachedDataByIndex::GetMemoryUsage();
MemoryUsage += ThisCachedDataByTime::GetMemoryUsage();
MemoryUsage += FGraphDataSourceDescription::GetMemoryUsage();
return MemoryUsage;
}
const uint32 GetNumFrames() const;
const float GetTotalTimeMS() const;
const TSharedRef<IDataProvider> GetDataProvider() const;
/**
* @return a session instance ID to the profiler session that owns this graph data source.
*/
const FGuid GetSessionInstanceID() const;
/**
* @return a const pointer to the aggregated stat for the specified stat ID or null if not found.
*/
const FProfilerAggregatedStat* GetAggregatedStat() const;
const bool CanBeDisplayedAsTimeBased() const
{
return true;
}
const bool CanBeDisplayedAsIndexBased() const
{
return true;
}
protected:
/* @return a sample value for the specified frame index from the data provider. */
const TGraphDataType GetUncachedValueFromIndex( const uint32 FrameIndex ) const;
/* @return an approximated sample value for the specified time range from the data provider. */
const TGraphDataType GetUncachedValueFromTimeRange( const float StartTimeMS, const float EndTimeMS ) const;
/** A reference to the profiler session that owns this graph data source. */
const TSharedRef<FProfilerSession> ProfilerSession;
// @TODO: This needs to be moved to 'filters and presets' filtering options.
float Scale;
};
/**
* A specialized view of a few data providers. Provides access only to the specified group of data.
* Data is interpolated for 60 frames per second.
* This class allows accessing data in linear way which may be used to draw a combined line graph with min, max and average values.
*/
class FCombinedGraphDataSource
: public FGraphDataSourceDescription
, public TCachedDataByTime< FVector, FCombinedGraphDataSource >
{
friend class TCachedDataByTime< FVector, FCombinedGraphDataSource >;
friend class FProfilerManager;
protected:
/**
* Initialization constructor, hidden on purpose.
*
* @param InStatID - the ID of the stat that will be drawn in the data graph widget
*
*/
FCombinedGraphDataSource( const uint32 InStatID, const FTimeAccuracy::Type InTimeAccuracy );
public:
/** Destructor. */
~FCombinedGraphDataSource()
{
const SIZE_T MemoryUsage = GetMemoryUsage();
}
const bool CanBeDisplayedAsMulti() const
{
const bool bResult = GetSourcesNum() > 1;
return bResult;
}
const bool CanBeDisplayedAsTimeBased() const
{
return GetSourcesNum() > 0;
}
const bool CanBeDisplayedAsIndexBased() const
{
return GetSourcesNum() == 1;
}
const bool IsProfilerSessionRegistered( const FGuid& SessionInstanceID ) const
{
const bool bIsRegistered = GraphDataSources.Contains( SessionInstanceID );
return bIsRegistered;
}
void RegisterWithProfilerSession( const FGuid& SessionInstanceID, const TSharedRef<const FGraphDataSource>& GraphDataSource )
{
GraphDataSources.Add( SessionInstanceID, GraphDataSource );
ThisCachedDataByTime::ClearCache();
}
void UnregisterWithProfilerSession( const FGuid& SessionInstanceID )
{
GraphDataSources.Remove( SessionInstanceID );
ThisCachedDataByTime::ClearCache();
}
/**
* @return number of bytes allocated by this graph data source.
*/
const SIZE_T GetMemoryUsage() const
{
SIZE_T MemoryUsage = 0;
return MemoryUsage;
}
const TMap<FGuid,TSharedRef<const FGraphDataSource>>::TConstIterator GetSourcesIterator() const
{
return GraphDataSources.CreateConstIterator();
}
const int GetSourcesNum() const
{
return GraphDataSources.Num();
}
const TSharedRef<const FGraphDataSource>* GetFirstSource() const
{
// TODO: Add global accessible Null FGraphDataSourceRef, and other instances used in the profiler to avoid returning empty pointer.
const TSharedRef<const FGraphDataSource>* Result = nullptr;
if( GetSourcesNum() > 0 )
{
Result = &GetSourcesIterator().Value();
}
return Result;
}
const uint32 GetNumFrames() const
{
if( GetSourcesNum() > 0 )
{
return FMath::TruncToInt( GetTotalTimeMS() * InvTimeAccuracyMS );
}
else
{
return 0;
}
}
const float GetTotalTimeMS() const
{
if( GetSourcesNum() > 0 )
{
float MinTotalTime = 1000.0f * 60 * 60 * 24 * 365;
for( auto It = GetSourcesIterator(); It; ++It )
{
const TSharedRef<const FGraphDataSource>& GraphDataSource = It.Value();
MinTotalTime = FMath::Min( MinTotalTime, GraphDataSource->GetTotalTimeMS() );
}
return MinTotalTime;
}
else
{
return 0.0f;
}
}
/**
*
* @param StartTimeMS - the start of the time range
* @param EndTimeMS - the end of the time range
* @param out_Indices - the map that contains calculated start index for each graph data source
*
*/
void GetStartIndicesFromTimeRange( const float StartTimeMS, const float EndTimeMS, TMap<FGuid,uint32>& out_StartIndices ) const;
protected:
/* @return an approximated sample value for the specified time range from the data provider. */
const FVector GetUncachedValueFromTimeRange( const float StartTimeMS, const float EndTimeMS ) const;
/** A map of graph data sources for all active profiler session instances for the specified stat ID. */
TMap<FGuid,TSharedRef<const FGraphDataSource>> GraphDataSources;
};
/*-----------------------------------------------------------------------------
Minimal event graph sample property management
-----------------------------------------------------------------------------*/
/** Enumerates event graph columns index. */
enum class EEventPropertyIndex : uint32
{
/** Stat name must be the first column, because of the expander arrow. */
StatName,
InclusiveTimeMS,
InclusiveTimePct,
ExclusiveTimeMS,
ExclusiveTimePct,
NumCallsPerFrame,
/** Special name used for unknown property. */
None,
MinInclusiveTimeMS,
MaxInclusiveTimeMS,
AvgInclusiveTimeMS,
MinNumCallsPerFrame,
MaxNumCallsPerFrame,
AvgNumCallsPerFrame,
ThreadName,
ThreadDurationMS,
FrameDurationMS,
ThreadPct,
FramePct,
ThreadToFramePct,
GroupName,
// Booleans
bIsHotPath,
bIsFiltered,
bIsCulled,
// Booleans internal
bNeedNotCulledChildrenUpdate,
/** Invalid enum type, may be used as a number of enumerations. */
InvalidOrMax,
};
/** Enumerates event graph sample value formatting types, usually match with event graph widget's columns. */
enum class EEventPropertyFormatters
{
/** Name, stored as a string, displayed as a regular string. */
Name,
/** Time in milliseconds, stored as a double, displayed as ".3f ms" */
TimeMS,
/** Time as percent, stored as a double, displayed as ".1f %" */
TimePct,
/** Number of calls, store as a double, displayed as ".1f" */
Number,
/** Boolean value, store as a bool, displaying is not supported yet. */
Bool,
/** Invalid enum type, may be used as a number of enumerations. */
InvalidOrMax,
};
/** Enumerates . */
enum class EEventPropertyTypes
{
/** double. */
Double,
/** FName. */
Name,
/** bool. */
Bool,
/** Invalid enum type, may be used as a number of enumerations. */
InvalidOrMax,
};
//namespace NEventProp
//{
class FEventProperty /*: public FNoncopyable*/
{
friend class FEventGraphSample;
protected:
FEventProperty
(
const EEventPropertyIndex PropertyIndex,
const FName PropertyName,
const uint32 PropertyOffset,
const EEventPropertyFormatters PropertyFormatter
)
:
Index( PropertyIndex ),
Name( PropertyName ),
Offset( PropertyOffset ),
Formatter( PropertyFormatter ),
Type( GetTypeFromFormatter(PropertyFormatter) )
{}
EEventPropertyTypes GetTypeFromFormatter( const EEventPropertyFormatters PropertyFormatter ) const
{
switch( PropertyFormatter )
{
case EEventPropertyFormatters::Name:
return EEventPropertyTypes::Name;
case EEventPropertyFormatters::TimeMS:
case EEventPropertyFormatters::TimePct:
case EEventPropertyFormatters::Number:
return EEventPropertyTypes::Double;
case EEventPropertyFormatters::Bool:
return EEventPropertyTypes::Bool;
default:
check(0);
return EEventPropertyTypes::InvalidOrMax;
}
}
public:
const bool IsDouble() const
{
return Type == EEventPropertyTypes::Double;
}
const bool IsBoolean() const
{
return Type == EEventPropertyTypes::Bool;
}
const bool IsName() const
{
return Type == EEventPropertyTypes::Name;
}
public:
const EEventPropertyIndex Index;
const FName Name;
const uint32 Offset;
const EEventPropertyFormatters Formatter;
const EEventPropertyTypes Type;
};
template< typename Type >
class TEventPropertyValue
{
public:
FORCEINLINE TEventPropertyValue( const FEventGraphSample& InEvent, const FEventProperty& EventProperty )
: Event( InEvent )
, PropertyOffset( EventProperty.Offset )
{}
FORCEINLINE_DEBUGGABLE const Type GetPropertyValue() const
{
const uint8* const PropertyAddress = GetPropertyAddress();
const Type Value = *(const Type*)PropertyAddress;
return Value;
}
FORCEINLINE_DEBUGGABLE Type& GetPropertyValueRef()
{
const uint8* const PropertyAddress = GetPropertyAddress();
Type& Value = (Type&)*(const Type*)PropertyAddress;
return Value;
}
FORCEINLINE_DEBUGGABLE const Type GetComparablePropertyValue() const
{
return GetPropertyValue();
}
FORCEINLINE const uint8* const GetPropertyAddress() const
{
const uint8* const SampleAddress = (const uint8* const)&Event;
const uint8* const PropertyAddress = SampleAddress + PropertyOffset;
return PropertyAddress;
}
protected:
const FEventGraphSample& Event;
const uint32 PropertyOffset;
};
typedef TEventPropertyValue< double > FEventPropertyValue_Double;
typedef TEventPropertyValue< bool > FEventPropertyValue_Bool;
class FEventPropertyValue_Name : public TEventPropertyValue< FName >
{
public:
FORCEINLINE FEventPropertyValue_Name( const FEventGraphSample& InEvent, const FEventProperty& EventProperty )
: TEventPropertyValue( InEvent, EventProperty )
{}
FORCEINLINE_DEBUGGABLE const FString GetComparablePropertyValue() const
{
const FString Value = GetPropertyValue().GetPlainNameString();
return Value;
}
};
namespace NEventFormatter
{
template< EEventPropertyFormatters PropertyType >
FORCEINLINE FString ToString( const FEventGraphSample& Event, const FEventProperty& EventProperty )
{
check(0);
return FString();
};
template <>
FORCEINLINE FString ToString< EEventPropertyFormatters::Name >( const FEventGraphSample& Event, const FEventProperty& EventProperty )
{
return FEventPropertyValue_Name(Event,EventProperty).GetPropertyValue().GetPlainNameString();
};
template <>
FORCEINLINE FString ToString< EEventPropertyFormatters::TimeMS >( const FEventGraphSample& Event, const FEventProperty& EventProperty )
{
return FString::Printf( TEXT("%.3f ms"), FEventPropertyValue_Double(Event,EventProperty).GetPropertyValue() );
};
template <>
FORCEINLINE FString ToString< EEventPropertyFormatters::TimePct >( const FEventGraphSample& Event, const FEventProperty& EventProperty )
{
return FString::Printf( TEXT("%.1f %%"), FEventPropertyValue_Double(Event,EventProperty).GetPropertyValue() );
};
template <>
FORCEINLINE FString ToString< EEventPropertyFormatters::Number >( const FEventGraphSample& Event, const FEventProperty& EventProperty )
{
return FString::Printf( TEXT("%.1f"), FEventPropertyValue_Double(Event,EventProperty).GetPropertyValue() );
};
}
//};
/** Hides all event graph related functionality under this namespace. */
//namespace NEventGraphSample {
/** Useful constants related to event graph functionality. */
struct FEventGraphConsts
{
static FName RootEvent;
static FName Self;
static FName FakeRoot;
};
// TODO: Rename to FProfilerEvent
/**
* Contains the same data as the profiler sample with some additions, doesn't depend on the other classes like profiler metadata or profiler aggregates.
* Modeled to be Slate compatible, thus it inherits from TSharedFromThis.
*/
class FEventGraphSample : public TSharedFromThis<FEventGraphSample>
{
struct FDuplicateHierarchyTag {};
struct FDuplicateSimpleTag {};
friend class FEventArraySorter;
friend class FEventGraphData;
public:
/** Initializes the minimal property manager for the event graph sample. */
static void InitializePropertyManagement();
static FORCEINLINE_DEBUGGABLE const FEventProperty& GetEventPropertyByIndex( const EEventPropertyIndex PropertyIndex )
{
return Properties[(const int32)PropertyIndex];
}
static FORCEINLINE_DEBUGGABLE const FEventProperty& GetEventPropertyByName( const FName PropertyName )
{
return *NamedProperties.FindChecked( PropertyName );
}
protected:
/** Contains all properties of the event graph sample class. */
static FEventProperty Properties[ (uint32)EEventPropertyIndex::InvalidOrMax ];
/** Contains all properties of the event graph sample class, indexed by the name of the property, stored as FName -> FEventProperty&. */
static TMap<FName,const FEventProperty*> NamedProperties;
public:
/** Initialization constructor. Only used for root event. */
FEventGraphSample( const FName InName )
: _ParentPtr( nullptr )
, _RootPtr( nullptr )
, _ThreadPtr( nullptr )
, _ThreadName( InName )
, _GroupName( InName )
, _StatName( InName )
, _StatID( 0 )
, _InclusiveTimeMS( 0.0 )
, _InclusiveTimePct( 0.0 )
, _MinInclusiveTimeMS( TNumericLimits<double>::Max() )
, _MaxInclusiveTimeMS( TNumericLimits<double>::Min() )
, _AvgInclusiveTimeMS( 0.0 )
, _AvgExclusiveTimeMS( 0.0 )
, _NumCallsPerFrame( 1.0 )
, _MinNumCallsPerFrame( TNumericLimits<double>::Max() )
, _MaxNumCallsPerFrame( TNumericLimits<double>::Min() )
, _AvgNumCallsPerFrame( 0.0 )
, _ExclusiveTimeMS( 0.0 )
, _ExclusiveTimePct( 0.0 )
, _FrameDurationMS( 0.0 )
, _ThreadDurationMS( 0.0 )
, _ThreadToFramePct( 0.0 )
, _ThreadPct( 0.0 )
, _FramePct( 0.0 )
, _bIsHotPath( false )
, _bIsFiltered( false )
, _bIsCulled( false )
, bNeedNotCulledChildrenUpdate( true )
{}
/**
* @return creates a named event
*/
static FEventGraphSamplePtr CreateNamedEvent( const FName EventName )
{
FEventGraphSample* RootEventGraphSample = new FEventGraphSample( EventName );
return MakeShareable( RootEventGraphSample );
}
protected:
/** Initialization constructor. */
FEventGraphSample
(
const FName InThreadName,
const FName InGroupName,
const uint32 InStatID,
const FName InStatName,
const double InInclusiveTimeMS,
const double InNumCallsPerFrame,
const FEventGraphSamplePtr InParentPtr = nullptr
)
: _ParentPtr( InParentPtr )
, _RootPtr( nullptr )
, _ThreadPtr( nullptr )
, _ThreadName( InThreadName )
, _GroupName( InGroupName )
, _StatName( InStatName )
, _StatID( InStatID )
, _InclusiveTimeMS( InInclusiveTimeMS )
, _InclusiveTimePct( 0.0f )
, _MinInclusiveTimeMS( InInclusiveTimeMS )
, _MaxInclusiveTimeMS( InInclusiveTimeMS )
, _AvgInclusiveTimeMS( InInclusiveTimeMS )
, _AvgExclusiveTimeMS( 0.0f )
, _NumCallsPerFrame( InNumCallsPerFrame )
, _MinNumCallsPerFrame( InNumCallsPerFrame )
, _MaxNumCallsPerFrame( InNumCallsPerFrame )
, _AvgNumCallsPerFrame( InNumCallsPerFrame )
, _ExclusiveTimeMS( 0.0f )
, _ExclusiveTimePct( 0.0f )
, _FrameDurationMS( 0.0f )
, _ThreadDurationMS( 0.0f )
, _ThreadToFramePct( 0.0f )
, _ThreadPct( 0.0f )
, _FramePct( 0.0f )
, _bIsHotPath( false )
, _bIsFiltered( false )
, _bIsCulled( false )
, bNeedNotCulledChildrenUpdate( true )
{}
/** Copy constructor, copies properties from the specified source event. */
FEventGraphSample( const FEventGraphSample& SourceEvent, const FDuplicateSimpleTag )
: _ParentPtr( nullptr )
, _RootPtr( nullptr )
, _ThreadPtr( nullptr )
, _ThreadName( SourceEvent._ThreadName )
, _GroupName( SourceEvent._GroupName )
, _StatName( SourceEvent._StatName )
, _StatID( SourceEvent._StatID )
, _InclusiveTimeMS( SourceEvent._InclusiveTimeMS )
, _InclusiveTimePct( SourceEvent._InclusiveTimePct )
, _MinInclusiveTimeMS( SourceEvent._MinInclusiveTimeMS )
, _MaxInclusiveTimeMS( SourceEvent._MaxInclusiveTimeMS )
, _AvgInclusiveTimeMS( SourceEvent._AvgInclusiveTimeMS )
, _AvgExclusiveTimeMS(SourceEvent._AvgExclusiveTimeMS)
, _NumCallsPerFrame( SourceEvent._NumCallsPerFrame )
, _MinNumCallsPerFrame( SourceEvent._MinNumCallsPerFrame )
, _MaxNumCallsPerFrame( SourceEvent._MaxNumCallsPerFrame )
, _AvgNumCallsPerFrame( SourceEvent._AvgNumCallsPerFrame )
, _ExclusiveTimeMS( SourceEvent._ExclusiveTimeMS )
, _ExclusiveTimePct( SourceEvent._ExclusiveTimePct )
, _FrameDurationMS( SourceEvent._FrameDurationMS )
, _ThreadDurationMS( SourceEvent._ThreadDurationMS )
, _ThreadToFramePct( SourceEvent._ThreadToFramePct )
, _ThreadPct( SourceEvent._ThreadPct )
, _FramePct( SourceEvent._FramePct )
, _bIsHotPath( false )
, _bIsFiltered( false )
, _bIsCulled( false )
, bNeedNotCulledChildrenUpdate( true )
{}
public:
/** Destructor. */
~FEventGraphSample()
{
#ifdef _DEBUG
int k = 0; k++;
#endif // _DEBUG
}
/*-----------------------------------------------------------------------------
Operations
-----------------------------------------------------------------------------*/
FORCEINLINE_DEBUGGABLE void Combine( const FEventGraphSamplePtr& Other )
{
// Total
_InclusiveTimeMS += Other->_InclusiveTimeMS;
_NumCallsPerFrame += Other->_NumCallsPerFrame;
_ExclusiveTimeMS += Other->_ExclusiveTimeMS;
// Min/Max
_MinInclusiveTimeMS = FMath::Min( _MinInclusiveTimeMS, Other->_MinInclusiveTimeMS );
_MaxInclusiveTimeMS = FMath::Max( _MaxInclusiveTimeMS, Other->_MaxInclusiveTimeMS );
_MinNumCallsPerFrame = FMath::Min( _MinNumCallsPerFrame, Other->_MinNumCallsPerFrame );
_MaxNumCallsPerFrame = FMath::Max( _MaxNumCallsPerFrame, Other->_MaxNumCallsPerFrame );
}
void RecalcTimes()
{
_FrameDurationMS = 0;
_ThreadDurationMS = 0;
_InclusiveTimeMS = 0;
for (auto Child : GetChildren())
{
_FrameDurationMS += Child->_FrameDurationMS;
_ThreadDurationMS += Child->_ThreadDurationMS;
_InclusiveTimeMS += Child->_InclusiveTimeMS;
}
for (auto Child : GetChildren())
{
Child->_InclusiveTimePct = (Child->_InclusiveTimeMS * 100) / _InclusiveTimeMS;
Child->_ThreadPct = (Child->_ThreadDurationMS * 100) / _ThreadDurationMS;
Child->_FramePct = (Child->_FrameDurationMS * 100) / _FrameDurationMS;
}
}
protected:
/** For creating per-frame average event graph. */
FORCEINLINE_DEBUGGABLE void CopyAverage( const double NumFrames )
{
_InclusiveTimeMS = _AvgInclusiveTimeMS;
_ExclusiveTimeMS = _AvgExclusiveTimeMS;
_NumCallsPerFrame = _AvgNumCallsPerFrame;
_FrameDurationMS /= NumFrames;
_ThreadDurationMS /= NumFrames;
FixFrameThreadPcts();
}
/** For creating highest "per-frame" event graph. */
FORCEINLINE_DEBUGGABLE void CopyMaximum( FEventGraphSample* RootEvent, FEventGraphSample* ThreadEvent )
{
_InclusiveTimeMS = _MaxInclusiveTimeMS;
_NumCallsPerFrame = _MaxNumCallsPerFrame;
_ThreadDurationMS = ThreadEvent->_MaxInclusiveTimeMS;
_FrameDurationMS = RootEvent->_MaxInclusiveTimeMS;
// Exclusive values don't make sense for max.
_ExclusiveTimeMS = 0.0;
_ExclusiveTimePct = 0.0;
FixFrameThreadPcts();
}
/** Calculates time and percentage values that may depend on a child's parent. */
void FixChildrenTimesAndCalcAverages( const double NumFrames )
{
if (_StatName != FEventGraphConsts::RootEvent)
{
// Get correct value for frame and thread duration.
_FrameDurationMS = GetRoot()->_InclusiveTimeMS;
_ThreadDurationMS = GetThread()->_InclusiveTimeMS;
FEventGraphSample* Parent = GetParent().Get();
if (Parent)
{
if (IsSelf())
{
Parent->_ExclusiveTimeMS = _InclusiveTimeMS;
Parent->_AvgExclusiveTimeMS = Parent->_ExclusiveTimeMS / NumFrames;
Parent->_ExclusiveTimePct = 100.0f * Parent->_ExclusiveTimeMS / Parent->_InclusiveTimeMS;
}
_InclusiveTimePct = 100.0f * _InclusiveTimeMS / Parent->_InclusiveTimeMS;
}
FixFrameThreadPcts();
}
else
{
_InclusiveTimePct = 100.0f;
_ThreadDurationMS = _InclusiveTimeMS;
_FrameDurationMS = _InclusiveTimeMS;
}
_AvgInclusiveTimeMS = _InclusiveTimeMS / NumFrames;
_AvgExclusiveTimeMS = _ExclusiveTimeMS / NumFrames;
_AvgNumCallsPerFrame = _NumCallsPerFrame / NumFrames;
}
void FixFrameThreadPcts()
{
_ThreadToFramePct = 100.0f*_ThreadDurationMS / _FrameDurationMS;
_ThreadPct = 100.0f*_InclusiveTimeMS / _ThreadDurationMS;
_FramePct = 100.0f*_InclusiveTimeMS / _FrameDurationMS;
}
/** Copy maximum values for all children, also fixes thread/frame time. */
void SetMaximumTimesForAllChildren();
/** Sets root and thread for all children. */
void SetRootAndThreadForAllChildren();
/** Fixes children times and calculates average values. */
void FixChildrenTimesAndCalcAveragesForAllChildren( const double NumFrames );
FORCEINLINE_DEBUGGABLE bool AreTheSamePtr( const FEventGraphSamplePtr& Other ) const
{
const bool bAreChildrenTheSame = _ThreadName == Other->_ThreadName && _StatID == Other->_StatID;// && _GroupName==Other._GroupName;
return bAreChildrenTheSame;
}
FEventGraphSamplePtr FindChildPtr( const FEventGraphSamplePtr& ChildToLook );
void Combine_Recurrent( const FEventGraphSamplePtr& Other );
public:
template< typename TFunc >
FORCEINLINE_DEBUGGABLE void ExecuteOperationForAllChildren( TFunc FuncToCall )
{
TArray<FEventGraphSample*>& Stack = FProfilerScratchArea::Get().ExecuteOperationArray;
Stack.Add( &AsShared().Get() );
int32 Idx = 1;
while (Stack.Num() > 0)
{
// Get the parent and assign events.
FEventGraphSample* Current = Stack.Pop( EAllowShrinking::No );
FuncToCall( Current );
// Push children onto the stack.
const TArray<FEventGraphSamplePtr>& ChildrenPtr = Current->GetChildren();
const int32 NumChildren = ChildrenPtr.Num();
for( int32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex )
{
const FEventGraphSamplePtr& ChildPtr = ChildrenPtr[ChildIndex];
Stack.Push( ChildPtr.Get() );
}
}
// Reset, but keep the allocation.
Stack.Reset();
}
template< typename TFunc, typename TArg0 >
FORCEINLINE_DEBUGGABLE void ExecuteOperationForAllChildren( TFunc FuncToCall, TArg0 Arg0 )
{
TArray<FEventGraphSample*>& Stack = FProfilerScratchArea::Get().ExecuteOperationArray;
Stack.Add( &AsShared().Get() );
int32 Idx = 1;
while (Stack.Num() > 0)
{
// Get the parent and assign events.
FEventGraphSample* Current = Stack.Pop( EAllowShrinking::No );
FuncToCall( Current, Arg0 );
// Push children onto the stack.
const TArray<FEventGraphSamplePtr>& ChildrenPtr = Current->GetChildren();
const int32 NumChildren = ChildrenPtr.Num();
for (int32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex)
{
const FEventGraphSamplePtr& ChildPtr = ChildrenPtr[ChildIndex];
Stack.Push( ChildPtr.Get() );
}
}
// Reset, but keep the allocation.
Stack.Reset();
}
template< typename TFunc, typename TArg0, typename TArg1 >
FORCEINLINE_DEBUGGABLE void ExecuteOperationForAllChildren( TFunc FuncToCall, TArg0 Arg0, TArg1 Arg1 )
{
TArray<FEventGraphSample*>& Stack = FProfilerScratchArea::Get().ExecuteOperationArray;
Stack.Add( &AsShared().Get() );
int32 Idx = 1;
while (Stack.Num() > 0)
{
// Get the parent and assign events.
FEventGraphSample* Current = Stack.Pop( EAllowShrinking::No );
FuncToCall( Current, Arg0, Arg1 );
// Push children onto the stack.
const TArray<FEventGraphSamplePtr>& ChildrenPtr = Current->GetChildren();
const int32 NumChildren = ChildrenPtr.Num();
for (int32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex)
{
const FEventGraphSamplePtr& ChildPtr = ChildrenPtr[ChildIndex];
Stack.Push( ChildPtr.Get() );
}
}
// Reset, but keep the allocation.
Stack.Reset();
}
protected:
/** @return a shared pointer to the newly created copy of this event graph sample, creates a full copy of hierarchy and duplicates all samples. */
FEventGraphSamplePtr DuplicateWithHierarchyPtr()
{
FEventGraphSamplePtr ParentPtr = DuplicateSimplePtr();
// Duplicate children
const int32 NumChildren = _ChildrenPtr.Num();
ParentPtr->_ChildrenPtr.Reserve( _ChildrenPtr.Num() );
for( int32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex )
{
FEventGraphSamplePtr ChildPtr = _ChildrenPtr[ChildIndex]->DuplicateWithHierarchyPtr();
//ParentPtr->AddChildAndSetParentPtr( ChildPtr );
ChildPtr->_ParentPtr = ParentPtr;
ParentPtr->_ChildrenPtr.Add( ChildPtr );
}
return ParentPtr;
}
FORCEINLINE void AddChildAndSetParentPtr( const FEventGraphSamplePtr& ChildPtr )
{
ChildPtr->_ParentPtr = AsShared();
_ChildrenPtr.Add( ChildPtr );
}
#if 0
FORCEINLINE_DEBUGGABLE void SetRootAndThreadEvents_Iterative( FEventGraphSample* RootEvent, FEventGraphSample* ThreadEvent )
{
TArray<FEventGraphSample*>& Stack = FProfilerScratchArea::Get().ExecuteOperationArray;
Stack.Add( &AsShared().Get() );
int32 Idx = 1;
while (Stack.Num() > 0)
{
// Get the parent and assign events.
FEventGraphSample* Current = Stack.Pop(EAllowShrinking::No);
FSetRootAndThread()(Current, RootEvent, ThreadEvent);
// Push children onto the stack.
const TArray<FEventGraphSamplePtr>& ChildrenPtr = Current->GetChildren();
const int32 NumChildren = ChildrenPtr.Num();
for (int32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex)
{
const FEventGraphSamplePtr& ChildPtr = ChildrenPtr[ChildIndex];
Stack.Push( ChildPtr.Get() );
}
}
// Reset, but keep the allocation.
Stack.Reset();
}
#endif // 0
public:
/** @return a shared pointer to the newly created copy of this event graph sample, without any children and with no parent. */
FEventGraphSamplePtr DuplicateSimplePtr()
{
return MakeShareable( new FEventGraphSample( *this, FDuplicateSimpleTag() ) );
}
/** Adds a child to this sample. */
FORCEINLINE void AddChildPtr( const FEventGraphSamplePtr& Child )
{
_ChildrenPtr.Add( Child );
}
/**
* @return true, if this event is a root event.
*/
bool IsRoot() const
{
return _StatName == FEventGraphConsts::RootEvent;
}
/**
* @return true, if this event is a fake self event.
*/
bool IsSelf() const
{
return _StatName == FEventGraphConsts::Self;
}
/**
* @return a shared pointer to the parent of this event, may be null.
*/
FORCEINLINE FEventGraphSamplePtr GetParent() const
{
return _ParentPtr.Pin();
}
/**
* @Reparent this event
*/
FORCEINLINE void SetParent(FEventGraphSamplePtr NewParent)
{
_ParentPtr = NewParent;
}
/**
* @return a shared pointer to the root event of this event, may be null.
*/
const FEventGraphSample* GetRoot() const
{
return _RootPtr;
}
/**
* @return a shared pointer to the thread event of this event, may be null.
*/
const FEventGraphSample* GetThread() const
{
return _ThreadPtr;
}
/**
* @return a const reference to the child samples of this sample.
*/
FORCEINLINE const TArray<FEventGraphSamplePtr>& GetChildren() const
{
return _ChildrenPtr;
}
/**
* @return a const array that contains all children that have not been culled.
*/
FORCEINLINE_DEBUGGABLE const TArray<FEventGraphSamplePtr>& GetNotCulledChildren() const
{
const_cast<FEventGraphSample*>(this)->UpdateNotCulledChildrenInternal();
return _NotCulledChildrenPtr;
}
FORCEINLINE void RequestNotCulledChildrenUpdate()
{
bNeedNotCulledChildrenUpdate = true;
}
protected:
/**
* @return an array that contains all children that have not been culled.
*/
FORCEINLINE_DEBUGGABLE TArray<FEventGraphSamplePtr>& GetNotCulledChildrenInternal()
{
UpdateNotCulledChildrenInternal();
return _NotCulledChildrenPtr;
}
/**
* Updates an array that will contain all children that have not been culled.
*/
FORCEINLINE_DEBUGGABLE void UpdateNotCulledChildrenInternal()
{
if( bNeedNotCulledChildrenUpdate )
{
_NotCulledChildrenPtr.Reset();
const int32 NumChildren = _ChildrenPtr.Num();
for( int32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex )
{
const FEventGraphSamplePtr& ChildPtr = _ChildrenPtr[ChildIndex];
if( !ChildPtr->_bIsCulled )
{
_NotCulledChildrenPtr.Add( ChildPtr );
}
}
bNeedNotCulledChildrenUpdate = false;
}
}
public:
/**
* @return true, if this event contains culled children.
*/
FORCEINLINE_DEBUGGABLE const bool HasCulledChildren() const
{
const bool bHasCulledChildren = GetChildren().Num() != GetNotCulledChildren().Num();
return bHasCulledChildren;
}
/**
* @return a reference to the child samples of this sample.
*/
FORCEINLINE TArray<FEventGraphSamplePtr>& GetChildren()
{
return _ChildrenPtr;
}
/**
* @return a shorter name of this event.
*/
FORCEINLINE const FString GetShortEventName() const
{
return FProfilerHelper::ShortenName( _StatName.GetPlainNameString() );
}
/**
* @return a topmost parent of this event, usually it is a thread event. The root event is excluded.
*/
FEventGraphSamplePtr GetOutermost()
{
FEventGraphSamplePtr Outermost;
for( FEventGraphSamplePtr Top = AsShared(); Top.IsValid() && Top->GetParent().IsValid(); Top = Top->GetParent() )
{
Outermost = Top;
}
return Outermost;
}
void GetStack( TArray<FEventGraphSamplePtr>& out_Stack )
{
for( FEventGraphSamplePtr Top = AsShared(); Top.IsValid() && Top->GetParent().IsValid(); Top = Top->GetParent() )
{
out_Stack.Add( Top );
}
}
/**
* Generates an array with all event samples, so they can be accessed in the linear way.
* None of the events are duplicated. The root event is excluded.
*/
FORCEINLINE_DEBUGGABLE void GetLinearEvents( TArray<FEventGraphSamplePtr>& out_LinearEvents, const bool bUseCulled )
{
out_LinearEvents.Reset();
const TArray<FEventGraphSamplePtr> RootChildren = bUseCulled ? GetNotCulledChildren() : GetChildren();
const int32 NumChildren = RootChildren.Num();
for( int32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex )
{
GetLinearEvents_InternalRecurrent( RootChildren[ChildIndex], out_LinearEvents, bUseCulled );
}
}
private:
/** Internal method used to store linearized events. */
FORCEINLINE_DEBUGGABLE void GetLinearEvents_InternalRecurrent( const FEventGraphSamplePtr& ParentEvent, TArray<FEventGraphSamplePtr>& out_LinearEvents, const bool bUseCulled )
{
out_LinearEvents.Add( ParentEvent );
const TArray<FEventGraphSamplePtr> Children = bUseCulled ? ParentEvent->GetNotCulledChildren() : ParentEvent->GetChildren();
const int32 NumChildren = Children.Num();
for( int32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex )
{
GetLinearEvents_InternalRecurrent( Children[ChildIndex], out_LinearEvents, bUseCulled );
}
}
public:
#if 0
/**
* @return children time in ms, excluding the self time by default
*/
double GetChildrenTimeMS( const bool bExcludeSelf = true ) const
{
double ChildrenTotalTimeMS = 0.0f;
const int32 NumChildren = _ChildrenPtr.Num();
for( int32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex )
{
const FEventGraphSamplePtr& ChildPtr = _ChildrenPtr[ChildIndex];
if( ChildPtr->IsSelf() )
{
if( !bExcludeSelf )
{
ChildrenTotalTimeMS += ChildPtr->_InclusiveTimeMS;
}
}
else
{
ChildrenTotalTimeMS += ChildPtr->_InclusiveTimeMS;
}
}
return ChildrenTotalTimeMS;
}
/**
* @return if exist, a fake self event for this event sample, otherwise null.
*/
FEventGraphSamplePtr GetSelfOrNull() const
{
FEventGraphSamplePtr Self;
const int32 NumChildren = _ChildrenPtr.Num();
for( int32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex )
{
const FEventGraphSamplePtr& ChildPtr = _ChildrenPtr[ChildIndex];
if( ChildPtr->IsSelf() )
{
Self = ChildPtr;
break;
}
}
return Self;
}
#endif // 0
public:
double& PropertyValueAsDouble( const EEventPropertyIndex PropertyIndex )
{
const FEventProperty& EventProperty = GetEventPropertyByIndex(PropertyIndex);
// In case if you want to get FName/bool as a double.
checkSlow( EventProperty.IsDouble() );
FEventPropertyValue_Double Value(*this, EventProperty );
return Value.GetPropertyValueRef();
}
FName& PropertyValueAsFName( const EEventPropertyIndex PropertyIndex )
{
const FEventProperty& EventProperty = GetEventPropertyByIndex(PropertyIndex);
// In case if you want to get a double/bool as FName.
checkSlow( EventProperty.IsName() );
FEventPropertyValue_Name Value(*this, EventProperty );
return Value.GetPropertyValueRef();
}
bool& PropertyValueAsBool( const EEventPropertyIndex PropertyIndex )
{
const FEventProperty& EventProperty = GetEventPropertyByIndex(PropertyIndex);
// In case if you want to get a double/FName as a bool.
checkSlow( EventProperty.IsBoolean() );
FEventPropertyValue_Bool Value(*this, EventProperty );
return Value.GetPropertyValueRef();
}
const FString GetPropertyValueAsString( const EEventPropertyIndex PropertyIndex ) const
{
const FEventProperty& EventProperty = GetEventPropertyByIndex(PropertyIndex);
// In case if you want to get a double/bool as FName.
checkSlow( EventProperty.IsName() );
FEventPropertyValue_Name Value(*this, EventProperty );
return Value.GetPropertyValueRef().GetPlainNameString();
}
const FString GetFormattedValue( EEventPropertyIndex PropertyIndex ) const
{
const FEventProperty& EventProperty = GetEventPropertyByIndex(PropertyIndex);
FString FormattedValue;
if( EventProperty.Formatter == EEventPropertyFormatters::Name )
{
FormattedValue = NEventFormatter::ToString<EEventPropertyFormatters::Name>( *this, EventProperty );
}
else if( EventProperty.Formatter == EEventPropertyFormatters::TimeMS )
{
FormattedValue = NEventFormatter::ToString<EEventPropertyFormatters::TimeMS>( *this, EventProperty );
}
else if( EventProperty.Formatter == EEventPropertyFormatters::TimePct )
{
FormattedValue = NEventFormatter::ToString<EEventPropertyFormatters::TimePct>( *this, EventProperty );
}
else if( EventProperty.Formatter == EEventPropertyFormatters::Number )
{
FormattedValue = NEventFormatter::ToString<EEventPropertyFormatters::Number>( *this, EventProperty );
}
else
{
check(0);
}
return FormattedValue;
}
/*-----------------------------------------------------------------------------
Boolean states
-----------------------------------------------------------------------------*/
template< bool FEventGraphSample::*BoolPtr >
struct FSetBooleanState
{
FORCEINLINE void operator()( FEventGraphSample* EventPtr, const bool bValue )
{
EventPtr->*BoolPtr = bValue;
}
};
public:
template< const EEventPropertyIndex BooleanPropertyIndex >
void SetBooleanStateForAllChildren( const bool bValue )
{
//static_assert(false, "Method not supported.");
// Not sure why but this if-statement is not optimized based on BooleanPropertyIndex
if( BooleanPropertyIndex == EEventPropertyIndex::bIsCulled )
{
ExecuteOperationForAllChildren( FSetBooleanState<&FEventGraphSample::_bIsCulled>(), bValue );
}
else if( BooleanPropertyIndex == EEventPropertyIndex::bIsFiltered )
{
ExecuteOperationForAllChildren( FSetBooleanState<&FEventGraphSample::_bIsFiltered>(), bValue );
}
else if( BooleanPropertyIndex == EEventPropertyIndex::bIsHotPath )
{
ExecuteOperationForAllChildren( FSetBooleanState<&FEventGraphSample::_bIsHotPath>(), bValue );
}
else if( BooleanPropertyIndex == EEventPropertyIndex::bNeedNotCulledChildrenUpdate )
{
ExecuteOperationForAllChildren( FSetBooleanState<&FEventGraphSample::bNeedNotCulledChildrenUpdate>(), bValue );
}
}
public:
/** Not used, optimized version @see SetBooleanStateForAllChildren. */
void SetBooleanStateForAllChildren_Recurrent( const EEventPropertyIndex BooleanPropertyIndex, const bool bValue )
{
PropertyValueAsBool(BooleanPropertyIndex) = bValue;
const int32 NumChildren = _ChildrenPtr.Num();
for( int32 ChildNx = 0; ChildNx < NumChildren; ++ChildNx )
{
const FEventGraphSamplePtr& Child = _ChildrenPtr[ChildNx];
Child->SetBooleanStateForAllChildren_Recurrent(BooleanPropertyIndex,bValue);
}
}
protected:
/** A weak pointer to the parent of this event. */
FEventGraphSampleWeak _ParentPtr;
/** A weak pointer to the parent of this event. */
FEventGraphSample* _RootPtr;
/** A weak pointer to the parent of this event. */
FEventGraphSample* _ThreadPtr;
/** Children of this event. */
TArray<FEventGraphSamplePtr> _ChildrenPtr;
/** Not culled children of this event. */
TArray<FEventGraphSamplePtr> _NotCulledChildrenPtr;
public:
/** Name of the thread that this event was captured on. */
FName _ThreadName;
/** Name of the stat group this event belongs to, ex. Engine. */
FName _GroupName;
/** Name of this event, ex. Frametime. If empty, it means that this sample is a root sample, use _ThreadName. */
FName _StatName;
/** Stat ID of the event. */ // TODO: Not needed, move to _statname if meta data is not available
uint32 _StatID;
/** Duration of this event and its children, in milliseconds. */
double _InclusiveTimeMS;
/** Duration of this event and its children as percent of the caller. */
double _InclusiveTimePct;
/** Minimum inclusive time of all instances for this event, in milliseconds. */
double _MinInclusiveTimeMS;
/** Maximum inclusive time of all instances for this event, in milliseconds. */
double _MaxInclusiveTimeMS;
/** Average inclusive time of all instances for this event, in milliseconds. */
double _AvgInclusiveTimeMS;
/** Average exclusive time of all instances for this event, in milliseconds. */
double _AvgExclusiveTimeMS;
/** Number of times this event was called. */
double _NumCallsPerFrame;
/** Minimum number of times this event was called. */
double _MinNumCallsPerFrame;
/** Maximum number of times this event was called. */
double _MaxNumCallsPerFrame;
/** Average number of times this event was called. */
double _AvgNumCallsPerFrame;
/** Exclusive time of this event, in milliseconds. */
double _ExclusiveTimeMS;
/** Exclusive time of this event as percent of this call's inclusive time. */
double _ExclusiveTimePct;
//{
/** Duration of the frame that this event belongs to, in milliseconds. */
double _FrameDurationMS;
/** Duration of the thread that this event was captured on, in milliseconds. */
double _ThreadDurationMS;
//}
/** Percent of time spent in the thread in relation to the entire frame. */
double _ThreadToFramePct;
/** Percent of inclusive time spent by this event in the particular thread. */
double _ThreadPct;
/** Percent of inclusive time spent by this event in the particular frame. */
double _FramePct;
/** True, if this event is marked as being in the hot path. */
bool _bIsHotPath;
/** True, if this event is marked as being filtered based, but still should be visible in the event graph, but faded. */
bool _bIsFiltered;
/** True, if this event is marked as being culled and shouldn't be visible in the event graph. */
bool _bIsCulled;
private:
/** Whether we need to update the array that contains non culled children. */
bool bNeedNotCulledChildrenUpdate;
};
/*-----------------------------------------------------------------------------
Sorting by property
-----------------------------------------------------------------------------*/
/** Enumerates compare operations. */
namespace EEventCompareOps
{
enum Type
{
/** A < B. */
Less,
/** B < A. */
Greater,
/** A == B. */
Equal,
/** A contains B. */
Contains,
/** Invalid enum type, may be used as a number of enumerations. */
InvalidOrMax,
};
}
namespace EventGraphPrivate
{
/** Helper class used to during sorting the events. */
template< const EEventPropertyTypes PropType, const EEventCompareOps::Type CompareOp >
struct TCompareByProperty
{
private:
const EEventPropertyIndex PropertyIndex;
public:
FORCEINLINE TCompareByProperty( const EEventPropertyIndex InPropertyIndex )
: PropertyIndex( InPropertyIndex )
{}
};
template<>
struct TCompareByProperty<EEventPropertyTypes::Name,EEventCompareOps::Greater>
{
private:
const EEventPropertyIndex PropertyIndex;
public:
FORCEINLINE TCompareByProperty( const EEventPropertyIndex InPropertyIndex )
: PropertyIndex( InPropertyIndex )
{}
FORCEINLINE_DEBUGGABLE bool operator()( const FEventGraphSamplePtr& A, const FEventGraphSamplePtr& B ) const
{
return A.Get()->PropertyValueAsFName(PropertyIndex).Compare(B.Get()->PropertyValueAsFName(PropertyIndex)) > 0;
}
};
template<>
struct TCompareByProperty<EEventPropertyTypes::Double,EEventCompareOps::Greater>
{
private:
const EEventPropertyIndex PropertyIndex;
public:
FORCEINLINE TCompareByProperty( const EEventPropertyIndex InPropertyIndex )
: PropertyIndex( InPropertyIndex )
{}
FORCEINLINE_DEBUGGABLE bool operator()( const FEventGraphSamplePtr& A, const FEventGraphSamplePtr& B ) const
{
return A.Get()->PropertyValueAsDouble(PropertyIndex) > B.Get()->PropertyValueAsDouble(PropertyIndex);
}
};
template<>
struct TCompareByProperty<EEventPropertyTypes::Name,EEventCompareOps::Less>
{
private:
const EEventPropertyIndex PropertyIndex;
public:
FORCEINLINE TCompareByProperty( const EEventPropertyIndex InPropertyIndex )
: PropertyIndex( InPropertyIndex )
{}
FORCEINLINE_DEBUGGABLE bool operator()( const FEventGraphSamplePtr& A, const FEventGraphSamplePtr& B ) const
{
return A.Get()->PropertyValueAsFName(PropertyIndex).Compare(B.Get()->PropertyValueAsFName(PropertyIndex)) < 0;
}
};
template<>
struct TCompareByProperty<EEventPropertyTypes::Double,EEventCompareOps::Less>
{
private:
const EEventPropertyIndex PropertyIndex;
public:
FORCEINLINE TCompareByProperty( const EEventPropertyIndex InPropertyIndex )
: PropertyIndex( InPropertyIndex )
{}
FORCEINLINE_DEBUGGABLE bool operator()( const FEventGraphSamplePtr& A, const FEventGraphSamplePtr& B ) const
{
return A.Get()->PropertyValueAsDouble(PropertyIndex) < B.Get()->PropertyValueAsDouble(PropertyIndex);
}
};
}
/**
* Class used to executing specified operation for the specified property on array of events.
* After executing the specified boolean property will be changed accordingly.
*/
class FEventArrayBooleanOp
{
// @TODO: Move parameters to constructor and remove static
// FEventArrayBooleanOp( parameters ).Execute()
// vs
// FEventGraphSample::ExecuteOperation( parameters )
public:
static void ExecuteOperation
(
FEventGraphSamplePtr DestPtr,
const EEventPropertyIndex DestPropertyIndex,
const FEventGraphSamplePtr SrcPtr,
const EEventPropertyIndex SrcPropertyIndex,
const EEventCompareOps::Type OpType
)
{
const FEventProperty& SrcEventProperty = FEventGraphSample::GetEventPropertyByIndex(SrcPropertyIndex);
const bool bCompareAsStrings = SrcEventProperty.IsName();
const bool bCompareAsFloat = SrcEventProperty.IsDouble();
const FEventProperty& DestEventProperty = FEventGraphSample::GetEventPropertyByIndex(DestPropertyIndex);
check( DestEventProperty.IsBoolean() );
// These branches should be gone once template is instantiated.
if( OpType == EEventCompareOps::Less )
{
if( bCompareAsStrings )
{
ExecuteOperationInternal( DestPtr->GetChildren(), DestEventProperty, SrcPtr, EventGraphPrivate::TCompareByProperty<EEventPropertyTypes::Name,EEventCompareOps::Less>(SrcEventProperty.Index) );
}
else if( bCompareAsFloat )
{
ExecuteOperationInternal( DestPtr->GetChildren(), DestEventProperty, SrcPtr, EventGraphPrivate::TCompareByProperty<EEventPropertyTypes::Double,EEventCompareOps::Less>(SrcEventProperty.Index) );
}
}
else if( OpType == EEventCompareOps::Greater )
{
if( bCompareAsStrings )
{
ExecuteOperationInternal( DestPtr->GetChildren(), DestEventProperty, SrcPtr, EventGraphPrivate::TCompareByProperty<EEventPropertyTypes::Name,EEventCompareOps::Greater>(SrcEventProperty.Index) );
}
else if( bCompareAsFloat )
{
ExecuteOperationInternal( DestPtr->GetChildren(), DestEventProperty, SrcPtr, EventGraphPrivate::TCompareByProperty<EEventPropertyTypes::Double,EEventCompareOps::Greater>(SrcEventProperty.Index) );
}
}
}
private:
template < const EEventPropertyTypes PropType, const EEventCompareOps::Type CompareOp >
static void ExecuteOperationInternal
(
TArray< FEventGraphSamplePtr >& DestEvents,
const FEventProperty& DestEventProperty,
const FEventGraphSamplePtr& SrcPtr,
const EventGraphPrivate::TCompareByProperty<PropType,CompareOp>& Comparator
)
{
for( int32 ChildIndex = 0; ChildIndex < DestEvents.Num(); ChildIndex++ )
{
FEventGraphSamplePtr& Child = DestEvents[ChildIndex];
const bool bBooleanState = Comparator( Child, SrcPtr );
Child->PropertyValueAsBool(DestEventProperty.Index) = bBooleanState;
ExecuteOperationInternal( Child->GetChildren(), DestEventProperty, SrcPtr, Comparator );
}
}
static void ExecuteAssignOperation( TArray< FEventGraphSamplePtr >& DestEvents, const FEventProperty& DestEventProperty )
{
for( int32 ChildIndex = 0; ChildIndex < DestEvents.Num(); ChildIndex++ )
{
FEventGraphSamplePtr& Child = DestEvents[ChildIndex];
Child->PropertyValueAsBool(DestEventProperty.Index) = true;
ExecuteAssignOperation( Child->GetChildren(), DestEventProperty );
}
}
};
/** Class used to sorting array of events, based on specified property. */
class FEventArraySorter
{
public:
static void Sort( TArray< FEventGraphSamplePtr >& ChildrenToSort, const FName PropertyName, const EEventCompareOps::Type OpType )
{
const FEventProperty& EventProperty = FEventGraphSample::GetEventPropertyByName(PropertyName);
const bool bCompareAsStrings = EventProperty.IsName();
const bool bCompareAsFloat = EventProperty.IsDouble();
if( OpType == EEventCompareOps::Greater )
{
if( bCompareAsStrings )
{
SortInternal( ChildrenToSort, EventGraphPrivate::TCompareByProperty<EEventPropertyTypes::Name,EEventCompareOps::Greater>(EventProperty.Index) );
}
else if( bCompareAsFloat )
{
SortInternal( ChildrenToSort, EventGraphPrivate::TCompareByProperty<EEventPropertyTypes::Double,EEventCompareOps::Greater>(EventProperty.Index) );
}
}
else if( OpType == EEventCompareOps::Less )
{
if( bCompareAsStrings )
{
SortInternal( ChildrenToSort, EventGraphPrivate::TCompareByProperty<EEventPropertyTypes::Name,EEventCompareOps::Less>(EventProperty.Index) );
}
else if( bCompareAsFloat )
{
SortInternal( ChildrenToSort, EventGraphPrivate::TCompareByProperty<EEventPropertyTypes::Double,EEventCompareOps::Less>(EventProperty.Index) );
}
}
}
private:
template< const EEventPropertyTypes PropType, const EEventCompareOps::Type CompareOp >
static void SortInternal( TArray< FEventGraphSamplePtr >& ChildrenToSort, const EventGraphPrivate::TCompareByProperty<PropType,CompareOp>& CompareInstance )
{
ChildrenToSort.Sort( CompareInstance );
for( int32 ChildIndex = 0; ChildIndex < ChildrenToSort.Num(); ChildIndex++ )
{
TArray<FEventGraphSamplePtr>& Children = ChildrenToSort[ChildIndex]->GetChildren();
if( Children.Num() )
{
SortInternal( Children, CompareInstance );
}
}
}
};
/*-----------------------------------------------------------------------------
FEventGraphData related classes
-----------------------------------------------------------------------------*/
/** Enumerates event graph types. */
struct EEventGraphTypes
{
enum Type
{
/** Per-frame average event graph. */
Average,
/** Highest "per-frame" event graph. */
Maximum,
/** Event graph for one frame, so both average and maximum can be used. */
OneFrame,
/** Selected frames event graph. */
Total,
/** Invalid enum type, may be used as a number of enumerations. */
InvalidOrMax,
};
/**
* @param EventGraphType - The value to get the string for.
*
* @return string representation of the specified EEventGraphTypes value.
*/
static FString ToName( const EEventGraphTypes::Type EventGraphType );
/**
* @param EventGraphType - The value to get the string for.
*
* @return string representation with more detailed explanation of the specified EEventGraphTypes value.
*/
static FString ToDescription( const EEventGraphTypes::Type EventGraphType );
};
/** Simple struct used to return a result. */
struct FEventGraphContainer
{
/** Initialization constructor. */
FEventGraphContainer( uint32 InFrameStartIndex, uint32 InFrameEndIndex, const FEventGraphDataRef& InAverage, const FEventGraphDataRef& InMaximum, const FEventGraphDataRef& InTotal )
: FrameStartIndex( InFrameStartIndex )
, FrameEndIndex( InFrameEndIndex )
, Average( InAverage )
, Maximum( InMaximum )
, Total( InTotal )
{}
const uint32 FrameStartIndex;
const uint32 FrameEndIndex;
FEventGraphDataRef Average;
FEventGraphDataRef Maximum;
FEventGraphDataRef Total;
};
/**
* Provides access only to the profiler samples specified by a frame index or frame indices.
* This class allows accessing root and child samples which may be used to create an event graph.
*/
class FEventGraphData : public FNoncopyable
{
friend class FProfilerSession;
public:
/** Minimal default constructor. */
FEventGraphData();
/** Destructor, for debugging purpose only. */
FORCEINLINE_DEBUGGABLE ~FEventGraphData()
{}
/** Copy constructor, create a full duplication of the source event graph data. */
FEventGraphData(const FEventGraphData& Source);
protected:
/**
* Initialization constructor, hidden on purpose, may be only called from the FProfilerSession class.
*
* @param InProfilerSession - a const pointer to the profiler session that will be used to generate this event graph data
* @param InFrameIndex - the frame number from which to generate this event graph data
*
*/
FEventGraphData( const FProfilerSession * const InProfilerSession, const uint32 InFrameIndex );
/**
* Recursively populates the hierarchy of the event graph samples.
*
* @param ParentEventGraphSample - <describe ParentEventGraphSample>
* @param ParentProfilerSample - <describe ParentProfilerSample>
* @param DataProvider - <describe DataProvider>
* @param FrameDurationMS - <describe FrameDurationMS>
* @param ThreadName - <describe ThreadName>
* @param ThreadDurationMS - <describe ThreadDurationMS>
*
*/
void PopulateHierarchy_Recurrent
(
const FProfilerSession * const ProfilerSession,
const FEventGraphSamplePtr ParentEvent,
const FProfilerSample& ParentSample,
const TSharedRef<IDataProvider> DataProvider
);
public:
/**
* @return a duplicated instance of this event graph, this is a deep duplication, which means that for all events a new event is created
*/
FEventGraphDataRef DuplicateAsRef();
/** Combines current event graph with the second one. */
void Combine( const FEventGraphData& Other );
/** Updates data to get per-frame average event graph. */
void SetAsAverage();
/** Updates data to get highest "per-frame" event graph. */
void SetAsMaximim();
/** Finalizes current event graph. */
void Finalize( const uint32 InFrameStartIndex, const uint32 InFrameEndIndex );
/**
* @return root event that contains all thread root events and its children
*/
FORCEINLINE const FEventGraphSamplePtr& GetRoot() const
{
return RootEvent;
}
public:
/**
* @return the frame start index this event graph data was generated from.
*/
FORCEINLINE const uint32 GetFrameStartIndex() const
{
return FrameStartIndex;
}
/**
* @return the frame end index this event graph data was generated from.
*/
FORCEINLINE const uint32 GetFrameEndIndex() const
{
return FrameEndIndex;
}
/**
* @return the number of frames used to create this event graph data.
*/
const uint32 GetNumFrames() const
{
return FrameEndIndex - FrameStartIndex;
}
/**
* @return the description for this event graph data.
*/
FORCEINLINE const FString& GetDescription() const
{
return Description;
}
protected:
// TODO: FEventGraphSamplePtr -> FEventGraphSampleRef
/** Root sample, contains all thread samples and its children. */
FEventGraphSamplePtr RootEvent;
/** Description as "SessionName - FrameIndex/Indices". */
FString Description;
/** The frame start index this event graph data was generated from. */
uint32 FrameStartIndex;
/** The frame end index this event graph data was generated from. */
uint32 FrameEndIndex;
};
//}//namespace FEventGraphSample
#endif // STATS