Files
UnrealEngine/Engine/Source/Runtime/StudioTelemetry/Private/StudioTelemetry.cpp
2025-05-18 13:04:45 +08:00

346 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "StudioTelemetry.h"
#include "StudioTelemetryLog.h"
#include "Analytics.h"
#include "AnalyticsProviderBroadcast.h"
#include "AnalyticsTracer.h"
#include "BuildSettings.h"
#include "RHI.h"
#include "HAL/PlatformTime.h"
#include "HAL/PlatformProcess.h"
#include "HAL/Thread.h"
#include "Misc/CommandLine.h"
#include "Misc/App.h"
#include "Misc/EngineVersion.h"
#include "Misc/EngineBuildSettings.h"
#include "GenericPlatform/GenericPlatformMisc.h"
DEFINE_LOG_CATEGORY(LogStudioTelemetry);
IMPLEMENT_MODULE(FStudioTelemetry, StudioTelemetry)
FString GetEnvironmentValriable(const FString& CommandLineVar, const FString& EnvironmentVar)
{
FString Result;
if (false == FParse::Value(FCommandLine::Get(), *CommandLineVar, Result))
{
Result = FPlatformMisc::GetEnvironmentVariable(*EnvironmentVar);
}
return Result;
}
FStudioTelemetry& FStudioTelemetry::Get()
{
static FStudioTelemetry StudioTelemetryInstance;
return StudioTelemetryInstance;
}
void FStudioTelemetry::SetRecordEventCallback(OnRecordEventCallback Callback )
{
RecordEventCallback = Callback;
// If the provider already exists then set the callback
if (AnalyticsProvider.IsValid())
{
AnalyticsProvider->SetRecordEventCallback(RecordEventCallback);
}
}
void FStudioTelemetry::StartSession()
{
if (IsSessionRunning())
{
return;
}
// Load the configuration
LoadConfiguration();
if (Config.bSendTelemetry == false)
{
// We did not wish to send any telemetry events
return;
}
FScopeLock ScopeLock(&CriticalSection);
AnalyticsProvider = FAnalyticsProviderBroadcast::CreateAnalyticsProvider();
if (AnalyticsProvider.IsValid())
{
TArray<FAnalyticsEventAttribute> DefaultEventAttributes;
const FString UserID = FPlatformProcess::UserName(false);
const FString ProjectName = FApp::GetProjectName();
FString ComputerName = FPlatformProcess::ComputerName();
FString ProjectIDString;
GConfig->GetString(TEXT("/Script/EngineSettings.GeneralProjectSettings"), TEXT("ProjectID"), ProjectIDString, GGameIni);
FGuid ProjectID;
if (!ProjectIDString.IsEmpty())
{
TArray<FString> Elements;
if (ProjectIDString.ParseIntoArray(Elements, TEXT("=")) == 5)
{
ProjectID = FGuid(FCString::Atoi(*(Elements[1])), FCString::Atoi(*(Elements[2])), FCString::Atoi(*(Elements[3])), FCString::Atoi(*(Elements[4])));
}
else
{
ProjectID = FGuid(ProjectIDString);
}
}
FGuid SessionID = FApp::GetInstanceId();
// Set the default event attributes, these will be appended to every event
DefaultEventAttributes.Emplace(TEXT("ProjectName"), ProjectName);
DefaultEventAttributes.Emplace(TEXT("ProjectID"), ProjectID);
DefaultEventAttributes.Emplace(TEXT("Session_ID"), SessionID.ToString(EGuidFormats::DigitsWithHyphensInBraces));
DefaultEventAttributes.Emplace(TEXT("Session_StartUTC"), FDateTime::UtcNow().ToUnixTimestampDecimal());
FString SessionLabel;
if (FParse::Value(FCommandLine::Get(), TEXT("SessionLabel="), SessionLabel))
{
DefaultEventAttributes.Emplace(TEXT("Session_Label"), SessionLabel);
}
DefaultEventAttributes.Emplace(TEXT("Build_Configuration"), LexToString(FApp::GetBuildConfiguration()));
DefaultEventAttributes.Emplace(TEXT("Build_BranchName"), FApp::GetBranchName().ToLower());
DefaultEventAttributes.Emplace(TEXT("Build_Changelist"), BuildSettings::GetCurrentChangelist());
DefaultEventAttributes.Emplace(TEXT("Config_IsEditor"), GIsEditor);
DefaultEventAttributes.Emplace(TEXT("Config_IsBuildMachine"), GIsBuildMachine);
DefaultEventAttributes.Emplace(TEXT("Config_IsRunningCommandlet"), IsRunningCommandlet());
// Only send user data if requested
if (Config.bSendUserData == true)
{
DefaultEventAttributes.Emplace(TEXT("User_ID"), UserID);
DefaultEventAttributes.Emplace(TEXT("Application_Commandline"), FCommandLine::Get());
}
// ALways send the platform
DefaultEventAttributes.Emplace(TEXT("Hardware_Platform"), FString(FPlatformProperties::IniPlatformName()));
// Only send detailed hardware data if requested
if (Config.bSendHardwareData == true)
{
DefaultEventAttributes.Emplace(TEXT("Hardware_GPU"), GRHIAdapterName);
DefaultEventAttributes.Emplace(TEXT("Hardware_CPU"), FPlatformMisc::GetCPUBrand());
DefaultEventAttributes.Emplace(TEXT("Hardware_CPU_Cores_Physical"), FPlatformMisc::NumberOfCores());
DefaultEventAttributes.Emplace(TEXT("Hardware_CPU_Cores_Logical"), FPlatformMisc::NumberOfCoresIncludingHyperthreads());
DefaultEventAttributes.Emplace(TEXT("Hardware_RAM"), static_cast<uint64>(FPlatformMemory::GetStats().TotalPhysical));
DefaultEventAttributes.Emplace(TEXT("Hardware_ComputerName"), ComputerName);
}
// Only send OS data if requested
if (Config.bSendOSData == true)
{
FString OSVersionLabel;
FString OSSubVersionLabel;
FPlatformMisc::GetOSVersions(OSVersionLabel, OSSubVersionLabel);
DefaultEventAttributes.Emplace(TEXT("OS_Version"), FPlatformMisc::GetOSVersion());
DefaultEventAttributes.Emplace(TEXT("OS_VersionLabel"), OSVersionLabel);
DefaultEventAttributes.Emplace(TEXT("OS_VersionSubLabel"), OSSubVersionLabel);
DefaultEventAttributes.Emplace(TEXT("OS_ID"), FPlatformMisc::GetOperatingSystemId());
}
const FString HordeJobId = GetEnvironmentValriable(TEXT("HordeJobId="), TEXT("UE_HORDE_JOBID")) ;
// Send Horde environment if available
if (!HordeJobId.IsEmpty())
{
DefaultEventAttributes.Emplace(TEXT("Horde_JobID"), HordeJobId);
DefaultEventAttributes.Emplace(TEXT("Horde_StepID"), GetEnvironmentValriable(TEXT("HordeStepId="), TEXT("UE_HORDE_STEPID")));
DefaultEventAttributes.Emplace(TEXT("Horde_StepName"), GetEnvironmentValriable(TEXT("HordeStepName="), TEXT("UE_HORDE_STEPNAME")));
DefaultEventAttributes.Emplace(TEXT("Horde_ServerURL"), GetEnvironmentValriable(TEXT("HordeServerUrl="), TEXT("UE_HORDE_URL")));
DefaultEventAttributes.Emplace(TEXT("Horde_TemplateName"), GetEnvironmentValriable(TEXT("HordeTemplateName="), TEXT("UE_HORDE_TEMPLATENAME")));
}
// Set up the analytics provider
AnalyticsProvider->SetUserID(UserID);
AnalyticsProvider->SetSessionID(SessionID.ToString(EGuidFormats::DigitsWithHyphensInBraces));
AnalyticsProvider->SetDefaultEventAttributes(MoveTemp(DefaultEventAttributes));
AnalyticsProvider->SetRecordEventCallback(RecordEventCallback);
// Start the analytics session
AnalyticsProvider->StartSession();
// Create the IAnalyticsTracer interface
AnalyticsTracer = FAnalytics::Get().CreateAnalyticsTracer();
AnalyticsTracer->SetProvider(AnalyticsProvider);
AnalyticsTracer->StartSession();
// Bind the pre-exit callback
FCoreDelegates::OnEnginePreExit.AddRaw(&FStudioTelemetry::Get(), &FStudioTelemetry::EndSession);
TArray<FAnalyticsEventAttribute> Attributes;
Attributes.Emplace(TEXT("SchemaVersion"), 1);
AnalyticsProvider->RecordEvent(TEXT("StudioTelemetry.SessionStart"), Attributes);
OnStartSession.Broadcast();
UE_LOG(LogStudioTelemetry, Log, TEXT("Started StudioTelemetry Session"));
}
}
void FStudioTelemetry::EndSession()
{
if (IsSessionRunning())
{
FScopeLock ScopeLock(&CriticalSection);
OnEndSession.Broadcast();
TArray<FAnalyticsEventAttribute> Attributes;
Attributes.Emplace(TEXT("SchemaVersion"), 1);
AnalyticsProvider->RecordEvent(TEXT("StudioTelemetry.SessionEnd"), Attributes);
// End session for the tracer and the provider
if (AnalyticsTracer.IsValid())
{
AnalyticsTracer->EndSession();
AnalyticsTracer.Reset();
}
if (AnalyticsProvider.IsValid())
{
AnalyticsProvider->EndSession();
AnalyticsProvider->FlushEvents();
AnalyticsProvider.Reset();
}
UE_LOG(LogStudioTelemetry, Log, TEXT("Ended StudioTelemetry Session"));
}
}
bool FStudioTelemetry::IsSessionRunning() const
{
return AnalyticsProvider.IsValid();
}
void FStudioTelemetry::LoadConfiguration()
{
const FString TelemetryConfigurationSection(TEXT("StudioTelemetry.Config"));
// Look for the configuration settings in the Engine.ini files
TArray<FString> SectionNames;
if (GConfig->GetSectionNames(GEngineIni, SectionNames))
{
for (const FString& SectionName : SectionNames)
{
if (SectionName.Find(TelemetryConfigurationSection) != INDEX_NONE)
{
GConfig->GetBool(*SectionName, TEXT("SendTelemetry="), Config.bSendTelemetry, GEngineIni);
GConfig->GetBool(*SectionName, TEXT("SendUserData="), Config.bSendUserData, GEngineIni);
GConfig->GetBool(*SectionName, TEXT("SendHardwareData="), Config.bSendHardwareData, GEngineIni);
GConfig->GetBool(*SectionName, TEXT("SendOSData="), Config.bSendOSData, GEngineIni);
}
}
}
// Parse the commandline for any local configuration overrides
FParse::Bool(FCommandLine::Get(), TEXT("ST_SendTelemetry="), Config.bSendTelemetry);
FParse::Bool(FCommandLine::Get(), TEXT("ST_SendUserData="), Config.bSendUserData);
FParse::Bool(FCommandLine::Get(), TEXT("ST_SendHardwareData="), Config.bSendHardwareData);
FParse::Bool(FCommandLine::Get(), TEXT("ST_SendOSData="), Config.bSendOSData);
}
void FStudioTelemetry::RecordEvent(const FString& EventName, const TArray<FAnalyticsEventAttribute>& Attributes)
{
FScopeLock ScopeLock(&CriticalSection);
if (AnalyticsProvider.IsValid())
{
AnalyticsProvider->RecordEvent(EventName, Attributes);
OnRecordEvent.Broadcast(EventName, Attributes);
}
}
void FStudioTelemetry::RecordEvent(const FName CategoryName, const FString& EventName, const TArray<FAnalyticsEventAttribute>& Attributes)
{
FScopeLock ScopeLock(&CriticalSection);
if (AnalyticsProvider.IsValid())
{
AnalyticsProvider->RecordEvent(EventName, Attributes);
OnRecordEvent.Broadcast(EventName, Attributes);
}
}
void FStudioTelemetry::FlushEvents()
{
FScopeLock ScopeLock(&CriticalSection);
if (AnalyticsProvider.IsValid())
{
AnalyticsProvider->FlushEvents();
}
}
void FStudioTelemetry::RecordEventToProvider(const FString& ProviderName, const FString& EventName, const TArray<FAnalyticsEventAttribute>& Attributes)
{
FScopeLock ScopeLock(&CriticalSection);
TSharedPtr<IAnalyticsProvider> NamedProvider = GetProvider(ProviderName).Pin();
if (NamedProvider.IsValid())
{
NamedProvider->RecordEvent(EventName, Attributes);
}
}
TWeakPtr<IAnalyticsProvider> FStudioTelemetry::GetProvider()
{
return AnalyticsProvider;
}
TWeakPtr<IAnalyticsProvider> FStudioTelemetry::GetProvider(const FString& Name)
{
return AnalyticsProvider.IsValid()? AnalyticsProvider->GetAnalyticsProvider(Name) : TWeakPtr<IAnalyticsProvider>();
}
TWeakPtr<IAnalyticsTracer> FStudioTelemetry::GetTracer()
{
return AnalyticsTracer;
}
TSharedPtr<IAnalyticsSpan> FStudioTelemetry::StartSpan(const FName Name, const TArray<FAnalyticsEventAttribute>& AdditionalAttributes)
{
return AnalyticsTracer.IsValid() ? AnalyticsTracer->StartSpan(Name, AdditionalAttributes) : TSharedPtr<IAnalyticsSpan>();
}
TSharedPtr<IAnalyticsSpan> FStudioTelemetry::StartSpan(const FName Name, TSharedPtr<IAnalyticsSpan> ParentSpan, const TArray<FAnalyticsEventAttribute>& AdditionalAttributes)
{
return AnalyticsTracer.IsValid() ? AnalyticsTracer->StartSpan(Name, ParentSpan, AdditionalAttributes) : TSharedPtr<IAnalyticsSpan>();
}
bool FStudioTelemetry::EndSpan(TSharedPtr<IAnalyticsSpan> Span, const TArray<FAnalyticsEventAttribute>& AdditionalAttributes)
{
return AnalyticsTracer.IsValid() ? AnalyticsTracer->EndSpan(Span, AdditionalAttributes) : false;
}
bool FStudioTelemetry::EndSpan(const FName Name, const TArray<FAnalyticsEventAttribute>& AdditionalAttributes)
{
return AnalyticsTracer.IsValid() ? AnalyticsTracer->EndSpan(Name, AdditionalAttributes) : false;
}
TSharedPtr<IAnalyticsSpan> FStudioTelemetry::GetSpan(const FName Name)
{
return AnalyticsTracer.IsValid() ? AnalyticsTracer->GetSpan(Name) : TSharedPtr<IAnalyticsSpan>();
}
TSharedPtr<IAnalyticsSpan> FStudioTelemetry::GetSessionSpan() const
{
return AnalyticsTracer.IsValid() ? AnalyticsTracer->GetSessionSpan() : TSharedPtr<IAnalyticsSpan>();
}