1257 lines
64 KiB
C++
1257 lines
64 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ThreadTimingTrack.h"
|
|
|
|
#include "CborReader.h"
|
|
#include "Fonts/FontMeasure.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
#include "Serialization/MemoryReader.h"
|
|
#include "Styling/SlateBrush.h"
|
|
|
|
// Insights
|
|
#include "Insights/Common/PaintUtils.h"
|
|
#include "Insights/Common/TimeUtils.h"
|
|
#include "Insights/InsightsManager.h"
|
|
#include "Insights/ITimingViewSession.h"
|
|
#include "Insights/TimingProfilerManager.h"
|
|
#include "Insights/ViewModels/TimerNode.h"
|
|
#include "Insights/ViewModels/ThreadTrackEvent.h"
|
|
#include "Insights/ViewModels/TimingEventSearch.h"
|
|
#include "Insights/ViewModels/TimingTrackViewport.h"
|
|
#include "Insights/ViewModels/TimingViewDrawHelper.h"
|
|
#include "Insights/ViewModels/TooltipDrawState.h"
|
|
#include "Insights/Widgets/STimingView.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "ThreadTimingTrack"
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void AppendMetadataToTooltip(FTooltipDrawState& Tooltip, TArrayView<const uint8>& Metadata)
|
|
{
|
|
FMemoryReaderView MemoryReader(Metadata);
|
|
FCborReader CborReader(&MemoryReader, ECborEndianness::StandardCompliant);
|
|
FCborContext Context;
|
|
|
|
if (!CborReader.ReadNext(Context) || Context.MajorType() != ECborCode::Map)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Tooltip.AddTitle(TEXT("Metadata:"));
|
|
|
|
while (true)
|
|
{
|
|
// Read key
|
|
if (!CborReader.ReadNext(Context) || !Context.IsString())
|
|
{
|
|
break;
|
|
}
|
|
|
|
FString Key(Context.AsLength(), Context.AsCString());
|
|
Key += TEXT(":");
|
|
|
|
if (!CborReader.ReadNext(Context))
|
|
{
|
|
break;
|
|
}
|
|
|
|
switch (Context.MajorType())
|
|
{
|
|
case ECborCode::Int:
|
|
case ECborCode::Uint: {
|
|
uint64 Value = Context.AsUInt();
|
|
FString 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: {
|
|
FAnsiStringView Value(Context.AsCString(), Context.AsLength());
|
|
FString ValueStr(Value);
|
|
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.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();
|
|
Str += FString::Printf(TEXT("%llu"), Value);
|
|
continue;
|
|
}
|
|
|
|
case ECborCode::TextString: {
|
|
Str += Context.AsString();
|
|
continue;
|
|
}
|
|
|
|
case ECborCode::ByteString: {
|
|
Str.AppendChars(Context.AsCString(), 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.IsFiniteContainer())
|
|
{
|
|
CborReader.SkipContainer(ECborCode::Array);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void AddTimingEventToBuilder(ITimingEventsTrackDrawStateBuilder& Builder,
|
|
double EventStartTime, double EventEndTime, uint32 EventDepth,
|
|
uint32 TimerIndex, const Trace::FTimingProfilerTimer* Timer)
|
|
{
|
|
// const uint32 EventColor = FTimingEvent::ComputeEventColor(Timer->Id);
|
|
const uint32 EventColor = FTimingEvent::ComputeEventColor(Timer->Name);
|
|
|
|
Builder.AddEvent(EventStartTime, EventEndTime, EventDepth, EventColor,
|
|
[TimerIndex, Timer, EventStartTime, EventEndTime](float Width)
|
|
{
|
|
FString EventName = Timer->Name;
|
|
|
|
if (Width > EventName.Len() * 4.0f + 32.0f)
|
|
{
|
|
// 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 Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
check(Session.IsValid());
|
|
|
|
// Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerTimerReader* TimerReader;
|
|
TimingProfilerProvider.ReadTimers([&TimerReader](const Trace::ITimingProfilerTimerReader& Out)
|
|
{
|
|
TimerReader = &Out;
|
|
});
|
|
|
|
TArrayView<const uint8> Metadata = TimerReader->GetMetadata(TimerIndex);
|
|
if (Metadata.Num() > 0)
|
|
{
|
|
AppendMetadataToString(EventName, Metadata);
|
|
}
|
|
}
|
|
}
|
|
|
|
return EventName;
|
|
});
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// FThreadTimingSharedState
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedPtr<FCpuTimingTrack> FThreadTimingSharedState::GetCpuTrack(uint32 InThreadId)
|
|
{
|
|
TSharedPtr<FCpuTimingTrack>* const TrackPtrPtr = CpuTracks.Find(InThreadId);
|
|
return TrackPtrPtr ? *TrackPtrPtr : nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool FThreadTimingSharedState::IsGpuTrackVisible() const
|
|
{
|
|
return (GpuTrack != nullptr && GpuTrack->IsVisible()) || (Gpu2Track != nullptr && Gpu2Track->IsVisible());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool FThreadTimingSharedState::IsCpuTrackVisible(uint32 InThreadId) const
|
|
{
|
|
const TSharedPtr<FCpuTimingTrack>* const TrackPtrPtr = CpuTracks.Find(InThreadId);
|
|
return TrackPtrPtr && (*TrackPtrPtr)->IsVisible();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingSharedState::GetVisibleCpuThreads(TSet<uint32>& OutSet) const
|
|
{
|
|
OutSet.Reset();
|
|
for (const auto& KV: CpuTracks)
|
|
{
|
|
const FCpuTimingTrack& Track = *KV.Value;
|
|
if (Track.IsVisible())
|
|
{
|
|
OutSet.Add(KV.Key);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingSharedState::OnBeginSession(Insights::ITimingViewSession& InSession)
|
|
{
|
|
if (&InSession != TimingView)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (TimingView && TimingView->IsAssetLoadingModeEnabled())
|
|
{
|
|
bShowHideAllGpuTracks = false;
|
|
bShowHideAllCpuTracks = false;
|
|
}
|
|
else
|
|
{
|
|
bShowHideAllGpuTracks = true;
|
|
bShowHideAllCpuTracks = true;
|
|
}
|
|
|
|
GpuTrack = nullptr;
|
|
Gpu2Track = nullptr;
|
|
CpuTracks.Reset();
|
|
ThreadGroups.Reset();
|
|
|
|
TimingProfilerTimelineCount = 0;
|
|
LoadTimeProfilerTimelineCount = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingSharedState::OnEndSession(Insights::ITimingViewSession& InSession)
|
|
{
|
|
if (&InSession != TimingView)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bShowHideAllGpuTracks = false;
|
|
bShowHideAllCpuTracks = false;
|
|
|
|
GpuTrack = nullptr;
|
|
Gpu2Track = nullptr;
|
|
CpuTracks.Reset();
|
|
ThreadGroups.Reset();
|
|
|
|
TimingProfilerTimelineCount = 0;
|
|
LoadTimeProfilerTimelineCount = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingSharedState::Tick(Insights::ITimingViewSession& InSession, const Trace::IAnalysisSession& InAnalysisSession)
|
|
{
|
|
if (&InSession != TimingView)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const Trace::ITimingProfilerProvider* TimingProfilerProvider = Trace::ReadTimingProfilerProvider(InAnalysisSession);
|
|
const Trace::ILoadTimeProfilerProvider* LoadTimeProfilerProvider = Trace::ReadLoadTimeProfilerProvider(InAnalysisSession);
|
|
|
|
if (TimingProfilerProvider)
|
|
{
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(InAnalysisSession);
|
|
|
|
const uint64 CurrentTimingProfilerTimelineCount = TimingProfilerProvider->GetTimelineCount();
|
|
const uint64 CurrentLoadTimeProfilerTimelineCount = (LoadTimeProfilerProvider) ? LoadTimeProfilerProvider->GetTimelineCount() : 0;
|
|
|
|
if (CurrentTimingProfilerTimelineCount != TimingProfilerTimelineCount ||
|
|
CurrentLoadTimeProfilerTimelineCount != LoadTimeProfilerTimelineCount)
|
|
{
|
|
TimingProfilerTimelineCount = CurrentTimingProfilerTimelineCount;
|
|
LoadTimeProfilerTimelineCount = CurrentLoadTimeProfilerTimelineCount;
|
|
|
|
// Check if we have a GPU track.
|
|
if (!GpuTrack.IsValid())
|
|
{
|
|
uint32 GpuTimelineIndex;
|
|
if (TimingProfilerProvider->GetGpuTimelineIndex(GpuTimelineIndex))
|
|
{
|
|
GpuTrack = MakeShared<FGpuTimingTrack>(*this, TEXT("GPU"), nullptr, GpuTimelineIndex, 0);
|
|
GpuTrack->SetOrder(FTimingTrackOrder::Gpu);
|
|
GpuTrack->SetVisibilityFlag(bShowHideAllGpuTracks);
|
|
InSession.AddScrollableTrack(GpuTrack);
|
|
}
|
|
}
|
|
if (!Gpu2Track.IsValid())
|
|
{
|
|
uint32 GpuTimelineIndex;
|
|
if (TimingProfilerProvider->GetGpu2TimelineIndex(GpuTimelineIndex))
|
|
{
|
|
Gpu2Track = MakeShared<FGpuTimingTrack>(*this, TEXT("GPU2"), nullptr, GpuTimelineIndex, 0);
|
|
Gpu2Track->SetOrder(FTimingTrackOrder::Gpu);
|
|
Gpu2Track->SetVisibilityFlag(bShowHideAllGpuTracks);
|
|
InSession.AddScrollableTrack(Gpu2Track);
|
|
}
|
|
}
|
|
|
|
bool bTracksOrderChanged = false;
|
|
int32 Order = FTimingTrackOrder::Cpu;
|
|
|
|
// Iterate through threads.
|
|
const Trace::IThreadProvider& ThreadProvider = Trace::ReadThreadProvider(InAnalysisSession);
|
|
ThreadProvider.EnumerateThreads([this, &InSession, &bTracksOrderChanged, &Order, TimingProfilerProvider, LoadTimeProfilerProvider](const Trace::FThreadInfo& ThreadInfo)
|
|
{
|
|
// Check if this thread is part of a group?
|
|
bool bIsGroupVisible = bShowHideAllCpuTracks;
|
|
const TCHAR* const GroupName = ThreadInfo.GroupName ? ThreadInfo.GroupName : ThreadInfo.Name;
|
|
if (GroupName != nullptr)
|
|
{
|
|
if (!ThreadGroups.Contains(GroupName))
|
|
{
|
|
// UE_LOG(TimingProfiler, Log, TEXT("New CPU Thread Group (%d) : \"%s\""), ThreadGroups.Num() + 1, GroupName);
|
|
ThreadGroups.Add(GroupName, {GroupName, bIsGroupVisible, 0, Order});
|
|
}
|
|
else
|
|
{
|
|
FThreadGroup& ThreadGroup = ThreadGroups[GroupName];
|
|
bIsGroupVisible = ThreadGroup.bIsVisible;
|
|
ThreadGroup.Order = Order;
|
|
}
|
|
}
|
|
|
|
// Check if there is an available Asset Loading track for this thread.
|
|
bool bIsLoadingThread = false;
|
|
uint32 LoadingTimelineIndex;
|
|
if (LoadTimeProfilerProvider && LoadTimeProfilerProvider->GetCpuThreadTimelineIndex(ThreadInfo.Id, LoadingTimelineIndex))
|
|
{
|
|
bIsLoadingThread = true;
|
|
}
|
|
|
|
// Check if there is an available CPU track for this thread.
|
|
uint32 CpuTimelineIndex;
|
|
if (TimingProfilerProvider->GetCpuThreadTimelineIndex(ThreadInfo.Id, CpuTimelineIndex))
|
|
{
|
|
TSharedPtr<FCpuTimingTrack>* TrackPtrPtr = CpuTracks.Find(ThreadInfo.Id);
|
|
if (TrackPtrPtr == nullptr)
|
|
{
|
|
FString TrackName(ThreadInfo.Name && *ThreadInfo.Name ? ThreadInfo.Name : FString::Printf(TEXT("Thread %u"), ThreadInfo.Id));
|
|
|
|
// Create new Timing Events track for the CPU thread.
|
|
TSharedPtr<FCpuTimingTrack> Track = MakeShared<FCpuTimingTrack>(*this, TrackName, GroupName, CpuTimelineIndex, ThreadInfo.Id);
|
|
Track->SetOrder(Order);
|
|
CpuTracks.Add(ThreadInfo.Id, Track);
|
|
|
|
FThreadGroup& ThreadGroup = ThreadGroups[GroupName];
|
|
ThreadGroup.NumTimelines++;
|
|
|
|
if (TimingView && TimingView->IsAssetLoadingModeEnabled() && bIsLoadingThread)
|
|
{
|
|
Track->SetVisibilityFlag(true);
|
|
ThreadGroup.bIsVisible = true;
|
|
}
|
|
else
|
|
{
|
|
Track->SetVisibilityFlag(bIsGroupVisible);
|
|
}
|
|
|
|
InSession.AddScrollableTrack(Track);
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<FCpuTimingTrack> Track = *TrackPtrPtr;
|
|
if (Track->GetOrder() != Order)
|
|
{
|
|
Track->SetOrder(Order);
|
|
bTracksOrderChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
constexpr int32 OrderIncrement = FTimingTrackOrder::GroupRange / 1000; // distribute max 1000 tracks in the order group range
|
|
static_assert(OrderIncrement >= 1, "Order group range too small");
|
|
Order += OrderIncrement;
|
|
});
|
|
|
|
if (bTracksOrderChanged)
|
|
{
|
|
InSession.InvalidateScrollableTracksOrder();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingSharedState::ExtendFilterMenu(Insights::ITimingViewSession& InSession, FMenuBuilder& InOutMenuBuilder)
|
|
{
|
|
if (&InSession != TimingView)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InOutMenuBuilder.BeginSection("ThreadProfiler", LOCTEXT("ThreadProfilerHeading", "Threads"));
|
|
{
|
|
// TODO: MenuBuilder.AddMenuEntry(Commands.ShowAllGpuTracks);
|
|
InOutMenuBuilder.AddMenuEntry(
|
|
LOCTEXT("ShowAllGpuTracks", "GPU Track - Y"),
|
|
LOCTEXT("ShowAllGpuTracks_Tooltip", "Show/hide the GPU track"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &FThreadTimingSharedState::ShowHideAllGpuTracks),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FThreadTimingSharedState::IsAllGpuTracksToggleOn)),
|
|
NAME_None, //"QuickFilterSeparator",
|
|
EUserInterfaceActionType::ToggleButton);
|
|
|
|
// TODO: MenuBuilder.AddMenuEntry(Commands.ShowAllCpuTracks);
|
|
InOutMenuBuilder.AddMenuEntry(
|
|
LOCTEXT("ShowAllCpuTracks", "CPU Thread Tracks - U"),
|
|
LOCTEXT("ShowAllCpuTracks_Tooltip", "Show/hide all CPU tracks (and all CPU thread groups)"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &FThreadTimingSharedState::ShowHideAllCpuTracks),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FThreadTimingSharedState::IsAllCpuTracksToggleOn)),
|
|
NAME_None, //"QuickFilterSeparator",
|
|
EUserInterfaceActionType::ToggleButton);
|
|
}
|
|
InOutMenuBuilder.EndSection();
|
|
|
|
InOutMenuBuilder.BeginSection("ThreadGroups", LOCTEXT("ThreadGroupsHeading", "CPU Thread Groups"));
|
|
CreateThreadGroupsMenu(InOutMenuBuilder);
|
|
InOutMenuBuilder.EndSection();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingSharedState::CreateThreadGroupsMenu(FMenuBuilder& InOutMenuBuilder)
|
|
{
|
|
// Sort the list of thread groups.
|
|
TArray<const FThreadGroup*> SortedThreadGroups;
|
|
SortedThreadGroups.Reserve(ThreadGroups.Num());
|
|
for (const auto& KV: ThreadGroups)
|
|
{
|
|
SortedThreadGroups.Add(&KV.Value);
|
|
}
|
|
Algo::SortBy(SortedThreadGroups, &FThreadGroup::GetOrder);
|
|
|
|
for (const FThreadGroup* ThreadGroupPtr: SortedThreadGroups)
|
|
{
|
|
const FThreadGroup& ThreadGroup = *ThreadGroupPtr;
|
|
if (ThreadGroup.NumTimelines > 0)
|
|
{
|
|
InOutMenuBuilder.AddMenuEntry(
|
|
// FText::FromString(ThreadGroup.Name),
|
|
FText::Format(LOCTEXT("ThreadGroupFmt", "{0} ({1})"), FText::FromString(ThreadGroup.Name), ThreadGroup.NumTimelines),
|
|
TAttribute<FText>(), // no tooltip
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &FThreadTimingSharedState::ToggleTrackVisibilityByGroup_Execute, ThreadGroup.Name),
|
|
FCanExecuteAction::CreateLambda([]
|
|
{
|
|
return true;
|
|
}),
|
|
FIsActionChecked::CreateSP(this, &FThreadTimingSharedState::ToggleTrackVisibilityByGroup_IsChecked, ThreadGroup.Name)),
|
|
NAME_None, EUserInterfaceActionType::ToggleButton);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingSharedState::SetAllCpuTracksToggle(bool bOnOff)
|
|
{
|
|
bShowHideAllCpuTracks = bOnOff;
|
|
|
|
for (const auto& KV: CpuTracks)
|
|
{
|
|
FCpuTimingTrack& Track = *KV.Value;
|
|
Track.SetVisibilityFlag(bShowHideAllCpuTracks);
|
|
}
|
|
|
|
for (auto& KV: ThreadGroups)
|
|
{
|
|
KV.Value.bIsVisible = bShowHideAllCpuTracks;
|
|
}
|
|
|
|
if (TimingView)
|
|
{
|
|
TimingView->OnTrackVisibilityChanged();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingSharedState::SetAllGpuTracksToggle(bool bOnOff)
|
|
{
|
|
bShowHideAllGpuTracks = bOnOff;
|
|
|
|
if (GpuTrack.IsValid())
|
|
{
|
|
GpuTrack->SetVisibilityFlag(bShowHideAllGpuTracks);
|
|
}
|
|
if (Gpu2Track.IsValid())
|
|
{
|
|
Gpu2Track->SetVisibilityFlag(bShowHideAllGpuTracks);
|
|
}
|
|
if (GpuTrack.IsValid() || Gpu2Track.IsValid())
|
|
{
|
|
if (TimingView)
|
|
{
|
|
TimingView->OnTrackVisibilityChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool FThreadTimingSharedState::ToggleTrackVisibilityByGroup_IsChecked(const TCHAR* InGroupName) const
|
|
{
|
|
if (ThreadGroups.Contains(InGroupName))
|
|
{
|
|
const FThreadGroup& ThreadGroup = ThreadGroups[InGroupName];
|
|
return ThreadGroup.bIsVisible;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingSharedState::ToggleTrackVisibilityByGroup_Execute(const TCHAR* InGroupName)
|
|
{
|
|
if (ThreadGroups.Contains(InGroupName))
|
|
{
|
|
FThreadGroup& ThreadGroup = ThreadGroups[InGroupName];
|
|
ThreadGroup.bIsVisible = !ThreadGroup.bIsVisible;
|
|
|
|
for (const auto& KV: CpuTracks)
|
|
{
|
|
FCpuTimingTrack& Track = *KV.Value;
|
|
if (Track.GetGroupName() == InGroupName)
|
|
{
|
|
Track.SetVisibilityFlag(ThreadGroup.bIsVisible);
|
|
}
|
|
}
|
|
|
|
if (TimingView)
|
|
{
|
|
TimingView->OnTrackVisibilityChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// FThreadTimingTrack
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
INSIGHTS_IMPLEMENT_RTTI(FThreadTimingTrack)
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingTrack::BuildDrawState(ITimingEventsTrackDrawStateBuilder& Builder, const ITimingTrackUpdateContext& Context)
|
|
{
|
|
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session.IsValid() && Trace::ReadTimingProfilerProvider(*Session.Get()))
|
|
{
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerTimerReader* TimerReader;
|
|
TimingProfilerProvider.ReadTimers([&TimerReader](const Trace::ITimingProfilerTimerReader& Out)
|
|
{
|
|
TimerReader = &Out;
|
|
});
|
|
|
|
const FTimingTrackViewport& Viewport = Context.GetViewport();
|
|
|
|
TimingProfilerProvider.ReadTimeline(TimelineIndex,
|
|
[&Viewport, this, &Builder, TimerReader](const Trace::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 Trace::FTimingProfilerEvent& Event)
|
|
{
|
|
const Trace::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 Trace::EEventEnumerate::Continue;
|
|
});
|
|
}
|
|
else
|
|
{
|
|
Timeline.EnumerateEvents(Viewport.GetStartTime(), Viewport.GetEndTime(),
|
|
[this, &Builder, TimerReader](double StartTime, double EndTime, uint32 Depth, const Trace::FTimingProfilerEvent& Event)
|
|
{
|
|
const Trace::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 Trace::EEventEnumerate::Continue;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingTrack::BuildFilteredDrawState(ITimingEventsTrackDrawStateBuilder& Builder, const ITimingTrackUpdateContext& Context)
|
|
{
|
|
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 Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session.IsValid() && Trace::ReadTimingProfilerProvider(*Session.Get()))
|
|
{
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerTimerReader* TimerReader;
|
|
TimingProfilerProvider.ReadTimers([&TimerReader](const Trace::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 Trace::ITimingProfilerProvider::Timeline& Timeline)
|
|
{
|
|
TArray<TArray<FPendingEventInfo>> FilteredEvents;
|
|
|
|
Trace::ITimeline<Trace::FTimingProfilerEvent>::EnumerateAsyncParams Params;
|
|
Params.IntervalStart = Viewport.GetStartTime();
|
|
Params.IntervalEnd = Viewport.GetEndTime();
|
|
Params.Resolution = 0.0;
|
|
Params.SetupCallback = [&FilteredEvents](uint32 NumTasks)
|
|
{
|
|
FilteredEvents.AddDefaulted(NumTasks);
|
|
};
|
|
Params.Callback = [this, &Builder, TimerReader, FilterEventType, &FilteredEvents](double StartTime, double EndTime, uint32 Depth, const Trace::FTimingProfilerEvent& Event, uint32 TaskIndex)
|
|
{
|
|
const Trace::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 Trace::EEventEnumerate::Continue;
|
|
};
|
|
|
|
// Note: Enumerating events for filtering should not use downsampling.
|
|
Timeline.EnumerateEventsDownSampledAsync(Params);
|
|
|
|
for (TArray<FPendingEventInfo>& Array: FilteredEvents)
|
|
{
|
|
for (FPendingEventInfo& TimelineEvent: Array)
|
|
{
|
|
const Trace::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 Trace::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 Trace::FTimingProfilerEvent& Event)
|
|
{
|
|
const Trace::FTimingProfilerTimer* Timer = TimerReader->GetTimer(Event.TimerIndex);
|
|
if (ensure(Timer != nullptr))
|
|
{
|
|
if (Timer->Id == FilterEventType)
|
|
{
|
|
AddTimingEventToBuilder(Builder, StartTime, EndTime, Depth, Event.TimerIndex, Timer);
|
|
}
|
|
}
|
|
return Trace::EEventEnumerate::Continue;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
else // generic filter
|
|
{
|
|
TimingProfilerProvider.ReadTimeline(TimelineIndex,
|
|
[&Viewport, this, &Builder, TimerReader, &EventFilterPtr](const Trace::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 Trace::FTimingProfilerEvent& Event)
|
|
{
|
|
const Trace::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 Trace::EEventEnumerate::Continue;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingTrack::PostDraw(const ITimingTrackDrawContext& Context) const
|
|
{
|
|
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();
|
|
DrawSelectedEventInfo(SelectedEvent, Context.GetViewport(), Context.GetDrawContext(), Helper.GetWhiteBrush(), Helper.GetEventFont());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingTrack::DrawSelectedEventInfo(const FThreadTrackEvent& SelectedEvent, const FTimingTrackViewport& Viewport, const FDrawContext& DrawContext, const FSlateBrush* WhiteBrush, const FSlateFontInfo& Font) const
|
|
{
|
|
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
check(Session.IsValid());
|
|
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerTimerReader* TimerReader;
|
|
TimingProfilerProvider.ReadTimers([&TimerReader](const Trace::ITimingProfilerTimerReader& Out)
|
|
{
|
|
TimerReader = &Out;
|
|
});
|
|
|
|
const Trace::FTimingProfilerTimer* Timer = TimerReader->GetTimer(SelectedEvent.GetTimerIndex());
|
|
if (Timer != nullptr)
|
|
{
|
|
FString Str = FString::Printf(TEXT("%s (Incl.: %s, Excl.: %s)"),
|
|
Timer->Name,
|
|
*TimeUtils::FormatTimeAuto(SelectedEvent.GetDuration()),
|
|
*TimeUtils::FormatTimeAuto(SelectedEvent.GetExclusiveTime()));
|
|
|
|
const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
const FVector2D Size = FontMeasureService->Measure(Str, Font);
|
|
const float X = Viewport.GetWidth() - Size.X - 23.0f;
|
|
const float Y = Viewport.GetHeight() - Size.Y - 18.0f;
|
|
|
|
const FLinearColor BackgroundColor(0.05f, 0.05f, 0.05f, 1.0f);
|
|
const FLinearColor TextColor(0.7f, 0.7f, 0.7f, 1.0f);
|
|
|
|
DrawContext.DrawBox(X - 8.0f, Y - 2.0f, Size.X + 16.0f, Size.Y + 4.0f, WhiteBrush, BackgroundColor);
|
|
DrawContext.LayerId++;
|
|
|
|
DrawContext.DrawText(X, Y, Str, Font, TextColor);
|
|
DrawContext.LayerId++;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingTrack::InitTooltip(FTooltipDrawState& InOutTooltip, const ITimingEvent& InTooltipEvent) const
|
|
{
|
|
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 Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
check(Session.IsValid());
|
|
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerTimerReader* TimerReader;
|
|
TimingProfilerProvider.ReadTimers([&TimerReader](const Trace::ITimingProfilerTimerReader& Out)
|
|
{
|
|
TimerReader = &Out;
|
|
});
|
|
|
|
const Trace::FTimingProfilerTimer* Timer = TimerReader->GetTimer(TooltipEvent.GetTimerIndex());
|
|
FString TimerName = (Timer != nullptr) ? Timer->Name : TEXT("N/A");
|
|
InOutTooltip.AddTitle(TimerName);
|
|
|
|
if (ParentTimingEvent.IsValid() && TooltipEvent.GetDepth() > 0)
|
|
{
|
|
Timer = TimerReader->GetTimer(ParentTimingEvent->GetTimerIndex());
|
|
const TCHAR* ParentTimerName = (Timer != nullptr) ? Timer->Name : TEXT("N/A");
|
|
FNumberFormattingOptions FormattingOptions;
|
|
FormattingOptions.MaximumFractionalDigits = 2;
|
|
const FString ValueStr = FString::Printf(TEXT("%s %s"), *FText::AsPercent(TooltipEvent.GetDuration() / ParentTimingEvent->GetDuration(), &FormattingOptions).ToString(), ParentTimerName);
|
|
InOutTooltip.AddNameValueTextLine(TEXT("% of Parent:"), ValueStr);
|
|
}
|
|
|
|
if (RootTimingEvent.IsValid() && TooltipEvent.GetDepth() > 1)
|
|
{
|
|
Timer = TimerReader->GetTimer(RootTimingEvent->GetTimerIndex());
|
|
const TCHAR* RootTimerName = (Timer != nullptr) ? Timer->Name : TEXT("N/A");
|
|
FNumberFormattingOptions FormattingOptions;
|
|
FormattingOptions.MaximumFractionalDigits = 2;
|
|
const FString ValueStr = FString::Printf(TEXT("%s %s"), *FText::AsPercent(TooltipEvent.GetDuration() / RootTimingEvent->GetDuration(), &FormattingOptions).ToString(), RootTimerName);
|
|
InOutTooltip.AddNameValueTextLine(TEXT("% of Root:"), ValueStr);
|
|
}
|
|
|
|
InOutTooltip.AddNameValueTextLine(TEXT("Inclusive Time:"), TimeUtils::FormatTimeAuto(TooltipEvent.GetDuration()));
|
|
|
|
{
|
|
FNumberFormattingOptions FormattingOptions;
|
|
FormattingOptions.MaximumFractionalDigits = 2;
|
|
const FString ExclStr = FString::Printf(TEXT("%s (%s)"), *TimeUtils::FormatTimeAuto(TooltipEvent.GetExclusiveTime()), *FText::AsPercent(TooltipEvent.GetExclusiveTime() / TooltipEvent.GetDuration(), &FormattingOptions).ToString());
|
|
InOutTooltip.AddNameValueTextLine(TEXT("Exclusive Time:"), ExclStr);
|
|
}
|
|
|
|
InOutTooltip.AddNameValueTextLine(TEXT("Depth:"), FString::Printf(TEXT("%d"), TooltipEvent.GetDepth()));
|
|
|
|
TArrayView<const uint8> Metadata = TimerReader->GetMetadata(TooltipEvent.GetTimerIndex());
|
|
if (Metadata.Num() > 0)
|
|
{
|
|
AppendMetadataToTooltip(InOutTooltip, Metadata);
|
|
}
|
|
}
|
|
|
|
InOutTooltip.UpdateLayout();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingTrack::GetParentAndRoot(const FThreadTrackEvent& TimingEvent, TSharedPtr<FThreadTrackEvent>& OutParentTimingEvent, TSharedPtr<FThreadTrackEvent>& OutRootTimingEvent) const
|
|
{
|
|
if (TimingEvent.GetDepth() > 0)
|
|
{
|
|
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session.IsValid())
|
|
{
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
if (Trace::ReadTimingProfilerProvider(*Session.Get()))
|
|
{
|
|
const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get());
|
|
|
|
TimingProfilerProvider.ReadTimeline(GetTimelineIndex(), [&TimingEvent, &OutParentTimingEvent, &OutRootTimingEvent](const Trace::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> FThreadTimingTrack::GetEvent(float InPosX, float InPosY, const FTimingTrackViewport& Viewport) const
|
|
{
|
|
TSharedPtr<FThreadTrackEvent> TimingEvent;
|
|
|
|
const FTimingViewLayout& Layout = Viewport.GetLayout();
|
|
const float TopLaneY = GetPosY() + 1.0f + Layout.TimelineDY; // +1.0f is for horizontal line between timelines
|
|
const float DY = InPosY - TopLaneY;
|
|
|
|
// If mouse is not above first sub-track or below last sub-track...
|
|
if (DY >= 0 && DY < GetHeight() - 1.0f - 2 * Layout.TimelineDY)
|
|
{
|
|
const int32 Depth = DY / (Layout.EventH + Layout.EventDY);
|
|
|
|
const double SecondsPerPixel = 1.0 / Viewport.GetScaleX();
|
|
|
|
const double EventTime = Viewport.SlateUnitsToTime(InPosX);
|
|
|
|
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session.IsValid())
|
|
{
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
if (Trace::ReadTimingProfilerProvider(*Session.Get()))
|
|
{
|
|
const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get());
|
|
|
|
TimingProfilerProvider.ReadTimeline(GetTimelineIndex(), [this, &EventTime, &Depth, &TimingEvent, &SecondsPerPixel](const Trace::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> FThreadTimingTrack::SearchEvent(const FTimingEventSearchParameters& InSearchParameters) const
|
|
{
|
|
TSharedPtr<FThreadTrackEvent> FoundEvent;
|
|
FindTimingProfilerEvent(InSearchParameters, [this, &FoundEvent](double InFoundStartTime, double InFoundEndTime, uint32 InFoundDepth, const Trace::FTimingProfilerEvent& InFoundEvent)
|
|
{
|
|
FoundEvent = MakeShared<FThreadTrackEvent>(SharedThis(this), InFoundStartTime, InFoundEndTime, InFoundDepth);
|
|
FoundEvent->SetTimerIndex(InFoundEvent.TimerIndex);
|
|
|
|
uint32 TimerId = 0;
|
|
bool ret = FThreadTimingTrack::TimerIndexToTimerId(InFoundEvent.TimerIndex, TimerId);
|
|
if (ret)
|
|
{
|
|
FoundEvent->SetTimerId(TimerId);
|
|
}
|
|
});
|
|
|
|
return FoundEvent;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingTrack::UpdateEventStats(ITimingEvent& InOutEvent) const
|
|
{
|
|
if (InOutEvent.CheckTrack(this) && InOutEvent.Is<FThreadTrackEvent>())
|
|
{
|
|
FThreadTrackEvent& TrackEvent = InOutEvent.As<FThreadTrackEvent>();
|
|
if (TrackEvent.IsExclusiveTimeComputed())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session.IsValid())
|
|
{
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
if (Trace::ReadTimingProfilerProvider(*Session.Get()))
|
|
{
|
|
const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get());
|
|
|
|
// Get Exclusive Time.
|
|
TimingProfilerProvider.ReadTimeline(GetTimelineIndex(), [&TrackEvent](const Trace::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 FThreadTimingTrack::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 FThreadTimingTrack::OnClipboardCopyEvent(const ITimingEvent& InSelectedEvent) const
|
|
{
|
|
if (InSelectedEvent.CheckTrack(this) && InSelectedEvent.Is<FThreadTrackEvent>())
|
|
{
|
|
const FThreadTrackEvent& TrackEvent = InSelectedEvent.As<FThreadTrackEvent>();
|
|
|
|
FTimerNodePtr TimerNodePtr = FTimingProfilerManager::Get()->GetTimerNode(TrackEvent.GetTimerId());
|
|
if (TimerNodePtr)
|
|
{
|
|
// Copy name of selected timing event to clipboard.
|
|
FPlatformApplicationMisc::ClipboardCopy(*TimerNodePtr->GetName().ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingTrack::BuildContextMenu(FMenuBuilder& MenuBuilder)
|
|
{
|
|
if (GetGroupName() != nullptr)
|
|
{
|
|
MenuBuilder.BeginSection(TEXT("Misc"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::Format(LOCTEXT("CpuThreadGroupFmt", "CPU Thread 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 ? "*" : "", 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();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool FThreadTimingTrack::FindTimingProfilerEvent(const FThreadTrackEvent& InTimingEvent, TFunctionRef<void(double, double, uint32, const Trace::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 FThreadTimingTrack::FindTimingProfilerEvent(const FTimingEventSearchParameters& InParameters, TFunctionRef<void(double, double, uint32, const Trace::FTimingProfilerEvent&)> InFoundPredicate) const
|
|
{
|
|
return TTimingEventSearch<Trace::FTimingProfilerEvent>::Search(
|
|
InParameters,
|
|
|
|
[this](TTimingEventSearch<Trace::FTimingProfilerEvent>::FContext& InContext)
|
|
{
|
|
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session.IsValid())
|
|
{
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
if (Trace::ReadTimingProfilerProvider(*Session.Get()))
|
|
{
|
|
const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get());
|
|
|
|
TimingProfilerProvider.ReadTimeline(GetTimelineIndex(), [&InContext](const Trace::ITimingProfilerProvider::Timeline& Timeline)
|
|
{
|
|
Timeline.EnumerateEvents(InContext.GetParameters().StartTime, InContext.GetParameters().EndTime, [&InContext](double EventStartTime, double EventEndTime, uint32 EventDepth, const Trace::FTimingProfilerEvent& Event)
|
|
{
|
|
InContext.Check(EventStartTime, EventEndTime, EventDepth, Event);
|
|
return InContext.ShouldContinueSearching() ? Trace::EEventEnumerate::Continue : Trace::EEventEnumerate::Stop;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
[&InFoundPredicate](double InFoundStartTime, double InFoundEndTime, uint32 InFoundDepth, const Trace::FTimingProfilerEvent& InEvent)
|
|
{
|
|
InFoundPredicate(InFoundStartTime, InFoundEndTime, InFoundDepth, InEvent);
|
|
},
|
|
|
|
SearchCache);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FThreadTimingTrack::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 = FThreadTimingTrack::TimerIndexToTimerId(InEventInfo.Event.TimerIndex, TimerId);
|
|
if (ret)
|
|
{
|
|
Event.SetTimerId(TimerId);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool FThreadTimingTrack::TimerIndexToTimerId(uint32 InTimerIndex, uint32& OutTimerId)
|
|
{
|
|
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
check(Session.IsValid())
|
|
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get());
|
|
|
|
const Trace::ITimingProfilerTimerReader* TimerReader;
|
|
TimingProfilerProvider.ReadTimers([&TimerReader](const Trace::ITimingProfilerTimerReader& Out)
|
|
{
|
|
TimerReader = &Out;
|
|
});
|
|
|
|
const Trace::FTimingProfilerTimer* Timer = TimerReader->GetTimer(InTimerIndex);
|
|
if (Timer == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
OutTimerId = Timer->Id;
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#undef LOCTEXT_NAMESPACE
|