Files
UnrealEngineUWP/Engine/Source/Runtime/Analytics/AnalyticsET/Private/AnalyticsFlowTracker.cpp
mark lintott b99d530ce0 Moved EpicStudioAnalytics plugin to StudioTelemetry which is now public facing.
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]
2023-11-23 07:06:10 -05:00

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;
}