1078 lines
37 KiB
C++
1078 lines
37 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "EditorTelemetry.h"
|
|
|
|
#include "AnalyticsTracer.h"
|
|
#include "AssetRegistry/AssetRegistryTelemetry.h"
|
|
#include "CollectionManagerModule.h"
|
|
#include "ContentBrowserModule.h"
|
|
#include "ContentBrowserTelemetry.h"
|
|
#include "CookOnTheSide/CookOnTheFlyServer.h"
|
|
#include "DerivedDataCacheInterface.h"
|
|
#include "DerivedDataCacheUsageStats.h"
|
|
#include "Editor.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "Engine/AssetManager.h"
|
|
#include "Experimental/ZenServerInterface.h"
|
|
#include "FileHelpers.h"
|
|
#include "HttpManager.h"
|
|
#include "HttpModule.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Policies/CondensedJsonPrintPolicy.h"
|
|
#include "ProfilingDebugging/CookStats.h"
|
|
#include "ShaderStats.h"
|
|
#include "StudioTelemetry.h"
|
|
#include "Subsystems/AssetEditorSubsystem.h"
|
|
#include "TelemetryRouter.h"
|
|
#include "UObject/ICookInfo.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "Virtualization/VirtualizationSystem.h"
|
|
|
|
namespace Private
|
|
{
|
|
const FName ContentBrowserModuleName = TEXT("ContentBrowser");
|
|
|
|
// Json writer subclass to allow us to avoid using a SharedPtr to write basic Json.
|
|
typedef TCondensedJsonPrintPolicy<TCHAR> FPrintPolicy;
|
|
class FAnalyticsJsonWriter : public TJsonStringWriter<FPrintPolicy>
|
|
{
|
|
public:
|
|
explicit FAnalyticsJsonWriter(FString* Out) : TJsonStringWriter<FPrintPolicy>(Out, 0)
|
|
{
|
|
}
|
|
};
|
|
}
|
|
|
|
const TCHAR* LexToString(ECollectionTelemetryAssetAddedWorkflow Enum)
|
|
{
|
|
switch(Enum)
|
|
{
|
|
case ECollectionTelemetryAssetAddedWorkflow::ContextMenu: return TEXT("ContextMenu");
|
|
case ECollectionTelemetryAssetAddedWorkflow::DragAndDrop: return TEXT("DragAndDrop");
|
|
default: return TEXT("");
|
|
}
|
|
}
|
|
|
|
const TCHAR* LexToString(ECollectionTelemetryAssetRemovedWorkflow Enum)
|
|
{
|
|
switch(Enum)
|
|
{
|
|
case ECollectionTelemetryAssetRemovedWorkflow::ContextMenu: return TEXT("ContextMenu");
|
|
default: return TEXT("");
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
FString AnalyticsOptionalToStringOrNull(const TOptional<T>& Opt)
|
|
{
|
|
return Opt.IsSet() ? AnalyticsConversionToString(Opt.GetValue()) : FString(TEXT("null"));
|
|
}
|
|
|
|
FEditorTelemetry& FEditorTelemetry::Get()
|
|
{
|
|
static FEditorTelemetry StudioTelemetryEditorInstance = FEditorTelemetry();
|
|
return StudioTelemetryEditorInstance;
|
|
}
|
|
|
|
void FEditorTelemetry::RecordEvent_Cooking(TArray<FAnalyticsEventAttribute> Attributes)
|
|
{
|
|
#if ENABLE_COOK_STATS
|
|
|
|
const int SchemaVersion = 3;
|
|
|
|
Attributes.Emplace(TEXT("SchemaVersion"), SchemaVersion);
|
|
|
|
TMap<FString,FAnalyticsEventAttribute> CookAttributes;
|
|
|
|
// Sends each cook stat to the studio analytics system.
|
|
auto GatherAnalyticsAttributes = [&CookAttributes](const FString& StatName, const TArray<FCookStatsManager::StringKeyValue>& StatAttributes)
|
|
{
|
|
for (const auto& Attr : StatAttributes)
|
|
{
|
|
const FString FormattedAttrName = (StatName + "_" + Attr.Key).Replace(TEXT("."), TEXT("_"));
|
|
|
|
if (CookAttributes.Find(FormattedAttrName)==nullptr)
|
|
{
|
|
CookAttributes.Emplace(FormattedAttrName, Attr.Value.IsNumeric() ? FAnalyticsEventAttribute(FormattedAttrName, FCString::Atof(*Attr.Value)) : FAnalyticsEventAttribute(FormattedAttrName, Attr.Value));
|
|
}
|
|
}
|
|
};
|
|
|
|
// Now actually grab the stats
|
|
FCookStatsManager::LogCookStats(GatherAnalyticsAttributes);
|
|
|
|
// Add the values to the attributes
|
|
for (TMap<FString, FAnalyticsEventAttribute>::TConstIterator it(CookAttributes); it; ++it)
|
|
{
|
|
Attributes.Emplace((*it).Value);
|
|
}
|
|
|
|
// Gather the DDC summary stats
|
|
FDerivedDataCacheSummaryStats SummaryStats;
|
|
|
|
GatherDerivedDataCacheSummaryStats(SummaryStats);
|
|
|
|
// Append to the attributes
|
|
for (const FDerivedDataCacheSummaryStat& Stat : SummaryStats.Stats)
|
|
{
|
|
FString AttributeName = TEXT("DDC_Summary") + Stat.Key.Replace(TEXT("."), TEXT("_"));
|
|
|
|
if (Stat.Value.IsNumeric())
|
|
{
|
|
Attributes.Emplace(AttributeName, FCString::Atof(*Stat.Value));
|
|
}
|
|
else
|
|
{
|
|
Attributes.Emplace(AttributeName, Stat.Value);
|
|
}
|
|
}
|
|
|
|
#if UE_WITH_ZEN
|
|
// Gather Zen analytics
|
|
if (UE::Zen::IsDefaultServicePresent())
|
|
{
|
|
UE::Zen::GetDefaultServiceInstance().GatherAnalytics(Attributes);
|
|
}
|
|
#endif
|
|
|
|
if (UE::Virtualization::IVirtualizationSystem::Get().IsEnabled())
|
|
{
|
|
// Gather Virtualization analytics
|
|
UE::Virtualization::IVirtualizationSystem::Get().GatherAnalytics(Attributes);
|
|
}
|
|
|
|
FShaderStatsFunctions::GatherShaderAnalytics(Attributes);
|
|
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Core.Cooking"), Attributes);
|
|
#endif
|
|
}
|
|
|
|
void FEditorTelemetry::RecordEvent_Loading(const FString& Context, double LoadingSeconds, TArray<FAnalyticsEventAttribute> Attributes )
|
|
{
|
|
const int SchemaVersion = 4;
|
|
|
|
Attributes.Emplace(TEXT("SchemaVersion"), SchemaVersion);
|
|
Attributes.Emplace(TEXT("Context"), Context);
|
|
Attributes.Emplace(TEXT("LoadingName"), Context);
|
|
Attributes.Emplace(TEXT("LoadingSeconds"), LoadingSeconds);
|
|
|
|
#if ENABLE_COOK_STATS
|
|
|
|
#if UE_WITH_ZEN
|
|
// Gather Zen analytics
|
|
if (UE::Zen::IsDefaultServicePresent())
|
|
{
|
|
UE::Zen::GetDefaultServiceInstance().GatherAnalytics(Attributes);
|
|
}
|
|
#endif
|
|
|
|
if (UE::Virtualization::IVirtualizationSystem::Get().IsEnabled())
|
|
{
|
|
// Gather Virtualization analytics
|
|
UE::Virtualization::IVirtualizationSystem::Get().GatherAnalytics(Attributes);
|
|
}
|
|
|
|
// Gather the DDC summary stats
|
|
FDerivedDataCacheSummaryStats SummaryStats;
|
|
|
|
GatherDerivedDataCacheSummaryStats(SummaryStats);
|
|
|
|
// Append to the attributes
|
|
for (const FDerivedDataCacheSummaryStat& Stat : SummaryStats.Stats)
|
|
{
|
|
FString AttributeName = TEXT("DDC_Summary_") + Stat.Key.Replace(TEXT("."), TEXT("_"));
|
|
|
|
if (Stat.Value.IsNumeric())
|
|
{
|
|
Attributes.Emplace(AttributeName, FCString::Atof(*Stat.Value));
|
|
}
|
|
else
|
|
{
|
|
Attributes.Emplace(AttributeName, Stat.Value);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Core.Loading"), Attributes);
|
|
}
|
|
|
|
void FEditorTelemetry::RecordEvent_DDCResource(const FString& Context, TArray<FAnalyticsEventAttribute> Attributes)
|
|
{
|
|
#if ENABLE_COOK_STATS
|
|
// Gather the latest resource stats
|
|
TArray<FDerivedDataCacheResourceStat> ResourceStats;
|
|
|
|
GatherDerivedDataCacheResourceStats(ResourceStats);
|
|
|
|
const int SchemaVersion = 4;
|
|
|
|
Attributes.Emplace(TEXT("SchemaVersion"), SchemaVersion);
|
|
Attributes.Emplace(TEXT("Context"), Context);
|
|
|
|
// Send a resource event per asset type
|
|
for (const FDerivedDataCacheResourceStat& Stat : ResourceStats)
|
|
{
|
|
const double TotalTimeSec = Stat.BuildTimeSec + Stat.LoadTimeSec;
|
|
const int64 TotalSizeMB = Stat.BuildSizeMB + Stat.LoadSizeMB;
|
|
|
|
if (Stat.AssetType.IsEmpty() || Stat.TotalCount==0)
|
|
{
|
|
// Empty asset type or nothing was built or loaded for this type
|
|
continue;
|
|
}
|
|
|
|
TArray<FAnalyticsEventAttribute> EventAttributes = Attributes;
|
|
|
|
EventAttributes.Emplace(TEXT("AssetType"), Stat.AssetType);
|
|
EventAttributes.Emplace(TEXT("Load_Count"), Stat.LoadCount);
|
|
EventAttributes.Emplace(TEXT("Load_TimeSec"), Stat.LoadTimeSec);
|
|
EventAttributes.Emplace(TEXT("Load_SizeMB"), Stat.LoadSizeMB);
|
|
EventAttributes.Emplace(TEXT("Build_Count"), Stat.BuildCount);
|
|
EventAttributes.Emplace(TEXT("Build_TimeSec"), Stat.BuildTimeSec);
|
|
EventAttributes.Emplace(TEXT("Build_SizeMB"), Stat.BuildSizeMB);
|
|
EventAttributes.Emplace(TEXT("Total_Count"), Stat.TotalCount);
|
|
EventAttributes.Emplace(TEXT("Total_TimeSec"), TotalTimeSec);
|
|
EventAttributes.Emplace(TEXT("Total_SizeMB"), TotalSizeMB);
|
|
EventAttributes.Emplace(TEXT("Efficiency"), Stat.Efficiency);
|
|
EventAttributes.Emplace(TEXT("Thread_TimeSec"), Stat.GameThreadTimeSec);
|
|
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Core.DDC.Resource"), EventAttributes);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void FEditorTelemetry::RecordEvent_DDCSummary(const FString& Context, TArray<FAnalyticsEventAttribute> Attributes)
|
|
{
|
|
#if ENABLE_COOK_STATS
|
|
const int SchemaVersion = 4;
|
|
|
|
Attributes.Emplace(TEXT("SchemaVersion"), SchemaVersion);
|
|
Attributes.Emplace(TEXT("Context"), Context);
|
|
|
|
// Gather the summary stats
|
|
FDerivedDataCacheSummaryStats SummaryStats;
|
|
|
|
GatherDerivedDataCacheSummaryStats(SummaryStats);
|
|
|
|
// Append to the attributes
|
|
for (const FDerivedDataCacheSummaryStat& Stat : SummaryStats.Stats)
|
|
{
|
|
FString AttributeName = Stat.Key.Replace(TEXT("."), TEXT("_"));
|
|
|
|
if (Stat.Value.IsNumeric())
|
|
{
|
|
Attributes.Emplace(AttributeName, FCString::Atof(*Stat.Value));
|
|
}
|
|
else
|
|
{
|
|
Attributes.Emplace(AttributeName, Stat.Value);
|
|
}
|
|
}
|
|
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Core.DDC.Summary"), Attributes);
|
|
#endif
|
|
}
|
|
|
|
void FEditorTelemetry::RecordEvent_Zen(const FString& Context, TArray<FAnalyticsEventAttribute> Attributes)
|
|
{
|
|
#if UE_WITH_ZEN
|
|
// Gather Zen analytics
|
|
if (UE::Zen::IsDefaultServicePresent())
|
|
{
|
|
const int SchemaVersion = 2;
|
|
|
|
Attributes.Emplace(TEXT("SchemaVersion"), SchemaVersion);
|
|
Attributes.Emplace(TEXT("Context"), Context);
|
|
|
|
UE::Zen::GetDefaultServiceInstance().GatherAnalytics(Attributes);
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Core.Zen"), Attributes);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FEditorTelemetry::RecordEvent_VirtualAssets(const FString& Context, TArray<FAnalyticsEventAttribute> Attributes)
|
|
{
|
|
if (UE::Virtualization::IVirtualizationSystem::Get().IsEnabled())
|
|
{
|
|
const int SchemaVersion = 2;
|
|
|
|
Attributes.Emplace(TEXT("SchemaVersion"), SchemaVersion);
|
|
Attributes.Emplace(TEXT("Context"), Context);
|
|
|
|
// Gather Virtualization analytics
|
|
UE::Virtualization::IVirtualizationSystem::Get().GatherAnalytics(Attributes);
|
|
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Core.VirtualAssets"), Attributes);
|
|
}
|
|
}
|
|
|
|
void FEditorTelemetry::RecordEvent_MemoryLLM(const FString& Context, TArray<FAnalyticsEventAttribute> Attributes)
|
|
{
|
|
#if ENABLE_LOW_LEVEL_MEM_TRACKER
|
|
if (FStudioTelemetry::Get().IsSessionRunning())
|
|
{
|
|
auto RecordLLMMemoryEvent = [&Attributes](const FString& Context, const FString& TagSet, TMap<FName, uint64>& LLMTrackedMemoryMap)
|
|
{
|
|
for (TMap<FName, uint64>::TConstIterator It(LLMTrackedMemoryMap); It; ++It)
|
|
{
|
|
const int SchemaVersion = 2;
|
|
|
|
TArray<FAnalyticsEventAttribute> EventAttributes = Attributes;
|
|
|
|
EventAttributes.Emplace(TEXT("SchemaVersion"), SchemaVersion);
|
|
EventAttributes.Emplace(TEXT("Context"), Context);
|
|
EventAttributes.Emplace(TEXT("TagSet"), TagSet);
|
|
EventAttributes.Emplace(TEXT("Name"), It->Key);
|
|
EventAttributes.Emplace(TEXT("Size"), It->Value);
|
|
|
|
FStudioTelemetry::Get().RecordEvent("Core.Memory.LLM", EventAttributes);
|
|
}
|
|
};
|
|
|
|
// None TagSet
|
|
TMap<FName, uint64> LLMTrackedNoneMemory;
|
|
FLowLevelMemTracker::Get().GetTrackedTagsNamesWithAmount(LLMTrackedNoneMemory, ELLMTracker::Default, ELLMTagSet::None);
|
|
RecordLLMMemoryEvent(Context, TEXT("None"), LLMTrackedNoneMemory);
|
|
|
|
// AssetClasses TagSet
|
|
TMap<FName, uint64> LLMTrackedAssetClassesMemory;
|
|
FLowLevelMemTracker::Get().GetTrackedTagsNamesWithAmount(LLMTrackedAssetClassesMemory, ELLMTracker::Default, ELLMTagSet::AssetClasses);
|
|
RecordLLMMemoryEvent(Context, TEXT("AssetClasses"), LLMTrackedAssetClassesMemory);
|
|
|
|
// Asset TagSet
|
|
TMap<FName, uint64> LLMTrackedAssetMemory;
|
|
FLowLevelMemTracker::Get().GetTrackedTagsNamesWithAmount(LLMTrackedAssetMemory, ELLMTracker::Default, ELLMTagSet::Assets);
|
|
RecordLLMMemoryEvent(Context, TEXT("Assets"), LLMTrackedAssetMemory);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FEditorTelemetry::RecordEvent_CoreSystems(const FString& Context, TArray<FAnalyticsEventAttribute> Attributes)
|
|
{
|
|
FEditorTelemetry::RecordEvent_DDCResource(Context, Attributes);
|
|
FEditorTelemetry::RecordEvent_DDCSummary(Context, Attributes);
|
|
FEditorTelemetry::RecordEvent_Zen(Context, Attributes);
|
|
FEditorTelemetry::RecordEvent_VirtualAssets(Context, Attributes);
|
|
FEditorTelemetry::RecordEvent_MemoryLLM(Context, Attributes);
|
|
}
|
|
|
|
void FEditorTelemetry::RegisterCollectionWorkflowDelegates(FTelemetryRouter& Router)
|
|
{
|
|
Router.OnTelemetry<FAssetAddedToCollectionTelemetryEvent>([](const FAssetAddedToCollectionTelemetryEvent& Event)
|
|
{
|
|
const int SchemaVersion = 1;
|
|
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.Collections.AssetsAdded"),
|
|
{
|
|
{ TEXT("SchemaVersion"), SchemaVersion},
|
|
{ TEXT("DurationSec"), Event.DurationSec},
|
|
{ TEXT("ObjectCount"), Event.NumAdded},
|
|
{ TEXT("Workflow"), Event.Workflow},
|
|
{ TEXT("CollectionShareType"), ECollectionShareType::ToString(Event.CollectionShareType)},
|
|
});
|
|
});
|
|
|
|
Router.OnTelemetry<FAssetRemovedFromCollectionTelemetryEvent>([](const FAssetRemovedFromCollectionTelemetryEvent& Event)
|
|
{
|
|
const int SchemaVersion = 1;
|
|
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.Collections.AssetsRemoved"),
|
|
{
|
|
{ TEXT("SchemaVersion"), SchemaVersion},
|
|
{ TEXT("DurationSec"), Event.DurationSec},
|
|
{ TEXT("ObjectCount"), Event.NumRemoved},
|
|
{ TEXT("Workflow"), Event.Workflow},
|
|
{ TEXT("CollectionShareType"), ECollectionShareType::ToString(Event.CollectionShareType) },
|
|
});
|
|
});
|
|
|
|
Router.OnTelemetry<FCollectionCreatedTelemetryEvent>([](const FCollectionCreatedTelemetryEvent& Event)
|
|
{
|
|
const int SchemaVersion = 1;
|
|
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.Collections.CollectionCreated"),
|
|
{
|
|
{ TEXT("SchemaVersion"), SchemaVersion},
|
|
{ TEXT("DurationSec"), Event.DurationSec},
|
|
{ TEXT("CollectionShareType"), ECollectionShareType::ToString(Event.CollectionShareType)},
|
|
});
|
|
});
|
|
|
|
Router.OnTelemetry<FCollectionsDeletedTelemetryEvent>([](const FCollectionsDeletedTelemetryEvent& Event)
|
|
{
|
|
const int SchemaVersion = 1;
|
|
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.Collections.CollectionDeleted"),
|
|
{
|
|
{ TEXT("SchemaVersion"), SchemaVersion},
|
|
{ TEXT("DurationSec"), Event.DurationSec},
|
|
{ TEXT("ObjectCount"), Event.CollectionsDeleted},
|
|
});
|
|
});
|
|
}
|
|
|
|
extern ENGINE_API float GAverageFPS;
|
|
|
|
void FEditorTelemetry::HitchSamplerCallback()
|
|
{
|
|
// Only sample framerate when we have focus
|
|
if (FApp::HasFocus())
|
|
{
|
|
// Sample a rolling average of FPS
|
|
HitchAvergageFPS = (HitchAvergageFPS * HitchSampleCount + GAverageFPS) / (double)(HitchSampleCount + 1);
|
|
HitchSampleCount++;
|
|
}
|
|
}
|
|
|
|
void FEditorTelemetry::HeartbeatCallback()
|
|
{
|
|
if (HitchSampleCount>0)
|
|
{
|
|
// Hitching is when FPS is below our threshold
|
|
const bool IsHitching = HitchAvergageFPS < MinFPSForHitching;
|
|
static uint32 HitchCount = 0;
|
|
HitchCount += IsHitching ? 1 : 0;
|
|
|
|
if (IsHitching == false && HitchingSpan.IsValid() == true)
|
|
{
|
|
// No longer hitching and we have started a hitch span
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
const double ElapsedTime = HitchingSpan->GetElapsedTime();
|
|
Attributes.Emplace(FAnalyticsEventAttribute(TEXT("Hitch_Count"), HitchCount));
|
|
Attributes.Emplace(FAnalyticsEventAttribute(TEXT("Hitch_HitchesPerSecond"), ElapsedTime>0? (float)HitchCount / ElapsedTime : 0.0));
|
|
Attributes.Emplace(FAnalyticsEventAttribute(TEXT("Hitch_AverageFPS"), HitchAvergageFPS));
|
|
Attributes.Emplace(TEXT("MapName"), EditorMapName);
|
|
Attributes.Emplace(TEXT("PIE_MapName"), PIEMapName);
|
|
|
|
// End the hitch Span
|
|
FStudioTelemetry::Get().EndSpan(HitchingSpan, Attributes);
|
|
|
|
// Record the hitch event
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Core.Hitch"), Attributes);
|
|
|
|
// Record core systems events for the hitch
|
|
RecordEvent_CoreSystems(TEXT("Hitch"));
|
|
|
|
// No longer need the hitch span for now so reset it
|
|
HitchingSpan.Reset();
|
|
}
|
|
else if (IsHitching == true && HitchingSpan.IsValid() == false)
|
|
{
|
|
// We are hitching and we have not started a hitch span
|
|
HitchingSpan = FStudioTelemetry::Get().StartSpan(HitchingSpanName);
|
|
HitchCount = 1;
|
|
}
|
|
|
|
// Reset the hitch sampler
|
|
HitchSampleCount = 0;
|
|
HitchAvergageFPS = 0;
|
|
}
|
|
}
|
|
|
|
void FEditorTelemetry::StartSession()
|
|
{
|
|
if (FStudioTelemetry::Get().IsSessionRunning() == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SessionStartTime = FPlatformTime::Seconds();
|
|
|
|
// Install Editor Only Mode callbacks. Do not record these for Editor Commandlet runs
|
|
if (GIsEditor==true && IsRunningCommandlet()==false)
|
|
{
|
|
// Start Editor and Editor Boot span. Note : this will only start when the plugin is loaded and as such will miss any activity that runs beforehand
|
|
EditorSpan = FStudioTelemetry::Get().StartSpan(EditorSpanName);
|
|
EditorBootSpan = FStudioTelemetry::Get().StartSpan(EditorBootSpanName, EditorSpan);
|
|
EditorMapName = TEXT("None");
|
|
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(FAnalyticsEventAttribute(TEXT("MapName"), EditorMapName));
|
|
EditorBootSpan->AddAttributes(Attributes);
|
|
|
|
FEditorDelegates::OnEditorBoot.AddLambda([this](double)
|
|
{
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("PluginCount"), TotalPluginCount);
|
|
EditorBootSpan->AddAttributes(Attributes);
|
|
|
|
FStudioTelemetry::Get().EndSpan(EditorBootSpan);
|
|
|
|
// Callback is received when the editor has booted but has not been initialized
|
|
FEditorTelemetry::RecordEvent_Loading(TEXT("Editor.Boot"), EditorBootSpan->GetDuration(), EditorBootSpan->GetAttributes());
|
|
FEditorTelemetry::RecordEvent_CoreSystems(TEXT("Editor.Boot"), EditorBootSpan->GetAttributes());
|
|
|
|
EditorInitilizeSpan = FStudioTelemetry::Get().StartSpan(EditorInitilizeSpanName, EditorSpan);
|
|
});
|
|
|
|
FEditorDelegates::OnEditorInitialized.AddLambda([this](double TimeToInitializeEditor)
|
|
{
|
|
TimeToBootEditor = TimeToInitializeEditor;
|
|
|
|
// Editor has initialized
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("MapName"), EditorMapName);
|
|
Attributes.Emplace(TEXT("PluginCount"), TotalPluginCount);
|
|
EditorInitilizeSpan->AddAttributes(Attributes);
|
|
|
|
// Editor has finished initializing so start the Editor Interact span
|
|
FStudioTelemetry::Get().EndSpan(EditorInitilizeSpan);
|
|
|
|
FEditorTelemetry::RecordEvent_Loading(TEXT("Editor.Initialize"), EditorInitilizeSpan->GetDuration(), EditorInitilizeSpan->GetAttributes());
|
|
FEditorTelemetry::RecordEvent_CoreSystems(TEXT("Editor.Initialize"), EditorInitilizeSpan->GetAttributes());
|
|
FEditorTelemetry::RecordEvent_Loading(TEXT("TimeToEdit"), TimeToInitializeEditor, EditorInitilizeSpan->GetAttributes());
|
|
|
|
EditorInteractSpan = FStudioTelemetry::Get().StartSpan(EditorInteractSpanName, EditorSpan);
|
|
|
|
|
|
// Install callbacks for Open Asset Dialogue
|
|
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OnAssetEditorRequestedOpen().AddLambda([this](UObject* Asset)
|
|
{
|
|
AssetOpenStartTime = FPlatformTime::Seconds();
|
|
FStudioTelemetry::Get().StartSpan(OpenAssetEditorSpan);
|
|
});
|
|
|
|
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OnAssetOpenedInEditor().AddLambda([this](UObject* Asset, IAssetEditorInstance*)
|
|
{
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("MapName"), EditorMapName);
|
|
|
|
if (Asset != nullptr)
|
|
{
|
|
Attributes.Emplace(TEXT("AssetPath"), Asset->GetFullName());
|
|
Attributes.Emplace(TEXT("AssetClass"), Asset->GetClass()->GetName());
|
|
}
|
|
|
|
FStudioTelemetry::Get().EndSpan(OpenAssetEditorSpan, Attributes);
|
|
});
|
|
|
|
// Setup a timer for a Heartbeat callback
|
|
FTimerDelegate HeartbeatDelegate;
|
|
HeartbeatDelegate.BindRaw(this, &FEditorTelemetry::HeartbeatCallback);
|
|
GEditor->GetTimerManager()->SetTimer(TelemetryHeartbeatTimerHandle, HeartbeatDelegate, HeartbeatIntervalSeconds, true);
|
|
|
|
// Setup the timer for the Hitch Detector callback
|
|
FTimerDelegate HitchSamplerDelegate;
|
|
HitchSamplerDelegate.BindRaw(this, &FEditorTelemetry::HitchSamplerCallback);
|
|
GEditor->GetTimerManager()->SetTimer(TelemetryHitchSamplerTimerHandle, HitchSamplerDelegate, HitchSamplerIntervalSeconds, true);
|
|
});
|
|
|
|
// Install PIE Mode callbacks
|
|
FEditorDelegates::StartPIE.AddLambda([this](bool)
|
|
{
|
|
// PIE mode has been started. The user has pressed the Start PIE button.
|
|
// Finish the Editor span
|
|
FStudioTelemetry::Get().EndSpan(EditorSpan);
|
|
EditorSessionCount++;
|
|
|
|
// Start PIE span
|
|
PIESpan = FStudioTelemetry::Get().StartSpan(PIESpanName);
|
|
|
|
// Append the PIE transition count to the PIE name
|
|
PIEStartupSpan = FStudioTelemetry::Get().StartSpan(PIESessionCount == 0 ? PIEStartupSpanName : FName(*FString::Printf(TEXT("%s%d"), *PIEStartupSpanName.ToString(), PIESessionCount)), PIESpan);
|
|
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("MapName"), EditorMapName);
|
|
|
|
PIESpan->AddAttributes(Attributes);
|
|
PIEStartupSpan->AddAttributes(Attributes);
|
|
});
|
|
|
|
FEditorDelegates::PreBeginPIE.AddLambda([this](bool)
|
|
{
|
|
PIEPreBeginSpan = FStudioTelemetry::Get().StartSpan(PIESessionCount == 0 ? PIEPreBeginSpanName : FName(*FString::Printf(TEXT("%s%d"), *PIEPreBeginSpanName.ToString(), PIESessionCount)), PIESpan);
|
|
PIEPreBeginSpan->AddAttributes(PIESpan->GetAttributes());
|
|
});
|
|
|
|
FEditorDelegates::BeginPIE.AddLambda([this](bool)
|
|
{
|
|
FStudioTelemetry::Get().EndSpan(PIEPreBeginSpan);
|
|
});
|
|
|
|
FWorldDelegates::OnPIEMapCreated.AddLambda([this](UGameInstance* GameInstance)
|
|
{
|
|
// A new PIE map was created
|
|
PIELoadMapSpan = FStudioTelemetry::Get().StartSpan(PIELoadMapSpanName, PIEStartupSpan);
|
|
});
|
|
|
|
FWorldDelegates::OnPIEMapReady.AddLambda([this](UGameInstance* GameInstance)
|
|
{
|
|
// PIE map is now loaded and ready to use
|
|
PIEMapName = FPaths::GetBaseFilename(GameInstance->PIEMapName);
|
|
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("MapName"), EditorMapName);
|
|
Attributes.Emplace(TEXT("PIE_MapName"), PIEMapName);
|
|
|
|
PIELoadMapSpan->AddAttributes(Attributes);
|
|
|
|
FStudioTelemetry::Get().EndSpan(PIELoadMapSpan);
|
|
|
|
FEditorTelemetry::RecordEvent_Loading(TEXT("PIE.LoadMap"), PIELoadMapSpan->GetDuration(), PIELoadMapSpan->GetAttributes());
|
|
FEditorTelemetry::RecordEvent_CoreSystems(TEXT("PIE.LoadMap"), PIELoadMapSpan->GetAttributes());
|
|
});
|
|
|
|
FWorldDelegates::OnPIEReady.AddLambda([this](UGameInstance* GameInstance)
|
|
{
|
|
if (PIESpan.IsValid())
|
|
{
|
|
if (PIEStartupSpan.IsValid())
|
|
{
|
|
// PIE is now ready for user interaction
|
|
|
|
// Keep track of the PIE transition counts
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("PIE_TransitionCount"), PIESessionCount);
|
|
|
|
PIESpan->AddAttributes(Attributes);
|
|
PIEStartupSpan->AddAttributes(Attributes);
|
|
|
|
FStudioTelemetry::Get().EndSpan(PIEStartupSpan);
|
|
|
|
// Record the PIE startup
|
|
FEditorTelemetry::RecordEvent_Loading(TEXT("PIE.Startup"), PIEStartupSpan->GetDuration(), PIEStartupSpan->GetAttributes());
|
|
FEditorTelemetry::RecordEvent_CoreSystems(TEXT("PIE.Startup"), PIEStartupSpan->GetAttributes());
|
|
|
|
// Record the time from start PIE to PIE
|
|
if (PIESessionCount == 0)
|
|
{
|
|
const double TimeInEditor = EditorLoadMapSpan.IsValid() ? EditorLoadMapSpan->GetDuration() : 0.0;
|
|
const double TimeToStartPIE = PIEStartupSpan->GetDuration();
|
|
const double TimeToBootToPIE = TimeToBootEditor + TimeInEditor + TimeToStartPIE;
|
|
|
|
// Record the absolute time from editor boot to PIE
|
|
FEditorTelemetry::RecordEvent_Loading(TEXT("TimeToPIE"), TimeToBootToPIE, PIEStartupSpan->GetAttributes());
|
|
}
|
|
}
|
|
|
|
PIEInteractSpan = FStudioTelemetry::Get().StartSpan(PIESessionCount == 0 ? PIEInteractSpanName : FName(*FString::Printf(TEXT("%s%d"), *PIEInteractSpanName.ToString(), PIESessionCount)), PIESpan);
|
|
PIEInteractSpan->AddAttributes(PIESpan->GetAttributes());
|
|
}
|
|
});
|
|
|
|
FEditorDelegates::EndPIE.AddLambda([this](bool)
|
|
{
|
|
if (PIESpan.IsValid())
|
|
{
|
|
// PIE is ending so no longer interactive
|
|
FStudioTelemetry::Get().EndSpan(PIEInteractSpan);
|
|
PIEShutdownSpan = FStudioTelemetry::Get().StartSpan(PIESessionCount == 0 ? PIEShutdownSpanName : FName(*FString::Printf(TEXT("%s%d"), *PIEShutdownSpanName.ToString(), PIESessionCount)), PIESpan);
|
|
PIEShutdownSpan->AddAttributes(PIESpan->GetAttributes());
|
|
}
|
|
});
|
|
|
|
FEditorDelegates::ShutdownPIE.AddLambda([this](bool)
|
|
{
|
|
if (PIESpan.IsValid())
|
|
{
|
|
// PIE has shutdown, ie. the user has pressed the Stop PIE button, and we are going back to interactive Editor mode
|
|
FStudioTelemetry::Get().EndSpan(PIEShutdownSpan);
|
|
FEditorTelemetry::RecordEvent_Loading(TEXT("PIE.Shutdown"), PIEShutdownSpan->GetDuration(), PIEShutdownSpan->GetAttributes());
|
|
FEditorTelemetry::RecordEvent_CoreSystems(TEXT("PIE.Shutdown"), PIEShutdownSpan->GetAttributes());
|
|
|
|
FStudioTelemetry::Get().EndSpan(PIESpan);
|
|
}
|
|
|
|
PIESessionCount++;
|
|
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("MapName"), EditorMapName);
|
|
|
|
// Restart the Editor span
|
|
EditorSpan = FStudioTelemetry::Get().StartSpan(EditorSpanName, Attributes);
|
|
EditorInteractSpan = FStudioTelemetry::Get().StartSpan(FName(*FString::Printf(TEXT("%s%d"), *EditorInteractSpanName.ToString(), EditorSessionCount)), EditorSpan);
|
|
});
|
|
|
|
FEditorDelegates::OnMapLoad.AddLambda([this](const FString& MapName, FCanLoadMap& OutCanLoadMap)
|
|
{
|
|
if (MapName.Len() > 0)
|
|
{
|
|
// The Editor loads a new map
|
|
EditorLoadMapSpan = FStudioTelemetry::Get().StartSpan(EditorLoadMapSpanName, EditorSpan);
|
|
}
|
|
});
|
|
|
|
FEditorDelegates::OnMapOpened.AddLambda([this](const FString& MapName, bool Unused)
|
|
{
|
|
if (EditorLoadMapSpan.IsValid())
|
|
{
|
|
// The new editor map was actually opened
|
|
EditorMapName = FPaths::GetBaseFilename(MapName);
|
|
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("MapName"), EditorMapName);
|
|
|
|
EditorSpan->AddAttributes(Attributes);
|
|
EditorLoadMapSpan->AddAttributes(Attributes);
|
|
|
|
FStudioTelemetry::Get().EndSpan(EditorLoadMapSpan);
|
|
|
|
FEditorTelemetry::RecordEvent_Loading(TEXT("Editor.LoadMap"), EditorLoadMapSpan->GetDuration(), EditorLoadMapSpan->GetAttributes());
|
|
FEditorTelemetry::RecordEvent_CoreSystems(TEXT("Editor.LoadMap"), EditorLoadMapSpan->GetAttributes());
|
|
|
|
EditorLoadMapSpan.Reset();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Install any plugin load/unload callbacks
|
|
FModuleManager::Get().OnModulesChanged().AddLambda([this](FName ModuleName, EModuleChangeReason ChangeReason)
|
|
{
|
|
switch (ChangeReason)
|
|
{
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
|
|
case EModuleChangeReason::ModuleLoaded:
|
|
{
|
|
TotalPluginCount++;
|
|
|
|
// Hook into Asset Registry Scan callbacks as as soon as it is loaded
|
|
if (ModuleName == TEXT("AssetRegistry"))
|
|
{
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
|
|
AssetRegistryModule.Get().OnScanStarted().AddLambda([this]()
|
|
{
|
|
if (AssetRegistryScanCount == 0)
|
|
{
|
|
// Start the Asset Registry Scan span
|
|
AssetRegistryScanSpan = FStudioTelemetry::Get().StartSpan(AssetRegistryScanSpanName, EditorSpan);
|
|
}
|
|
|
|
AssetRegistryScanCount++;
|
|
});
|
|
|
|
AssetRegistryModule.Get().OnScanEnded().AddLambda([this]()
|
|
{
|
|
AssetRegistryScanCount--;
|
|
|
|
if (AssetRegistryScanCount == 0)
|
|
{
|
|
// End the Asset Registry Scan span
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("MapName"), EditorMapName);
|
|
FStudioTelemetry::Get().EndSpan(AssetRegistryScanSpan, Attributes);
|
|
}
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case EModuleChangeReason::ModuleUnloaded:
|
|
{
|
|
TotalPluginCount--;
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Set up Slow Task callbacks
|
|
ensureMsgf(GWarn, TEXT("GWarn was not valid"));
|
|
|
|
if (GWarn != nullptr)
|
|
{
|
|
// Start the SlowTask span
|
|
GWarn->OnStartSlowTaskWithGuid().AddLambda([this](FGuid TaskGuid, const FText& TaskName)
|
|
{
|
|
// Slow tasks can possibly be started from multiple threads, so we need to protect the registered span table
|
|
FScopeLock ScopeLock(&TaskSpanCriticalSection);
|
|
|
|
TSharedPtr<IAnalyticsSpan>* SpanPtr = TaskSpans.Find(TaskGuid);
|
|
|
|
// Only one task with this Guid is running asynchronously is supported at this time.
|
|
if (SpanPtr == nullptr)
|
|
{
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("MapName"), EditorMapName);
|
|
Attributes.Emplace(TEXT("TaskName"), TaskName.ToString());
|
|
|
|
// Create and start a new slow task span
|
|
TSharedPtr<IAnalyticsSpan> SlowTaskSpan = FStudioTelemetry::Get().StartSpan(TEXT("SlowTask"), Attributes);
|
|
|
|
// Store this SlowTask span so we can find it when it finishes
|
|
TaskSpans.Add(TaskGuid, SlowTaskSpan);
|
|
}
|
|
|
|
TRACE_BEGIN_REGION(*TaskName.ToString());
|
|
});
|
|
|
|
// End the SlowTask span
|
|
GWarn->OnFinalizeSlowTaskWithGuid().AddLambda([this](FGuid TaskGuid, const FText& TaskName)
|
|
{
|
|
TRACE_END_REGION(*TaskName.ToString());
|
|
|
|
// Slow tasks can possibly be finalized from multiple threads, so we need to protect the registered span table
|
|
FScopeLock ScopeLock(&TaskSpanCriticalSection);
|
|
|
|
// Find the task we stored off when we started this task
|
|
TSharedPtr<IAnalyticsSpan>* SpanPtr = TaskSpans.Find(TaskGuid);
|
|
|
|
if (SpanPtr != nullptr)
|
|
{
|
|
TSharedPtr<IAnalyticsSpan> SlowTaskSpan = *SpanPtr;
|
|
|
|
FStudioTelemetry::Get().EndSpan(SlowTaskSpan);
|
|
|
|
// Remove the SlowTask span from the registry
|
|
TaskSpans.Remove(TaskGuid);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Install Cooking Callbacks
|
|
if (GUnrealEd != nullptr && GUnrealEd->CookServer != nullptr)
|
|
{
|
|
UE::Cook::FDelegates::CookFinished.AddLambda([this](UE::Cook::ICookInfo& CookInfo)
|
|
{
|
|
if (CookInfo.GetCookType() != UE::Cook::ECookType::ByTheBook)
|
|
{
|
|
return;
|
|
}
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("MapName"), EditorMapName);
|
|
|
|
FEditorTelemetry::RecordEvent_Cooking(Attributes);
|
|
FEditorTelemetry::RecordEvent_CoreSystems(TEXT("Cooking"), Attributes);
|
|
});
|
|
}
|
|
|
|
UE::Cook::FDelegates::CookStarted.AddLambda([this](UE::Cook::ICookInfo& CookInfo)
|
|
{
|
|
if (CookInfo.GetCookType() != UE::Cook::ECookType::ByTheBook)
|
|
{
|
|
return;
|
|
}
|
|
// Begin the cooking span
|
|
CookingSpan = FStudioTelemetry::Get().StartSpan(TEXT("Cooking"));
|
|
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Emplace(TEXT("MapName"), EditorMapName);
|
|
|
|
CookingSpan->AddAttributes(Attributes);
|
|
});
|
|
|
|
UE::Cook::FDelegates::CookFinished.AddLambda([this](UE::Cook::ICookInfo& CookInfo)
|
|
{
|
|
if (CookInfo.GetCookType() != UE::Cook::ECookType::ByTheBook)
|
|
{
|
|
return;
|
|
}
|
|
// End the cooking span
|
|
|
|
// Suppress sending telemetry from CookWorkers for now.
|
|
uint32 MultiprocessId = 0;
|
|
FParse::Value(FCommandLine::Get(), TEXT("-MultiprocessId="), MultiprocessId);
|
|
if (MultiprocessId != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FEditorTelemetry::RecordEvent_Cooking(CookingSpan->GetAttributes());
|
|
FEditorTelemetry::RecordEvent_CoreSystems(TEXT("Cooking"), CookingSpan->GetAttributes());
|
|
|
|
FStudioTelemetry::Get().EndSpan(CookingSpan);
|
|
});
|
|
|
|
// Install Content Browser callbacks
|
|
FContentBrowserModule* ContentBrowserModule = FModuleManager::GetModulePtr<FContentBrowserModule>( TEXT("ContentBrowser") );
|
|
|
|
FTelemetryRouter& Router = FTelemetryRouter::Get();
|
|
{
|
|
using namespace UE::Telemetry::ContentBrowser;
|
|
Router.OnTelemetry<FBackendFilterTelemetry>([this](const FBackendFilterTelemetry& Data)
|
|
{
|
|
FString DataFilterText = LexToString(FJsonNull{});
|
|
if (Data.DataFilter)
|
|
{
|
|
Private::FAnalyticsJsonWriter J(&DataFilterText);
|
|
J.WriteObjectStart();
|
|
J.WriteValue("RecursivePaths", Data.DataFilter->bRecursivePaths);
|
|
J.WriteValue("ItemTypeFilter", UEnum::GetValueOrBitfieldAsString(Data.DataFilter->ItemTypeFilter));
|
|
J.WriteValue("ItemCategoryFilter", UEnum::GetValueOrBitfieldAsString(Data.DataFilter->ItemCategoryFilter));
|
|
J.WriteValue("ItemAttributeFilter", UEnum::GetValueOrBitfieldAsString(Data.DataFilter->ItemAttributeFilter));
|
|
TArray<const UScriptStruct*> FilterTypes = Data.DataFilter->ExtraFilters.GetFilterTypes();
|
|
if (FilterTypes.Num() > 0)
|
|
{
|
|
J.WriteArrayStart("FilterTypes");
|
|
for (const UScriptStruct* Type : FilterTypes)
|
|
{
|
|
J.WriteValue(Type->GetPathName());
|
|
}
|
|
J.WriteArrayEnd();
|
|
}
|
|
J.WriteObjectEnd();
|
|
J.Close();
|
|
}
|
|
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.AssetView.BackendFilter"),
|
|
{
|
|
{ TEXT("SchemaVersion"), 1 },
|
|
{ TEXT("ViewCorrelationGuid"), Data.ViewCorrelationGuid },
|
|
{ TEXT("FilterSessionCorrelationGuid"), Data.FilterSessionCorrelationGuid },
|
|
{ TEXT("HasCustomItemSources"), Data.bHasCustomItemSources },
|
|
{ TEXT("RefreshSourceItemsDurationSeconds"), Data.RefreshSourceItemsDurationSeconds },
|
|
{ TEXT("NumBackendItems"), Data.NumBackendItems },
|
|
{ TEXT("DataFilter"), FJsonFragment(MoveTemp(DataFilterText)) },
|
|
});
|
|
});
|
|
|
|
Router.OnTelemetry<FFrontendFilterTelemetry>([this](const FFrontendFilterTelemetry& Data)
|
|
{
|
|
FString FilterText = LexToString(FJsonNull{});
|
|
if (Data.FrontendFilters.IsValid() && Data.FrontendFilters->Num())
|
|
{
|
|
Private::FAnalyticsJsonWriter J(&FilterText);
|
|
J.WriteArrayStart();
|
|
for (int32 i=0; i < Data.FrontendFilters->Num(); ++i)
|
|
{
|
|
TSharedPtr<IFilter<FAssetFilterType>> Filter = Data.FrontendFilters->GetFilterAtIndex(i);
|
|
J.WriteValue(Filter->GetName());
|
|
}
|
|
J.WriteArrayEnd();
|
|
J.Close();
|
|
}
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.AssetView.FrontendFilter"),
|
|
{
|
|
{ TEXT("SchemaVersion"), 1 },
|
|
{ TEXT("ViewCorrelationGuid"), Data.ViewCorrelationGuid },
|
|
{ TEXT("FilterSessionCorrelationGuid"), Data.FilterSessionCorrelationGuid },
|
|
{ TEXT("TotalItemsToFilter"), Data.TotalItemsToFilter },
|
|
{ TEXT("PriorityItemsToFilter"), Data.PriorityItemsToFilter },
|
|
{ TEXT("TotalResults"), Data.TotalResults },
|
|
{ TEXT("AmortizeDurationSeconds"), Data.AmortizeDuration },
|
|
{ TEXT("WorkDurationSeconds"), Data.WorkDuration },
|
|
{ TEXT("ResultLatency"), AnalyticsOptionalToStringOrNull(Data.ResultLatency) },
|
|
{ TEXT("TimeUntilInteractionSeconds"), AnalyticsOptionalToStringOrNull(Data.TimeUntilInteraction) },
|
|
{ TEXT("Completed"), Data.bCompleted },
|
|
{ TEXT("FrontendFilters"), FJsonFragment(MoveTemp(FilterText)) },
|
|
});
|
|
});
|
|
|
|
RegisterCollectionWorkflowDelegates(Router);
|
|
}
|
|
{
|
|
using namespace UE::Telemetry::AssetRegistry;
|
|
|
|
Router.OnTelemetry<FStartupTelemetry>([this](const FStartupTelemetry& Data){
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.AssetRegistry.Startup"),
|
|
{
|
|
{ TEXT("SchemaVersion"), 1 },
|
|
{ TEXT("Duration"), Data.StartupDuration },
|
|
{ TEXT("StartedAsyncGather"), Data.bStartedAsyncGather },
|
|
});
|
|
});
|
|
Router.OnTelemetry<FSynchronousScanTelemetry>([this](const FSynchronousScanTelemetry& Data){
|
|
if (Data.Duration < 0.5)
|
|
{
|
|
return;
|
|
}
|
|
FString DirectoriesText;
|
|
{
|
|
Private::FAnalyticsJsonWriter J(&DirectoriesText);
|
|
J.WriteArrayStart();
|
|
for (const FString& Directory : MakeArrayView(Data.Directories).Left(100))
|
|
{
|
|
J.WriteValue(Directory);
|
|
}
|
|
J.WriteArrayEnd();
|
|
J.Close();
|
|
}
|
|
FString FilesText;
|
|
{
|
|
Private::FAnalyticsJsonWriter J(&FilesText);
|
|
J.WriteArrayStart();
|
|
for (const FString& File : MakeArrayView(Data.Files).Left(100))
|
|
{
|
|
J.WriteValue(File);
|
|
}
|
|
J.WriteArrayEnd();
|
|
J.Close();
|
|
}
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.AssetRegistry.SynchronousScan"),
|
|
{
|
|
{ TEXT("SchemaVersion"), 1 },
|
|
{ TEXT("Directories"), FJsonFragment(MoveTemp(DirectoriesText)) },
|
|
{ TEXT("Files"), FJsonFragment(MoveTemp(FilesText)) },
|
|
{ TEXT("Flags"), LexToString(Data.Flags) },
|
|
{ TEXT("NumFoundAssets"), Data.NumFoundAssets },
|
|
{ TEXT("DurationSeconds"), Data.Duration },
|
|
{ TEXT("InitialSearchStarted"), Data.bInitialSearchStarted },
|
|
{ TEXT("InitialSearchCompleted"), Data.bInitialSearchCompleted },
|
|
{ TEXT("AdditionalMountSearchInProgress"), Data.bAdditionalMountSearchInProgress },
|
|
});
|
|
});
|
|
Router.OnTelemetry<FGatherTelemetry>([this](const FGatherTelemetry& Data){
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.AssetRegistry.InitialScan"),
|
|
{
|
|
{ TEXT("SchemaVersion"), 1 },
|
|
{ TEXT("TotalDurationSeconds"), Data.TotalSearchDurationSeconds },
|
|
{ TEXT("TotalWorkSeconds"), Data.TotalWorkTimeSeconds },
|
|
{ TEXT("DiscoverySeconds"), Data.DiscoveryTimeSeconds },
|
|
{ TEXT("GatherSeconds"), Data.GatherTimeSeconds },
|
|
{ TEXT("StoreSeconds"), Data.StoreTimeSeconds },
|
|
{ TEXT("NumCachedDirectories"), Data.NumCachedDirectories },
|
|
{ TEXT("NumUncachedDirectories"), Data.NumUncachedDirectories },
|
|
{ TEXT("NumCachedAssetFiles"), Data.NumCachedAssetFiles },
|
|
{ TEXT("NumUncachedAssetFiles"), Data.NumUncachedAssetFiles },
|
|
});
|
|
});
|
|
Router.OnTelemetry<FDirectoryWatcherUpdateTelemetry>([this](const FDirectoryWatcherUpdateTelemetry& Data){
|
|
if (Data.DurationSeconds < 0.5)
|
|
{
|
|
return;
|
|
}
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.AssetRegistry.DirectoryWatcherUpdate"),
|
|
{
|
|
{ TEXT("SchemaVersion"), 1 },
|
|
{ TEXT("NumChanges"), Data.Changes.Num() },
|
|
{ TEXT("DurationSeconds"), Data.DurationSeconds },
|
|
{ TEXT("InitialSearchStarted"), Data.bInitialSearchStarted },
|
|
{ TEXT("InitialSearchCompleted"), Data.bInitialSearchCompleted },
|
|
});
|
|
});
|
|
Router.OnTelemetry<FFileJournalErrorTelemetry>([this](const FFileJournalErrorTelemetry& Data){
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.AssetRegistry.FileJournalError"),
|
|
{
|
|
{ TEXT("SchemaVersion"), 1 },
|
|
{ TEXT("Directory"), Data.Directory },
|
|
{ TEXT("ErrorString"), Data.ErrorString },
|
|
});
|
|
});
|
|
Router.OnTelemetry<FFileJournalWrappedTelemetry>([this](const FFileJournalWrappedTelemetry& Data) {
|
|
FStudioTelemetry::Get().RecordEvent(TEXT("Editor.AssetRegistry.FileJournalWrapped"),
|
|
{
|
|
{ TEXT("SchemaVersion"), 1 },
|
|
{ TEXT("VolumeName"), Data.VolumeName},
|
|
{ TEXT("JournalMaximumSize"), Data.JournalMaximumSize},
|
|
});
|
|
});
|
|
}
|
|
|
|
{
|
|
UE::Virtualization::GetAnalyticsRecordEvent().AddLambda([](const FString& EventName, const TArray<FAnalyticsEventAttribute>& Attributes, UE::Virtualization::EAnalyticsFlags Flags)
|
|
{
|
|
FStudioTelemetry::Get().RecordEvent(EventName, Attributes);
|
|
|
|
if (EnumHasAllFlags(Flags, UE::Virtualization::EAnalyticsFlags::Flush))
|
|
{
|
|
FStudioTelemetry::Get().FlushEvents();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void FEditorTelemetry::EndSession()
|
|
{
|
|
if (EditorSpan.IsValid())
|
|
{
|
|
FStudioTelemetry::Get().EndSpan(EditorSpan);
|
|
EditorSpan.Reset();
|
|
}
|
|
}
|