// Copyright Epic Games, Inc. All Rights Reserved. #include "Insights/TimingProfiler/Tracks/ThreadTimingTrack.h" #include "ThreadTimingTrackPrivate.h" #include "Async/TaskGraphInterfaces.h" // for ENamedThreads #include "CborReader.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "HAL/PlatformApplicationMisc.h" #include "Serialization/MemoryReader.h" // TraceServices #include "TraceServices/Containers/Timelines.h" #include "TraceServices/Model/AnalysisSession.h" #include "TraceServices/Model/Threads.h" #include "TraceServices/Model/TimingProfiler.h" // TraceInsightsCore #include "InsightsCore/Common/TimeUtils.h" #include "InsightsCore/Filter/ViewModels/FilterConfigurator.h" #include "InsightsCore/Filter/ViewModels/Filters.h" // TraceInsights #include "Insights/InsightsManager.h" #include "Insights/TimingProfiler/TimingProfilerManager.h" #include "Insights/TimingProfiler/Tracks/CpuTimingTrack.h" #include "Insights/TimingProfiler/ViewModels/ThreadTimingSharedState.h" #include "Insights/ViewModels/ThreadTrackEvent.h" #include "Insights/ViewModels/TimingEvent.h" #include "Insights/ViewModels/TimingEventSearch.h" #include "Insights/ViewModels/TimingTrackViewport.h" #include "Insights/ViewModels/TimingViewDrawHelper.h" #include "Insights/ViewModels/TooltipDrawState.h" #define LOCTEXT_NAMESPACE "UE::Insights::TimingProfiler::ThreadTiming" namespace UE::Insights::TimingProfiler { INSIGHTS_IMPLEMENT_RTTI(FThreadTimingTrack); //////////////////////////////////////////////////////////////////////////////////////////////////// static void AppendMetadataToTooltip(FTooltipDrawState& Tooltip, const TraceServices::FMetadataSpec* MetadataSpec, TArrayView& Metadata) { FMemoryReaderView MemoryReader(Metadata); FCborReader CborReader(&MemoryReader, ECborEndianness::StandardCompliant); FCborContext Context; if (MetadataSpec == nullptr) { if (!CborReader.ReadNext(Context)) { return; } if (Context.MajorType() != ECborCode::Map) { return; } } Tooltip.AddTitle(TEXT("Metadata:")); uint32 Index = 0; while (true) { FString Key; if (MetadataSpec) { if (Index < (uint32) MetadataSpec->FieldNames.Num()) { Key = MetadataSpec->FieldNames[Index]; ++Index; } else { Key = TEXT("UndefinedField"); } } else { // Read key if (!CborReader.ReadNext(Context) || !Context.IsString()) { break; } Key = FString::ConstructFromPtrSize(Context.AsCString(), static_cast(Context.AsLength())); } Key += TEXT(":"); if (!CborReader.ReadNext(Context)) { break; } switch (Context.MajorType()) { case ECborCode::Int: case ECborCode::Uint: { uint64 Value = Context.AsUInt(); FString ValueStr; if (Value > 999'999'999ULL) { ValueStr = FString::Printf(TEXT("0x%llX"), Value); } else { ValueStr = FString::Printf(TEXT("%llu"), Value); } Tooltip.AddNameValueTextLine(Key, ValueStr); continue; } case ECborCode::TextString: { FString Value = Context.AsString(); Tooltip.AddNameValueTextLine(Key, Value); continue; } case ECborCode::ByteString: { FString ValueStr = FString::ConstructFromPtrSize(Context.AsCString(), static_cast(Context.AsLength())); Tooltip.AddNameValueTextLine(Key, ValueStr); continue; } } if (Context.RawCode() == (ECborCode::Prim|ECborCode::Value_4Bytes)) { float Value = Context.AsFloat(); FString ValueStr = FString::Printf(TEXT("%f"), Value); Tooltip.AddNameValueTextLine(Key, ValueStr); continue; } if (Context.RawCode() == (ECborCode::Prim|ECborCode::Value_8Bytes)) { double Value = Context.AsDouble(); FString ValueStr = FString::Printf(TEXT("%g"), Value); Tooltip.AddNameValueTextLine(Key, ValueStr); continue; } if (Context.RawCode() == (ECborCode::Prim | ECborCode::False)) { Tooltip.AddNameValueTextLine(Key, FString(TEXT("false"))); continue; } if (Context.RawCode() == (ECborCode::Prim | ECborCode::True)) { Tooltip.AddNameValueTextLine(Key, FString(TEXT("true"))); continue; } if (Context.IsFiniteContainer()) { CborReader.SkipContainer(ECborCode::Array); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// static void AppendMetadataToString(FString& Str, TArrayView& Metadata) { FMemoryReaderView MemoryReader(Metadata); FCborReader CborReader(&MemoryReader, ECborEndianness::StandardCompliant); FCborContext Context; if (!CborReader.ReadNext(Context) || Context.MajorType() != ECborCode::Map) { return; } bool bFirst = true; while (true) { // Read key if (!CborReader.ReadNext(Context) || !Context.IsString()) { break; } if (bFirst) { bFirst = false; Str += TEXT(" - "); } else { Str += TEXT(", "); } //FString Key(Context.AsLength(), Context.AsCString()); //Str += Key; //Str += TEXT(":"); if (!CborReader.ReadNext(Context)) { break; } switch (Context.MajorType()) { case ECborCode::Int: case ECborCode::Uint: { uint64 Value = Context.AsUInt(); if (Value > 999'999'999ULL) { Str += FString::Printf(TEXT("0x%llX"), Value); } else { Str += FString::Printf(TEXT("%llu"), Value); } continue; } case ECborCode::TextString: { Str += Context.AsString(); continue; } case ECborCode::ByteString: { Str.AppendChars(Context.AsCString(), static_cast(Context.AsLength())); continue; } } if (Context.RawCode() == (ECborCode::Prim | ECborCode::Value_4Bytes)) { float Value = Context.AsFloat(); Str += FString::Printf(TEXT("%f"), Value); continue; } if (Context.RawCode() == (ECborCode::Prim | ECborCode::Value_8Bytes)) { double Value = Context.AsDouble(); Str += FString::Printf(TEXT("%g"), Value); continue; } if (Context.RawCode() == (ECborCode::Prim | ECborCode::False)) { Str += TEXT("false"); continue; } if (Context.RawCode() == (ECborCode::Prim | ECborCode::True)) { Str += TEXT("true"); continue; } if (Context.IsFiniteContainer()) { CborReader.SkipContainer(ECborCode::Array); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// static void AppendMetadataToString(FString& Str, const TraceServices::FMetadataSpec* MetadataSpec, TArrayView& Metadata) { if (MetadataSpec == nullptr) { return; } FMemoryReaderView MemoryReader(Metadata); FCborReader CborReader(&MemoryReader, ECborEndianness::StandardCompliant); FCborContext Context; FString Format; Format = MetadataSpec->Format; const FString Specifiers = TEXT("diuoxXfFeEgGaAcspn"); auto GetNextFormatSection = [&Format, &Specifiers]() { int Index = 0; bool bIsInFormatSpecifier = false; while (Index < Format.Len()) { if (bIsInFormatSpecifier) { int32 SpecIndex = -1; if (Specifiers.FindChar(Format[Index], SpecIndex)) { FString NextFormat = Format.Left(Index+1); Format.MidInline(Index+1); return NextFormat; } } if (Format[Index] == TEXT('%')) { bIsInFormatSpecifier = !bIsInFormatSpecifier; } ++Index; } FString Copy = Format; Format.Empty(); return Copy; }; bool bFirst = true; while (!Format.IsEmpty()) { if (!CborReader.ReadNext(Context)) { break; } if (bFirst) { bFirst = false; Str += TEXT(" - "); } else { if (Format.IsEmpty()) { Str += TEXT(", "); } } constexpr int MaxLength = 256; TCHAR Data[MaxLength]; auto AddValueToName = [&Str](TCHAR* Dest, int MaxLength, const FString& Format, auto Value) { int32 Result = FCString::Snprintf(Dest, MaxLength, reinterpret_cast(**Format), Value); if (Result > 0) { Str.Append(Dest); } }; switch (Context.MajorType()) { case ECborCode::Int: case ECborCode::Uint: { uint64 Value = Context.AsUInt(); AddValueToName(Data, MaxLength, GetNextFormatSection(), Value); continue; } case ECborCode::TextString: { AddValueToName(Data, MaxLength, GetNextFormatSection(), *Context.AsString()); continue; } case ECborCode::ByteString: { AddValueToName(Data, FMath::Min(MaxLength, (int)Context.AsLength()), GetNextFormatSection(), Context.AsCString()); continue; } } if (Context.RawCode() == (ECborCode::Prim | ECborCode::Value_4Bytes)) { AddValueToName(Data, MaxLength, GetNextFormatSection(), Context.AsFloat()); continue; } if (Context.RawCode() == (ECborCode::Prim | ECborCode::Value_8Bytes)) { AddValueToName(Data, MaxLength, GetNextFormatSection(), Context.AsDouble()); continue; } if (Context.RawCode() == (ECborCode::Prim | ECborCode::False)) { AddValueToName(Data, MaxLength, GetNextFormatSection(), false); continue; } if (Context.RawCode() == (ECborCode::Prim | ECborCode::True)) { AddValueToName(Data, MaxLength, GetNextFormatSection(), true); continue; } if (Context.IsFiniteContainer()) { CborReader.SkipContainer(ECborCode::Array); } } // Append what's left of the format string. Str.Append(Format); } //////////////////////////////////////////////////////////////////////////////////////////////////// static void AddTimingEventToBuilder(ITimingEventsTrackDrawStateBuilder& Builder, double EventStartTime, double EventEndTime, uint32 EventDepth, uint32 TimerIndex, const TraceServices::FTimingProfilerTimer* Timer) { if (EventDepth >= FTimingProfilerManager::Get()->GetEventDepthLimit()) { return; } uint32 EventColor; switch (FTimingProfilerManager::Get()->GetColoringMode()) { case ETimingEventsColoringMode::ByTimerName: EventColor = FTimingEvent::ComputeEventColor(Timer->Name); break; case ETimingEventsColoringMode::ByTimerId: EventColor = FTimingEvent::ComputeEventColor(Timer->Id); break; case ETimingEventsColoringMode::BySourceFile: EventColor = FTimingEvent::ComputeEventColor(Timer->File); break; case ETimingEventsColoringMode::ByDuration: { const double EventDuration = EventEndTime - EventStartTime; EventColor = (EventDuration >= 0.01) ? 0xFF883333 : // red: >= 10ms (EventDuration >= 0.001) ? 0xFF998833 : // yellow: [1ms .. 10ms) (EventDuration >= 0.0001) ? 0xFF338833 : // green: [100us .. 1ms) (EventDuration >= 0.00001) ? 0xFF338888 : // cyan: [10us .. 100us) (EventDuration >= 0.000001) ? 0xFF333388 : // blue: [1us .. 10us) 0xFF888888; // gray: < 1us break; } default: EventColor = 0xFF000000; } Builder.AddEvent(EventStartTime, EventEndTime, EventDepth, EventColor, [TimerIndex, Timer, EventStartTime, EventEndTime](float Width) { FString EventName = Timer->Name; const float MinWidth = static_cast(EventName.Len()) * 4.0f + 32.0f; if (Width > MinWidth) { //EventName = TEXT("*") + EventName; // for debugging const double Duration = EventEndTime - EventStartTime; FTimingEventsTrackDrawStateBuilder::AppendDurationToEventName(EventName, Duration); if (int32(TimerIndex) < 0) // has metadata? { //EventName = TEXT("!") + EventName; // for debugging TSharedPtr Session = FInsightsManager::Get()->GetSession(); check(Session.IsValid()); //TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); const TraceServices::ITimingProfilerTimerReader* TimerReader; TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); TArrayView Metadata = TimerReader->GetMetadata(TimerIndex); if (Metadata.Num() > 0) { const TraceServices::FMetadataSpec* MetadataSpec = nullptr; if (Timer->HasValidMetadataSpecId()) { MetadataSpec = TimingProfilerProvider.GetMetadataSpec(Timer->MetadataSpecId); } if (MetadataSpec) { AppendMetadataToString(EventName, MetadataSpec, Metadata); } else { AppendMetadataToString(EventName, Metadata); } } } } return EventName; }); } //////////////////////////////////////////////////////////////////////////////////////////////////// // FThreadTimingTrackImpl //////////////////////////////////////////////////////////////////////////////////////////////////// INSIGHTS_IMPLEMENT_RTTI(FThreadTimingTrackImpl) //////////////////////////////////////////////////////////////////////////////////////////////////// void FThreadTimingTrackImpl::BuildDrawState(ITimingEventsTrackDrawStateBuilder& Builder, const ITimingTrackUpdateContext& Context) { TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session.IsValid() && TraceServices::ReadTimingProfilerProvider(*Session.Get())) { TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); const TraceServices::ITimingProfilerTimerReader* TimerReader; TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); const FTimingTrackViewport& Viewport = Context.GetViewport(); TimingProfilerProvider.ReadTimeline(TimelineIndex, [&Viewport, this, &Builder, TimerReader](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { if (FTimingEventsTrack::bUseDownSampling) { const double SecondsPerPixel = 1.0 / Viewport.GetScaleX(); Timeline.EnumerateEventsDownSampled(Viewport.GetStartTime(), Viewport.GetEndTime(), SecondsPerPixel, [this, &Builder, TimerReader](double StartTime, double EndTime, uint32 Depth, const TraceServices::FTimingProfilerEvent& Event) { const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(Event.TimerIndex); if (ensure(Timer != nullptr)) { AddTimingEventToBuilder(Builder, StartTime, EndTime, Depth, Event.TimerIndex, Timer); } else { Builder.AddEvent(StartTime, EndTime, Depth, 0xFF000000, [&Event](float) { return FString::Printf(TEXT("[%u]"), Event.TimerIndex); }); } return TraceServices::EEventEnumerate::Continue; }); } else { Timeline.EnumerateEvents(Viewport.GetStartTime(), Viewport.GetEndTime(), [this, &Builder, TimerReader](double StartTime, double EndTime, uint32 Depth, const TraceServices::FTimingProfilerEvent& Event) { const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(Event.TimerIndex); if (ensure(Timer != nullptr)) { AddTimingEventToBuilder(Builder, StartTime, EndTime, Depth, Event.TimerIndex, Timer); } else { Builder.AddEvent(StartTime, EndTime, Depth, 0xFF000000, [&Event](float) { return FString::Printf(TEXT("[%u]"), Event.TimerIndex); }); } return TraceServices::EEventEnumerate::Continue; }); } }); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FThreadTimingTrackImpl::BuildFilteredDrawState(ITimingEventsTrackDrawStateBuilder& Builder, const ITimingTrackUpdateContext& Context) { struct FPendingEventInfo { double StartTime; double EndTime; uint32 Depth; uint32 TimerIndex; }; const TSharedPtr EventFilterPtr = Context.GetEventFilter(); if (EventFilterPtr.IsValid() && EventFilterPtr->FilterTrack(*this)) { bool bFilterOnlyByEventType = false; // this is the most often use case, so the below code tries to optimize it uint64 FilterEventType = 0; if (EventFilterPtr->Is()) { bFilterOnlyByEventType = true; const FTimingEventFilterByEventType& EventFilter = EventFilterPtr->As(); FilterEventType = EventFilter.GetEventType(); } TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session.IsValid() && TraceServices::ReadTimingProfilerProvider(*Session.Get())) { TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); const TraceServices::ITimingProfilerTimerReader* TimerReader; TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); const FTimingTrackViewport& Viewport = Context.GetViewport(); if (bFilterOnlyByEventType) { //TODO: Add a setting to switch this on/off if (true) { TimingProfilerProvider.ReadTimeline(TimelineIndex, [&Viewport, this, &Builder, TimerReader, FilterEventType](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { TArray> FilteredEvents; TraceServices::ITimeline::EnumerateAsyncParams Params; Params.IntervalStart = Viewport.GetStartTime(); Params.IntervalEnd = Viewport.GetEndTime(); Params.Resolution = 0.0; Params.SetupCallback = [&FilteredEvents](uint32 NumTasks) { FilteredEvents.AddDefaulted(NumTasks); }; Params.EventRangeCallback = [this, &Builder, TimerReader, FilterEventType, &FilteredEvents](double StartTime, double EndTime, uint32 Depth, const TraceServices::FTimingProfilerEvent& Event, uint32 TaskIndex) { const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(Event.TimerIndex); if (ensure(Timer != nullptr)) { if (Timer->Id == FilterEventType) { FPendingEventInfo TimelineEvent; TimelineEvent.StartTime = StartTime; TimelineEvent.EndTime = EndTime; TimelineEvent.Depth = Depth; TimelineEvent.TimerIndex = Event.TimerIndex; FilteredEvents[TaskIndex].Add(TimelineEvent); } } return TraceServices::EEventEnumerate::Continue; }; // Note: Enumerating events for filtering should not use downsampling. Timeline.EnumerateEventsDownSampledAsync(Params); for (TArray& Array : FilteredEvents) { for (FPendingEventInfo& TimelineEvent : Array) { const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(TimelineEvent.TimerIndex); AddTimingEventToBuilder(Builder, TimelineEvent.StartTime, TimelineEvent.EndTime, TimelineEvent.Depth, TimelineEvent.TimerIndex, Timer); } } }); } else { TimingProfilerProvider.ReadTimeline(TimelineIndex, [&Viewport, this, &Builder, TimerReader, FilterEventType](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { // Note: Enumerating events for filtering should not use downsampling. Timeline.EnumerateEventsDownSampled(Viewport.GetStartTime(), Viewport.GetEndTime(), 0, [this, &Builder, TimerReader, FilterEventType](double StartTime, double EndTime, uint32 Depth, const TraceServices::FTimingProfilerEvent& Event) { const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(Event.TimerIndex); if (ensure(Timer != nullptr)) { if (Timer->Id == FilterEventType) { AddTimingEventToBuilder(Builder, StartTime, EndTime, Depth, Event.TimerIndex, Timer); } } return TraceServices::EEventEnumerate::Continue; }); }); } } else // generic filter { TimingProfilerProvider.ReadTimeline(TimelineIndex, [&Viewport, this, &Builder, TimerReader, &EventFilterPtr](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { // Note: Enumerating events for filtering should not use downsampling. //const double SecondsPerPixel = 1.0 / Viewport.GetScaleX(); //Timeline.EnumerateEventsDownSampled(Viewport.GetStartTime(), Viewport.GetEndTime(), SecondsPerPixel, Timeline.EnumerateEvents(Viewport.GetStartTime(), Viewport.GetEndTime(), [this, &Builder, TimerReader, &EventFilterPtr](double StartTime, double EndTime, uint32 Depth, const TraceServices::FTimingProfilerEvent& Event) { const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(Event.TimerIndex); if (ensure(Timer != nullptr)) { FThreadTrackEvent TimingEvent(SharedThis(this), StartTime, EndTime, Depth); TimingEvent.SetTimerId(Timer->Id); TimingEvent.SetTimerIndex(Event.TimerIndex); if (EventFilterPtr->FilterEvent(TimingEvent)) { AddTimingEventToBuilder(Builder, StartTime, EndTime, Depth, Event.TimerIndex, Timer); } } return TraceServices::EEventEnumerate::Continue; }); }); } } } if (HasCustomFilter()) // Custom filter (from the filtering widget) { TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session.IsValid() && TraceServices::ReadTimingProfilerProvider(*Session.Get())) { TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); const TraceServices::ITimingProfilerTimerReader* TimerReader; TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); const FTimingTrackViewport& Viewport = Context.GetViewport(); TimingProfilerProvider.ReadTimeline(TimelineIndex, [&Viewport, this, &Builder, TimerReader](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { TArray> FilteredEvents; TArray FilterContexts; TraceServices::ITimeline::EnumerateAsyncParams Params; constexpr uint32 LargeTimelineThreshold = 50 * 1000 * 1000; if (Timeline.GetEventCount() > LargeTimelineThreshold) { if (FilterConfigurator->IsKeyUsed(static_cast(EFilterField::Metadata))) { Params.MaxOccupancy = 0.75f; // This filter can be slow so reduce occupancy to avoid starvation. } } Params.IntervalStart = Viewport.GetStartTime(); Params.IntervalEnd = Viewport.GetEndTime(); // Note: Enumerating events for filtering should not use downsampling. Params.Resolution = 0.0; Params.SetupCallback = [&FilteredEvents, &FilterContexts, this](uint32 NumTasks) { FilteredEvents.AddDefaulted(NumTasks); FilterContexts.AddDefaulted(NumTasks); for (FFilterContext& Context : FilterContexts) { Context.SetReturnValueForUnsetFilters(false); Context.AddFilterData(static_cast(EFilterField::StartTime), 0.0f); Context.AddFilterData(static_cast(EFilterField::EndTime), 0.0f); Context.AddFilterData(static_cast(EFilterField::Duration), 0.0f); Context.AddFilterData(static_cast(EFilterField::TrackName), this->GetName()); Context.AddFilterData(static_cast(EFilterField::TimerId), 0); Context.AddFilterData(static_cast(EFilterField::TimerName), 0); Context.AddFilterData(static_cast(EFilterField::Metadata), 0); } }; Params.EventRangeCallback = [this, &Builder, TimerReader, &FilteredEvents, &FilterContexts](double StartTime, double EndTime, uint32 Depth, const TraceServices::FTimingProfilerEvent& Event, uint32 TaskIndex) { const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(Event.TimerIndex); if (ensure(Timer != nullptr)) { FFilterContext& Context = FilterContexts[TaskIndex]; Context.SetFilterData(static_cast(EFilterField::StartTime), StartTime); Context.SetFilterData(static_cast(EFilterField::EndTime), EndTime); Context.SetFilterData(static_cast(EFilterField::Duration), EndTime - StartTime); // The TimerName filter also translates to the numeric Id for performance reasons. Context.SetFilterData(static_cast(EFilterField::TimerId), Timer->Id); Context.SetFilterData(static_cast(EFilterField::TimerName), Timer->Id); Context.SetFilterData(static_cast(EFilterField::Metadata), Event.TimerIndex); if (FilterConfigurator->ApplyFilters(Context)) { FPendingEventInfo TimelineEvent; TimelineEvent.StartTime = StartTime; TimelineEvent.EndTime = EndTime; TimelineEvent.Depth = Depth; TimelineEvent.TimerIndex = Event.TimerIndex; FilteredEvents[TaskIndex].Add(TimelineEvent); } } return TraceServices::EEventEnumerate::Continue; }; Timeline.EnumerateEventsDownSampledAsync(Params); for (TArray& Array : FilteredEvents) { for (FPendingEventInfo& TimelineEvent : Array) { const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(TimelineEvent.TimerIndex); AddTimingEventToBuilder(Builder, TimelineEvent.StartTime, TimelineEvent.EndTime, TimelineEvent.Depth, TimelineEvent.TimerIndex, Timer); } } }); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FThreadTimingTrackImpl::PostDraw(const ITimingTrackDrawContext& Context) const { for (const TSharedRef& Track : GetChildTracks()) { Track->PostDraw(Context); } const TSharedPtr SelectedEventPtr = Context.GetSelectedEvent(); if (SelectedEventPtr.IsValid() && SelectedEventPtr->CheckTrack(this) && SelectedEventPtr->Is()) { const FThreadTrackEvent& SelectedEvent = SelectedEventPtr->As(); const ITimingViewDrawHelper& Helper = Context.GetHelper(); TSharedPtr Session = FInsightsManager::Get()->GetSession(); check(Session.IsValid()); TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); const TraceServices::ITimingProfilerTimerReader* TimerReader; TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(SelectedEvent.GetTimerIndex()); if (Timer != nullptr) { FString TimerName(Timer->Name); const double SelectedEventDuration = SelectedEvent.GetDuration(); TStringBuilder<1024> StringBuilder; StringBuilder.Appendf(TEXT(" Incl.: %s"), *FormatTimeAuto(SelectedEventDuration, 2)); if (SelectedEventDuration != std::numeric_limits::infinity()) { StringBuilder.Appendf(TEXT(" Excl.: %s"), *FormatTimeAuto(SelectedEvent.GetExclusiveTime(), 2)); } FString StatsText(StringBuilder.ToView()); if (Timer->File) { FString SourceFile = FPaths::GetCleanFilename(FString(Timer->File)); FString SourceFileAndLine = FString::Printf(TEXT("%s (%d)"), *SourceFile, Timer->Line); DrawSelectedEventInfoEx(StatsText, TimerName, SourceFileAndLine, Context.GetViewport(), Context.GetDrawContext(), Helper.GetWhiteBrush(), Helper.GetEventFont()); } else { DrawSelectedEventInfoEx(StatsText, TimerName, FString(), Context.GetViewport(), Context.GetDrawContext(), Helper.GetWhiteBrush(), Helper.GetEventFont()); } } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FThreadTimingTrackImpl::InitTooltip(FTooltipDrawState& InOutTooltip, const ITimingEvent& InTooltipEvent) const { if (!IsChildTrack()) { InOutTooltip.ResetContent(); } if (InTooltipEvent.CheckTrack(this) && InTooltipEvent.Is()) { const FThreadTrackEvent& TooltipEvent = InTooltipEvent.As(); TSharedPtr ParentTimingEvent; TSharedPtr RootTimingEvent; GetParentAndRoot(TooltipEvent, ParentTimingEvent, RootTimingEvent); TSharedPtr Session = FInsightsManager::Get()->GetSession(); check(Session.IsValid()); TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); const TraceServices::ITimingProfilerTimerReader* TimerReader; TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(TooltipEvent.GetTimerIndex()); const TCHAR* TimerName = (Timer != nullptr) ? Timer->Name : TEXT("N/A"); InOutTooltip.AddTitle(TimerName); const double TooltipEventDuration = TooltipEvent.GetDuration(); if (TooltipEvent.GetDepth() > 0 && ParentTimingEvent.IsValid() && ParentTimingEvent->GetDuration() > 0.0 && ParentTimingEvent->GetDuration() != std::numeric_limits::infinity()) { const TraceServices::FTimingProfilerTimer* ParentTimer = TimerReader->GetTimer(ParentTimingEvent->GetTimerIndex()); const TCHAR* ParentTimerName = (ParentTimer != nullptr) ? ParentTimer->Name : TEXT("N/A"); FNumberFormattingOptions FormattingOptions; FormattingOptions.MaximumFractionalDigits = 2; const FString ValueStr = FString::Printf(TEXT("%s %s"), *FText::AsPercent(TooltipEventDuration / ParentTimingEvent->GetDuration(), &FormattingOptions).ToString(), ParentTimerName); InOutTooltip.AddNameValueTextLine(TEXTVIEW("% of Parent:"), ValueStr); } if (TooltipEvent.GetDepth() > 1 && RootTimingEvent.IsValid() && RootTimingEvent->GetDuration() > 0.0 && RootTimingEvent->GetDuration() != std::numeric_limits::infinity()) { const TraceServices::FTimingProfilerTimer* RootTimer = TimerReader->GetTimer(RootTimingEvent->GetTimerIndex()); const TCHAR* RootTimerName = (RootTimer != nullptr) ? RootTimer->Name : TEXT("N/A"); FNumberFormattingOptions FormattingOptions; FormattingOptions.MaximumFractionalDigits = 2; const FString ValueStr = FString::Printf(TEXT("%s %s"), *FText::AsPercent(TooltipEventDuration / RootTimingEvent->GetDuration(), &FormattingOptions).ToString(), RootTimerName); InOutTooltip.AddNameValueTextLine(TEXTVIEW("% of Root:"), ValueStr); } InOutTooltip.AddNameValueTextLine(TEXTVIEW("Inclusive Time:"), FormatTimeAuto(TooltipEventDuration, 2)); if (TooltipEventDuration > 0.0 && TooltipEventDuration != std::numeric_limits::infinity()) { const double ExclusiveTimePercent = TooltipEvent.GetExclusiveTime() / TooltipEventDuration; FNumberFormattingOptions FormattingOptions; FormattingOptions.MaximumFractionalDigits = 2; const FString ExclStr = FString::Printf(TEXT("%s (%s)"), *FormatTimeAuto(TooltipEvent.GetExclusiveTime(), 2), *FText::AsPercent(ExclusiveTimePercent, &FormattingOptions).ToString()); InOutTooltip.AddNameValueTextLine(TEXTVIEW("Exclusive Time:"), ExclStr); } InOutTooltip.AddNameValueTextLine(TEXTVIEW("Depth:"), FString::Printf(TEXT("%d"), TooltipEvent.GetDepth())); const TraceServices::FMetadataSpec* MetadataSpec = nullptr; if (Timer && Timer->HasValidMetadataSpecId()) { MetadataSpec = TimingProfilerProvider.GetMetadataSpec(Timer->MetadataSpecId); } TArrayView Metadata = TimerReader->GetMetadata(TooltipEvent.GetTimerIndex()); if (Metadata.Num() > 0) { AppendMetadataToTooltip(InOutTooltip, MetadataSpec, Metadata); } PostInitTooltip(InOutTooltip, TooltipEvent, *Session.Get(), TimerName); } else { for (const TSharedRef& Track : GetChildTracks()) { Track->InitTooltip(InOutTooltip, InTooltipEvent); } } InOutTooltip.UpdateLayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FThreadTimingTrackImpl::GetParentAndRoot(const FThreadTrackEvent& TimingEvent, TSharedPtr& OutParentTimingEvent, TSharedPtr& OutRootTimingEvent) const { if (TimingEvent.GetDepth() > 0) { TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session.IsValid()) { TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); if (TraceServices::ReadTimingProfilerProvider(*Session.Get())) { const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); TimingProfilerProvider.ReadTimeline(GetTimelineIndex(), [&TimingEvent, &OutParentTimingEvent, &OutRootTimingEvent](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { const double Time = (TimingEvent.GetStartTime() + TimingEvent.GetEndTime()) / 2; TimelineEventInfo EventInfo; bool IsFound = Timeline.GetEventInfo(Time, 0, TimingEvent.GetDepth() - 1, EventInfo); if (IsFound) { CreateFThreadTrackEventFromInfo(EventInfo, TimingEvent.GetTrack(), TimingEvent.GetDepth() - 1, OutParentTimingEvent); } IsFound = Timeline.GetEventInfo(Time, 0, 0, EventInfo); if (IsFound) { CreateFThreadTrackEventFromInfo(EventInfo, TimingEvent.GetTrack(), 0, OutRootTimingEvent); } }); } } } } //////////////////////////////////////////////////////////////////////////////////////////////////// const TSharedPtr FThreadTimingTrackImpl::GetEvent(float InPosX, float InPosY, const FTimingTrackViewport& Viewport) const { TSharedPtr TimingEvent; const FTimingViewLayout& Layout = Viewport.GetLayout(); float TopLaneY = GetPosY() + Layout.TimelineDY; float TrackLanesHeight = GetHeight(); for (const TSharedRef& Track : GetChildTracks()) { float HeaderDY = InPosY - Track->GetPosY(); float TrackHeightWithPadding = Track->GetHeight() + Layout.ChildTimelineDY; if (HeaderDY >= 0.0f && HeaderDY < TrackHeightWithPadding) { return Track->GetEvent(InPosX, InPosY, Viewport); } TopLaneY += TrackHeightWithPadding; TrackLanesHeight -= TrackHeightWithPadding; } const float DY = InPosY - TopLaneY; // If mouse is not above first sub-track or below last sub-track... if (DY >= 0 && DY < TrackLanesHeight) { const int32 Depth = static_cast(DY / (Layout.EventH + Layout.EventDY)); const double SecondsPerPixel = 1.0 / Viewport.GetScaleX(); const double EventTime = Viewport.SlateUnitsToTime(InPosX); TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session.IsValid()) { TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); if (EventTime <= Session->GetDurationSeconds() && TraceServices::ReadTimingProfilerProvider(*Session.Get())) { const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); TimingProfilerProvider.ReadTimeline(GetTimelineIndex(), [this, &EventTime, &Depth, &TimingEvent, &SecondsPerPixel](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { TimelineEventInfo EventInfo; bool IsFound = Timeline.GetEventInfo(EventTime, 2 * SecondsPerPixel, Depth, EventInfo); if (IsFound) { CreateFThreadTrackEventFromInfo(EventInfo, SharedThis(this), Depth, TimingEvent); } }); } } } return TimingEvent; } //////////////////////////////////////////////////////////////////////////////////////////////////// const TSharedPtr FThreadTimingTrackImpl::SearchEvent(const FTimingEventSearchParameters& InSearchParameters) const { TSharedPtr FoundEvent; FindTimingProfilerEvent(InSearchParameters, [this, &FoundEvent](double InFoundStartTime, double InFoundEndTime, uint32 InFoundDepth, const TraceServices::FTimingProfilerEvent& InFoundEvent) { FoundEvent = MakeShared(SharedThis(this), InFoundStartTime, InFoundEndTime, InFoundDepth); FoundEvent->SetTimerIndex(InFoundEvent.TimerIndex); uint32 TimerId = 0; bool ret = FThreadTimingTrackImpl::TimerIndexToTimerId(InFoundEvent.TimerIndex, TimerId); if (ret) { FoundEvent->SetTimerId(TimerId); } }); return FoundEvent; } //////////////////////////////////////////////////////////////////////////////////////////////////// void FThreadTimingTrackImpl::UpdateEventStats(ITimingEvent& InOutEvent) const { if (InOutEvent.CheckTrack(this) && InOutEvent.Is()) { FThreadTrackEvent& TrackEvent = InOutEvent.As(); if (TrackEvent.IsExclusiveTimeComputed()) { return; } TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session.IsValid()) { TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); if (TraceServices::ReadTimingProfilerProvider(*Session.Get())) { const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); // Get Exclusive Time. TimingProfilerProvider.ReadTimeline(GetTimelineIndex(), [&TrackEvent](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { TimelineEventInfo EventInfo; bool bIsFound = Timeline.GetEventInfo(TrackEvent.GetStartTime(), 0.0, TrackEvent.GetDepth(), EventInfo); if (bIsFound) { TrackEvent.SetExclusiveTime(EventInfo.ExclTime); TrackEvent.SetIsExclusiveTimeComputed(true); } }); } } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FThreadTimingTrackImpl::OnEventSelected(const ITimingEvent& InSelectedEvent) const { if (InSelectedEvent.CheckTrack(this) && InSelectedEvent.Is()) { const FThreadTrackEvent& TrackEvent = InSelectedEvent.As(); // Select the timer node corresponding to timing event type of selected timing event. FTimingProfilerManager::Get()->SetSelectedTimer(TrackEvent.GetTimerId()); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FThreadTimingTrackImpl::OnClipboardCopyEvent(const ITimingEvent& InSelectedEvent) const { if (InSelectedEvent.CheckTrack(this) && InSelectedEvent.Is()) { const FThreadTrackEvent& TrackEvent = InSelectedEvent.As(); TSharedPtr Session = FInsightsManager::Get()->GetSession(); check(Session.IsValid()); TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); const TraceServices::ITimingProfilerTimerReader* TimerReader = nullptr; TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); check(TimerReader); const TraceServices::FTimingProfilerTimer* TimerPtr = TimerReader->GetTimer(TrackEvent.GetTimerIndex()); if (TimerPtr) { FString EventName(TimerPtr->Name); FTimingEventsTrackDrawStateBuilder::AppendDurationToEventName(EventName, TrackEvent.GetDuration()); const uint32 TimerIndex = TrackEvent.GetTimerIndex(); if (int32(TimerIndex) < 0) // has metadata? { TArrayView Metadata = TimerReader->GetMetadata(TimerIndex); const TraceServices::FMetadataSpec* MetadataSpec = nullptr; if (TimerPtr && TimerPtr->HasValidMetadataSpecId()) { MetadataSpec = TimingProfilerProvider.GetMetadataSpec(TimerPtr->MetadataSpecId); } if (Metadata.Num() > 0) { if (MetadataSpec) { AppendMetadataToString(EventName, MetadataSpec, Metadata); } else { AppendMetadataToString(EventName, Metadata); } } } // Copy name of selected timing event to clipboard. FPlatformApplicationMisc::ClipboardCopy(*EventName); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FThreadTimingTrackImpl::BuildContextMenu(FMenuBuilder& MenuBuilder) { if (GetGroupName() != nullptr) { MenuBuilder.BeginSection("CpuThread", LOCTEXT("ContextMenu_Section_CpuThread", "CPU Thread")); { MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("CpuThreadGroupFmt", "Group: {0}"), FText::FromString(GetGroupName())), FText(), FSlateIcon(), FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([]() { return false; })), NAME_None, EUserInterfaceActionType::Button ); const FString ThreadIdStr = FString::Printf(TEXT("%s%u (0x%X)"), ThreadId & 0x70000000 ? TEXT("*") : TEXT(""), ThreadId & ~0x70000000, ThreadId); MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("CpuThreadIdFmt", "Thread Id: {0}"), FText::FromString(ThreadIdStr)), FText(), FSlateIcon(), FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([]() { return false; })), NAME_None, EUserInterfaceActionType::Button ); } MenuBuilder.EndSection(); } for (const TSharedRef& Track : GetChildTracks()) { Track->BuildContextMenu(MenuBuilder); } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FThreadTimingTrackImpl::FindTimingProfilerEvent(const FThreadTrackEvent& InTimingEvent, TFunctionRef InFoundPredicate) const { auto MatchEvent = [&InTimingEvent](double InStartTime, double InEndTime, uint32 InDepth) { return InDepth == InTimingEvent.GetDepth() && InStartTime == InTimingEvent.GetStartTime() && InEndTime == InTimingEvent.GetEndTime(); }; const double Time = (InTimingEvent.GetStartTime() + InTimingEvent.GetEndTime()) / 2; FTimingEventSearchParameters SearchParameters(Time, Time, ETimingEventSearchFlags::StopAtFirstMatch, MatchEvent); SearchParameters.SearchHandle = &InTimingEvent.GetSearchHandle(); return FindTimingProfilerEvent(SearchParameters, InFoundPredicate); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FThreadTimingTrackImpl::FindTimingProfilerEvent(const FTimingEventSearchParameters& InParameters, TFunctionRef InFoundPredicate) const { FFilterContext FilterConfiguratorContext; FilterConfiguratorContext.SetReturnValueForUnsetFilters(false); FilterConfiguratorContext.AddFilterData(static_cast(EFilterField::StartTime), 0.0f); FilterConfiguratorContext.AddFilterData(static_cast(EFilterField::EndTime), 0.0f); FilterConfiguratorContext.AddFilterData(static_cast(EFilterField::Duration), 0.0f); FilterConfiguratorContext.AddFilterData(static_cast(EFilterField::TrackName), this->GetName()); FilterConfiguratorContext.AddFilterData(static_cast(EFilterField::TimerId), 0); FilterConfiguratorContext.AddFilterData(static_cast(EFilterField::TimerName), 0); FilterConfiguratorContext.AddFilterData(static_cast(EFilterField::Metadata), 0); return TTimingEventSearch::Search( InParameters, [this](TTimingEventSearch::FContext& InContext) { TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session.IsValid()) { TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); if (TraceServices::ReadTimingProfilerProvider(*Session.Get())) { const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); TimingProfilerProvider.ReadTimeline(GetTimelineIndex(), [&InContext](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { auto Callback = [&InContext](double EventStartTime, double EventEndTime, uint32 EventDepth, const TraceServices::FTimingProfilerEvent& Event) { InContext.Check(EventStartTime, EventEndTime, EventDepth, Event); return InContext.ShouldContinueSearching() ? TraceServices::EEventEnumerate::Continue : TraceServices::EEventEnumerate::Stop; }; if (InContext.GetParameters().SearchDirection == FTimingEventSearchParameters::ESearchDirection::Forward) { Timeline.EnumerateEvents(InContext.GetParameters().StartTime, InContext.GetParameters().EndTime, Callback); } else { Timeline.EnumerateEventsBackwards(InContext.GetParameters().EndTime, InContext.GetParameters().StartTime, Callback); } }); } } }, [&FilterConfiguratorContext, &InParameters](double EventStartTime, double EventEndTime, uint32 EventDepth, const TraceServices::FTimingProfilerEvent& Event) { if (!InParameters.FilterExecutor.IsValid()) { return true; } TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session.IsValid()) { TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); if (TraceServices::ReadTimingProfilerProvider(*Session.Get())) { const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); const TraceServices::ITimingProfilerTimerReader* TimerReader; TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(Event.TimerIndex); if (ensure(Timer != nullptr)) { FilterConfiguratorContext.SetFilterData(static_cast(EFilterField::StartTime), EventStartTime); FilterConfiguratorContext.SetFilterData(static_cast(EFilterField::EndTime), EventEndTime); FilterConfiguratorContext.SetFilterData(static_cast(EFilterField::Duration), EventEndTime - EventStartTime); FilterConfiguratorContext.SetFilterData(static_cast(EFilterField::TimerId), Timer->Id); FilterConfiguratorContext.SetFilterData(static_cast(EFilterField::TimerName), Timer->Id); FilterConfiguratorContext.SetFilterData(static_cast(EFilterField::Metadata), Event.TimerIndex); return InParameters.FilterExecutor->ApplyFilters(FilterConfiguratorContext); } } } return false; }, [&InFoundPredicate](double InFoundStartTime, double InFoundEndTime, uint32 InFoundDepth, const TraceServices::FTimingProfilerEvent& InEvent) { InFoundPredicate(InFoundStartTime, InFoundEndTime, InFoundDepth, InEvent); }, TTimingEventSearch::NoMatch, &SearchCache); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FThreadTimingTrackImpl::CreateFThreadTrackEventFromInfo(const TimelineEventInfo& InEventInfo, const TSharedRef InTrack, int32 InDepth, TSharedPtr &OutTimingEvent) { OutTimingEvent = MakeShared(InTrack, InEventInfo.StartTime, InEventInfo.EndTime, InDepth); FThreadTrackEvent& Event = OutTimingEvent->As(); Event.SetExclusiveTime(InEventInfo.ExclTime); Event.SetIsExclusiveTimeComputed(true); Event.SetTimerIndex(InEventInfo.Event.TimerIndex); uint32 TimerId = 0; bool ret = FThreadTimingTrackImpl::TimerIndexToTimerId(InEventInfo.Event.TimerIndex, TimerId); if (ret) { Event.SetTimerId(TimerId); } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FThreadTimingTrackImpl::TimerIndexToTimerId(uint32 InTimerIndex, uint32& OutTimerId) { TSharedPtr Session = FInsightsManager::Get()->GetSession(); check(Session.IsValid()) TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); const TraceServices::ITimingProfilerTimerReader* TimerReader; TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(InTimerIndex); if (Timer == nullptr) { return false; } OutTimerId = Timer->Id; return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FThreadTimingTrackImpl::HasCustomFilter() const { return FilterConfigurator.IsValid() && !FilterConfigurator->IsEmpty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// int32 FThreadTimingTrackImpl::GetDepthAt(double Time) const { int32 Depth = 0; TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session.IsValid() && TraceServices::ReadTimingProfilerProvider(*Session.Get())) { TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); TimingProfilerProvider.ReadTimeline(TimelineIndex, [Time, &Depth](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { Depth = Timeline.GetDepthAt(Time); }); } return Depth; } //////////////////////////////////////////////////////////////////////////////////////////////////// void FThreadTimingTrackImpl::SetFilterConfigurator(TSharedPtr InFilterConfigurator) { if (FilterConfigurator != InFilterConfigurator) { FilterConfigurator = InFilterConfigurator; SetDirtyFlag(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FThreadTimingTrackImpl::FindMaxEventInstance(uint32 InTimerId, double InStartTime, double InEndTime) const { TSharedPtr Session = FInsightsManager::Get()->GetSession(); check(Session.IsValid()); TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); const TraceServices::ITimingProfilerTimerReader* TimerReader; TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); struct CandidateEvent { double StartTime = 0.0f; double EndTime = -1.0f; uint32 Depth = 0; uint32 TimerIndex = 0; }; TSharedPtr TimingEvent; TimingProfilerProvider.ReadTimeline(TimelineIndex, [TimerReader, InStartTime, InEndTime, InTimerId, &TimingEvent, this](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { TArray CandidateEvents; TraceServices::ITimeline::EnumerateAsyncParams Params; Params.IntervalStart = InStartTime; Params.IntervalEnd = InEndTime; Params.Resolution = 0.0; Params.SetupCallback = [&CandidateEvents](uint32 NumTasks) { CandidateEvents.AddDefaulted(NumTasks); }; Params.EventRangeCallback = [TimerReader, &CandidateEvents, InTimerId](double StartTime, double EndTime, uint32 Depth, const TraceServices::FTimingProfilerEvent& Event, uint32 TaskIndex) { const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(Event.TimerIndex); if (ensure(Timer != nullptr)) { if (Timer->Id == InTimerId) { double CandidateDuration = CandidateEvents[TaskIndex].EndTime - CandidateEvents[TaskIndex].StartTime; double EventDuration = EndTime - StartTime; if (EventDuration > CandidateDuration) { CandidateEvents[TaskIndex].StartTime = StartTime; CandidateEvents[TaskIndex].EndTime = EndTime; CandidateEvents[TaskIndex].Depth = Depth; CandidateEvents[TaskIndex].TimerIndex = Event.TimerIndex; } } } return TraceServices::EEventEnumerate::Continue; }; // Note: Enumerating events for filtering should not use downsampling. Timeline.EnumerateEventsDownSampledAsync(Params); CandidateEvent BestMatch; for (const CandidateEvent& Event : CandidateEvents) { if ((Event.EndTime - Event.StartTime) > BestMatch.EndTime - BestMatch.StartTime) { BestMatch = Event; } } if (BestMatch.EndTime > BestMatch.StartTime) { TimingEvent = MakeShared(SharedThis(this), BestMatch.StartTime, BestMatch.EndTime, BestMatch.Depth); TimingEvent->SetTimerId(InTimerId); TimingEvent->SetTimerIndex(BestMatch.TimerIndex); } }); return TimingEvent; } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FThreadTimingTrackImpl::FindMinEventInstance(uint32 InTimerId, double InStartTime, double InEndTime) const { TSharedPtr Session = FInsightsManager::Get()->GetSession(); check(Session.IsValid()); TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get()); const TraceServices::ITimingProfilerTimerReader* TimerReader; TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); struct CandidateEvent { double StartTime = -std::numeric_limits::infinity(); double EndTime = std::numeric_limits::infinity(); uint32 Depth = 0; uint32 TimerIndex = 0; }; TSharedPtr TimingEvent; TimingProfilerProvider.ReadTimeline(TimelineIndex, [TimerReader, InStartTime, InEndTime, InTimerId, &TimingEvent, this](const TraceServices::ITimingProfilerProvider::Timeline& Timeline) { TArray CandidateEvents; TraceServices::ITimeline::EnumerateAsyncParams Params; Params.IntervalStart = InStartTime; Params.IntervalEnd = InEndTime; Params.Resolution = 0.0; Params.SetupCallback = [&CandidateEvents](uint32 NumTasks) { CandidateEvents.AddDefaulted(NumTasks); }; Params.EventRangeCallback = [TimerReader, &CandidateEvents, InTimerId](double StartTime, double EndTime, uint32 Depth, const TraceServices::FTimingProfilerEvent& Event, uint32 TaskIndex) { const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(Event.TimerIndex); if (ensure(Timer != nullptr)) { if (Timer->Id == InTimerId) { double CandidateDuration = CandidateEvents[TaskIndex].EndTime - CandidateEvents[TaskIndex].StartTime; double EventDuration = EndTime - StartTime; if (EventDuration < CandidateDuration) { CandidateEvents[TaskIndex].StartTime = StartTime; CandidateEvents[TaskIndex].EndTime = EndTime; CandidateEvents[TaskIndex].Depth = Depth; CandidateEvents[TaskIndex].TimerIndex = Event.TimerIndex; } } } return TraceServices::EEventEnumerate::Continue; }; // Note: Enumerating events for filtering should not use downsampling. Timeline.EnumerateEventsDownSampledAsync(Params); CandidateEvent BestMatch; for (const CandidateEvent& Event : CandidateEvents) { if ((Event.EndTime - Event.StartTime) < BestMatch.EndTime - BestMatch.StartTime) { BestMatch = Event; } } if (BestMatch.StartTime != -std::numeric_limits::infinity()) { TimingEvent = MakeShared(SharedThis(this), BestMatch.StartTime, BestMatch.EndTime, BestMatch.Depth); TimingEvent->SetTimerId(InTimerId); TimingEvent->SetTimerIndex(BestMatch.TimerIndex); } }); return TimingEvent; } //////////////////////////////////////////////////////////////////////////////////////////////////// } // namespace UE::Insights::TimingProfiler #undef LOCTEXT_NAMESPACE