// Copyright Epic Games, Inc. All Rights Reserved. #include "TraceAnalyzer.h" #include "Containers/Map.h" #include "Containers/StringConv.h" #include "Containers/UnrealString.h" #include "HAL/PlatformTime.h" #include "Templates/UniquePtr.h" #include "Trace/Analysis.h" #include "Trace/Analyzer.h" #include "Trace/DataStream.h" #include "Command.h" #include "Io.h" #include "TextSerializer.h" namespace UE { namespace TraceAnalyzer { //////////////////////////////////////////////////////////////////////////////////////////////////// // See \Engine\Source\Runtime\TraceLog\Public\Trace\Detail\Protocols\Protocol6.h enum class EEventFlags : uint8 { Important = 1 << 0, MaybeHasAux = 1 << 1, NoSync = 1 << 2, Definition = 1 << 3, }; //////////////////////////////////////////////////////////////////////////////////////////////////// static uint64 Decode7bit(const uint8*& BufferPtr) { uint64 Value = 0; uint64 ByteIndex = 0; bool HasMoreBytes; do { uint8 ByteValue = *BufferPtr++; HasMoreBytes = ByteValue & 0x80; Value |= uint64(ByteValue & 0x7f) << (ByteIndex * 7); ++ByteIndex; } while (HasMoreBytes); return Value; } //////////////////////////////////////////////////////////////////////////////////////////////////// static uint32 GetThreadIdField(const UE::Trace::IAnalyzer::FOnEventContext& Context, const ANSICHAR* FieldName = "ThreadId") { // Trace analysis was changed to be able to provide a suitable id. Prior to // this users of Trace would send along their own thread ids. For backwards // compatibility we'll bias field thread ids to avoid collision with Trace's. static const uint32 Bias = 0x70000000; uint32 ThreadId = Context.EventData.GetValue(FieldName, 0); ThreadId |= ThreadId ? Bias : Context.ThreadInfo.GetId(); return ThreadId; } //////////////////////////////////////////////////////////////////////////////////////////////////// // FConvertToTextAnalyzer //////////////////////////////////////////////////////////////////////////////////////////////////// class FConvertToTextAnalyzer : public UE::Trace::IAnalyzer { public: FConvertToTextAnalyzer(FileHandle InHandle); virtual ~FConvertToTextAnalyzer(); private: virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) override; virtual void OnAnalysisEnd() override; virtual void OnVersion(uint32 InTransportVersion, uint32 InProtocolVersion) override; virtual bool OnNewEvent(uint16 RouteId, const FEventTypeInfo& TypeInfo) override; virtual bool OnEvent(uint16 RouteId, EStyle, const FOnEventContext& Context) override; uint32 GetTotalEventSize(EStyle Style, const FOnEventContext& Context) const; void DefineTimer(uint32 SpecId, const TCHAR* TimerName); void ConvertToTextEventBatch(const FOnEventContext& Context, FTextSerializer& Serializer, const uint8* BufferPtr, uint32 BufferSize); void ConvertToTextEventBatchV2(const FOnEventContext& Context, FTextSerializer& Serializer, const uint8* BufferPtr, uint32 BufferSize); void PrintUnknownTimers(FTextSerializer& Serializer); void PrintEventStats(FTextSerializer& Serializer); public: bool bNoNewEventLog = false; bool bNoEventLog = false; bool bNoAnalysisStats = false; bool bNoEventStats = false; private: FileHandle Handle; TUniquePtr TextSerializer; uint32 TransportVersion = 4; uint32 ProtocolVersion = 7; TMap TimerMap; TMap ThreadMap; uint64 NumNewEvents = 0; uint64 NumEvents = 0; uint64 NumEnterScopeEvents = 0; uint64 NumLeaveScopeEvents = 0; uint64 NumCpuBatches = 0; uint64 NumBeginCpuEvents = 0; uint64 NumEndCpuEvents = 0; uint64 EventsTotalSize = 0; struct FEventStat { const FEventTypeInfo* TypeInfo = nullptr; uint64 Count = 0; uint64 ScopedCount = 0; uint64 Bytes = 0; uint64 ScopedBytes = 0; uint32 MinSize = 0xFFFFFFFF; uint32 MaxSize = 0; }; TMap EventStats; uint64 AnalysisBeginTimestamp = 0; enum class EKnownEvent : uint32 { Invalid, Trace_NewEvent, Trace_Timing, Trace_ThreadTiming, Trace_ThreadInfo, Trace_ThreadGroupBegin, Trace_ThreadGroupEnd, Trace_ChannelAnnounce, // "$Trace", deprecated in 4.26 Trace_ChannelAnnounce2, // "Trace" new in 4.26 Diagnostics_Session, Diagnostics_Session2, Misc_CreateThread, Misc_SetThreadGroup, Misc_BeginThreadGroupScope, Misc_BookmarkSpec, Misc_Bookmark, Misc_RegionBegin, Misc_RegionEnd, Misc_BeginGameFrame, // deprecated Misc_EndGameFrame, // deprecated Misc_BeginRenderFrame, // deprecated Misc_EndRenderFrame, // deprecated Misc_BeginFrame, Misc_EndFrame, Logging_LogCategory, Logging_LogMessageSpec, Logging_LogMessage, PlatformFile_BeginOpen, PlatformFile_EndOpen, PlatformFile_BeginRead, PlatformFile_EndRead, PlatformFile_BeginClose, PlatformFile_EndClose, LoadTime_ClassInfo, LoadTime_NewAsyncPackage, GpuProfiler_EventSpec, CpuProfiler_EventSpec, CpuProfiler_EventBatch, // deprecated CpuProfiler_EndCapture, // deprecated CpuProfiler_EventBatchV2, CpuProfiler_EndCaptureV2, Stats_Spec, Counters_Spec, CsvProfiler_RegisterCategory, CsvProfiler_DefineInlineStat, CsvProfiler_DefineDeclaredStat, CsvProfiler_Metadata, CsvProfiler_BeginCapture, Memory_Marker, LLM_TagValue, LLM_TagsSpec, LLM_TrackerSpec, SlateTrace_AddWidget, Count }; static const uint32 InvalidEventId = (uint32)-1; typedef TFunction KnownEventCallback; struct FKnownEvent { uint32 Id = InvalidEventId; KnownEventCallback Callback = nullptr; KnownEventCallback AttachmentCallback = nullptr; }; FKnownEvent KnownEvents[(int)EKnownEvent::Count]; static const uint32 MaxKnownEventId = 0xFFFF; EKnownEvent KnownEventMap[MaxKnownEventId + 1]; // index == EventId --> EKnownEvent bool IsKnownEvent(uint32 EventId, EKnownEvent KnownEvent) const { return EventId == KnownEvents[(int)KnownEvent].Id; } void RegisterCallbacksForKnownEvents(); void RegisterAttachmentCallbacksForKnownEvents(); }; //////////////////////////////////////////////////////////////////////////////////////////////////// FConvertToTextAnalyzer::FConvertToTextAnalyzer(FileHandle InHandle) : Handle(InHandle) { if (Handle < 0) { TextSerializer = MakeUnique(); } else { TextSerializer = MakeUnique(Handle); } } //////////////////////////////////////////////////////////////////////////////////////////////////// FConvertToTextAnalyzer::~FConvertToTextAnalyzer() { } //////////////////////////////////////////////////////////////////////////////////////////////////// void FConvertToTextAnalyzer::OnAnalysisBegin(const FOnAnalysisContext& Context) { AnalysisBeginTimestamp = FPlatformTime::Cycles64(); if (TextSerializer) { TextSerializer->Append("BEGIN conv2text\n"); } Context.InterfaceBuilder.RouteAllEvents(0, false); Context.InterfaceBuilder.RouteAllEvents(1, true); KnownEvents[(int)EKnownEvent::Invalid].Id = InvalidEventId; KnownEvents[(int)EKnownEvent::Trace_NewEvent].Id = 0; for (uint32 EventId = 0; EventId <= MaxKnownEventId; ++EventId) { KnownEventMap[EventId] = EKnownEvent::Invalid; } KnownEventMap[0] = EKnownEvent::Trace_NewEvent; RegisterCallbacksForKnownEvents(); RegisterAttachmentCallbacksForKnownEvents(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FConvertToTextAnalyzer::OnAnalysisEnd() { if (TextSerializer) { if (!bNoAnalysisStats) { TextSerializer->Append("\n"); TextSerializer->Append("Stats (conv2text):\n"); TextSerializer->Appendf(" New Events: % llu\n", NumNewEvents); TextSerializer->Appendf(" Events: %llu\n", NumEvents); TextSerializer->Appendf(" | Normal Events: %llu\n", NumEvents - NumEnterScopeEvents - NumLeaveScopeEvents); TextSerializer->Appendf(" | Scoped Events: %llu\n", NumEnterScopeEvents + NumLeaveScopeEvents); TextSerializer->Appendf(" | | Enter Scope Events: %llu\n", NumEnterScopeEvents); if (NumEnterScopeEvents == NumLeaveScopeEvents) { TextSerializer->Appendf(" | | Leave Scope Events: %llu\n", NumLeaveScopeEvents); } else if (NumEnterScopeEvents > NumLeaveScopeEvents) { TextSerializer->Appendf(" | | Leave Scope Events: %llu (-%llu)\n", NumLeaveScopeEvents, NumEnterScopeEvents - NumLeaveScopeEvents); } else // if (NumEnterScopeEvents < NumLeaveScopeEvents) { TextSerializer->Appendf(" | | Leave Scope Events: %llu (+%llu)\n", NumLeaveScopeEvents, NumLeaveScopeEvents - NumEnterScopeEvents); } if (!bNoEventLog) { TextSerializer->Appendf(" CPU Batches: %llu\n", NumCpuBatches); TextSerializer->Appendf(" CPU Events: %llu\n", NumBeginCpuEvents + NumEndCpuEvents); TextSerializer->Appendf(" | Begin Events: %llu\n", NumBeginCpuEvents); if (NumBeginCpuEvents == NumEndCpuEvents) { TextSerializer->Appendf(" | End Events: %llu\n", NumEndCpuEvents); } else if (NumBeginCpuEvents > NumEndCpuEvents) { TextSerializer->Appendf(" | End Events: %llu (-%llu)\n", NumEndCpuEvents, NumBeginCpuEvents - NumEndCpuEvents); } else // if (NumBeginCpuEvents < NumEndCpuEvents) { TextSerializer->Appendf(" | End Events: %llu (+%llu)\n", NumEndCpuEvents, NumEndCpuEvents - NumBeginCpuEvents); } TextSerializer->Appendf(" CPU Timers: %d\n", TimerMap.Num()); } TextSerializer->Appendf(" Total Event Size: %llu bytes\n", EventsTotalSize); PrintUnknownTimers(*TextSerializer); } if (!bNoEventStats) { TextSerializer->Append("\n"); PrintEventStats(*TextSerializer); } TextSerializer->Append("\n"); uint64 AnalysisDuration = FPlatformTime::Cycles64() - AnalysisBeginTimestamp; double Duration = (double)AnalysisDuration * FPlatformTime::GetSecondsPerCycle(); if (Duration < 60.0) { TextSerializer->Appendf("END conv2text -- %.02fs\n", Duration); } else if (Duration < 120.0) { TextSerializer->Appendf("END conv2text -- %.02fs (1 minute %lli seconds)\n", Duration, (int64)Duration % 60); } else { TextSerializer->Appendf("END conv2text -- %.02fs (%lli minutes %lli seconds)\n", Duration, (int64)Duration / 60, (int64)Duration % 60); } TextSerializer->Commit(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FConvertToTextAnalyzer::PrintEventStats(FTextSerializer& Serializer) { TArray Ids; for (const auto Iter : EventStats) { Ids.Add(Iter.Key); } // Sort by Id. //Ids.Sort(); // Sort by LoggerName/Name. Ids.Sort([&](const uint32& A, const uint32& B) { const FEventTypeInfo* TypeNameA = EventStats[A].TypeInfo; const FEventTypeInfo* TypeNameB = EventStats[B].TypeInfo; const ANSICHAR* LoggerNameA = TypeNameA ? TypeNameA->GetLoggerName() : ""; const ANSICHAR* LoggerNameB = TypeNameB ? TypeNameB->GetLoggerName() : ""; int32 ret = FCStringAnsi::Strcmp(LoggerNameA, LoggerNameB); if (ret == 0) { const ANSICHAR* EventNameA = TypeNameA ? TypeNameA->GetName() : ""; const ANSICHAR* EventNameB = TypeNameB ? TypeNameB->GetName() : ""; ret = FCStringAnsi::Strcmp(EventNameA, EventNameB); } return ret < 0; }); uint64 TotalCount = 0; uint64 TotalSize = 0; Serializer.Append(" ID COUNT BYTES AVSZ FXSZ MNSZ MXSZ\n"); for (int StatIndex = 0, StatCount = Ids.Num(); StatIndex < StatCount; ++StatIndex) { const FEventStat& Stat = *EventStats.Find(Ids[StatIndex]); const FEventTypeInfo* TypeInfo = Stat.TypeInfo; const uint32 FixedEventSize = TypeInfo ? TypeInfo->GetSize() : 0; const uint32 MinEventSize = (Stat.MinSize <= Stat.MaxSize) ? Stat.MinSize : 0; const uint32 MaxEventSize = Stat.MaxSize; const ANSICHAR* LoggerName = TypeInfo ? TypeInfo->GetLoggerName() : ""; const ANSICHAR* EventName = TypeInfo ? TypeInfo->GetName() : ""; if (Stat.Count > 0) { Serializer.Appendf("%3d %11llu %11llu %6u %6u %6u %6u %s.%s\n", Ids[StatIndex], Stat.Count, Stat.Bytes, Stat.Count != 0 ? uint32(Stat.Bytes / Stat.Count) : 0, FixedEventSize, MinEventSize, MaxEventSize, LoggerName, EventName); TotalCount += Stat.Count; TotalSize += Stat.Bytes; } if (Stat.ScopedCount > 0) { Serializer.Appendf("%3d %11llu %11llu %6u %6u %6u %6u %s.%s*\n", Ids[StatIndex], Stat.ScopedCount, Stat.ScopedBytes, Stat.ScopedCount != 0 ? uint32(Stat.ScopedBytes / Stat.ScopedCount) : 0, FixedEventSize, MinEventSize, MaxEventSize, LoggerName, EventName); TotalCount += Stat.ScopedCount; TotalSize += Stat.ScopedBytes; } } Serializer.Append("--------------------------------------------------------------------------------\n"); Serializer.Appendf(" %11llu %11llu\n", TotalCount, TotalSize); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FConvertToTextAnalyzer::OnVersion(uint32 InTransportVersion, uint32 InProtocolVersion) { TransportVersion = InTransportVersion; ProtocolVersion = InProtocolVersion; } //////////////////////////////////////////////////////////////////////////////////////////////////// void FConvertToTextAnalyzer::RegisterCallbacksForKnownEvents() { KnownEvents[(int)EKnownEvent::GpuProfiler_EventSpec].Callback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const auto& Name = EventData.GetArray("Name"); auto NameTChar = StringCast(Name.GetData(), Name.Num()); FStringView NameView(NameTChar.Get(), NameTChar.Length()); FString NameStr(NameView.GetData(), NameView.Len()); Serializer.WriteAttributeString("#Name", TCHAR_TO_UTF8(*NameStr)); }; KnownEvents[(int)EKnownEvent::CpuProfiler_EventSpec].Callback = [this](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const uint32 SpecId = EventData.GetValue("Id"); FString Name; const TCHAR* TimerName = nullptr; if (EventData.GetString("Name", Name)) { TimerName = *Name; } else { uint8 CharSize = EventData.GetValue("CharSize"); if (CharSize == sizeof(ANSICHAR)) { check(EventData.GetAttachment() != nullptr); TimerName = StringCast(reinterpret_cast(EventData.GetAttachment())).Get(); } else if (CharSize == 0 || CharSize == sizeof(TCHAR)) // 0 for backwards compatibility { check(EventData.GetAttachment() != nullptr); TimerName = reinterpret_cast(EventData.GetAttachment()); } if (TimerName) { Serializer.WriteAttributeString("@Name", TCHAR_TO_UTF8(TimerName)); } else { Serializer.WriteAttributeString("@Name", ""); } } DefineTimer(SpecId, TimerName); }; KnownEvents[(int)EKnownEvent::CpuProfiler_EventBatch].Callback = KnownEvents[(int)EKnownEvent::CpuProfiler_EndCapture].Callback = [this](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; TArrayView Out = EventData.GetArrayView("Data"); if (Out.GetData() != nullptr) { Serializer.BeginAttribute(); Serializer.WriteKey("#Data"); ConvertToTextEventBatch(Context, Serializer, Out.GetData(), Out.Num()); Serializer.EndAttribute(); } else { check(EventData.GetAttachment() != nullptr); Serializer.BeginAttribute(); Serializer.WriteKey("Attached"); ConvertToTextEventBatch(Context, Serializer, EventData.GetAttachment(), EventData.GetAttachmentSize()); Serializer.EndAttribute(); } }; KnownEvents[(int)EKnownEvent::CpuProfiler_EventBatchV2].Callback = KnownEvents[(int)EKnownEvent::CpuProfiler_EndCaptureV2].Callback = [this](const FOnEventContext& Context, FTextSerializer& Serializer) { TArrayView Out = Context.EventData.GetArrayView("Data"); if (Out.GetData() != nullptr) { Serializer.BeginAttribute(); Serializer.WriteKey("#Data"); ConvertToTextEventBatchV2(Context, Serializer, Out.GetData(), Out.Num()); Serializer.EndAttribute(); } }; //KnownEvents[(int)EKnownEvent::Misc_Bookmark].Callback = //KnownEvents[(int)EKnownEvent::Misc_RegionBegin].Callback = //KnownEvents[(int)EKnownEvent::Misc_RegionEnd].Callback = //KnownEvents[(int)EKnownEvent::Misc_BeginFrame].Callback = //KnownEvents[(int)EKnownEvent::Misc_EndFrame].Callback = //KnownEvents[(int)EKnownEvent::Logging_LogMessage].Callback = //KnownEvents[(int)EKnownEvent::PlatformFile_BeginOpen].Callback = //KnownEvents[(int)EKnownEvent::PlatformFile_EndOpen].Callback = //KnownEvents[(int)EKnownEvent::PlatformFile_BeginRead].Callback = //KnownEvents[(int)EKnownEvent::PlatformFile_EndRead].Callback = //KnownEvents[(int)EKnownEvent::PlatformFile_BeginClose].Callback = //KnownEvents[(int)EKnownEvent::PlatformFile_EndClose].Callback = //KnownEvents[(int)EKnownEvent::Memory_Marker].Callback = //KnownEvents[(int)EKnownEvent::LLM_TagValue].Callback = //KnownEvents[(int)EKnownEvent::SlateTrace_AddWidget].Callback = // [](const FOnEventContext& Context, FTextSerializer& Serializer) // { // uint64 Cycle = Context.EventData.GetValue("Cycle"); // double Time = Context.EventTime.AsSeconds(Cycle); // Serializer.WriteAttributeFloat("#Time", Time); // }; } //////////////////////////////////////////////////////////////////////////////////////////////////// void FConvertToTextAnalyzer::RegisterAttachmentCallbacksForKnownEvents() { KnownEvents[(int)EKnownEvent::Trace_ChannelAnnounce].AttachmentCallback = KnownEvents[(int)EKnownEvent::Trace_ChannelAnnounce2].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); if (TypeInfo.GetFieldIndex("Name") < 0) { const ANSICHAR* ChannelName = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@ChannelName", ChannelName); } }; KnownEvents[(int)EKnownEvent::Trace_ThreadInfo].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); if (TypeInfo.GetFieldIndex("Name") < 0) { const ANSICHAR* ThreadName = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@Name", ThreadName); } }; KnownEvents[(int)EKnownEvent::Diagnostics_Session].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const uint8 AppNameOffset = EventData.GetValue("AppNameOffset"); const uint8 CommandLineOffset = EventData.GetValue("CommandLineOffset"); const ANSICHAR* Platform = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@Platform", Platform, AppNameOffset); const ANSICHAR* AppName = reinterpret_cast(EventData.GetAttachment() + AppNameOffset); Serializer.WriteAttributeString("@AppName", AppName, CommandLineOffset - AppNameOffset); const ANSICHAR* CommandLine = reinterpret_cast(EventData.GetAttachment() + CommandLineOffset); Serializer.WriteAttributeString("@CommandLine", CommandLine, EventData.GetAttachmentSize() - CommandLineOffset); }; KnownEvents[(int)EKnownEvent::Diagnostics_Session2].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { // hide attachment }; KnownEvents[(int)EKnownEvent::Misc_CreateThread].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const TCHAR* ThreadName = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@Name", TCHAR_TO_UTF8(ThreadName)); }; KnownEvents[(int)EKnownEvent::Misc_BeginGameFrame].AttachmentCallback = KnownEvents[(int)EKnownEvent::Misc_EndGameFrame].AttachmentCallback = KnownEvents[(int)EKnownEvent::Misc_BeginRenderFrame].AttachmentCallback = KnownEvents[(int)EKnownEvent::Misc_EndRenderFrame].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const uint8* BufferPtr = EventData.GetAttachment(); uint64 CycleDiff = Decode7bit(BufferPtr); Serializer.WriteAttributeInteger("@CycleDiff", CycleDiff); }; KnownEvents[(int)EKnownEvent::Trace_ThreadGroupBegin].AttachmentCallback = KnownEvents[(int)EKnownEvent::Misc_SetThreadGroup].AttachmentCallback = KnownEvents[(int)EKnownEvent::Misc_BeginThreadGroupScope].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); if (TypeInfo.GetFieldIndex("Name") < 0) { const ANSICHAR* GroupName = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@GroupName", GroupName); } }; KnownEvents[(int)EKnownEvent::Misc_BookmarkSpec].AttachmentCallback = KnownEvents[(int)EKnownEvent::Logging_LogMessageSpec].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); if (TypeInfo.GetFieldIndex("FileName") < 0) { const ANSICHAR* File = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@File", File); const TCHAR* FormatString = reinterpret_cast(EventData.GetAttachment() + strlen(File) + 1); Serializer.WriteAttributeString("@FormatString", TCHAR_TO_UTF8(FormatString)); } }; KnownEvents[(int)EKnownEvent::Logging_LogCategory].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); if (TypeInfo.GetFieldIndex("Name") < 0) { const TCHAR* CategoryName = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@CategoryName", TCHAR_TO_UTF8(CategoryName)); } }; KnownEvents[(int)EKnownEvent::PlatformFile_BeginOpen].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const TCHAR* ClassName = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@File", TCHAR_TO_UTF8(ClassName)); }; KnownEvents[(int)EKnownEvent::LoadTime_ClassInfo].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); if (TypeInfo.GetFieldIndex("Name") < 0) { const TCHAR* ClassName = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@ClassName", TCHAR_TO_UTF8(ClassName)); } }; KnownEvents[(int)EKnownEvent::LoadTime_NewAsyncPackage].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const TCHAR* ClassName = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@PackageName", TCHAR_TO_UTF8(ClassName)); }; KnownEvents[(int)EKnownEvent::CpuProfiler_EventSpec].AttachmentCallback = [this](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const uint32 SpecId = EventData.GetValue("Id"); FString Name; const TCHAR* TimerName = nullptr; if (EventData.GetString("Name", Name)) { TimerName = *Name; } else { uint8 CharSize = EventData.GetValue("CharSize"); if (CharSize == sizeof(ANSICHAR)) { check(EventData.GetAttachment() != nullptr); TimerName = StringCast(reinterpret_cast(EventData.GetAttachment())).Get(); } else if (CharSize == 0 || CharSize == sizeof(TCHAR)) // 0 for backwards compatibility { check(EventData.GetAttachment() != nullptr); TimerName = reinterpret_cast(EventData.GetAttachment()); } if (TimerName) { Serializer.WriteAttributeString("@Name", TCHAR_TO_UTF8(TimerName)); } else { Serializer.WriteAttributeString("@Name", ""); } } DefineTimer(SpecId, TimerName); }; KnownEvents[(int)EKnownEvent::CpuProfiler_EventBatch].AttachmentCallback = KnownEvents[(int)EKnownEvent::CpuProfiler_EndCapture].AttachmentCallback = [this](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; check(EventData.GetAttachment() != nullptr); Serializer.BeginAttribute(); Serializer.WriteKey("Attached"); ConvertToTextEventBatch(Context, Serializer, EventData.GetAttachment(), EventData.GetAttachmentSize()); Serializer.EndAttribute(); }; KnownEvents[(int)EKnownEvent::Stats_Spec].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); if (TypeInfo.GetFieldIndex("Name") < 0) { const ANSICHAR* Name = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@Name", Name); const TCHAR* Description = reinterpret_cast(EventData.GetAttachment() + strlen(Name) + 1); Serializer.WriteAttributeString("@Description", TCHAR_TO_UTF8(Description)); } }; KnownEvents[(int)EKnownEvent::Counters_Spec].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); if (TypeInfo.GetFieldIndex("Name") < 0) { const TCHAR* Name = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@Name", TCHAR_TO_UTF8(Name)); } }; KnownEvents[(int)EKnownEvent::CsvProfiler_RegisterCategory].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); if (TypeInfo.GetFieldIndex("Name") < 0) { const TCHAR* Name = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@Name", TCHAR_TO_UTF8(Name)); } }; KnownEvents[(int)EKnownEvent::CsvProfiler_DefineInlineStat].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); if (TypeInfo.GetFieldIndex("Name") < 0) { const ANSICHAR* Name = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@Name", Name); } }; KnownEvents[(int)EKnownEvent::CsvProfiler_DefineDeclaredStat].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); if (TypeInfo.GetFieldIndex("Name") < 0) { const TCHAR* Name = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@Name", TCHAR_TO_UTF8(Name)); } }; KnownEvents[(int)EKnownEvent::CsvProfiler_Metadata].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const TCHAR* Key = reinterpret_cast(EventData.GetAttachment()); const TCHAR* Value = reinterpret_cast(EventData.GetAttachment() + EventData.GetValue("ValueOffset")); Serializer.WriteAttributeString("@Key", TCHAR_TO_UTF8(Key)); Serializer.WriteAttributeString("@Value", TCHAR_TO_UTF8(Value)); }; KnownEvents[(int)EKnownEvent::CsvProfiler_BeginCapture].AttachmentCallback = [](const FOnEventContext& Context, FTextSerializer& Serializer) { const FEventData& EventData = Context.EventData; const TCHAR* Filename = reinterpret_cast(EventData.GetAttachment()); Serializer.WriteAttributeString("@Filename", TCHAR_TO_UTF8(Filename)); }; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FConvertToTextAnalyzer::OnNewEvent(uint16 RouteId, const FEventTypeInfo& TypeInfo) { ++NumNewEvents; const uint32 EventId = TypeInfo.GetId(); const ANSICHAR* LoggerName = TypeInfo.GetLoggerName(); check(LoggerName != nullptr); const ANSICHAR* EventName = TypeInfo.GetName(); check(EventName != nullptr); if (!bNoEventStats) { FEventStat* Stat = EventStats.Find(EventId); if (!Stat) { EventStats.Add(EventId, { &TypeInfo }); } } #define BEGIN_KNOWN_LOGGER(_Name) \ if (strcmp(LoggerName, _Name) == 0) \ { #define KNOWN_EVENT(_Name,_Id) \ if (strcmp(EventName, _Name) == 0) \ { \ KnownEvents[(int)EKnownEvent::_Id].Id = EventId; \ KnownEventMap[EventId] = EKnownEvent::_Id; \ break; \ } #define END_KNOWN_LOGGER() \ break; \ } if (!bNoEventLog) while (true) { BEGIN_KNOWN_LOGGER("$Trace") KNOWN_EVENT("NewEvent", Trace_NewEvent) KNOWN_EVENT("ThreadTiming", Trace_ThreadTiming) KNOWN_EVENT("ThreadInfo", Trace_ThreadInfo) KNOWN_EVENT("ThreadGroupBegin", Trace_ThreadGroupBegin) KNOWN_EVENT("ChannelAnnounce", Trace_ChannelAnnounce) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("Trace") KNOWN_EVENT("ChannelAnnounce", Trace_ChannelAnnounce2) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("Diagnostics") KNOWN_EVENT("Session", Diagnostics_Session) KNOWN_EVENT("Session2", Diagnostics_Session2) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("Misc") KNOWN_EVENT("CreateThread", Misc_CreateThread) KNOWN_EVENT("SetThreadGroup", Misc_SetThreadGroup) KNOWN_EVENT("BeginThreadGroupScope", Misc_BeginThreadGroupScope) KNOWN_EVENT("BookmarkSpec", Misc_BookmarkSpec) KNOWN_EVENT("Bookmark", Misc_Bookmark) KNOWN_EVENT("RegionBegin", Misc_RegionBegin) KNOWN_EVENT("RegionEnd", Misc_RegionEnd) KNOWN_EVENT("BeginGameFrame", Misc_BeginGameFrame) KNOWN_EVENT("EndGameFrame", Misc_EndGameFrame) KNOWN_EVENT("BeginRenderFrame", Misc_BeginRenderFrame) KNOWN_EVENT("EndRenderFrame", Misc_EndRenderFrame) KNOWN_EVENT("BeginFrame", Misc_BeginFrame) KNOWN_EVENT("EndFrame", Misc_EndFrame) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("Logging") KNOWN_EVENT("LogCategory", Logging_LogCategory) KNOWN_EVENT("LogMessageSpec", Logging_LogMessageSpec) KNOWN_EVENT("LogMessage", Logging_LogMessage) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("PlatformFile") KNOWN_EVENT("BeginOpen", PlatformFile_BeginOpen) KNOWN_EVENT("EndOpen", PlatformFile_EndOpen) KNOWN_EVENT("BeginRead", PlatformFile_BeginRead) KNOWN_EVENT("EndRead", PlatformFile_EndRead) KNOWN_EVENT("BeginClose", PlatformFile_BeginClose) KNOWN_EVENT("EndClose", PlatformFile_EndClose) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("LoadTime") KNOWN_EVENT("ClassInfo", LoadTime_ClassInfo) KNOWN_EVENT("NewAsyncPackage", LoadTime_NewAsyncPackage) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("GpuProfiler") KNOWN_EVENT("EventSpec", GpuProfiler_EventSpec) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("CpuProfiler") KNOWN_EVENT("EventSpec", CpuProfiler_EventSpec) KNOWN_EVENT("EventBatch", CpuProfiler_EventBatch) KNOWN_EVENT("EndCapture", CpuProfiler_EndCapture) KNOWN_EVENT("EventBatchV2", CpuProfiler_EventBatchV2) KNOWN_EVENT("EndCaptureV2", CpuProfiler_EndCaptureV2) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("Stats") KNOWN_EVENT("Spec", Stats_Spec) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("Counters") KNOWN_EVENT("Spec", Counters_Spec) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("CsvProfiler") KNOWN_EVENT("RegisterCategory", CsvProfiler_RegisterCategory) KNOWN_EVENT("DefineInlineStat", CsvProfiler_DefineInlineStat) KNOWN_EVENT("DefineDeclaredStat", CsvProfiler_DefineDeclaredStat) KNOWN_EVENT("Metadata", CsvProfiler_Metadata) KNOWN_EVENT("BeginCapture", CsvProfiler_BeginCapture) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("Memory") KNOWN_EVENT("Marker", Memory_Marker) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("LLM") KNOWN_EVENT("TagValue", LLM_TagValue) KNOWN_EVENT("TagsSpec", LLM_TagsSpec) KNOWN_EVENT("TrackerSpec", LLM_TrackerSpec) END_KNOWN_LOGGER() BEGIN_KNOWN_LOGGER("SlateTrace") KNOWN_EVENT("AddWidget", SlateTrace_AddWidget) END_KNOWN_LOGGER() break; } #undef BEGIN_KNOWN_LOGGER #undef KNOWN_EVENT #undef END_KNOWN_LOGGER ////////////////////////////////////////////////// // Filter if (bNoNewEventLog) { return true; } ////////////////////////////////////////////////// FTextSerializer& Serializer = *TextSerializer; if (Serializer.IsWriteEventHeaderEnabled()) { Serializer.BeginNewEventHeader(); Serializer.WriteAttributeInteger("Id", TypeInfo.GetId()); Serializer.WriteAttributeString("LoggerName", TypeInfo.GetLoggerName()); Serializer.WriteAttributeString("Name", TypeInfo.GetName()); Serializer.BeginAttribute(); Serializer.WriteKey("Flags"); Serializer.Append("\""); const uint8 EventFlags = TypeInfo.GetFlags(); if ((EventFlags & (uint8)EEventFlags::Definition) != 0) { Serializer.Append("Definition|"); } if ((EventFlags & (uint8)EEventFlags::Important) != 0) { Serializer.Append("Important|"); } if ((EventFlags & (uint8)EEventFlags::MaybeHasAux) != 0) { Serializer.Append("MaybeHasAux|"); } if ((EventFlags & (uint8)EEventFlags::NoSync) != 0) { Serializer.Append("NoSync"); } else { Serializer.Append("Sync"); } Serializer.Append("\""); Serializer.EndAttribute(); Serializer.EndNewEventHeader(); } Serializer.BeginNewEventFields(); for (int FieldIndex = 0, FieldCount = TypeInfo.GetFieldCount(); FieldIndex < FieldCount; ++FieldIndex) { Serializer.BeginField(); const FEventFieldInfo& FieldInfo = *(TypeInfo.GetFieldInfo(FieldIndex)); Serializer.WriteAttributeString("Name", FieldInfo.GetName()); /** Offset from the start of the event to this field's data. */ Serializer.WriteAttributeInteger("Offset", FieldInfo.GetOffset()); /** The size of the field's data in bytes. */ Serializer.WriteAttributeInteger("Size", FieldInfo.GetSize()); Serializer.BeginAttribute(); /** What type of field is this? */ Serializer.WriteKey("Type"); switch (FieldInfo.GetType()) { case IAnalyzer::FEventFieldInfo::EType::Integer: // int64 Serializer.Append("Integer"); break; case IAnalyzer::FEventFieldInfo::EType::Float: // double Serializer.Append("Float"); break; case IAnalyzer::FEventFieldInfo::EType::AnsiString: Serializer.Append("AnsiString"); break; case IAnalyzer::FEventFieldInfo::EType::WideString: Serializer.Append("WideString"); break; case IAnalyzer::FEventFieldInfo::EType::Reference8: Serializer.Append("Reference8"); break; case IAnalyzer::FEventFieldInfo::EType::Reference16: Serializer.Append("Reference16"); break; case IAnalyzer::FEventFieldInfo::EType::Reference32: Serializer.Append("Reference32"); break; case IAnalyzer::FEventFieldInfo::EType::Reference64: Serializer.Append("Reference64"); break; default: Serializer.WriteValueUInt32(static_cast(FieldInfo.GetType())); break; } Serializer.EndAttribute(); /** Is this field an array-type field? */ Serializer.WriteAttributeBool("IsArray", FieldInfo.IsArray()); Serializer.EndField(); } Serializer.EndNewEventFields(); return Serializer.Commit(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FConvertToTextAnalyzer::OnEvent(uint16 RouteId, EStyle Style, const FOnEventContext& Context) { const FEventData& EventData = Context.EventData; const FEventTypeInfo& TypeInfo = EventData.GetTypeInfo(); const uint32 EventId = TypeInfo.GetId(); ////////////////////////////////////////////////// // Update event stats. ++NumEvents; if (Style == EStyle::EnterScope) { ++NumEnterScopeEvents; } else if (Style == EStyle::LeaveScope) { ++NumLeaveScopeEvents; } uint32 TotalEventSize = EventData.GetTotalSize(Style, Context, ProtocolVersion); if (!bNoEventStats) { FEventStat* Stat = EventStats.Find(EventId); if (Stat == nullptr) { if (TypeInfo.GetLoggerName()[0] == 0) // empty dispatch, expected on LeaveScope events { Stat = &EventStats.Add(0, { nullptr }); } else { Stat = &EventStats.Add(EventId, { &TypeInfo }); check(TypeInfo.GetLoggerName() != nullptr); check(TypeInfo.GetName() != nullptr); } } if (RouteId == 0) { Stat->Count++; Stat->Bytes += TotalEventSize; } else // if (RouteId == 1) { Stat->ScopedCount++; Stat->ScopedBytes += TotalEventSize; } Stat->MinSize = FMath::Min(Stat->MinSize, TotalEventSize); Stat->MaxSize = FMath::Max(Stat->MaxSize, TotalEventSize); } ////////////////////////////////////////////////// // Filter 1 // Allows only the specified type of events. //if (strcmp(TypeInfo.GetLoggerName(), "...") != 0 || // strcmp(TypeInfo.GetName(), "...") != 0) //{ // return true; //} //if (!IsKnownEvent(EventId, EKnownEvent::...)) //{ // return true; //} ////////////////////////////////////////////////// // Filter 2 // Allows all but the specified type of events. //if (strcmp(TypeInfo.GetLoggerName(), "...") == 0) //{ // if (strcmp(TypeInfo.GetName(), "...") == 0) // { // return true; // } //} //if (IsKnownEvent(EventId, EKnownEvent::...)) //{ // return true; //} ////////////////////////////////////////////////// const uint32 FieldCount = (Style == EStyle::LeaveScope) ? 0 : TypeInfo.GetFieldCount(); const uint32 FixedSize = TypeInfo.GetSize(); const uint32 RawSize = EventData.GetRawSize(); const uint32 AuxSize = EventData.GetAuxSize(); const uint32 AttachmentSize = EventData.GetAttachmentSize(); EventsTotalSize += TotalEventSize; const bool bHasAttachment = ((TypeInfo.GetFlags() & (uint8)EEventFlags::Important) == 0) && (AuxSize == 0) && (RawSize > FixedSize) && (AttachmentSize > 0) && EventData.GetAttachment() != nullptr; bool bEmitSizeStats = false; const uint32 ContextThreadId = Context.ThreadInfo.GetId(); ////////////////////////////////////////////////// if (bNoEventLog) { return true; } FTextSerializer& Serializer = *TextSerializer; Serializer.BeginEvent(ContextThreadId); if (RouteId == 1) { Serializer.BeginAttribute(); if (Style == EStyle::EnterScope) { Serializer.Append("!EnterScope"); } else if (Style == EStyle::LeaveScope) { Serializer.Append("!LeaveScope"); check(FieldCount == 0); check(!bHasAttachment); } else if (Style == EStyle::Normal) { Serializer.Append("!Normal"); } else { Serializer.Appendf("!%u", (uint32)Style); } Serializer.EndAttribute(); } Serializer.WriteEventName(TypeInfo.GetLoggerName(), TypeInfo.GetName()); ////////////////////////////////////////////////// for (uint32 FieldIndex = 0; FieldIndex < FieldCount; ++FieldIndex) { const FEventFieldInfo& FieldInfo = *(TypeInfo.GetFieldInfo(FieldIndex)); if (FieldInfo.IsArray()) { if (IsKnownEvent(EventId, EKnownEvent::GpuProfiler_EventSpec) || // "Name" array IsKnownEvent(EventId, EKnownEvent::CpuProfiler_EventBatch) || // "Data" array IsKnownEvent(EventId, EKnownEvent::CpuProfiler_EndCapture) || // "Data" array IsKnownEvent(EventId, EKnownEvent::CpuProfiler_EventBatchV2) || // "Data" array IsKnownEvent(EventId, EKnownEvent::CpuProfiler_EndCaptureV2)) // "Data" array { continue; } } Serializer.BeginAttribute(); Serializer.WriteKey(FieldInfo.GetName()); if (FieldInfo.IsArray()) { switch (FieldInfo.GetType()) { case FEventFieldInfo::EType::Integer: // int64 { #if 0 // Probe element size... uint8 ElementSize = 0; { const TArrayReader& Reader8 = EventData.GetArray(FieldInfo.GetName()); if (Reader8.GetData()) { ElementSize = 1; } else { const TArrayReader& Reader16 = EventData.GetArray(FieldInfo.GetName()); if (Reader16.GetData()) { ElementSize = 2; } else { const TArrayReader& Reader32 = EventData.GetArray(FieldInfo.GetName()); if (Reader16.GetData()) { ElementSize = 4; } else { ElementSize = 8; } } } } #endif if (FieldInfo.IsSigned()) { Serializer.BeginArray(); const TArrayReader& Reader = EventData.GetArray(FieldInfo.GetName()); const uint32 ArrayCount = Reader.Num(); const uint32 ActualCount = FMath::Min(ArrayCount, 8u); for (uint32 ArrayIndex = 0; ArrayIndex < ActualCount; ++ArrayIndex) { if (ArrayIndex != 0) { Serializer.NextArrayElement(); } const int64 Value = Reader[ArrayIndex]; Serializer.WriteValueInt64Auto(Value); } if (ActualCount != ArrayCount) { Serializer.Appendf(" ... | %u elements", ArrayCount); } Serializer.EndArray(); } else // unsigned { const TArrayReader& Reader8 = EventData.GetArray(FieldInfo.GetName()); const uint8* Data8 = Reader8.GetData(); if (Data8) { // Write array as 0x[00 01 ... FF] Serializer.Append("0x"); Serializer.BeginArray(); const uint32 ArrayCount = Reader8.Num(); const uint32 ActualCount = FMath::Min(ArrayCount, 32u); for (uint32 ArrayIndex = 0; ArrayIndex < ActualCount; ++ArrayIndex) { if (ArrayIndex != 0) { Serializer.NextArrayElement(); } const uint8 Value8 = Reader8[ArrayIndex]; const uint32 Value = (uint32)Data8[ArrayIndex]; check(Value8 == Data8[ArrayIndex]); Serializer.Appendf("%02X", Value); } if (ActualCount != ArrayCount) { Serializer.Appendf(" ... | %u bytes", ArrayCount); } Serializer.EndArray(); break; } Serializer.BeginArray(); const TArrayReader& Reader = EventData.GetArray(FieldInfo.GetName()); const uint32 ArrayCount = Reader.Num(); const uint32 ActualCount = FMath::Min(ArrayCount, 8u); for (uint32 ArrayIndex = 0; ArrayIndex < ActualCount; ++ArrayIndex) { if (ArrayIndex != 0) { Serializer.NextArrayElement(); } const uint64 Value = Reader[ArrayIndex]; Serializer.WriteValueUInt64Auto(Value); } if (ActualCount != ArrayCount) { Serializer.Appendf(" ... | %u elements", ArrayCount); } Serializer.EndArray(); } break; } case FEventFieldInfo::EType::Float: // double { Serializer.BeginArray(); const TArrayReader& Reader = EventData.GetArray(FieldInfo.GetName()); const uint32 ArrayCount = Reader.Num(); const uint32 ActualCount = FMath::Min(ArrayCount, 4u); for (uint32 ArrayIndex = 0; ArrayIndex < ActualCount; ++ArrayIndex) { if (ArrayIndex != 0) { Serializer.NextArrayElement(); } const double Value = Reader[ArrayIndex]; Serializer.WriteValueDouble(Value); } if (ActualCount != ArrayCount) { Serializer.Appendf(" ... | %u elements", ArrayCount); } Serializer.EndArray(); break; } case FEventFieldInfo::EType::AnsiString: { FAnsiStringView Value; EventData.GetString(FieldInfo.GetName(), Value); Serializer.Appendf("\"%.*s\"", Value.Len(), Value.GetData()); break; } case FEventFieldInfo::EType::WideString: { FString Value; EventData.GetString(FieldInfo.GetName(), Value); Serializer.WriteValueString(TCHAR_TO_UTF8(*Value)); break; } default: { // error Serializer.Append(""); } } } else { switch (FieldInfo.GetType()) { case FEventFieldInfo::EType::Integer: { if (FieldInfo.IsSigned()) { const int64 Value = EventData.GetValue(FieldInfo.GetName()); if (strcmp(FieldInfo.GetName(), "Cycle") == 0) { Serializer.WriteValueUInt64(uint64(Value)); Serializer.AppendChar('('); double Time = Context.EventTime.AsSeconds(uint64(Value)); Serializer.WriteValueTime(Time); Serializer.AppendChar(')'); } else { Serializer.WriteValueInt64Auto(Value); } } else // unsigned { const uint64 Value = EventData.GetValue(FieldInfo.GetName()); if (strcmp(FieldInfo.GetName(), "Cycle") == 0) { Serializer.WriteValueUInt64(Value); Serializer.AppendChar('('); double Time = Context.EventTime.AsSeconds(Value); Serializer.WriteValueTime(Time); Serializer.AppendChar(')'); } else { Serializer.WriteValueUInt64Auto(Value); } } break; } case FEventFieldInfo::EType::Float: // double { const double Value = EventData.GetValue(FieldInfo.GetName()); Serializer.WriteValueDouble(Value); break; } case FEventFieldInfo::EType::Reference8: { check(FieldInfo.GetSize() == 1); UE::Trace::FEventRef8 RefValue = EventData.GetReferenceValue(FieldIndex); Serializer.WriteValueReference(RefValue); break; } case FEventFieldInfo::EType::Reference16: { check(FieldInfo.GetSize() == 2); UE::Trace::FEventRef16 RefValue = EventData.GetReferenceValue(FieldIndex); Serializer.WriteValueReference(RefValue); break; } case FEventFieldInfo::EType::Reference32: { check(FieldInfo.GetSize() == 4); UE::Trace::FEventRef32 RefValue = EventData.GetReferenceValue(FieldIndex); Serializer.WriteValueReference(RefValue); break; } case FEventFieldInfo::EType::Reference64: { check(FieldInfo.GetSize() == 8); UE::Trace::FEventRef64 RefValue = EventData.GetReferenceValue(FieldIndex); Serializer.WriteValueReference(RefValue); break; } default: { // error Serializer.Append(""); } } } Serializer.EndAttribute(); } ////////////////////////////////////////////////// check(EventId <= MaxKnownEventId); EKnownEvent KnownEvent = KnownEventMap[EventId]; check((int)KnownEvent < (int)EKnownEvent::Count); const FKnownEvent& Event = KnownEvents[(int)KnownEvent]; if (Event.Callback) { Event.Callback(Context, Serializer); } if (bHasAttachment) { if (Event.AttachmentCallback) { Event.AttachmentCallback(Context, Serializer); } else { Serializer.WriteAttributeBinary("Attached", EventData.GetAttachment(), AttachmentSize); } } if (bEmitSizeStats) { Serializer.AppendChar('\n'); Serializer.BeginAttributeSet(); Serializer.BeginAttribute(); Serializer.Appendf("> SIZE Fix=%u Aux=%u Att=%u Raw=%u Total=%u", FixedSize, AuxSize, AttachmentSize, RawSize, TotalEventSize); Serializer.EndAttribute(); } ////////////////////////////////////////////////// Serializer.EndEvent(); return Serializer.Commit(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FConvertToTextAnalyzer::ConvertToTextEventBatch(const FOnEventContext& Context, FTextSerializer& Serializer, const uint8* BufferPtr, uint32 BufferSize) { ++NumCpuBatches; const FEventData& EventData = Context.EventData; Serializer.Append("["); bool bFirstElement = true; const uint32 ThreadId = GetThreadIdField(Context); uint64* LastCyclePtr = ThreadMap.Find(ThreadId); if (!LastCyclePtr) { LastCyclePtr = &ThreadMap.Add(ThreadId, 0); } uint64 LastCycle = *LastCyclePtr; const uint8* BufferEnd = BufferPtr + BufferSize; while (BufferPtr < BufferEnd) { if (!bFirstElement) { Serializer.AppendChar(' '); } else { bFirstElement = false; } uint64 DecodedCycle = Decode7bit(BufferPtr); uint64 Cycle = DecodedCycle >> 1; uint64 ActualCycle = Cycle + LastCycle; LastCycle = ActualCycle; const bool bIsAbsoluteTimestamp = (Cycle > 100000000000ull); if (DecodedCycle & 1ull) { ++NumBeginCpuEvents; //if (DecodedCycle >> 1 == 16) //{ // Serializer.Append("!!!"); // BufferPtr = BufferEnd; // break; //} bool bIsUnknownSpecId = false; uint32 SpecId = Decode7bit(BufferPtr); FString* NamePtr = TimerMap.Find(SpecId); if (!NamePtr) { // SpecId not yet declared (no "EventSpec" event for this id)! DefineTimer(SpecId, nullptr); bIsUnknownSpecId = true; } //Serializer.WriteInteger("B", ActualCycle); if (bIsAbsoluteTimestamp) { Serializer.Append("*B="); Serializer.WriteValueInt64(Cycle); Serializer.AppendChar('('); double Time = Context.EventTime.AsSeconds(Cycle); Serializer.WriteValueTime(Time); Serializer.AppendChar(')'); } else { Serializer.Append("B="); Serializer.WriteValueInt64(Cycle); } Serializer.Append(bIsUnknownSpecId ? " ?id=" : " id="); Serializer.WriteValueInt64(SpecId); } else { ++NumEndCpuEvents; //Serializer.WriteInteger("E", ActualCycle); if (bIsAbsoluteTimestamp) { Serializer.Append("*E="); Serializer.WriteValueInt64(Cycle); Serializer.AppendChar('('); double Time = Context.EventTime.AsSeconds(Cycle); Serializer.WriteValueTime(Time); Serializer.AppendChar(')'); } else { Serializer.Append("E="); Serializer.WriteValueInt64(Cycle); } } } if (!ensure(BufferPtr == BufferEnd)) { Serializer.Append(" ++"); } *LastCyclePtr = LastCycle; Serializer.Append("]"); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FConvertToTextAnalyzer::ConvertToTextEventBatchV2(const FOnEventContext& Context, FTextSerializer& Serializer, const uint8* BufferPtr, uint32 BufferSize) { ++NumCpuBatches; const FEventData& EventData = Context.EventData; Serializer.Append("["); bool bFirstElement = true; const uint32 ThreadId = GetThreadIdField(Context); uint64* LastCyclePtr = ThreadMap.Find(ThreadId); if (!LastCyclePtr) { LastCyclePtr = &ThreadMap.Add(ThreadId, 0); } uint64 LastCycle = *LastCyclePtr; const uint8* BufferEnd = BufferPtr + BufferSize; while (BufferPtr < BufferEnd) { if (!bFirstElement) { Serializer.AppendChar(' '); } else { bFirstElement = false; } uint64 DecodedCycle = Decode7bit(BufferPtr); uint64 Cycle = DecodedCycle >> 2; uint64 ActualCycle = Cycle + LastCycle; LastCycle = ActualCycle; if (DecodedCycle & 2ull) // coroutine { if (DecodedCycle & 1ull) // Restore { Serializer.Append("coR="); Serializer.WriteValueInt64(Cycle); uint64 CoroutineId = Decode7bit(BufferPtr); Serializer.Append(" coId="); Serializer.WriteValueInt64(CoroutineId); uint32 TimerScopeDepth = Decode7bit(BufferPtr); Serializer.Append(" coDepth="); Serializer.WriteValueInt64(TimerScopeDepth); } else // Save { Serializer.Append("coS="); Serializer.WriteValueInt64(Cycle); uint32 TimerScopeDepth = Decode7bit(BufferPtr); Serializer.Append(" coDepth="); Serializer.WriteValueInt64(TimerScopeDepth); } } else // normal CPU events { const bool bIsAbsoluteTimestamp = (Cycle > 100000000000ull); if (DecodedCycle & 1ull) { ++NumBeginCpuEvents; bool bIsUnknownSpecId = false; uint32 SpecId = Decode7bit(BufferPtr); FString* NamePtr = TimerMap.Find(SpecId); if (!NamePtr) { // SpecId not yet declared (no "EventSpec" event for this id)! DefineTimer(SpecId, nullptr); bIsUnknownSpecId = true; } //Serializer.WriteInteger("B", ActualCycle); if (bIsAbsoluteTimestamp) { Serializer.Append("*B="); Serializer.WriteValueInt64(Cycle); Serializer.AppendChar('('); double Time = Context.EventTime.AsSeconds(Cycle); Serializer.WriteValueTime(Time); Serializer.AppendChar(')'); } else { Serializer.Append("B="); Serializer.WriteValueInt64(Cycle); } Serializer.Append(bIsUnknownSpecId ? " ?id=" : " id="); Serializer.WriteValueInt64(SpecId); } else { ++NumEndCpuEvents; //Serializer.WriteInteger("E", ActualCycle); if (bIsAbsoluteTimestamp) { Serializer.Append("*E="); Serializer.WriteValueInt64(Cycle); Serializer.AppendChar('('); double Time = Context.EventTime.AsSeconds(Cycle); Serializer.WriteValueTime(Time); Serializer.AppendChar(')'); } else { Serializer.Append("E="); Serializer.WriteValueInt64(Cycle); } } } } if (!ensure(BufferPtr == BufferEnd)) { Serializer.Append(" ++"); } *LastCyclePtr = LastCycle; Serializer.Append("]"); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FConvertToTextAnalyzer::DefineTimer(uint32 SpecId, const TCHAR* Name) { TimerMap.Add(SpecId, FString(Name)); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FConvertToTextAnalyzer::PrintUnknownTimers(FTextSerializer& Serializer) { TArray UnknownTimers; for (auto& KV : TimerMap) { if (KV.Value.IsEmpty()) { UnknownTimers.Add(KV.Key); } } if (UnknownTimers.Num() > 0) { Serializer.Append("\n"); Serializer.Appendf(" Unknown CPU Timers: %d / %d [", UnknownTimers.Num(), TimerMap.Num()); for (uint32 TimerId : UnknownTimers) { Serializer.Appendf("%u ", TimerId); } Serializer.Append("]\n"); } } //////////////////////////////////////////////////////////////////////////////////////////////////// static void PrintConvertToTextUsage() { puts("Usage:"); puts(" TraceAnalyzer []"); puts(" Where:"); puts(" : the input *.utrace file"); puts(" -o= : the output text file; defaults to stdout"); puts(" -no_new_event_log : no 'new event' logging"); puts(" -no_event_log : no 'event' logging"); puts(" -no_analysis_stats : no analysis stats"); puts(" -no_event_stats : no event stats"); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Main //////////////////////////////////////////////////////////////////////////////////////////////////// static int32 ConvertToTextMain(int32 ArgC, TCHAR const* const* ArgV) { if (ArgC < 2) { PrintConvertToTextUsage(); return 0; } const TCHAR* TraceFileName = ArgV[1]; FileHandle Input = -1; FileHandle Output = -1; bool bNoNewEventLog = false; bool bNoEventLog = false; bool bNoAnalysisStats = false; bool bNoEventStats = false; Input = OpenFile(TraceFileName, false); if (Input < 0) { wprintf(TEXT("Error: Cannot open the input trace file ('%s')!"), TraceFileName); return -2; } for (int32 ArgIndex = 2; ArgIndex < ArgC; ++ArgIndex) { if (FCString::Strnicmp(TEXT("-o="), ArgV[ArgIndex], 3) == 0) { const TCHAR* OutputFileName = ArgV[ArgIndex] + 3; Output = OpenFile(OutputFileName, true); if (Output < 0) { wprintf(TEXT("Warning: Cannot open the output text file ('%s')!\n"), OutputFileName); CloseFile(Input); return -3; } } else if (FCString::Stricmp(TEXT("-no_new_event_log"), ArgV[ArgIndex]) == 0) { bNoNewEventLog = true; } else if (FCString::Stricmp(TEXT("-no_event_log"), ArgV[ArgIndex]) == 0) { bNoEventLog = true; } else if (FCString::Stricmp(TEXT("-no_analysis_stats"), ArgV[ArgIndex]) == 0) { bNoAnalysisStats = true; } else if (FCString::Stricmp(TEXT("-no_event_stats"), ArgV[ArgIndex]) == 0) { bNoEventStats = true; } else { wprintf(TEXT("Warning: Unknown cmd line argument '%s'!\n"), ArgV[ArgIndex]); } } struct FDataStream : public UE::Trace::IInDataStream { virtual int32 Read(void* Data, uint32 Size) override { return FileRead(Handle, Data, Size); } FileHandle Handle = -1; }; { FDataStream DataStream; DataStream.Handle = Input; { FConvertToTextAnalyzer ConvertAnalyzer(Output); ConvertAnalyzer.bNoNewEventLog = bNoNewEventLog; ConvertAnalyzer.bNoEventLog = bNoEventLog; ConvertAnalyzer.bNoAnalysisStats = bNoAnalysisStats; ConvertAnalyzer.bNoEventStats = bNoEventStats; UE::Trace::FAnalysisContext Context; Context.AddAnalyzer(ConvertAnalyzer); Context.Process(DataStream).Wait(); } } CloseFile(Output); CloseFile(Input); return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// FCommand GetConvertToTextCommand() { return { TEXT("conv2text"), TEXT("Converts analysis events into a text stream"), ConvertToTextMain, }; } //////////////////////////////////////////////////////////////////////////////////////////////////// } // namespace TraceAnalyzer } // namespace UE