FGraphDataSource::FGraphDataSource( const FProfilerSessionRef& InProfilerSession, const uint32 InStatID ) : FGraphDataSourceDescription( InStatID ) , ThisCachedDataByIndex() , ThisCachedDataByTime( FTimeAccuracy::FPS060 ) , ProfilerSession( InProfilerSession ) { const FProfilerStatMetaDataRef MetaData = ProfilerSession->GetMetaData(); const FProfilerStat& Stat = MetaData->GetStatByID(InStatID); const FProfilerGroup& Group = Stat.OwningGroup(); Initialize( Stat.Name().GetPlainNameString(), Group.Name().GetPlainNameString(), Stat.Type(), ProfilerSession->GetCreationTime() ); switch( GetSampleType() ) { case EProfilerSampleTypes::Memory: { // By default we show memory data as KBs. Scale = 1.0f / 1024.0f; break; } default: { Scale = 1.0f; } } }
void FProfilerSession::PopulateHierarchy_Recurrent ( const FProfilerCycleGraph& ParentGraph, const double ParentStartTimeMS, const double ParentDurationMS, const uint32 ParentSampleIndex ) { const FProfilerStatMetaDataRef MetaData = GetMetaData(); const uint32& ThreadID = MetaData->ThreadIDtoStatID.FindChecked( ParentGraph.ThreadId ); const uint32 SampleIndex = DataProvider->AddHierarchicalSample ( ThreadID, MetaData->GetStatByID(ParentGraph.StatId).OwningGroup().ID(), ParentGraph.StatId, ParentStartTimeMS, ParentDurationMS, ParentGraph.CallsPerFrame, ParentSampleIndex ); double ChildStartTimeMS = ParentStartTimeMS; double ChildrenDurationMS = 0.0f; for( int32 DataIndex = 0; DataIndex < ParentGraph.Children.Num(); DataIndex++ ) { const FProfilerCycleGraph& ChildCyclesCounter = ParentGraph.Children[DataIndex]; const double ChildDurationMS = MetaData->ConvertCyclesToMS( ChildCyclesCounter.Value ); if (ChildDurationMS > 0.0) { PopulateHierarchy_Recurrent( ChildCyclesCounter, ChildStartTimeMS, ChildDurationMS, SampleIndex ); } ChildStartTimeMS += ChildDurationMS; ChildrenDurationMS += ChildDurationMS; } const double SelfTimeMS = ParentDurationMS - ChildrenDurationMS; if( SelfTimeMS > 0.0f && ParentGraph.Children.Num() > 0 ) { const FName& ParentStatName = MetaData->GetStatByID( ParentGraph.StatId ).Name(); const FName& ParentGroupName = MetaData->GetStatByID( ParentGraph.StatId ).OwningGroup().Name(); // Create a fake stat that represents this profiler sample's exclusive time. // This is required if we want to create correct combined event graphs later. DataProvider->AddHierarchicalSample ( ThreadID, MetaData->GetStatByID(0).OwningGroup().ID(), 0, // @see FProfilerStatMetaData.Update, 0 means "Self" ChildStartTimeMS, SelfTimeMS, 1, SampleIndex ); } }
bool FProfilerManager::TrackStat( const uint32 StatID ) { bool bAdded = false; // Check if all profiler instances have this stat ready. int32 NumReadyStats = 0; for( auto It = GetProfilerInstancesIterator(); It; ++It ) { const FProfilerSessionRef ProfilerSession = It.Value(); NumReadyStats += ProfilerSession->GetAggregatedStat(StatID) != nullptr ? 1 : 0; } const bool bStatIsReady = NumReadyStats == GetProfilerInstancesNum(); if( StatID != 0 && bStatIsReady ) { FTrackedStat* TrackedStat = TrackedStats.Find( StatID ); if( TrackedStat == nullptr ) { // R = H, G = S, B = V const FLinearColor& ColorAverage = GetColorForStatID( StatID ); const FLinearColor ColorAverageHSV = ColorAverage.LinearRGBToHSV(); FLinearColor ColorBackgroundHSV = ColorAverageHSV; ColorBackgroundHSV.G = FMath::Max( 0.0f, ColorBackgroundHSV.G-0.25f ); FLinearColor ColorExtremesHSV = ColorAverageHSV; ColorExtremesHSV.G = FMath::Min( 1.0f, ColorExtremesHSV.G+0.25f ); ColorExtremesHSV.B = FMath::Min( 1.0f, ColorExtremesHSV.B+0.25f ); const FLinearColor ColorBackground = ColorBackgroundHSV.HSVToLinearRGB(); const FLinearColor ColorExtremes = ColorExtremesHSV.HSVToLinearRGB(); TrackedStat = &TrackedStats.Add( StatID, FTrackedStat(CreateCombinedGraphDataSource( StatID ),ColorAverage,ColorExtremes,ColorBackground,StatID) ); bAdded = true; // @TODO: Convert a reference parameter to copy parameter/sharedptr/ref/weak, to avoid problems when a reference is no longer valid. TrackedStatChangedEvent.Broadcast( *TrackedStat, true ); } if( TrackedStat != nullptr ) { uint32 NumAddedInstances = 0; bool bMetadataInitialized = false; for( auto It = GetProfilerInstancesIterator(); It; ++It ) { const FGuid& SessionInstanceID = It.Key();// ProfilerSessionInstanceID, ProfilerInstanceID, InstanceID const FProfilerSessionRef ProfilerSession = It.Value(); const bool bInstanceAdded = TrackStatForSessionInstance( StatID, SessionInstanceID ); NumAddedInstances += bInstanceAdded ? 1 : 0; // Initialize metadata for combine graph data source. // TODO: This should be checked against the remaining elements to detect inconsistent data. // The first instance should be the main. if( !bMetadataInitialized ) { const bool bIsStatReady = ProfilerSession->GetMetaData()->IsStatInitialized( StatID ); if( bIsStatReady ) { const FProfilerStatMetaDataRef MetaData = ProfilerSession->GetMetaData(); const FProfilerStat& Stat = MetaData->GetStatByID( StatID ); const FProfilerGroup& Group = Stat.OwningGroup(); TrackedStat->CombinedGraphDataSource->Initialize( Stat.Name().GetPlainNameString(), Group.ID(), Group.Name().GetPlainNameString(), Stat.Type(), ProfilerSession->GetCreationTime() ); bMetadataInitialized = true; } } } } } return bAdded; }
void FRawProfilerSession::ProcessStatPacketArray( const FStatPacketArray& StatPacketArray, FProfilerFrame& out_ProfilerFrame, int32 FrameIndex ) { // @TODO yrx 2014-03-24 Standardize thread names and id // @TODO yrx 2014-04-22 Remove all references to the data provider, event graph etc once data graph can visualize. // Raw stats callstack for this stat packet array. TMap<FName,FProfilerStackNode*> ThreadNodes; const FProfilerStatMetaDataRef MetaData = GetMetaData(); FProfilerSampleArray& MutableCollection = const_cast<FProfilerSampleArray&>(DataProvider->GetCollection()); // Add a root sample for this frame. const uint32 FrameRootSampleIndex = DataProvider->AddHierarchicalSample( 0, MetaData->GetStatByID( 1 ).OwningGroup().ID(), 1, 0.0f, 0.0f, 1 ); // Iterate through all stats packets and raw stats messages. FName GameThreadFName = NAME_None; for( int32 PacketIndex = 0; PacketIndex < StatPacketArray.Packets.Num(); PacketIndex++ ) { const FStatPacket& StatPacket = *StatPacketArray.Packets[PacketIndex]; FName ThreadFName = StatsThreadStats.Threads.FindChecked( StatPacket.ThreadId ); const uint32 NewThreadID = MetaData->ThreadIDtoStatID.FindChecked( StatPacket.ThreadId ); // @TODO yrx 2014-04-29 Only game or render thread is supported at this moment. if( StatPacket.ThreadType != EThreadType::Game && StatPacket.ThreadType != EThreadType::Renderer ) { continue; } // Workaround for issue with rendering thread names. if( StatPacket.ThreadType == EThreadType::Renderer ) { ThreadFName = NAME_RenderThread; } else if( StatPacket.ThreadType == EThreadType::Game ) { GameThreadFName = ThreadFName; } FProfilerStackNode* ThreadNode = ThreadNodes.FindRef( ThreadFName ); if( !ThreadNode ) { FString ThreadIdName = FStatsUtils::BuildUniqueThreadName( StatPacket.ThreadId ); FStatMessage ThreadMessage( ThreadFName, EStatDataType::ST_int64, STAT_GROUP_TO_FStatGroup( STATGROUP_Threads )::GetGroupName(), STAT_GROUP_TO_FStatGroup( STATGROUP_Threads )::GetGroupCategory(), *ThreadIdName, true, true ); //FStatMessage ThreadMessage( ThreadFName, EStatDataType::ST_int64, nullptr, nullptr, TEXT( "" ), true, true ); ThreadMessage.NameAndInfo.SetFlag( EStatMetaFlags::IsPackedCCAndDuration, true ); ThreadMessage.Clear(); // Add a thread sample. const uint32 ThreadRootSampleIndex = DataProvider->AddHierarchicalSample ( NewThreadID, MetaData->GetStatByID( NewThreadID ).OwningGroup().ID(), NewThreadID, -1.0f, -1.0f, 1, FrameRootSampleIndex ); ThreadNode = ThreadNodes.Add( ThreadFName, new FProfilerStackNode( nullptr, ThreadMessage, ThreadRootSampleIndex, FrameIndex ) ); } TArray<const FStatMessage*> StartStack; TArray<FProfilerStackNode*> Stack; Stack.Add( ThreadNode ); FProfilerStackNode* Current = Stack.Last(); const FStatMessagesArray& Data = StatPacket.StatMessages; for( int32 Index = 0; Index < Data.Num(); Index++ ) { const FStatMessage& Item = Data[Index]; const EStatOperation::Type Op = Item.NameAndInfo.GetField<EStatOperation>(); const FName LongName = Item.NameAndInfo.GetRawName(); const FName ShortName = Item.NameAndInfo.GetShortName(); const FName RenderingThreadTickCommandName = TEXT("RenderingThreadTickCommand"); // Workaround for render thread hierarchy. EStatOperation::AdvanceFrameEventRenderThread is called within the scope. if( ShortName == RenderingThreadTickCommandName ) { continue; } if( Op == EStatOperation::CycleScopeStart || Op == EStatOperation::CycleScopeEnd || Op == EStatOperation::AdvanceFrameEventRenderThread ) { //check( Item.NameAndInfo.GetFlag( EStatMetaFlags::IsCycle ) ); if( Op == EStatOperation::CycleScopeStart ) { FProfilerStackNode* ChildNode = new FProfilerStackNode( Current, Item, -1, FrameIndex ); Current->Children.Add( ChildNode ); // Add a child sample. const uint32 SampleIndex = DataProvider->AddHierarchicalSample ( NewThreadID, MetaData->GetStatByFName( ShortName ).OwningGroup().ID(), // GroupID MetaData->GetStatByFName( ShortName ).ID(), // StatID MetaData->ConvertCyclesToMS( ChildNode->CyclesStart ), // StartMS MetaData->ConvertCyclesToMS( 0 ), // DurationMS 1, Current->SampleIndex ); ChildNode->SampleIndex = SampleIndex; Stack.Add( ChildNode ); StartStack.Add( &Item ); Current = ChildNode; } // Workaround for render thread hierarchy. EStatOperation::AdvanceFrameEventRenderThread is called within the scope. if( Op == EStatOperation::AdvanceFrameEventRenderThread ) { int k=0;k++; } if( Op == EStatOperation::CycleScopeEnd ) { const FStatMessage ScopeStart = *StartStack.Pop(); const FStatMessage ScopeEnd = Item; const int64 Delta = int32( uint32( ScopeEnd.GetValue_int64() ) - uint32( ScopeStart.GetValue_int64() ) ); Current->CyclesEnd = Current->CyclesStart + Delta; Current->CycleCounterStartTimeMS = MetaData->ConvertCyclesToMS( Current->CyclesStart ); Current->CycleCounterEndTimeMS = MetaData->ConvertCyclesToMS( Current->CyclesEnd ); if( Current->CycleCounterStartTimeMS > Current->CycleCounterEndTimeMS ) { int k=0;k++; } check( Current->CycleCounterEndTimeMS >= Current->CycleCounterStartTimeMS ); FProfilerStackNode* ChildNode = Current; // Update the child sample's DurationMS. MutableCollection[ChildNode->SampleIndex].SetDurationMS( MetaData->ConvertCyclesToMS( Delta ) ); verify( Current == Stack.Pop() ); Current = Stack.Last(); } } } } // Calculate thread times. for( auto It = ThreadNodes.CreateIterator(); It; ++It ) { FProfilerStackNode& ThreadNode = *It.Value(); const int32 ChildrenNum = ThreadNode.Children.Num(); if( ChildrenNum > 0 ) { const int32 LastChildIndex = ThreadNode.Children.Num() - 1; ThreadNode.CyclesStart = ThreadNode.Children[0]->CyclesStart; ThreadNode.CyclesEnd = ThreadNode.Children[LastChildIndex]->CyclesEnd; ThreadNode.CycleCounterStartTimeMS = MetaData->ConvertCyclesToMS( ThreadNode.CyclesStart ); ThreadNode.CycleCounterEndTimeMS = MetaData->ConvertCyclesToMS( ThreadNode.CyclesEnd ); FProfilerSample& ProfilerSample = MutableCollection[ThreadNode.SampleIndex]; ProfilerSample.SetStartAndEndMS( MetaData->ConvertCyclesToMS( ThreadNode.CyclesStart ), MetaData->ConvertCyclesToMS( ThreadNode.CyclesEnd ) ); } } // Get the game thread time. check( GameThreadFName != NAME_None ); const FProfilerStackNode& GameThreadNode = *ThreadNodes.FindChecked( GameThreadFName ); const double GameThreadStartMS = MetaData->ConvertCyclesToMS( GameThreadNode.CyclesStart ); const double GameThreadEndMS = MetaData->ConvertCyclesToMS( GameThreadNode.CyclesEnd ); MutableCollection[FrameRootSampleIndex].SetStartAndEndMS( GameThreadStartMS, GameThreadEndMS ); // Advance frame const uint32 LastFrameIndex = DataProvider->GetNumFrames(); DataProvider->AdvanceFrame( GameThreadEndMS - GameThreadStartMS ); // Update aggregated stats UpdateAggregatedStats( LastFrameIndex ); // Update aggregated events. UpdateAggregatedEventGraphData( LastFrameIndex ); // RootNode is the same as the game thread node. out_ProfilerFrame.Root->CycleCounterStartTimeMS = GameThreadStartMS; out_ProfilerFrame.Root->CycleCounterEndTimeMS = GameThreadEndMS; for( auto It = ThreadNodes.CreateIterator(); It; ++It ) { out_ProfilerFrame.AddChild( It.Value() ); } out_ProfilerFrame.SortChildren(); }