You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
The intention is to provide a set of "in the box" telemetry hooks to our most common developer workflows for use internal Epic use and licensees Moved startup of StudioTelemetry plugin much earlier to PostConfigInit stage so that sessions can start much earlier in the workflow Added WIP Client support to telemetry plugin Added data driven provider support for FAnlayiticProvidersET interfaces via BaseEngine.ini for common games and sample projects. Settings and URLs for EPic are only avaliable in Resttricted/NotForLicensee config folders for Lyra and Shooter game. Added support for Horde telemetry and fixed up various problems with duplicate attributes being sent. Added Core.VirtualAssets telemetry event Added Core.Zen telemetry event Added Core.IAS telemetry event Added check for IOStoreOnDemand IsEnabled to avoid sending empty IAS events Added FAnalyticsMulticastProvider to forward telemetry events to multiple providers contained within Removed deprecated Fire_LoadingEvent from StudioAnalytics [FYI] paul.chipchase #rb Wes.Hunt [CL 29908039 by mark lintott in ue5-main branch]
333 lines
10 KiB
C++
333 lines
10 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AnalyticsFlowTracker.h"
|
|
#include "ProfilingDebugging/MiscTrace.h"
|
|
|
|
void FAnalyticsFlowTracker::SetProvider(TSharedPtr<IAnalyticsProvider> InProvider)
|
|
{
|
|
AnalyticsProvider = InProvider;
|
|
}
|
|
|
|
void FAnalyticsFlowTracker::StartSession()
|
|
{
|
|
}
|
|
|
|
void FAnalyticsFlowTracker::EndSession()
|
|
{
|
|
FScopeLock ScopeLock(&CriticalSection);
|
|
|
|
// End all the open flows from the stack
|
|
while (FlowDataStack.Num())
|
|
{
|
|
// We are forcibly shutting down the sub flows so mark them as unsuccesful
|
|
EndFlowInternal(FlowDataStack.Last(), false);
|
|
}
|
|
|
|
ensure(FlowDataRegistry.IsEmpty());
|
|
ensure(FlowGuidRegistry.IsEmpty());
|
|
|
|
AnalyticsProvider.Reset();
|
|
}
|
|
|
|
FGuid FAnalyticsFlowTracker::StartFlow(const FName& NewFlowName)
|
|
{
|
|
FScopeLock ScopeLock(&CriticalSection);
|
|
TRACE_BEGIN_REGION(*NewFlowName.ToString());
|
|
|
|
// Create a new Guid for this flow, can we assume it is unique?
|
|
FGuid NewFlowGuid = FGuid::NewGuid();
|
|
ensureMsgf(FlowDataRegistry.Find(NewFlowGuid) == nullptr, TEXT("Could not generate a unique flow guid."));
|
|
|
|
FFlowData FlowData;
|
|
|
|
FlowData.StartTime = FDateTime::UtcNow();
|
|
FlowData.FlowName = NewFlowName;
|
|
FlowData.FlowGuid = NewFlowGuid;
|
|
FlowData.ThreadId = FPlatformTLS::GetCurrentThreadId();
|
|
|
|
// Register the name and guid pair
|
|
FlowGuidRegistry.Add(NewFlowName, NewFlowGuid);
|
|
FlowDataRegistry.Add(NewFlowGuid, FlowData);
|
|
FlowDataStack.Add(NewFlowGuid);
|
|
|
|
return NewFlowGuid;
|
|
}
|
|
|
|
FGuid FAnalyticsFlowTracker::StartSubFlow(const FName& NewSubFlowName)
|
|
{
|
|
FScopeLock ScopeLock(&CriticalSection);
|
|
|
|
if (FlowDataStack.Num()>0)
|
|
{
|
|
return StartSubFlowInternal(NewSubFlowName, FlowDataStack.Last(0));
|
|
}
|
|
|
|
return FGuid();
|
|
}
|
|
|
|
FGuid FAnalyticsFlowTracker::StartSubFlow(const FName& NewSubFlowName, const FGuid& FlowGuid)
|
|
{
|
|
FScopeLock ScopeLock(&CriticalSection);
|
|
return StartSubFlowInternal(NewSubFlowName, FlowGuid);
|
|
}
|
|
|
|
FGuid FAnalyticsFlowTracker::StartSubFlowInternal(const FName& NewSubFlowName, const FGuid& FlowGuid)
|
|
{
|
|
FFlowData* FlowData = FlowDataRegistry.Find(FlowGuid);
|
|
|
|
if (ensureMsgf(FlowData, TEXT("SubFlow started outside of a valid flow scope")))
|
|
{
|
|
TRACE_BEGIN_REGION(*NewSubFlowName.ToString());
|
|
|
|
// Create a new Guid for this SubFlow, can we assume it is unique?
|
|
FGuid NewSubFlowGuid = FGuid::NewGuid();
|
|
ensureMsgf(SubFlowDataRegistry.Find(NewSubFlowGuid) == nullptr, TEXT("Could not generate a unique SubFlow guid."));
|
|
|
|
// Register the name and guid pair
|
|
SubFlowGuidRegistry.Add(NewSubFlowName, NewSubFlowGuid);
|
|
|
|
FSubFlowData NewSubFlow;
|
|
|
|
NewSubFlow.SubFlowGuid = NewSubFlowGuid;
|
|
NewSubFlow.SubFlowName = NewSubFlowName;
|
|
NewSubFlow.StartTime = FDateTime::UtcNow();
|
|
NewSubFlow.EndTime = 0;
|
|
NewSubFlow.ScopeDepth = FlowData->SubFlowDataStack.Num();
|
|
NewSubFlow.ThreadId = FPlatformTLS::GetCurrentThreadId();
|
|
|
|
// Add SubFlow to Flow
|
|
NewSubFlow.FlowGuid = FlowData->FlowGuid;
|
|
NewSubFlow.FlowName = FlowData->FlowName;
|
|
FlowData->SubFlowDataArray.Add(NewSubFlowGuid);
|
|
FlowData->SubFlowDataStack.Add(NewSubFlowGuid);
|
|
|
|
// Register the name and guid pair
|
|
SubFlowGuidRegistry.Add(NewSubFlowName, NewSubFlowGuid);
|
|
SubFlowDataRegistry.Add(NewSubFlowGuid, NewSubFlow);
|
|
|
|
return NewSubFlowGuid;
|
|
}
|
|
|
|
return FGuid();
|
|
}
|
|
|
|
bool FAnalyticsFlowTracker::EndSubFlowInternal(const FGuid& SubFlowGuid, bool bSuccess, const TArray<FAnalyticsEventAttribute>& AdditionalAttributes)
|
|
{
|
|
if (SubFlowGuid.IsValid() == false)
|
|
return false;
|
|
|
|
FSubFlowData* SubFlowData = SubFlowDataRegistry.Find(SubFlowGuid);
|
|
|
|
if (SubFlowData == nullptr)
|
|
return false;
|
|
|
|
const FGuid FlowGuid = SubFlowData->FlowGuid;
|
|
|
|
if (SubFlowData->EndTime<SubFlowData->StartTime)
|
|
{
|
|
// Don't record again if it has already ended
|
|
SubFlowData->EndTime = FDateTime::UtcNow();
|
|
SubFlowData->bSuccess = bSuccess;
|
|
|
|
const FTimespan TimeTaken = SubFlowData->EndTime - SubFlowData->StartTime;
|
|
SubFlowData->TimeInSeconds = TimeTaken.GetTotalSeconds();
|
|
SubFlowData->AdditionalEventAttributes = AdditionalAttributes;
|
|
|
|
TArray<FAnalyticsEventAttribute> EventAttributes = AdditionalAttributes;
|
|
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("SchemaVersion"), SubFlowSchemaVersion));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("SubFlowGUID"), *SubFlowData->SubFlowGuid.ToString()));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("SubFlowName"), *SubFlowData->SubFlowName.ToString()));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("FlowGUID"), SubFlowData->FlowGuid.ToString()));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("FlowName"), *SubFlowData->FlowName.ToString()));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("ThreadId"), SubFlowData->ThreadId));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("StartUTC"), SubFlowData->StartTime.ToUnixTimestampDecimal()));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("TimeInSec"), SubFlowData->TimeInSeconds));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("Success"), SubFlowData->bSuccess));
|
|
|
|
if (AnalyticsProvider.IsValid())
|
|
{
|
|
AnalyticsProvider->RecordEvent(SubFlowEventName, EventAttributes);
|
|
}
|
|
|
|
TRACE_END_REGION(*SubFlowData->SubFlowName.ToString());
|
|
|
|
FFlowData* FlowData = FlowDataRegistry.Find(FlowGuid);
|
|
|
|
if (ensureMsgf(FlowData, TEXT("A sub flow does not belong to a valid flow.")))
|
|
{
|
|
// Most likely it will be the ending item
|
|
for (int32 index = FlowData->SubFlowDataStack.Num() - 1; index >= 0; index--)
|
|
{
|
|
if (FlowData->SubFlowDataStack[index] == SubFlowGuid)
|
|
{
|
|
// Remove the sub flow from the stack
|
|
FlowData->SubFlowDataStack.RemoveAt(index);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FAnalyticsFlowTracker::EndSubFlow(const FGuid& SubFlowGuid, bool bSuccess, const TArray<FAnalyticsEventAttribute>& AdditionalAttributes)
|
|
{
|
|
FScopeLock ScopeLock(&CriticalSection);
|
|
if (SubFlowGuid.IsValid())
|
|
{
|
|
return EndSubFlowInternal(SubFlowGuid, bSuccess, AdditionalAttributes);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FAnalyticsFlowTracker::EndSubFlow(const FName& SubFlowName, bool bSuccess, const TArray<FAnalyticsEventAttribute>& AdditionalAttributes)
|
|
{
|
|
FScopeLock ScopeLock(&CriticalSection);
|
|
FGuid* SubFlowGuid = SubFlowGuidRegistry.Find(SubFlowName);
|
|
if (SubFlowGuid)
|
|
{
|
|
return EndSubFlowInternal(*SubFlowGuid, bSuccess, AdditionalAttributes);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void AggregateAttributes(TArray<FAnalyticsEventAttribute>& AggregatedAttibutes, const TArray<FAnalyticsEventAttribute>& Attributes)
|
|
{
|
|
// Aggregates all attributes
|
|
for (const FAnalyticsEventAttribute& Attribute : Attributes)
|
|
{
|
|
bool AttributeWasFound=false;
|
|
|
|
for (FAnalyticsEventAttribute& AggregatedAttribute : AggregatedAttibutes )
|
|
{
|
|
if (Attribute.GetName() == AggregatedAttribute.GetName())
|
|
{
|
|
AggregatedAttribute += Attribute;
|
|
|
|
// If we already have this attribute then great no more to do for this attribute
|
|
AttributeWasFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (AttributeWasFound == false)
|
|
{
|
|
// No matching attribute so append
|
|
AggregatedAttibutes.Add(Attribute);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FAnalyticsFlowTracker::EndFlow(const FName& FlowName, bool bSuccess, const TArray<FAnalyticsEventAttribute>& AdditionalAttributes)
|
|
{
|
|
FScopeLock ScopeLock(&CriticalSection);
|
|
if (FGuid* FlowGuid = FlowGuidRegistry.Find(FlowName))
|
|
{
|
|
return EndFlowInternal(*FlowGuid, bSuccess, AdditionalAttributes);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FAnalyticsFlowTracker::EndFlow(bool bSuccess, const TArray<FAnalyticsEventAttribute>& AdditionalAttributes)
|
|
{
|
|
FScopeLock ScopeLock(&CriticalSection);
|
|
|
|
if (FlowDataStack.IsEmpty() == false)
|
|
{
|
|
return EndFlowInternal(FlowDataStack.Last(0), bSuccess, AdditionalAttributes);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FAnalyticsFlowTracker::EndFlow(const FGuid& FlowGuid, bool bSuccess, const TArray<FAnalyticsEventAttribute>& AdditionalAttributes)
|
|
{
|
|
FScopeLock ScopeLock(&CriticalSection);
|
|
return EndFlowInternal(FlowGuid, bSuccess, AdditionalAttributes);
|
|
}
|
|
|
|
bool FAnalyticsFlowTracker::EndFlowInternal(const FGuid& FlowGuid, bool bSuccess, const TArray<FAnalyticsEventAttribute>& AdditionalAttributes)
|
|
{
|
|
if (FlowGuid.IsValid() == false)
|
|
return false;
|
|
|
|
FFlowData* FlowData = FlowDataRegistry.Find(FlowGuid);
|
|
|
|
if (FlowData == nullptr)
|
|
return false;
|
|
|
|
FlowData->EndTime = FDateTime::UtcNow();
|
|
const FTimespan WallTime = FlowData->EndTime - FlowData->StartTime;
|
|
FlowData->TimeInSeconds = WallTime.GetTotalSeconds();
|
|
|
|
TArray<FAnalyticsEventAttribute> EventAttributes = AdditionalAttributes;
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("SchemaVersion"), FlowSchemaVersion));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("FlowGUID"), FlowData->FlowGuid.ToString()));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("FlowName"), FlowData->FlowName.ToString()));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("ThreadId"), FlowData->ThreadId));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("StartUTC"), FlowData->StartTime.ToUnixTimestampDecimal()));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("Success"), bSuccess));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("WallTimeInSec"), FlowData->TimeInSeconds));
|
|
|
|
double TotalTimeInSeconds = 0;
|
|
|
|
for (FGuid SubFlowGuid : FlowData->SubFlowDataArray)
|
|
{
|
|
EndSubFlowInternal(SubFlowGuid);
|
|
|
|
FSubFlowData* SubFlowData = SubFlowDataRegistry.Find(SubFlowGuid);
|
|
|
|
if (ensureMsgf(SubFlowData, TEXT("SubFlow does not exist.")))
|
|
{
|
|
// Aggregate the additional attributes from the sub flows
|
|
AggregateAttributes(EventAttributes, SubFlowData->AdditionalEventAttributes);
|
|
TotalTimeInSeconds += SubFlowData->TimeInSeconds;
|
|
}
|
|
}
|
|
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("TotalTimeInSec"), TotalTimeInSeconds));
|
|
|
|
if (AnalyticsProvider.IsValid())
|
|
{
|
|
AnalyticsProvider->RecordEvent(FlowEventName, EventAttributes);
|
|
AnalyticsProvider->FlushEvents();
|
|
}
|
|
|
|
TRACE_END_REGION(*FlowData->FlowName.ToString());
|
|
|
|
// Clear up our data
|
|
for (FGuid SubFlowGuid : FlowData->SubFlowDataArray)
|
|
{
|
|
FSubFlowData* SubFlowData = SubFlowDataRegistry.Find(SubFlowGuid);
|
|
|
|
if (ensureMsgf(SubFlowData, TEXT("SubFlow does not exist.")))
|
|
{
|
|
// Remove the SubFlow and guid from the registry
|
|
SubFlowDataRegistry.Remove(SubFlowGuid);
|
|
SubFlowGuidRegistry.Remove(SubFlowData->SubFlowName);
|
|
}
|
|
}
|
|
|
|
// Remove the flow and guid from the registry
|
|
FlowDataRegistry.Remove(FlowData->FlowGuid);
|
|
FlowGuidRegistry.Remove(FlowData->FlowName);
|
|
|
|
// Remove the FlowData from the stack
|
|
for (int32 index = FlowDataStack.Num() - 1; index >= 0; index--)
|
|
{
|
|
if (FlowDataStack[index] == FlowGuid)
|
|
{
|
|
// Remove the flow from the stack
|
|
FlowDataStack.RemoveAt(index);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|