// Copyright Epic Games, Inc. All Rights Reserved. #include "ContextSwitchesTimingTrack.h" #include "Framework/MultiBox/MultiBoxBuilder.h" // TraceServices #include "TraceServices/Model/ContextSwitches.h" // TraceInsightsCore #include "InsightsCore/Common/TimeUtils.h" // TraceInsights #include "Insights/ContextSwitches/ViewModels/ContextSwitchesSharedState.h" #include "Insights/ContextSwitches/ViewModels/ContextSwitchTimingEvent.h" #include "Insights/InsightsManager.h" #include "Insights/ViewModels/TimingTrackViewport.h" #include "Insights/ViewModels/TimingViewDrawHelper.h" #include "Insights/ViewModels/TooltipDrawState.h" #include "Insights/Widgets/STimingView.h" namespace UE::Insights::ContextSwitches { //////////////////////////////////////////////////////////////////////////////////////////////////// INSIGHTS_IMPLEMENT_RTTI(FContextSwitchesTimingTrack) //////////////////////////////////////////////////////////////////////////////////////////////////// void FContextSwitchesTimingTrack::BuildDrawState(ITimingEventsTrackDrawStateBuilder& Builder, const ITimingTrackUpdateContext& Context) { TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (!Session.IsValid()) { return; } TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::IContextSwitchesProvider* ContextSwitchesProvider = TraceServices::ReadContextSwitchesProvider(*Session.Get()); if (ContextSwitchesProvider == nullptr) { return; } const FTimingTrackViewport& Viewport = Context.GetViewport(); ContextSwitchesProvider->EnumerateContextSwitches(ThreadId, Viewport.GetStartTime(), Viewport.GetEndTime(), [&Builder](const TraceServices::FContextSwitch& ContextSwitch) { Builder.AddEvent(ContextSwitch.Start, ContextSwitch.End, 0, *FString::Printf(TEXT("Core %d"), ContextSwitch.CoreNumber), 0, 0); return TraceServices::EContextSwitchEnumerationResult::Continue; }); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FContextSwitchesTimingTrack::BuildFilteredDrawState(ITimingEventsTrackDrawStateBuilder& Builder, const ITimingTrackUpdateContext& Context) { BuildDrawState(Builder, Context); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FContextSwitchesTimingTrack::Draw(const ITimingTrackDrawContext& Context) const { DrawLineEvents(Context, 1.0f); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FContextSwitchesTimingTrack::DrawLineEvents(const ITimingTrackDrawContext& Context, const float OffsetY) const { const FTimingViewDrawHelper& Helper = *static_cast(&Context.GetHelper()); if ((Context.GetEventFilter().IsValid() && Context.GetEventFilter()->FilterTrack(*this)) || HasCustomFilter()) { Helper.DrawFadedLineEvents(GetDrawState(), *this, OffsetY, 0.1f); if (UpdateFilteredDrawStateOpacity()) { Helper.DrawLineEvents(GetFilteredDrawState(), *this, OffsetY); } else { Helper.DrawFadedLineEvents(GetFilteredDrawState(), *this, OffsetY, GetFilteredDrawStateOpacity()); } } else { Helper.DrawLineEvents(GetDrawState(), *this, OffsetY); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FContextSwitchesTimingTrack::PostDraw(const ITimingTrackDrawContext& Context) const { if (SharedState.AreOverlaysVisible() || SharedState.AreExtendedLinesVisible()) { float LineY1 = 0.0f; float LineY2 = 0.0f; ETimingTrackLocation LocalLocation = ETimingTrackLocation::None; TSharedPtr LocalParentTrack = GetParentTrack().Pin(); if (LocalParentTrack) { LineY1 = LocalParentTrack->GetPosY(); LineY2 = LineY1 + LocalParentTrack->GetHeight(); LocalLocation = LocalParentTrack->GetLocation(); } else { LineY1 = GetPosY(); LineY2 = LineY1 + GetHeight(); LocalLocation = GetLocation(); } const FTimingTrackViewport& Viewport = Context.GetViewport(); switch (LocalLocation) { case ETimingTrackLocation::Scrollable: { const float TopY = Viewport.GetPosY() + Viewport.GetTopOffset(); if (LineY1 < TopY) { LineY1 = TopY; } const float BottomY = Viewport.GetPosY() + Viewport.GetHeight() - Viewport.GetBottomOffset(); if (LineY2 > BottomY) { LineY2 = BottomY; } break; } case ETimingTrackLocation::TopDocked: { const float TopY = Viewport.GetPosY(); if (LineY1 < TopY) { LineY1 = TopY; } const float BottomY = Viewport.GetPosY() + Viewport.GetTopOffset(); if (LineY2 > BottomY) { LineY2 = BottomY; } break; } case ETimingTrackLocation::BottomDocked: { const float TopY = Viewport.GetPosY() + Viewport.GetHeight() - Viewport.GetBottomOffset(); if (LineY1 < TopY) { LineY1 = TopY; } const float BottomY = Viewport.GetPosY() + Viewport.GetHeight(); if (LineY2 > BottomY) { LineY2 = BottomY; } break; } } const float LineH = LineY2 - LineY1; if (LineH > 0.0f) { const FTimingViewDrawHelper& Helper = *static_cast(&Context.GetHelper()); Helper.DrawContextSwitchMarkers(GetDrawState(), LineY1, LineH, 0.25f, SharedState.AreOverlaysVisible(), SharedState.AreExtendedLinesVisible()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// const TSharedPtr FContextSwitchesTimingTrack::GetEvent(double InTime, double SecondsPerPixel, int32 Depth) const { TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (!Session.IsValid()) { return nullptr; } TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const TraceServices::IContextSwitchesProvider* ContextSwitchesProvider = TraceServices::ReadContextSwitchesProvider(*Session.Get()); if (ContextSwitchesProvider == nullptr) { return nullptr; } TraceServices::FContextSwitch BestMatchContextSwitch; double Delta = 2 * SecondsPerPixel; ContextSwitchesProvider->EnumerateContextSwitches(ThreadId, InTime - Delta, InTime + Delta, [InTime, &BestMatchContextSwitch, &Delta](const TraceServices::FContextSwitch& ContextSwitch) { if (ContextSwitch.Start <= InTime && ContextSwitch.End >= InTime) { BestMatchContextSwitch = ContextSwitch; Delta = 0.0f; return TraceServices::EContextSwitchEnumerationResult::Stop; } double DeltaLeft = InTime - ContextSwitch.End; if (DeltaLeft >= 0 && DeltaLeft < Delta) { Delta = DeltaLeft; BestMatchContextSwitch = ContextSwitch; } double DeltaRight = ContextSwitch.Start - InTime; if (DeltaRight >= 0 && DeltaRight < Delta) { Delta = DeltaRight; BestMatchContextSwitch = ContextSwitch; } return TraceServices::EContextSwitchEnumerationResult::Continue; }); if (Delta < 2 * SecondsPerPixel) { TSharedPtr TimingEvent = MakeShared(SharedThis(this), BestMatchContextSwitch.Start, BestMatchContextSwitch.End, 0); TimingEvent->SetCoreNumber(BestMatchContextSwitch.CoreNumber); return TimingEvent; } return nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// void FContextSwitchesTimingTrack::InitTooltip(FTooltipDrawState& InOutTooltip, const ITimingEvent& InTooltipEvent) const { if (!InTooltipEvent.CheckTrack(this) || !InTooltipEvent.Is()) { return; } const FContextSwitchTimingEvent& ContextSwitchEvent = InTooltipEvent.As(); InOutTooltip.AddTitle(FString::Printf(TEXT("Core %d"), ContextSwitchEvent.GetCoreNumber())); using namespace UE::Insights; InOutTooltip.AddNameValueTextLine(TEXT("Start Time:"), FormatTimeAuto(InTooltipEvent.GetStartTime(), 6)); InOutTooltip.AddNameValueTextLine(TEXT("End Time:"), FormatTimeAuto(InTooltipEvent.GetEndTime(), 6)); InOutTooltip.AddNameValueTextLine(TEXT("Duration:"), FormatTimeAuto(InTooltipEvent.GetDuration())); InOutTooltip.UpdateLayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FContextSwitchesTimingTrack::BuildContextMenu(FMenuBuilder& InOutMenuBuilder) { if (SharedState.GetTimingView()) { const TSharedPtr HoveredEvent = SharedState.GetTimingView()->GetHoveredEvent(); if (HoveredEvent.IsValid() && &HoveredEvent->GetTrack().Get() == this && HoveredEvent->Is()) { const FContextSwitchTimingEvent& CpuCoreEvent = HoveredEvent->As(); const uint32 CoreNumber = CpuCoreEvent.GetCoreNumber(); SharedState.SetTargetTimingEvent(HoveredEvent); FString SectionName = FString::Printf(TEXT("Core %d"), CoreNumber); InOutMenuBuilder.BeginSection("CpuCore", FText::FromString(SectionName)); InOutMenuBuilder.AddMenuEntry(FContextSwitchesStateCommands::Get().Command_NavigateToCpuCoreEvent); InOutMenuBuilder.AddMenuEntry(FContextSwitchesStateCommands::Get().Command_DockCpuCoreTrackToTop); InOutMenuBuilder.EndSection(); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// const TSharedPtr FContextSwitchesTimingTrack::SearchEvent(const FTimingEventSearchParameters& InSearchParameters) const { TSharedPtr FoundEvent; TTimingEventSearch::Search( InSearchParameters, [this, &InSearchParameters](TTimingEventSearch::FContext& InContext) { TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session.IsValid()) { TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); if (Session.IsValid() && TraceServices::ReadContextSwitchesProvider(*Session.Get())) { const TraceServices::IContextSwitchesProvider* ContextSwitchesProvider = TraceServices::ReadContextSwitchesProvider(*Session.Get()); auto Callback = [&InContext](double EventStartTime, double EventEndTime, uint32 EventDepth, const TraceServices::FContextSwitch& Event) { InContext.Check(EventStartTime, EventEndTime, EventDepth, Event); return InContext.ShouldContinueSearching() ? TraceServices::EContextSwitchEnumerationResult::Continue : TraceServices::EContextSwitchEnumerationResult::Stop; }; ContextSwitchesProvider->EnumerateContextSwitches(ThreadId, InSearchParameters.StartTime, InSearchParameters.EndTime, [ContextSwitchesProvider, Callback](const TraceServices::FContextSwitch& ContextSwitchEvent) { return Callback(ContextSwitchEvent.Start, ContextSwitchEvent.End, 0, ContextSwitchEvent); }); } } }, [](double EventStartTime, double EventEndTime, uint32 EventDepth, const TraceServices::FContextSwitch& Event) { return true; }, [&FoundEvent, this](double InFoundStartTime, double InFoundEndTime, uint32 InFoundDepth, const TraceServices::FContextSwitch& InEvent) { FoundEvent = MakeShared(SharedThis(this), InFoundStartTime, InFoundEndTime, InFoundDepth, InEvent.CoreNumber); }, TTimingEventSearch::NoMatch ); return FoundEvent; } //////////////////////////////////////////////////////////////////////////////////////////////////// } // namespace UE::Insights::ContextSwitches