1571 lines
56 KiB
C++
1571 lines
56 KiB
C++
// 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<const uint8>& 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<int32>(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<int32>(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<const uint8>& 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<int32>(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<const uint8>& 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<TCHAR const(&)[1]>(**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<float>(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<const TraceServices::IAnalysisSession> 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<const uint8> 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<const TraceServices::IAnalysisSession> 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<ITimingEventFilter> 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<FTimingEventFilterByEventType>())
|
|
{
|
|
bFilterOnlyByEventType = true;
|
|
const FTimingEventFilterByEventType& EventFilter = EventFilterPtr->As<FTimingEventFilterByEventType>();
|
|
FilterEventType = EventFilter.GetEventType();
|
|
}
|
|
|
|
TSharedPtr<const TraceServices::IAnalysisSession> 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<TArray<FPendingEventInfo>> FilteredEvents;
|
|
|
|
TraceServices::ITimeline<TraceServices::FTimingProfilerEvent>::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<FPendingEventInfo>& 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<const TraceServices::IAnalysisSession> 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<TArray<FPendingEventInfo>> FilteredEvents;
|
|
TArray<FFilterContext> FilterContexts;
|
|
|
|
TraceServices::ITimeline<TraceServices::FTimingProfilerEvent>::EnumerateAsyncParams Params;
|
|
constexpr uint32 LargeTimelineThreshold = 50 * 1000 * 1000;
|
|
if (Timeline.GetEventCount() > LargeTimelineThreshold)
|
|
{
|
|
if (FilterConfigurator->IsKeyUsed(static_cast<int32>(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<double>(static_cast<int32>(EFilterField::StartTime), 0.0f);
|
|
Context.AddFilterData<double>(static_cast<int32>(EFilterField::EndTime), 0.0f);
|
|
Context.AddFilterData<double>(static_cast<int32>(EFilterField::Duration), 0.0f);
|
|
Context.AddFilterData<FString>(static_cast<int32>(EFilterField::TrackName), this->GetName());
|
|
Context.AddFilterData<int64>(static_cast<int32>(EFilterField::TimerId), 0);
|
|
Context.AddFilterData<int64>(static_cast<int32>(EFilterField::TimerName), 0);
|
|
Context.AddFilterData<int64>(static_cast<int32>(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<double>(static_cast<int32>(EFilterField::StartTime), StartTime);
|
|
Context.SetFilterData<double>(static_cast<int32>(EFilterField::EndTime), EndTime);
|
|
Context.SetFilterData<double>(static_cast<int32>(EFilterField::Duration), EndTime - StartTime);
|
|
// The TimerName filter also translates to the numeric Id for performance reasons.
|
|
Context.SetFilterData<int64>(static_cast<int32>(EFilterField::TimerId), Timer->Id);
|
|
Context.SetFilterData<int64>(static_cast<int32>(EFilterField::TimerName), Timer->Id);
|
|
Context.SetFilterData<int64>(static_cast<int32>(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<FPendingEventInfo>& 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<FBaseTimingTrack>& Track : GetChildTracks())
|
|
{
|
|
Track->PostDraw(Context);
|
|
}
|
|
|
|
const TSharedPtr<const ITimingEvent> SelectedEventPtr = Context.GetSelectedEvent();
|
|
if (SelectedEventPtr.IsValid() &&
|
|
SelectedEventPtr->CheckTrack(this) &&
|
|
SelectedEventPtr->Is<FThreadTrackEvent>())
|
|
{
|
|
const FThreadTrackEvent& SelectedEvent = SelectedEventPtr->As<FThreadTrackEvent>();
|
|
const ITimingViewDrawHelper& Helper = Context.GetHelper();
|
|
|
|
TSharedPtr<const TraceServices::IAnalysisSession> 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<double>::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<FThreadTrackEvent>())
|
|
{
|
|
const FThreadTrackEvent& TooltipEvent = InTooltipEvent.As<FThreadTrackEvent>();
|
|
|
|
TSharedPtr<FThreadTrackEvent> ParentTimingEvent;
|
|
TSharedPtr<FThreadTrackEvent> RootTimingEvent;
|
|
GetParentAndRoot(TooltipEvent, ParentTimingEvent, RootTimingEvent);
|
|
|
|
TSharedPtr<const TraceServices::IAnalysisSession> 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<double>::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<double>::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<double>::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<const uint8> Metadata = TimerReader->GetMetadata(TooltipEvent.GetTimerIndex());
|
|
if (Metadata.Num() > 0)
|
|
{
|
|
AppendMetadataToTooltip(InOutTooltip, MetadataSpec, Metadata);
|
|
}
|
|
|
|
PostInitTooltip(InOutTooltip, TooltipEvent, *Session.Get(), TimerName);
|
|
}
|
|
else
|
|
{
|
|
for (const TSharedRef<FBaseTimingTrack>& Track : GetChildTracks())
|
|
{
|
|
Track->InitTooltip(InOutTooltip, InTooltipEvent);
|
|
}
|
|
}
|
|
|
|
InOutTooltip.UpdateLayout();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingTrackImpl::GetParentAndRoot(const FThreadTrackEvent& TimingEvent, TSharedPtr<FThreadTrackEvent>& OutParentTimingEvent, TSharedPtr<FThreadTrackEvent>& OutRootTimingEvent) const
|
|
{
|
|
if (TimingEvent.GetDepth() > 0)
|
|
{
|
|
TSharedPtr<const TraceServices::IAnalysisSession> 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<const ITimingEvent> FThreadTimingTrackImpl::GetEvent(float InPosX, float InPosY, const FTimingTrackViewport& Viewport) const
|
|
{
|
|
TSharedPtr<FThreadTrackEvent> TimingEvent;
|
|
|
|
const FTimingViewLayout& Layout = Viewport.GetLayout();
|
|
|
|
float TopLaneY = GetPosY() + Layout.TimelineDY;
|
|
float TrackLanesHeight = GetHeight();
|
|
|
|
for (const TSharedRef<FBaseTimingTrack>& 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<int32>(DY / (Layout.EventH + Layout.EventDY));
|
|
|
|
const double SecondsPerPixel = 1.0 / Viewport.GetScaleX();
|
|
|
|
const double EventTime = Viewport.SlateUnitsToTime(InPosX);
|
|
|
|
TSharedPtr<const TraceServices::IAnalysisSession> 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<const ITimingEvent> FThreadTimingTrackImpl::SearchEvent(const FTimingEventSearchParameters& InSearchParameters) const
|
|
{
|
|
TSharedPtr<FThreadTrackEvent> FoundEvent;
|
|
FindTimingProfilerEvent(InSearchParameters, [this, &FoundEvent](double InFoundStartTime, double InFoundEndTime, uint32 InFoundDepth, const TraceServices::FTimingProfilerEvent& InFoundEvent)
|
|
{
|
|
FoundEvent = MakeShared<FThreadTrackEvent>(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>())
|
|
{
|
|
FThreadTrackEvent& TrackEvent = InOutEvent.As<FThreadTrackEvent>();
|
|
if (TrackEvent.IsExclusiveTimeComputed())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedPtr<const TraceServices::IAnalysisSession> 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<FThreadTrackEvent>())
|
|
{
|
|
const FThreadTrackEvent& TrackEvent = InSelectedEvent.As<FThreadTrackEvent>();
|
|
|
|
// 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<FThreadTrackEvent>())
|
|
{
|
|
const FThreadTrackEvent& TrackEvent = InSelectedEvent.As<FThreadTrackEvent>();
|
|
|
|
TSharedPtr<const TraceServices::IAnalysisSession> 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<const uint8> 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<FBaseTimingTrack>& Track : GetChildTracks())
|
|
{
|
|
Track->BuildContextMenu(MenuBuilder);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool FThreadTimingTrackImpl::FindTimingProfilerEvent(const FThreadTrackEvent& InTimingEvent, TFunctionRef<void(double, double, uint32, const TraceServices::FTimingProfilerEvent&)> 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<void(double, double, uint32, const TraceServices::FTimingProfilerEvent&)> InFoundPredicate) const
|
|
{
|
|
FFilterContext FilterConfiguratorContext;
|
|
FilterConfiguratorContext.SetReturnValueForUnsetFilters(false);
|
|
FilterConfiguratorContext.AddFilterData<double>(static_cast<int32>(EFilterField::StartTime), 0.0f);
|
|
FilterConfiguratorContext.AddFilterData<double>(static_cast<int32>(EFilterField::EndTime), 0.0f);
|
|
FilterConfiguratorContext.AddFilterData<double>(static_cast<int32>(EFilterField::Duration), 0.0f);
|
|
FilterConfiguratorContext.AddFilterData<FString>(static_cast<int32>(EFilterField::TrackName), this->GetName());
|
|
FilterConfiguratorContext.AddFilterData<int64>(static_cast<int32>(EFilterField::TimerId), 0);
|
|
FilterConfiguratorContext.AddFilterData<int64>(static_cast<int32>(EFilterField::TimerName), 0);
|
|
FilterConfiguratorContext.AddFilterData<int64>(static_cast<int32>(EFilterField::Metadata), 0);
|
|
|
|
return TTimingEventSearch<TraceServices::FTimingProfilerEvent>::Search(
|
|
InParameters,
|
|
|
|
[this](TTimingEventSearch<TraceServices::FTimingProfilerEvent>::FContext& InContext)
|
|
{
|
|
TSharedPtr<const TraceServices::IAnalysisSession> 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<const TraceServices::IAnalysisSession> 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<double>(static_cast<int32>(EFilterField::StartTime), EventStartTime);
|
|
FilterConfiguratorContext.SetFilterData<double>(static_cast<int32>(EFilterField::EndTime), EventEndTime);
|
|
FilterConfiguratorContext.SetFilterData<double>(static_cast<int32>(EFilterField::Duration), EventEndTime - EventStartTime);
|
|
|
|
FilterConfiguratorContext.SetFilterData<int64>(static_cast<int32>(EFilterField::TimerId), Timer->Id);
|
|
FilterConfiguratorContext.SetFilterData<int64>(static_cast<int32>(EFilterField::TimerName), Timer->Id);
|
|
|
|
FilterConfiguratorContext.SetFilterData<int64>(static_cast<int32>(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<TraceServices::FTimingProfilerEvent>::NoMatch,
|
|
|
|
&SearchCache);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingTrackImpl::CreateFThreadTrackEventFromInfo(const TimelineEventInfo& InEventInfo, const TSharedRef<const FBaseTimingTrack> InTrack, int32 InDepth, TSharedPtr<FThreadTrackEvent> &OutTimingEvent)
|
|
{
|
|
OutTimingEvent = MakeShared<FThreadTrackEvent>(InTrack, InEventInfo.StartTime, InEventInfo.EndTime, InDepth);
|
|
FThreadTrackEvent& Event = OutTimingEvent->As<FThreadTrackEvent>();
|
|
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<const TraceServices::IAnalysisSession> 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<const TraceServices::IAnalysisSession> 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<FFilterConfigurator> InFilterConfigurator)
|
|
{
|
|
if (FilterConfigurator != InFilterConfigurator)
|
|
{
|
|
FilterConfigurator = InFilterConfigurator;
|
|
SetDirtyFlag();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedPtr<const ITimingEvent> FThreadTimingTrackImpl::FindMaxEventInstance(uint32 InTimerId, double InStartTime, double InEndTime) const
|
|
{
|
|
TSharedPtr<const TraceServices::IAnalysisSession> 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<FThreadTrackEvent> TimingEvent;
|
|
|
|
TimingProfilerProvider.ReadTimeline(TimelineIndex,
|
|
[TimerReader, InStartTime, InEndTime, InTimerId, &TimingEvent, this](const TraceServices::ITimingProfilerProvider::Timeline& Timeline)
|
|
{
|
|
TArray<CandidateEvent> CandidateEvents;
|
|
|
|
TraceServices::ITimeline<TraceServices::FTimingProfilerEvent>::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<FThreadTrackEvent>(SharedThis(this), BestMatch.StartTime, BestMatch.EndTime, BestMatch.Depth);
|
|
TimingEvent->SetTimerId(InTimerId);
|
|
TimingEvent->SetTimerIndex(BestMatch.TimerIndex);
|
|
}
|
|
});
|
|
|
|
return TimingEvent;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedPtr<const ITimingEvent> FThreadTimingTrackImpl::FindMinEventInstance(uint32 InTimerId, double InStartTime, double InEndTime) const
|
|
{
|
|
TSharedPtr<const TraceServices::IAnalysisSession> 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<double>::infinity();
|
|
double EndTime = std::numeric_limits<double>::infinity();
|
|
uint32 Depth = 0;
|
|
uint32 TimerIndex = 0;
|
|
};
|
|
|
|
TSharedPtr<FThreadTrackEvent> TimingEvent;
|
|
|
|
TimingProfilerProvider.ReadTimeline(TimelineIndex,
|
|
[TimerReader, InStartTime, InEndTime, InTimerId, &TimingEvent, this](const TraceServices::ITimingProfilerProvider::Timeline& Timeline)
|
|
{
|
|
TArray<CandidateEvent> CandidateEvents;
|
|
|
|
TraceServices::ITimeline<TraceServices::FTimingProfilerEvent>::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<double>::infinity())
|
|
{
|
|
TimingEvent = MakeShared<FThreadTrackEvent>(SharedThis(this), BestMatch.StartTime, BestMatch.EndTime, BestMatch.Depth);
|
|
TimingEvent->SetTimerId(InTimerId);
|
|
TimingEvent->SetTimerIndex(BestMatch.TimerIndex);
|
|
}
|
|
});
|
|
|
|
return TimingEvent;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
} // namespace UE::Insights::TimingProfiler
|
|
|
|
#undef LOCTEXT_NAMESPACE
|