Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1192 lines
40 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SInsightsStatusBar.h"
#include "CoreGlobals.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Commands/Commands.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/Notifications/NotificationManager.h"
#include "GenericPlatform/GenericPlatformFile.h"
#include "HAL/FileManager.h"
#include "Logging/MessageLog.h"
#include "Logging/TokenizedMessage.h"
#include "Math/Color.h"
#include "MessageLogModule.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/DateTime.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"
#include "ProfilingDebugging/MiscTrace.h"
#include "ProfilingDebugging/PlatformEvents.h"
#include "ProfilingDebugging/TraceScreenshot.h"
#include "Styling/StyleColors.h"
#include "ToolMenus.h"
#include "Trace/Detail/Channel.h"
#include "Trace/StoreClient.h"
#include "Trace/Trace.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Widgets/SBoxPanel.h"
// TraceTools
#include "TraceTools/Interfaces/ITraceToolsModule.h"
#include "TraceTools/Widgets/SToggleTraceButton.h"
// TraceInsightsFrontend
#include "InsightsFrontend/StoreService/TraceServerControl.h"
// EditorTraceUtilities
#include "EditorTraceUtilities.h"
#include "EditorTraceUtilitiesStyle.h"
#include "SRecentTracesList.h"
#include "UnrealInsightsLauncher.h"
#define LOCTEXT_NAMESPACE "InsightsEditor"
namespace UE::EditorTraceUtilities
{
FStatusBarTraceSettings SInsightsStatusBarWidget::StatusBarTraceSettings;
const TCHAR* SInsightsStatusBarWidget::DefaultPreset = TEXT("default");
const TCHAR* SInsightsStatusBarWidget::MemoryPreset= TEXT("default,memory");
const TCHAR* SInsightsStatusBarWidget::TaskGraphPreset = TEXT("default,task");
const TCHAR* SInsightsStatusBarWidget::ContextSwitchesPreset = TEXT("default,contextswitches");
const TCHAR* SInsightsStatusBarWidget::SettingsCategory = TEXT("EditorTraceUtilities");
const TCHAR* SInsightsStatusBarWidget::OpenLiveSessionOnTraceStartSettingName = TEXT("OpenLiveSessionOnTraceStart");
const TCHAR* SInsightsStatusBarWidget::OpenInsightsAfterTraceSettingName = TEXT("OpenInsightsAfterTrace");
const TCHAR* SInsightsStatusBarWidget::TraceRegionSettingName = TEXT("InsightsToolbarTraceRegion");
const TCHAR* SInsightsStatusBarWidget::ShowInExplorerAfterTraceSettingName = TEXT("ShowInExplorerAfterTrace");
////////////////////////////////////////////////////////////////////////////////////////////////////
// FInsightsStatusBarWidgetCommands
////////////////////////////////////////////////////////////////////////////////////////////////////
class FInsightsStatusBarWidgetCommands : public TCommands<FInsightsStatusBarWidgetCommands>
{
public:
FInsightsStatusBarWidgetCommands()
: TCommands<FInsightsStatusBarWidgetCommands>(
TEXT("InsightsStatusBarWidgetCommands"),
NSLOCTEXT("Contexts", "InsightsStatusBarWidgetCommands", "Insights Status Bar"),
NAME_None,
FEditorTraceUtilitiesStyle::Get().GetStyleSetName())
{
}
virtual ~FInsightsStatusBarWidgetCommands()
{
}
// UI_COMMAND takes long for the compiler to optimize
UE_DISABLE_OPTIMIZATION_SHIP
virtual void RegisterCommands() override
{
UI_COMMAND(Command_TraceScreenshot, "Trace Screenshot", "Takes a screenshot and sends it to the trace.", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::F9));
UI_COMMAND(Command_TraceBookmark, "Trace Bookmark", "Traces a bookmark.", EUserInterfaceActionType::Button, FInputChord());
}
UE_ENABLE_OPTIMIZATION_SHIP
TSharedPtr<FUICommandInfo> Command_TraceScreenshot;
TSharedPtr<FUICommandInfo> Command_TraceBookmark;
};
TSharedRef<SWidget> CreateInsightsStatusBarWidget()
{
return SNew(SInsightsStatusBarWidget);
}
void RegisterInsightsStatusWidgetWithToolMenu()
{
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu(TEXT("LevelEditor.StatusBar.ToolBar"));
FToolMenuSection& InsightsSection = Menu->AddSection(TEXT("Insights"), FText::GetEmpty(), FToolMenuInsert(NAME_None, EToolMenuInsertType::First));
InsightsSection.AddEntry(
FToolMenuEntry::InitWidget(TEXT("InsightsStatusBar"), CreateInsightsStatusBarWidget(), FText::GetEmpty(), true, false)
);
}
void SInsightsStatusBarWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
TRACE_CPUPROFILER_EVENT_SCOPE(SInsightsStatusBarWidget::Tick);
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
bShouldUpdateChannels = true;
if (bShouldTryOpenLiveSession)
{
constexpr double Delay = 1.0f;
OpenLiveSessionScheduledTime = InCurrentTime + Delay;
bShouldTryOpenLiveSession = false;
}
if (OpenLiveSessionScheduledTime > 0.0f && OpenLiveSessionScheduledTime <= InCurrentTime)
{
OpenLiveSessionScheduledTime = 0.0f;
FUnrealInsightsLauncher::Get()->TryOpenTraceFromDestination(FTraceAuxiliary::GetTraceDestinationString());
}
}
void SInsightsStatusBarWidget::Construct(const FArguments& InArgs)
{
FModuleManager::LoadModuleChecked<UE::TraceTools::ITraceToolsModule>("TraceTools");
this->ChildSlot
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SComboButton)
.ContentPadding(FMargin(6.0f, 0.0f))
.MenuPlacement(MenuPlacement_AboveAnchor)
.ComboButtonStyle(&FAppStyle::Get().GetWidgetStyle<FComboButtonStyle>("SimpleComboButton"))
.OnGetMenuContent(this, &SInsightsStatusBarWidget::MakeTraceMenu)
.HasDownArrow(true)
.ToolTipText(this, &SInsightsStatusBarWidget::GetTitleToolTipText)
.ButtonContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(0, 0, 3, 0)
.AutoWidth()
[
SNew(SImage)
.ColorAndOpacity(FSlateColor::UseForeground())
.Image(FEditorTraceUtilitiesStyle::Get().GetBrush("Icons.Trace.StatusBar"))
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(STextBlock)
.Text(LOCTEXT("Trace", "Trace"))
.TextStyle(&FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("DialogButtonText"))
]
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(UE::TraceTools::SToggleTraceButton)
.OnToggleTraceRequested(this, &SInsightsStatusBarWidget::ToggleTrace_OnClicked)
.IsTraceRunning_Lambda([]() {return UE::Trace::IsTracing(); })
.ButtonSize(UE::TraceTools::SToggleTraceButton::EButtonSize::StatusBar)
.IsEnabled(this, &SInsightsStatusBarWidget::ToggleTrace_CanExecute)
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.ContentPadding(FMargin(4.0f, 0.0f, 0.0f, 3.0f))
.HAlign(HAlign_Left)
.VAlign(VAlign_Bottom)
.OnClicked_Lambda([this]() { SaveSnapshot(); return FReply::Handled(); })
.IsEnabled(this, &SInsightsStatusBarWidget::SaveSnapshot_CanExecute)
.Content()
[
SNew(SImage)
.DesiredSizeOverride(CoreStyleConstants::Icon16x16)
.Image(FEditorTraceUtilitiesStyle::Get().GetBrush("Icons.TraceSnapshot.StatusBar"))
.ToolTipText(LOCTEXT("SaveSnapShot","Snapshot: Save Current Trace Buffer to active destination."))
]
]
];
if (FTraceAuxiliary::GetConnectionType() == FTraceAuxiliary::EConnectionType::Network)
{
StatusBarTraceSettings.TraceDestination = ETraceDestination::TraceStore;
}
if (FTraceAuxiliary::GetConnectionType() == FTraceAuxiliary::EConnectionType::File)
{
StatusBarTraceSettings.TraceDestination = ETraceDestination::File;
}
LogListingName = TEXT("UnrealInsights");
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
if (!MessageLogModule.IsRegisteredLogListing(LogListingName))
{
MessageLogModule.RegisterLogListing(LogListingName, LOCTEXT("UnrealInsights", "Unreal Insights"));
}
FTraceAuxiliary::OnTraceStarted.AddSP(this, &SInsightsStatusBarWidget::OnTraceStarted);
FTraceAuxiliary::OnTraceStopped.AddSP(this, &SInsightsStatusBarWidget::OnTraceStopped);
FTraceAuxiliary::OnSnapshotSaved.AddSP(this, &SInsightsStatusBarWidget::OnSnapshotSaved);
LiveSessionTracker = MakeShared<FLiveSessionTracker>();
InitCommandList();
}
TSharedRef<SWidget> SInsightsStatusBarWidget::MakeTraceMenu()
{
LiveSessionTracker->StartQuery();
if (ServerControls.IsEmpty())
{
ServerControls.Emplace(TEXT("127.0.0.1"), 0, FEditorTraceUtilitiesStyle::Get().GetStyleSetName());
}
FMenuBuilder MenuBuilder(true, CommandList.ToSharedRef());
MenuBuilder.BeginSection("TraceData", LOCTEXT("TraceMenu_Section_Data", "Trace Data"));
{
MenuBuilder.AddSubMenu
(
LOCTEXT("Channels", "Channels"),
LOCTEXT("Channels_Desc", "Select what trace channels to enable when tracing."),
FNewMenuDelegate::CreateSP(this, &SInsightsStatusBarWidget::Channels_BuildMenu),
false,
FSlateIcon(FEditorTraceUtilitiesStyle::Get().GetStyleSetName(), ("Icons.Trace.Menu"))
);
MenuBuilder.AddMenuEntry
(
FInsightsStatusBarWidgetCommands::Get().Command_TraceScreenshot,
NAME_None,
TAttribute<FText>(),
TAttribute<FText>::CreateSP(this, &SInsightsStatusBarWidget::GetTraceScreenshotTooltipText),
FSlateIcon(FEditorTraceUtilitiesStyle::Get().GetStyleSetName(), "Icons.Screenshot.Menu")
);
MenuBuilder.AddMenuEntry
(
FInsightsStatusBarWidgetCommands::Get().Command_TraceBookmark,
NAME_None,
TAttribute<FText>(),
TAttribute<FText>::CreateSP(this, &SInsightsStatusBarWidget::GetTraceBookmarkTooltipText),
FSlateIcon(FEditorTraceUtilitiesStyle::Get().GetStyleSetName(), "Icons.Bookmark.Menu")
);
MenuBuilder.AddEditableText(LOCTEXT("TraceRegionNameLabel", "Region Name:"),
GetTraceRegionNameDesc(),
FSlateIcon(),
TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateRaw(this, &SInsightsStatusBarWidget::GetTraceRegionName)),
FOnTextCommitted::CreateLambda([](const FText& NewRegionName, ETextCommit::Type)
{
GConfig->SetText(SettingsCategory, TraceRegionSettingName, NewRegionName, FEditorTraceUtilitiesModule::GetTraceUtilitiesIni());
}),
FOnTextChanged(),
RegionIsActive());
MenuBuilder.AddMenuEntry(
GetRegionSwitchLabelText(),
TAttribute<FText>::CreateSP(this, &SInsightsStatusBarWidget::GetRegionSwitchDescText),
FSlateIcon(FEditorTraceUtilitiesStyle::Get().GetStyleSetName(), RegionIsActive() ? "Icons.EndRegion.Menu" : "Icons.BeginRegion.Menu"),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::ToggleRegion_Execute),
FCanExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::ToggleRegion_CanExecute)),
NAME_None,
EUserInterfaceActionType::Button
);
MenuBuilder.AddMenuEntry(
LOCTEXT("StatNamedEventsLabel", "Stat Named Events"),
LOCTEXT("StatNamedEventsDesc", "Enable or disable named events in the stats system."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([]() { GCycleStatsShouldEmitNamedEvents = GCycleStatsShouldEmitNamedEvents == 0 ? 1 : 0; }),
FCanExecuteAction::CreateLambda([]() { return true; }),
FIsActionChecked::CreateLambda([]() { return GCycleStatsShouldEmitNamedEvents > 0; })),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("TraceDestination", LOCTEXT("TraceMenu_Section_Destination", "Trace Destination"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ServerLabel", "Trace Store"),
LOCTEXT("ServerLabelDesc", "Set the trace store as the trace destination."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::SetTraceDestination_Execute, ETraceDestination::TraceStore),
FCanExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::SetTraceDestination_CanExecute),
FIsActionChecked::CreateSP(this, &SInsightsStatusBarWidget::SetTraceDestination_IsChecked, ETraceDestination::TraceStore)),
NAME_None,
EUserInterfaceActionType::RadioButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("FileLabel", "File"),
LOCTEXT("FileLabelDesc", "Set file as the trace destination."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::SetTraceDestination_Execute, ETraceDestination::File),
FCanExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::SetTraceDestination_CanExecute),
FIsActionChecked::CreateSP(this, &SInsightsStatusBarWidget::SetTraceDestination_IsChecked, ETraceDestination::File)),
NAME_None,
EUserInterfaceActionType::RadioButton
);
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("Tracing", LOCTEXT("TraceMenu_Section_Tracing", "Tracing"));
{
MenuBuilder.AddMenuEntry(
TAttribute<FText>::CreateSP(this, &SInsightsStatusBarWidget::GetTraceMenuItemText),
TAttribute<FText>::CreateSP(this, &SInsightsStatusBarWidget::GetTraceMenuItemTooltipText),
FSlateIcon(FEditorTraceUtilitiesStyle::Get().GetStyleSetName(), "Icons.StartTrace.Menu"),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::ToggleTrace_OnClicked),
FCanExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::ToggleTrace_CanExecute)),
NAME_None,
EUserInterfaceActionType::Button
);
if (FTraceAuxiliary::IsPaused())
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ResumeTraceButtonText", "Resume Trace"),
LOCTEXT("ResumesTraceButtonTooltip", "Enables all channels that were active when tracing was paused."),
FSlateIcon(FEditorTraceUtilitiesStyle::Get().GetStyleSetName(), "Icons.ResumeTrace.Menu"),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::TogglePauseTrace_OnClicked),
FCanExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::PauseTrace_CanExecute)),
NAME_None,
EUserInterfaceActionType::Button
);
}
else
{
MenuBuilder.AddMenuEntry(
LOCTEXT("PauseTraceButtonText", "Pause Trace"),
TAttribute<FText>::CreateSP(this, &SInsightsStatusBarWidget::GetPauseTraceMenuItemTooltipText),
FSlateIcon(FEditorTraceUtilitiesStyle::Get().GetStyleSetName(), "Icons.PauseTrace.Menu"),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::TogglePauseTrace_OnClicked),
FCanExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::PauseTrace_CanExecute)),
NAME_None,
EUserInterfaceActionType::Button
);
}
MenuBuilder.AddMenuEntry(
LOCTEXT("SaveSnapshotLabel", "Save Trace Snapshot"),
LOCTEXT("SaveSnapshotTooltip", "Save the current trace buffer to the selected trace destination."),
FSlateIcon(FEditorTraceUtilitiesStyle::Get().GetStyleSetName(), "Icons.TraceSnapshot.Menu"),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::SaveSnapshot),
FCanExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::SaveSnapshot_CanExecute)),
NAME_None,
EUserInterfaceActionType::Button
);
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("Options", LOCTEXT("TraceMenu_Section_Options", "Options"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("OpenLiveSesssionOnTraceStart", "Open Live Session on Trace Start"),
LOCTEXT("OpenLiveSesssionOnTraceStartDesc", "When set, the live session will be automatically opened in Unreal Insights when tracing is started.\nThis option will only apply when tracing to the trace store."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::ToggleBooleanSettingValue, OpenLiveSessionOnTraceStartSettingName),
FCanExecuteAction::CreateLambda([]() { return true; }),
FIsActionChecked::CreateSP(this, &SInsightsStatusBarWidget::GetBooleanSettingValue, OpenLiveSessionOnTraceStartSettingName)),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("OpenInsightsAfterTrace", "Open Insights after Trace"),
LOCTEXT("OpenInsightsAfterTraceDesc", "When set, the session will be automatically opened in Unreal Insights when tracing is stopped or when a snapshot is saved."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::ToggleBooleanSettingValue, OpenInsightsAfterTraceSettingName),
FCanExecuteAction::CreateLambda([]() { return true; }),
FIsActionChecked::CreateSP(this, &SInsightsStatusBarWidget::GetBooleanSettingValue, OpenInsightsAfterTraceSettingName)),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("ShowInExplorerAfterTrace", "Show in Explorer after Trace"),
LOCTEXT("ShowInExplorerAfterTraceDesc", "When set, folder containing the recorded session will be opened automatically when trace is stopped or when a snapshot is saved."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::ToggleBooleanSettingValue, ShowInExplorerAfterTraceSettingName),
FCanExecuteAction::CreateLambda([]() { return true; }),
FIsActionChecked::CreateSP(this, &SInsightsStatusBarWidget::GetBooleanSettingValue, ShowInExplorerAfterTraceSettingName)),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("Locations", LOCTEXT("TraceMenu_Section_Locations", "Locations"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("OpenTraceStoreLabel", "Open Trace Store Directory"),
LOCTEXT("OpenTraceStoreTooltip", "Open Trace Store Directory. This is the location where traces saved to the trace server are stored."),
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "Icons.FolderOpen"),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::OpenTraceStoreDirectory_OnClicked)),
NAME_None,
EUserInterfaceActionType::Button
);
MenuBuilder.AddMenuEntry(
LOCTEXT("OpenProfilingDirectoryLabel", "Open Profiling Directory"),
LOCTEXT("OpenProfilingDirectoryTooltip", "Opens the profiling directory of the current project. This is the location where traces to file are stored."),
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "Icons.FolderOpen"),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::OpenProfilingDirectory_OnClicked)),
NAME_None,
EUserInterfaceActionType::Button
);
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("Insights", LOCTEXT("TraceMenu_Section_Insights", "Insights"));
{
MenuBuilder.AddSubMenu(
LOCTEXT("ServerControlLabel", "Unreal Trace Server"),
LOCTEXT("ServerControlTooltip", "Info and controls for the Unreal Trace Server instances"),
FNewMenuDelegate::CreateLambda([this](FMenuBuilder& MenuBuilder)
{
for (auto& ServerControl : ServerControls)
{
ServerControl.MakeMenu(MenuBuilder);
}
}),
false,
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "Icons.Server")
);
MenuBuilder.AddMenuEntry(
LOCTEXT("OpenInsightsLabel", "Unreal Insights (Session Browser)"),
LOCTEXT("OpenInsightsTooltip", "Launch the Unreal Insights Session Browser."),
FSlateIcon(FEditorTraceUtilitiesStyle::Get().GetStyleSetName(), "Icons.UnrealInsights.Menu"),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::LaunchUnrealInsights_OnClicked)),
NAME_None,
EUserInterfaceActionType::Button
);
MenuBuilder.AddMenuEntry(
LOCTEXT("OpenLiveSessionLabel", "Open Live Session"),
LOCTEXT("OpenLiveSessionTooltip", "Opening the live session is possible only while tracing to the trace store."),
FSlateIcon(FEditorTraceUtilitiesStyle::Get().GetStyleSetName(), "Icons.OpenLiveSession.Menu"),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::OpenLiveSession_OnClicked),
FCanExecuteAction::CreateLambda([this]() { return FTraceAuxiliary::GetConnectionType() == FTraceAuxiliary::EConnectionType::Network; })),
NAME_None,
EUserInterfaceActionType::Button
);
MenuBuilder.AddSubMenu
(
LOCTEXT("RecentTracesLabel", "Recent Traces"),
LOCTEXT("RecentTracesTooltop", "Open the latest traces recorded to the trace store or as a file."),
FNewMenuDelegate::CreateSP(this, &SInsightsStatusBarWidget::Traces_BuildMenu),
false,
FSlateIcon(FEditorTraceUtilitiesStyle::Get().GetStyleSetName(), ("Icons.Trace.Menu"))
);
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
void SInsightsStatusBarWidget::Channels_BuildMenu(FMenuBuilder& MenuBuilder)
{
CreateChannelsInfo();
MenuBuilder.BeginSection("Channels", LOCTEXT("Channels", "Channels"));
{
for (int32 Index = 0; Index < ChannelsInfo.Num(); ++Index)
{
const FChannelData& Data = ChannelsInfo[Index];
FString ChannelDisplayName = Data.Name;
ChannelDisplayName.RemoveFromEnd(TEXT("Channel"), 7);
FText ChannelTooltip = FText::FromString(Data.Desc);
if (Data.bIsReadOnly)
{
if (Data.Desc.EndsWith("."))
{
ChannelTooltip = FText::Format(LOCTEXT("ChannelTooltipFmt1", "{0} This channel is readonly and can only be enabled from the command line."), FText::FromString(Data.Desc));
}
else
{
ChannelTooltip = FText::Format(LOCTEXT("ChannelTooltipFmt2", "{0}. This channel is readonly and can only be enabled from the command line."), FText::FromString(Data.Desc));
}
}
MenuBuilder.AddMenuEntry(
FText::FromString(ChannelDisplayName),
ChannelTooltip,
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::ToggleChannel_Execute, Index),
FCanExecuteAction::CreateLambda([Value = !Data.bIsReadOnly]() { return Value; }),
FIsActionChecked::CreateSP(this, &SInsightsStatusBarWidget::ToggleChannel_IsChecked, Index)),
NAME_None,
EUserInterfaceActionType::ToggleButton);
}
}
MenuBuilder.EndSection();
}
void SInsightsStatusBarWidget::Traces_BuildMenu(FMenuBuilder& MenuBuilder)
{
CacheTraceStorePath();
PopulateRecentTracesList();
for (int32 Index = 0; Index < Traces.Num(); ++Index)
{
MenuBuilder.AddMenuEntry(
FUIAction(FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::OpenTrace, Index),
FCanExecuteAction()),
SNew(SRecentTracesListEntry, Traces[Index], TraceStorePath, LiveSessionTracker),
NAME_None,
FText::FromString(Traces[Index]->FilePath),
EUserInterfaceActionType::Button);
}
}
void SInsightsStatusBarWidget::InitCommandList()
{
FInsightsStatusBarWidgetCommands::Register();
CommandList = MakeShared<FUICommandList>();
CommandList->MapAction(FInsightsStatusBarWidgetCommands::Get().Command_TraceScreenshot, FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::TraceScreenshot_Execute), FCanExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::TraceScreenshot_CanExecute));
CommandList->MapAction(FInsightsStatusBarWidgetCommands::Get().Command_TraceBookmark, FExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::TraceBookmark_Execute), FCanExecuteAction::CreateSP(this, &SInsightsStatusBarWidget::TraceBookmark_CanExecute));
}
FText SInsightsStatusBarWidget::GetTitleToolTipText() const
{
#if UE_TRACE_ENABLED
FTextBuilder DescBuilder;
const FString Dest = FTraceAuxiliary::GetTraceDestinationString();
if (!Dest.IsEmpty())
{
DescBuilder.AppendLineFormat(LOCTEXT("TracingToText", "Tracing to: {0}"), FText::FromString(Dest));
}
else if (UE::Trace::IsTracing())
{
DescBuilder.AppendLine(LOCTEXT("TracingToUnknownText", "Tracing to unknown target (externally set)."));
}
else
{
DescBuilder.AppendLine(LOCTEXT("NotTracingText","Not currently tracing."));
}
return DescBuilder.ToText();
#else
return LOCTEXT("TraceStatusDisabled", "Trace system is disabled at compile time. Check the UE_TRACE_ENABLED define.");
#endif
}
void SInsightsStatusBarWidget::LogMessage(const FText& Text)
{
FMessageLog ReportMessageLog(LogListingName);
TSharedRef<FTokenizedMessage> TokenizedMessage = FTokenizedMessage::Create(EMessageSeverity::Error, Text);
ReportMessageLog.AddMessage(TokenizedMessage);
ReportMessageLog.Notify();
}
void SInsightsStatusBarWidget::ShowNotification(const FText& Text, const FText& SubText)
{
FNotificationInfo Info(Text);
Info.bFireAndForget = true;
Info.FadeOutDuration = 1.0f;
Info.ExpireDuration = 4.0f;
Info.SubText = SubText;
FSlateNotificationManager::Get().AddNotification(Info);
}
void SInsightsStatusBarWidget::LaunchUnrealInsights_OnClicked()
{
FUnrealInsightsLauncher::Get()->StartUnrealInsights(FUnrealInsightsLauncher::Get()->GetInsightsApplicationPath());
}
void SInsightsStatusBarWidget::OpenLiveSession_OnClicked()
{
OpenLiveSession(FTraceAuxiliary::GetTraceDestinationString());
}
void SInsightsStatusBarWidget::OpenLiveSession(const FString& InTraceDestination)
{
FUnrealInsightsLauncher::Get()->TryOpenTraceFromDestination(InTraceDestination);
}
void SInsightsStatusBarWidget::OpenProfilingDirectory_OnClicked()
{
OpenProfilingDirectory();
}
void SInsightsStatusBarWidget::OpenProfilingDirectory()
{
FString FullPath(FPaths::ConvertRelativePathToFull(FPaths::ProfilingDir()));
if (!IFileManager::Get().DirectoryExists(*FullPath))
{
IFileManager::Get().MakeDirectory(*FullPath);
}
FPlatformProcess::ExploreFolder(*FullPath);
}
void SInsightsStatusBarWidget::OpenTraceStoreDirectory_OnClicked()
{
OpenTraceStoreDirectory(ESelectLatestTraceCriteria::None);
}
void SInsightsStatusBarWidget::OpenTraceStoreDirectory(ESelectLatestTraceCriteria Criteria)
{
CacheTraceStorePath();
FString Path = TraceStorePath;
if (Criteria != ESelectLatestTraceCriteria::None)
{
Path = GetLatestTraceFileFromFolder(TraceStorePath, Criteria);
}
FPlatformProcess::ExploreFolder(*Path);
}
void SInsightsStatusBarWidget::OpenLatestTraceFromFolder(const FString& InFolder, ESelectLatestTraceCriteria InCriteria)
{
FString Path = GetLatestTraceFileFromFolder(InFolder, InCriteria);
if (!Path.IsEmpty())
{
FUnrealInsightsLauncher::Get()->TryOpenTraceFromDestination(Path);
}
}
FString SInsightsStatusBarWidget::GetLatestTraceFileFromFolder(const FString& InFolder, ESelectLatestTraceCriteria InCriteria)
{
FString Result;
FString MostRecentTraceName;
FDateTime LatestDateTime;
auto Visitor = [&MostRecentTraceName, &LatestDateTime, InCriteria](const TCHAR* Filename, const FFileStatData& StatData)
{
if (FPaths::GetExtension(Filename) == TEXT("utrace"))
{
if (InCriteria == ESelectLatestTraceCriteria::ModifiedTime && LatestDateTime < StatData.ModificationTime)
{
LatestDateTime = StatData.ModificationTime;
MostRecentTraceName = Filename;
}
if (InCriteria == ESelectLatestTraceCriteria::CreatedTime && LatestDateTime < StatData.CreationTime)
{
LatestDateTime = StatData.CreationTime;
MostRecentTraceName = Filename;
}
}
return true;
};
IFileManager::Get().IterateDirectoryStat(*InFolder, Visitor);
if (!MostRecentTraceName.IsEmpty())
{
Result = FPaths::ConvertRelativePathToFull(MostRecentTraceName);
}
return Result;
}
void SInsightsStatusBarWidget::SetTraceDestination_Execute(ETraceDestination InDestination)
{
StatusBarTraceSettings.TraceDestination = InDestination;
}
bool SInsightsStatusBarWidget::SetTraceDestination_IsChecked(ETraceDestination InDestination)
{
return InDestination == StatusBarTraceSettings.TraceDestination;
}
bool SInsightsStatusBarWidget::SetTraceDestination_CanExecute()
{
if (!UE::Trace::IsTracing())
{
return true;
}
return false;
}
void SInsightsStatusBarWidget::SaveSnapshot()
{
if (StatusBarTraceSettings.TraceDestination == ETraceDestination::File)
{
const bool bResult = FTraceAuxiliary::WriteSnapshot(nullptr);
if (bResult)
{
ShowNotification(LOCTEXT("SnapshotSavedHeading", "Insights Snapshot saved."), LOCTEXT("SnapshotSavedFileText", "A snapshot .utrace with the most recent events has been saved to your Saved/Profiling/ directory."));
return;
}
}
else
{
const bool bResult = FTraceAuxiliary::SendSnapshot(nullptr);
if (bResult)
{
ShowNotification(LOCTEXT("SnapshotSavedHeading", "Insights Snapshot saved."), LOCTEXT("SnapshotSavedServerText", "A snapshot .utrace with the most recent events has been saved to your trace server."));
return;
}
}
LogMessage(LOCTEXT("SnapshotSavedError", "The snapshot could not be saved."));
}
bool SInsightsStatusBarWidget::SaveSnapshot_CanExecute() const
{
#if UE_TRACE_ENABLED
return true;
#else
return false;
#endif
}
FText SInsightsStatusBarWidget::GetTraceMenuItemText() const
{
if (UE::Trace::IsTracing())
{
return LOCTEXT("StopTraceButtonText", "Stop Trace");
}
return LOCTEXT("StartTraceButtonText", "Start Trace");
}
FText SInsightsStatusBarWidget::GetTraceMenuItemTooltipText() const
{
#if UE_TRACE_ENABLED
if (UE::Trace::IsTracing())
{
return LOCTEXT("StopTraceButtonTooltip", "Stop tracing");
}
return LOCTEXT("StartTraceButtonTooltip", "Start tracing to the selected trace destination.");
#else
return LOCTEXT("StartTraceDisabledButtonTooltip", "Trace system is disabled at compile time. Check the UE_TRACE_ENABLED define.");
#endif
}
bool SInsightsStatusBarWidget::ToggleTrace_CanExecute() const
{
#if UE_TRACE_ENABLED
return true;
#else
return false;
#endif
}
void SInsightsStatusBarWidget::ToggleTrace_OnClicked()
{
if (UE::Trace::IsTracing())
{
bool bResult = FTraceAuxiliary::Stop();
if (!bResult)
{
LogMessage(LOCTEXT("TraceStopFailedMsg", "There was no trace connection to stop."));
}
}
else
{
bool bResult = StartTracing();
if (bResult)
{
FString TraceDestinationStr = FTraceAuxiliary::GetTraceDestinationString();
if (TraceDestinationStr.IsEmpty())
{
TraceDestinationStr = TEXT("External Target");
}
ShowNotification(LOCTEXT("TraceMsg", "Trace Started"), FText::Format(LOCTEXT("TracingStartedText", "Trace is now active and saving to the following location (file or tracestore):\n{0}"), FText::FromString(TraceDestinationStr)));
}
else
{
LogMessage(LOCTEXT("TraceFailedToStartMsg", "Trace Failed to Start."));
}
}
}
bool SInsightsStatusBarWidget::PauseTrace_CanExecute()
{
return UE::Trace::IsTracing();
}
FText SInsightsStatusBarWidget::GetPauseTraceMenuItemTooltipText() const
{
if (!UE::Trace::IsTracing())
{
return LOCTEXT("PauseTraceDisabledButtonTooltip", "Tracing must be running to enable the pause functionality.");
}
return LOCTEXT("PauseTraceButtonTooltip", "Disables all enabled trace channels. The same channels will be re-enabled when tracing is resumed.");
}
void SInsightsStatusBarWidget::TogglePauseTrace_OnClicked()
{
if (!UE::Trace::IsTracing())
{
return;
}
if (FTraceAuxiliary::IsPaused())
{
FTraceAuxiliary::Resume();
}
else
{
FTraceAuxiliary::Pause();
}
}
bool SInsightsStatusBarWidget::StartTracing()
{
if (StatusBarTraceSettings.TraceDestination == ETraceDestination::TraceStore)
{
return FTraceAuxiliary::Start(FTraceAuxiliary::EConnectionType::Network, TEXT("localhost"), nullptr);
}
else if (StatusBarTraceSettings.TraceDestination == ETraceDestination::File)
{
return FTraceAuxiliary::Start(FTraceAuxiliary::EConnectionType::File, nullptr, nullptr);
}
return false;
}
bool SInsightsStatusBarWidget::GetBooleanSettingValue(const TCHAR* InSettingName)
{
bool Value = false;
GConfig->GetBool(SettingsCategory, InSettingName, Value, FEditorTraceUtilitiesModule::GetTraceUtilitiesIni());
return Value;
}
void SInsightsStatusBarWidget::ToggleBooleanSettingValue(const TCHAR* InSettingName)
{
bool Value = false;
GConfig->GetBool(SettingsCategory, InSettingName, Value, FEditorTraceUtilitiesModule::GetTraceUtilitiesIni());
GConfig->SetBool(SettingsCategory, InSettingName, !Value, FEditorTraceUtilitiesModule::GetTraceUtilitiesIni());
}
void SInsightsStatusBarWidget::OnTraceStarted(FTraceAuxiliary::EConnectionType InTraceType, const FString& InTraceDestination)
{
if (InTraceType == FTraceAuxiliary::EConnectionType::Network && GetBooleanSettingValue(OpenLiveSessionOnTraceStartSettingName))
{
bShouldTryOpenLiveSession = true;
}
}
void SInsightsStatusBarWidget::OnTraceStopped(FTraceAuxiliary::EConnectionType InTraceType, const FString& InTraceDestination)
{
if (GetBooleanSettingValue(OpenInsightsAfterTraceSettingName))
{
OpenLiveSession(InTraceDestination);
}
if (GetBooleanSettingValue(ShowInExplorerAfterTraceSettingName))
{
if (InTraceType == FTraceAuxiliary::EConnectionType::Network)
{
OpenTraceStoreDirectory(ESelectLatestTraceCriteria::ModifiedTime);
}
else if (InTraceType == FTraceAuxiliary::EConnectionType::File)
{
FPlatformProcess::ExploreFolder(*InTraceDestination);
}
}
}
void SInsightsStatusBarWidget::OnSnapshotSaved(FTraceAuxiliary::EConnectionType InTraceType, const FString& InTraceDestination)
{
if (GetBooleanSettingValue(OpenInsightsAfterTraceSettingName))
{
if (InTraceType == FTraceAuxiliary::EConnectionType::Network)
{
CacheTraceStorePath();
OpenLatestTraceFromFolder(TraceStorePath, ESelectLatestTraceCriteria::CreatedTime);
}
else if (InTraceType == FTraceAuxiliary::EConnectionType::File)
{
FUnrealInsightsLauncher::Get()->TryOpenTraceFromDestination(InTraceDestination);
}
}
if (GetBooleanSettingValue(ShowInExplorerAfterTraceSettingName))
{
if (InTraceType == FTraceAuxiliary::EConnectionType::Network)
{
OpenTraceStoreDirectory(ESelectLatestTraceCriteria::CreatedTime);
}
else if (InTraceType == FTraceAuxiliary::EConnectionType::File)
{
FPlatformProcess::ExploreFolder(*InTraceDestination);
}
}
}
void SInsightsStatusBarWidget::CacheTraceStorePath()
{
if (TraceStorePath.IsEmpty())
{
using UE::Trace::FStoreClient;
FStoreClient* StoreClientPtr = FStoreClient::Connect(TEXT("localhost"));
TUniquePtr<FStoreClient> StoreClient = TUniquePtr<FStoreClient>(StoreClientPtr);
if (!StoreClient)
{
LogMessage(LOCTEXT("FailedConnectionToStoreMsg", "Failed to connect to the store client."));
return;
}
const FStoreClient::FStatus* Status = StoreClient->GetStatus();
if (!Status)
{
LogMessage(LOCTEXT("FailedToGetStoreStatusMsg", "Failed to get the status of the store client."));
return;
}
TraceStorePath = FString(Status->GetStoreDir());
}
}
void SInsightsStatusBarWidget::CreateChannelsInfo()
{
#if UE_TRACE_ENABLED
ChannelsInfo.Empty();
UE::Trace::EnumerateChannels([](const UE::Trace::FChannelInfo& Info, void* User)
{
TArray<FChannelData>* Channels = (TArray<FChannelData>*) User;
FChannelData NewChannelInfo;
NewChannelInfo.Name = Info.Name;
NewChannelInfo.Desc = Info.Desc;
NewChannelInfo.bIsEnabled = Info.bIsEnabled;
NewChannelInfo.bIsReadOnly = Info.bIsReadOnly;
Channels->Add(NewChannelInfo);
return true;
}, &ChannelsInfo);
Algo::SortBy(ChannelsInfo, [](const FChannelData& Entry) { return Entry.Name; }, [](const FString& Rhs, const FString& Lhs)
{
return Rhs.Compare(Lhs) < 0;
});
#endif
}
void SInsightsStatusBarWidget::UpdateChannelsInfo()
{
#if UE_TRACE_ENABLED
UE::Trace::EnumerateChannels([](const UE::Trace::FChannelInfo& Info, void* User)
{
TArray<FChannelData>* Channels = (TArray<FChannelData>*) User;
FString Name = Info.Name;
int32 Index = Algo::BinarySearchBy(*Channels, Name, [](const FChannelData& Entry) { return Entry.Name; }, [](const FString& Rhs, const FString& Lhs)
{
return Rhs.Compare(Lhs) < 0;
});
if (Index != INDEX_NONE)
{
FChannelData& Data = (*Channels)[Index];
Data.Name = Info.Name;
Data.bIsEnabled = Info.bIsEnabled;
Data.bIsReadOnly = Info.bIsReadOnly;
}
return true;
}, &ChannelsInfo);
bShouldUpdateChannels = false;
#endif
}
void SInsightsStatusBarWidget::ToggleChannel_Execute(int32 Index)
{
if (Index < ChannelsInfo.Num())
{
const FString& ChannelName = ChannelsInfo[Index].Name;
bool bChannelShouldBeEnabled = !ChannelsInfo[Index].bIsEnabled;
UE::Trace::ToggleChannel(*ChannelName, bChannelShouldBeEnabled);
}
}
bool SInsightsStatusBarWidget::ToggleChannel_IsChecked(int32 Index)
{
if (bShouldUpdateChannels)
{
UpdateChannelsInfo();
}
if (Index < ChannelsInfo.Num())
{
return ChannelsInfo[Index].bIsEnabled;
}
return false;
}
bool SInsightsStatusBarWidget::TraceScreenshot_CanExecute() const
{
return SHOULD_TRACE_SCREENSHOT();
}
void SInsightsStatusBarWidget::TraceScreenshot_Execute()
{
#if UE_SCREENSHOT_TRACE_ENABLED
FTraceScreenshot::RequestScreenshot(TEXT(""), false);
#endif
}
bool SInsightsStatusBarWidget::TraceBookmark_CanExecute() const
{
return SHOULD_TRACE_BOOKMARK();
}
void SInsightsStatusBarWidget::TraceBookmark_Execute()
{
const FString Bookmark = FDateTime::Now().ToString(TEXT("Bookmark_%Y%m%d_%H%M%S"));
TRACE_BOOKMARK(TEXT("%s"), *Bookmark);
}
FText SInsightsStatusBarWidget::GetTraceRegionName()
{
return GConfig->GetTextOrDefault(SettingsCategory, TraceRegionSettingName, FText::FromString("ToolbarCustomRegion"), FEditorTraceUtilitiesModule::GetTraceUtilitiesIni());
}
FText SInsightsStatusBarWidget::GetTraceRegionNameDesc()
{
return RegionIsActive() ? LOCTEXT("TraceRegionNameDisabledDesc", "The name of the region to start or stop. Cannot be edited while a region is active.") : LOCTEXT("TraceRegionNameEnabledDesc", "The name of the region to start or stop.");
}
void SInsightsStatusBarWidget::ToggleRegion_Execute()
{
if(RegionIsActive())
{
TRACE_END_REGION_WITH_ID(RegionId);
RegionId = 0;
}
else
{
RegionId = TRACE_BEGIN_REGION_WITH_ID(*GetTraceRegionName().ToString());
}
}
bool SInsightsStatusBarWidget::ToggleRegion_CanExecute() const
{
return SHOULD_TRACE_REGION();
}
bool SInsightsStatusBarWidget::RegionIsActive() const
{
return RegionId > 0;
}
FText SInsightsStatusBarWidget::GetRegionSwitchLabelText() const
{
if(RegionIsActive())
{
return LOCTEXT("TraceEndRegionLabel", "End Region");
}
return LOCTEXT("TraceBeginRegionLabel", "Begin Region");
}
FText SInsightsStatusBarWidget::GetRegionSwitchDescText() const
{
if (!ToggleRegion_CanExecute())
{
return LOCTEXT("TraceRegionDisabledDesc", "Regions can only be traced when the Region channel in enabled.");
}
if(RegionIsActive())
{
return LOCTEXT("TraceEndRegionDesc", "Marks the ending of a trace region with the name input above.");
}
return LOCTEXT("TraceBeginRegionDesc", "Marks the beginning of a trace region with the name input above.");
}
void SInsightsStatusBarWidget::PopulateRecentTracesList()
{
Traces.Empty();
bool bIsFromTraceStore = true;
auto Visitor = [this, &bIsFromTraceStore](const TCHAR* Filename, const FFileStatData& StatData)
{
if (FPaths::GetExtension(Filename) == TEXT("utrace"))
{
TSharedPtr<FTraceFileInfo> TraceInfo = MakeShared<FTraceFileInfo>();
TraceInfo->FilePath = Filename;
TraceInfo->ModifiedTime = StatData.ModificationTime;
TraceInfo->bIsFromTraceStore = bIsFromTraceStore;
Traces.Add(TraceInfo);
}
return true;
};
IFileManager::Get().IterateDirectoryStat(*TraceStorePath, Visitor);
bIsFromTraceStore = false;
IFileManager::Get().IterateDirectoryStat(*FPaths::ProfilingDir(), Visitor);
Algo::SortBy(Traces, [](TSharedPtr<FTraceFileInfo> TraceInfo) { return *TraceInfo; });
constexpr int32 MaxRecentTraces = 15;
if (Traces.Num() > MaxRecentTraces)
{
Traces.RemoveAt(MaxRecentTraces - 1, Traces.Num() - MaxRecentTraces);
}
}
void SInsightsStatusBarWidget::OpenTrace(int32 Index)
{
if (Index < Traces.Num())
{
if (FTraceAuxiliary::GetConnectionType() == FTraceAuxiliary::EConnectionType::Network)
{
if (LiveSessionTracker->HasData())
{
const FLiveSessionsMap& Sessions = LiveSessionTracker->GetLiveSessions();
FString FileName = FPaths::GetBaseFilename(Traces[Index]->FilePath);
const uint32* TraceId = Sessions.Find(FileName);
if (TraceId)
{
FUnrealInsightsLauncher::Get()->OpenRemoteTrace(TEXT("localhost"), uint16(LiveSessionTracker->GetStorePort()), *TraceId);
return;
}
}
}
FUnrealInsightsLauncher::Get()->TryOpenTraceFromDestination(Traces[Index]->FilePath);
}
}
FText SInsightsStatusBarWidget::GetTraceScreenshotTooltipText() const
{
if (TraceScreenshot_CanExecute())
{
return LOCTEXT("TraceScreenshotTooltip1", "Takes a screenshot and sends it to the trace.");
}
else
{
return LOCTEXT("TraceScreenshotTooltip2", "Screenshots can only be traced when the Screenshot channel is enabled.");
}
}
FText SInsightsStatusBarWidget::GetTraceBookmarkTooltipText() const
{
if (TraceBookmark_CanExecute())
{
return LOCTEXT("TraceBookmarkTooltip1", "Traces a bookmark.");
}
else
{
return LOCTEXT("TraceBookmarkTooltip2", "Bookmarks can only be traced when the Bookmark channel is enabled.");
}
}
} // namespace UE::EditorTraceUtilities
#undef LOCTEXT_NAMESPACE