// Copyright Epic Games, Inc. All Rights Reserved. #include "MiscTraceAnalysis.h" #include "Common/ProviderLock.h" #include "Common/Utils.h" #include "HAL/LowLevelMemTracker.h" #include "Logging/MessageLog.h" #include "Model/BookmarksPrivate.h" #include "Model/Channel.h" #include "Model/DefinitionProvider.h" #include "Model/FramesPrivate.h" #include "Model/LogPrivate.h" #include "Model/RegionsPrivate.h" #include "Model/ScreenshotProviderPrivate.h" #include "Model/ThreadsPrivate.h" #include "TraceServices/Model/AnalysisSession.h" #include "TraceServices/Model/Strings.h" namespace TraceServices { FMiscTraceAnalyzer::FMiscTraceAnalyzer(IAnalysisSession& InSession, FThreadProvider& InThreadProvider, FLogProvider& InLogProvider, FFrameProvider& InFrameProvider, FChannelProvider& InChannelProvider, FScreenshotProvider& InScreenshotProvider, FRegionProvider& InRegionProvider) : Session(InSession) , ThreadProvider(InThreadProvider) , LogProvider(InLogProvider) , FrameProvider(InFrameProvider) , ChannelProvider(InChannelProvider) , ScreenshotProvider(InScreenshotProvider) , RegionProvider(InRegionProvider) { // Todo: update this to use provider locking instead of session locking // FProviderEditScopeLock LogProviderLock (LogProvider); FAnalysisSessionEditScope _(Session); ScreenshotLogCategoryId = LogProvider.RegisterCategory(); FLogCategoryInfo& ScreenshotLogCategory = LogProvider.GetCategory(ScreenshotLogCategoryId); ScreenshotLogCategory.Name = TEXT("Screenshot"); } void FMiscTraceAnalyzer::OnAnalysisBegin(const FOnAnalysisContext& Context) { auto& Builder = Context.InterfaceBuilder; // Threads Builder.RouteEvent(RouteId_RegisterGameThread, "Misc", "RegisterGameThread"); Builder.RouteEvent(RouteId_CreateThread, "Misc", "CreateThread"); Builder.RouteEvent(RouteId_SetThreadGroup, "Misc", "SetThreadGroup"); Builder.RouteEvent(RouteId_BeginThreadGroupScope, "Misc", "BeginThreadGroupScope"); Builder.RouteEvent(RouteId_EndThreadGroupScope, "Misc", "EndThreadGroupScope"); // Frames Builder.RouteEvent(RouteId_BeginFrame, "Misc", "BeginFrame"); Builder.RouteEvent(RouteId_EndFrame, "Misc", "EndFrame"); Builder.RouteEvent(RouteId_BeginGameFrame, "Misc", "BeginGameFrame"); Builder.RouteEvent(RouteId_EndGameFrame, "Misc", "EndGameFrame"); Builder.RouteEvent(RouteId_BeginRenderFrame, "Misc", "BeginRenderFrame"); Builder.RouteEvent(RouteId_EndRenderFrame, "Misc", "EndRenderFrame"); // Channels Builder.RouteEvent(RouteId_ChannelAnnounce, "Trace", "ChannelAnnounce"); Builder.RouteEvent(RouteId_ChannelToggle, "Trace", "ChannelToggle"); // Screenshots Builder.RouteEvent(RouteId_ScreenshotHeader, "Misc", "ScreenshotHeader"); Builder.RouteEvent(RouteId_ScreenshotChunk, "Misc", "ScreenshotChunk"); // Timing Regions Builder.RouteEvent(RouteId_RegionBegin, "Misc", "RegionBegin"); Builder.RouteEvent(RouteId_RegionBeginWithId, "Misc", "RegionBeginWithId"); Builder.RouteEvent(RouteId_RegionEnd, "Misc", "RegionEnd"); Builder.RouteEvent(RouteId_RegionEndWithId, "Misc", "RegionEndWithId"); } void FMiscTraceAnalyzer::OnAnalysisEnd() { FProviderEditScopeLock RegionProviderScopedLock(static_cast(RegionProvider)); RegionProvider.OnAnalysisSessionEnded(); } void FMiscTraceAnalyzer::OnThreadInfo(const FThreadInfo& ThreadInfo) { LLM_SCOPE_BYNAME(TEXT("Insights/FMiscTraceAnalyzer")); uint32 ThreadId = ThreadInfo.GetId(); FString Name = ThreadInfo.GetName(); FAnalysisSessionEditScope _(Session); ThreadProvider.AddThread(ThreadId, *Name, EThreadPriority(ThreadInfo.GetSortHint())); const ANSICHAR* GroupNameA = ThreadInfo.GetGroupName(); if (*GroupNameA) { const TCHAR* GroupName = Session.StoreString(ANSI_TO_TCHAR(GroupNameA)); ThreadProvider.SetThreadGroup(ThreadId, GroupName); } } bool FMiscTraceAnalyzer::OnEvent(uint16 RouteId, EStyle Style, const FOnEventContext& Context) { LLM_SCOPE_BYNAME(TEXT("Insights/FMiscTraceAnalyzer")); FAnalysisSessionEditScope _(Session); const auto& EventData = Context.EventData; switch (RouteId) { case RouteId_BeginFrame: { uint64 Cycle = EventData.GetValue("Cycle"); uint8 FrameType = EventData.GetValue("FrameType"); check(FrameType < TraceFrameType_Count); FrameProvider.BeginFrame(ETraceFrameType(FrameType), Context.EventTime.AsSeconds(Cycle)); break; } case RouteId_EndFrame: { uint64 Cycle = EventData.GetValue("Cycle"); uint8 FrameType = EventData.GetValue("FrameType"); check(FrameType < TraceFrameType_Count); FrameProvider.EndFrame(ETraceFrameType(FrameType), Context.EventTime.AsSeconds(Cycle)); break; } case RouteId_BeginGameFrame: case RouteId_EndGameFrame: case RouteId_BeginRenderFrame: case RouteId_EndRenderFrame: { ETraceFrameType FrameType; if (RouteId == RouteId_BeginGameFrame || RouteId == RouteId_EndGameFrame) { FrameType = TraceFrameType_Game; } else { FrameType = TraceFrameType_Rendering; } const uint8* BufferPtr = EventData.GetAttachment(); uint64 CycleDiff = FTraceAnalyzerUtils::Decode7bit(BufferPtr); uint64 Cycle = LastFrameCycle[FrameType] + CycleDiff; LastFrameCycle[FrameType] = Cycle; if (RouteId == RouteId_BeginGameFrame || RouteId == RouteId_BeginRenderFrame) { FrameProvider.BeginFrame(FrameType, Context.EventTime.AsSeconds(Cycle)); } else { FrameProvider.EndFrame(FrameType, Context.EventTime.AsSeconds(Cycle)); } break; } case RouteId_ChannelAnnounce: OnChannelAnnounce(Context); break; case RouteId_ChannelToggle: OnChannelToggle(Context); break; case RouteId_ScreenshotHeader: { uint32 Id = EventData.GetValue("Id"); TSharedPtr Screenshot = ScreenshotProvider.AddScreenshot(Id); Screenshot->Id = Id; EventData.GetString("Name", Screenshot->Name); uint64 Cycle = EventData.GetValue("Cycle"); Screenshot->Timestamp = Context.EventTime.AsSeconds(Cycle); Screenshot->Width = EventData.GetValue("Width"); Screenshot->Height = EventData.GetValue("Height"); Screenshot->ChunkNum = EventData.GetValue("TotalChunkNum"); Screenshot->Size = EventData.GetValue("Size"); Screenshot->Data.Reserve(Screenshot->Size); FLogMessageSpec& LogMessageSpec = LogProvider.GetMessageSpec(Cycle); LogMessageSpec.Category = &LogProvider.GetCategory(ScreenshotLogCategoryId); LogMessageSpec.Line = Id; LogMessageSpec.File = nullptr; LogMessageSpec.FormatString = nullptr; LogMessageSpec.Verbosity = ELogVerbosity::Log; LogProvider.AppendMessage(Cycle, Screenshot->Timestamp, Screenshot->Name); break; } case RouteId_ScreenshotChunk: { uint32 Id = EventData.GetValue("Id"); uint16 ChunkNum = EventData.GetValue("ChunkNum"); uint16 Size = EventData.GetValue("Size"); TArrayView Data = EventData.GetArrayView("Data"); ScreenshotProvider.AddScreenshotChunk(Id, ChunkNum, Size, Data); break; } // Begin retired events // case RouteId_RegisterGameThread: { const uint32 ThreadId = FTraceAnalyzerUtils::GetThreadIdField(Context); ThreadProvider.AddGameThread(ThreadId); break; } case RouteId_CreateThread: { const uint32 CreatedThreadId = FTraceAnalyzerUtils::GetThreadIdField(Context, "CreatedThreadId"); const EThreadPriority Priority = static_cast(EventData.GetValue("Priority")); const TCHAR* CreatedThreadName = reinterpret_cast(EventData.GetAttachment()); ThreadProvider.AddThread(CreatedThreadId, CreatedThreadName, Priority); const uint32 CurrentThreadId = FTraceAnalyzerUtils::GetThreadIdField(Context, "CurrentThreadId"); FThreadState* ThreadState = GetThreadState(CurrentThreadId); if (ThreadState->ThreadGroupStack.Num()) { ThreadProvider.SetThreadGroup(CreatedThreadId, ThreadState->ThreadGroupStack.Top()); } break; } case RouteId_SetThreadGroup: { const TCHAR* GroupName = Session.StoreString(ANSI_TO_TCHAR(reinterpret_cast(EventData.GetAttachment()))); const uint32 ThreadId = FTraceAnalyzerUtils::GetThreadIdField(Context); ThreadProvider.SetThreadGroup(ThreadId, GroupName); break; } case RouteId_BeginThreadGroupScope: { const TCHAR* GroupName = Session.StoreString(ANSI_TO_TCHAR(reinterpret_cast(EventData.GetAttachment()))); const uint32 CurrentThreadId = FTraceAnalyzerUtils::GetThreadIdField(Context, "CurrentThreadId"); FThreadState* ThreadState = GetThreadState(CurrentThreadId); ThreadState->ThreadGroupStack.Push(GroupName); break; } case RouteId_EndThreadGroupScope: { const uint32 CurrentThreadId = FTraceAnalyzerUtils::GetThreadIdField(Context, "CurrentThreadId"); FThreadState* ThreadState = GetThreadState(CurrentThreadId); ThreadState->ThreadGroupStack.Pop(); break; } // // End retired events case RouteId_RegionBegin: { uint64 Cycle = EventData.GetValue("Cycle"); FString Name; check(EventData.GetString("RegionName", Name)); // Category has been added after the initial implementation, so it's treated as optional FString Category; EventData.GetString("Category", Category); double Time = Context.EventTime.AsSeconds(Cycle); { FProviderEditScopeLock RegionProviderScopedLock(RegionProvider); RegionProvider.AppendRegionBegin(*Name, Time, Category.IsEmpty() ? nullptr : *Category); } Session.UpdateDurationSeconds(Time); break; } case RouteId_RegionBeginWithId: { uint64 CycleAndId = EventData.GetValue("CycleAndId"); FString Name; check(EventData.GetString("RegionName", Name)); // Category has been added after the initial implementation, so it's treated as optional FString Category; EventData.GetString("Category", Category); double Time = Context.EventTime.AsSeconds(CycleAndId); { FProviderEditScopeLock RegionProviderScopedLock(RegionProvider); RegionProvider.AppendRegionBeginWithId(*Name, CycleAndId, Time, Category.IsEmpty() ? nullptr : *Category); } Session.UpdateDurationSeconds(Time); break; } case RouteId_RegionEnd: { uint64 Cycle = EventData.GetValue("Cycle"); FString Name; EventData.GetString("RegionName", Name); double Time = Context.EventTime.AsSeconds(Cycle); { FProviderEditScopeLock RegionProviderScopedLock(RegionProvider); RegionProvider.AppendRegionEnd(*Name, Time); } Session.UpdateDurationSeconds(Time); break; } case RouteId_RegionEndWithId: { uint64 Cycle = EventData.GetValue("Cycle"); uint64 RegionId = EventData.GetValue("RegionId", 0); double Time = Context.EventTime.AsSeconds(Cycle); { FProviderEditScopeLock RegionProviderScopedLock(RegionProvider); RegionProvider.AppendRegionEndWithId(RegionId, Time); } Session.UpdateDurationSeconds(Time); break; } } return true; } void FMiscTraceAnalyzer::OnChannelAnnounce(const FOnEventContext& Context) { FString ChannelName = FTraceAnalyzerUtils::LegacyAttachmentString("Name", Context); uint32 ChannelId = Context.EventData.GetValue("Id"); bool bEnabled = Context.EventData.GetValue("IsEnabled"); bool bReadOnly = Context.EventData.GetValue("ReadOnly", false); ChannelProvider.AnnounceChannel(*ChannelName, ChannelId, bReadOnly); ChannelProvider.UpdateChannel(ChannelId, bEnabled); } void FMiscTraceAnalyzer::OnChannelToggle(const FOnEventContext& Context) { uint32 ChannelId = Context.EventData.GetValue("Id"); bool bEnabled = Context.EventData.GetValue("IsEnabled"); ChannelProvider.UpdateChannel(ChannelId, bEnabled); } FMiscTraceAnalyzer::FThreadState* FMiscTraceAnalyzer::GetThreadState(uint32 ThreadId) { if (!ThreadStateMap.Contains(ThreadId)) { TSharedRef ThreadState = MakeShared(); ThreadStateMap.Add(ThreadId, ThreadState); } return &ThreadStateMap[ThreadId].Get(); } } // namespace TraceServices