Files
UnrealEngineUWP/Engine/Source/Developer/EditorAnalyticsSession/Private/EditorAnalyticsSession.cpp
Marcus Wassmer 3b81cf8201 Merging using //UE5/Main_to_//UE5/Release-Engine-Staging @14384769
autoresolved files
#rb none

[CL 14384911 by Marcus Wassmer in ue5-main branch]
2020-09-24 00:43:27 -04:00

818 lines
36 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "EditorAnalyticsSession.h"
#include "Modules/ModuleManager.h"
#include "Internationalization/Regex.h"
#include "HAL/PlatformProcess.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
IMPLEMENT_MODULE(FEditorAnalyticsSessionModule, EditorAnalyticsSession);
namespace EditorAnalyticsDefs
{
static const FString FalseValueString(TEXT("0"));
static const FString TrueValueString(TEXT("1"));
static const FString DefaultUserActivity(TEXT("Unknown"));
static const FString UnknownProjectValueString(TEXT("UnknownProject"));
static const FString UnknownAppIdString(TEXT("UnknownAppId"));
static const FString UnknownAppVersionString(TEXT("UnknownAppVersion"));
static const FString UnknownUserIdString(TEXT("UnknownUserID"));
// The storage location is used to version the different data format. This is to prevent one version of the Editor/CRC to send sessions produced by another incompatible version.
// Version 1_0 : Used from creation up to 4.25.0 release (included).
// Version 1_1 : To avoid public API changes in 4.25.1, TotalUserInactivitySeconds was repurposed to contain the SessionDuration read from FPlatformTime::Seconds() to detect cases where the user system date time is unreliable.
// Version 1_2 : Removed TotalUserInactivitySeconds and added SessionDuration.
// Version 1_3 : Added SessionTickCount, UserInteractionCount, IsCrcExeMissing, IsUserLoggingOut, MonitorExitCode and readded lost code to save/load/delete IsLowDriveSpace for 4.26.0.
static const FString StoreId(TEXT("Epic Games"));
static const FString SessionSummaryRoot(TEXT("Unreal Engine/Session Summary"));
static const FString SessionSummarySection_1_0 = SessionSummaryRoot / TEXT("1_0"); // The session format used by older versions.
static const FString SessionSummarySection_1_1 = SessionSummaryRoot / TEXT("1_1");
static const FString SessionSummarySection_1_2 = SessionSummaryRoot / TEXT("1_2");
static const FString SessionSummarySection = SessionSummaryRoot / TEXT("1_3"); // The current session format.
static const FString GlobalLockName(TEXT("UE4_SessionSummary_Lock"));
static const FString SessionListStoreKey(TEXT("SessionList"));
// capture context
static const FString AppIdStoreKey(TEXT("AppId"));
static const FString AppVersionStoreKey(TEXT("AppVersion"));
static const FString UserIdStoreKey(TEXT("UserId"));
// general values
static const FString ProjectNameStoreKey(TEXT("ProjectName"));
static const FString ProjectIDStoreKey(TEXT("ProjectID"));
static const FString ProjectDescriptionStoreKey(TEXT("ProjectDescription"));
static const FString ProjectVersionStoreKey(TEXT("ProjectVersion"));
static const FString EngineVersionStoreKey(TEXT("EngineVersion"));
static const FString PlatformProcessIDStoreKey(TEXT("PlatformProcessID"));
static const FString MonitorProcessIDStoreKey(TEXT("MonitorProcessID"));
static const FString ExitCodeStoreKey(TEXT("ExitCode"));
static const FString MonitorExceptCodeStoreKey(TEXT("MonitorExceptCode"));
static const FString MonitorExitCodeStoreKey(TEXT("MonitorExitCode"));
static const FString SessionTickCountStoreKey(TEXT("SessionTickCount"));
static const FString UserInteractionCountStoreKey(TEXT("UserInteractionCount"));
// timestamps
static const FString StartupTimestampStoreKey(TEXT("StartupTimestamp"));
static const FString TimestampStoreKey(TEXT("Timestamp"));
static const FString SessionDurationStoreKey(TEXT("SessionDuration"));
static const FString Idle1MinStoreKey(TEXT("Idle1Min"));
static const FString Idle5MinStoreKey(TEXT("Idle5Min"));
static const FString Idle30MinStoreKey(TEXT("Idle30Min"));
static const FString TotalEditorInactivitySecondsStoreKey(TEXT("TotalEditorInactivitySecs"));
static const FString CurrentUserActivityStoreKey(TEXT("CurrentUserActivity"));
static const FString PluginsStoreKey(TEXT("Plugins"));
static const FString AverageFPSStoreKey(TEXT("AverageFPS"));
// GPU details
static const FString DesktopGPUAdapterStoreKey(TEXT("DesktopGPUAdapter"));
static const FString RenderingGPUAdapterStoreKey(TEXT("RenderingGPUAdapter"));
static const FString GPUVendorIDStoreKey(TEXT("GPUVendorID"));
static const FString GPUDeviceIDStoreKey(TEXT("GPUDeviceID"));
static const FString GRHIDeviceRevisionStoreKey(TEXT("GRHIDeviceRevision"));
static const FString GRHIAdapterInternalDriverVersionStoreKey(TEXT("GRHIAdapterInternalDriverVersion"));
static const FString GRHIAdapterUserDriverVersionStoreKey(TEXT("GRHIAdapterUserDriverVersion"));
static const FString GRHINameStoreKey(TEXT("GRHIName"));
// CPU details
static const FString TotalPhysicalRAMStoreKey(TEXT("TotalPhysicalRAM"));
static const FString CPUPhysicalCoresStoreKey(TEXT("CPUPhysicalCores"));
static const FString CPULogicalCoresStoreKey(TEXT("CPULogicalCores"));
static const FString CPUVendorStoreKey(TEXT("CPUVendor"));
static const FString CPUBrandStoreKey(TEXT("CPUBrand"));
// OS details
static const FString OSMajorStoreKey(TEXT("OSMajor"));
static const FString OSMinorStoreKey(TEXT("OSMinor"));
static const FString OSVersionStoreKey(TEXT("OSVersion"));
static const FString bIs64BitOSStoreKey(TEXT("bIs64BitOS"));
// boolean flags
static const FString IsCrashStoreKey(TEXT("IsCrash"));
static const FString IsGPUCrashStoreKey(TEXT("IsGPUCrash"));
static const FString IsDebuggerStoreKey(TEXT("IsDebugger"));
static const FString WasDebuggerStoreKey(TEXT("WasEverDebugger"));
static const FString IsVanillaStoreKey(TEXT("IsVanilla"));
static const FString IsTerminatingKey(TEXT("Terminating"));
static const FString WasShutdownStoreKey(TEXT("WasShutdown"));
static const FString IsUserLoggingOutStoreKey(TEXT("IsUserLoggingOut"));
static const FString IsInPIEStoreKey(TEXT("IsInPIE"));
static const FString IsInEnterpriseStoreKey(TEXT("IsInEnterprise"));
static const FString IsInVRModeStoreKey(TEXT("IsInVRMode"));
static const FString IsCrcExeMissingStoreKey(TEXT("IsCrcExeMissing"));
static const FString IsLowDriveSpaceStoreKey(TEXT("IsLowDriveSpace"));
}
// Utilities for writing to stored values
namespace EditorAnalyticsUtils
{
static FString TimestampToString(FDateTime InTimestamp)
{
return LexToString(InTimestamp.ToUnixTimestamp());
}
static FDateTime StringToTimestamp(FString InString)
{
int64 TimestampUnix;
if (LexTryParseString(TimestampUnix, *InString))
{
return FDateTime::FromUnixTimestamp(TimestampUnix);
}
return FDateTime::MinValue();
}
static FString BoolToStoredString(bool bInValue)
{
return bInValue ? EditorAnalyticsDefs::TrueValueString : EditorAnalyticsDefs::FalseValueString;
}
static bool GetStoredBool(const FString& SectionName, const FString& StoredKey)
{
FString StoredString = EditorAnalyticsDefs::FalseValueString;
FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, StoredKey, StoredString);
return StoredString == EditorAnalyticsDefs::TrueValueString;
}
static FString GetSessionStorageLocation(const FString& SessionID)
{
return EditorAnalyticsDefs::SessionSummarySection + TEXT("/") + SessionID;
}
static FString GetSessionEventLogDir()
{
return FString::Printf(TEXT("%sAnalytics"), FPlatformProcess::ApplicationSettingsDir());
}
static void LogSessionEvent(FEditorAnalyticsSession& Session, FEditorAnalyticsSession::EEventType InEventType, const FDateTime& InTimestamp)
{
// The logger uses the robustness of the file system to log events. It creates a file (a directory) for each event and encodes the event payload in the file name.
// This doesn't require any fancy synchronization or complicated concurrent file IO implementation. Since the number of events is low (0 to 5 per session), that's
// a straight forward working solution. The files are deleted when the session is deleted. Also avoid memory allocation, this can be called when the heap is corrupted.
TCHAR TimestampStr[256];
FCString::Sprintf(TimestampStr, TFormatSpecifier<decltype(InTimestamp.ToUnixTimestamp())>::GetFormatSpecifier(), InTimestamp.ToUnixTimestamp());
TCHAR Pathname[512];
FCString::Sprintf(Pathname, TEXT("%s/%s_%d_%d_%d_%d_%d_%s"),
*EditorAnalyticsUtils::GetSessionEventLogDir(),
*Session.SessionId,
static_cast<int32>(InEventType),
FPlatformAtomics::AtomicRead(&Session.Idle1Min),
FPlatformAtomics::AtomicRead(&Session.Idle5Min),
FPlatformAtomics::AtomicRead(&Session.Idle30Min),
FPlatformAtomics::AtomicRead(&Session.SessionDuration),
TimestampStr);
IFileManager::Get().MakeDirectory(Pathname, /*Tree*/true);
}
/** Analyze the events logged with LogEvent() and update the session fields to reflect the last state of the session. */
static void UpdateSessionFromLogAnalysis(FEditorAnalyticsSession& Session)
{
// Read and aggregate the log events. The event data is encoded in the directory names created by the logger
FRegexPattern Pattern(TEXT(R"((^[a-fA-F0-9-]+)_([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+))")); // Need help with regex? Try https://regex101.com/
IFileManager::Get().IterateDirectoryRecursively(*EditorAnalyticsUtils::GetSessionEventLogDir(), [&Session, &Pattern](const TCHAR* Pathname, bool bIsDir)
{
if (bIsDir)
{
FRegexMatcher Matcher(Pattern, FPaths::GetCleanFilename(Pathname));
if (Matcher.FindNext() && Matcher.GetCaptureGroup(1) == Session.SessionId)
{
FEditorAnalyticsSession::EEventType EventType = static_cast<FEditorAnalyticsSession::EEventType>(FCString::Atoi(*Matcher.GetCaptureGroup(2))); // Event
switch (EventType)
{
case FEditorAnalyticsSession::EEventType::Crashed: Session.bCrashed = true; break;
case FEditorAnalyticsSession::EEventType::GpuCrashed : Session.bGPUCrashed = true; break;
case FEditorAnalyticsSession::EEventType::Terminated: Session.bIsTerminating = true; break;
case FEditorAnalyticsSession::EEventType::Shutdown: Session.bWasShutdown = true; break;
case FEditorAnalyticsSession::EEventType::LogOut: Session.bIsUserLoggingOut = true; break;
default: break;
}
int32 ParsedTime = FCString::Atoi(*Matcher.GetCaptureGroup(3)); // Idle1Min.
if (ParsedTime > Session.Idle1Min)
{
Session.Idle1Min = ParsedTime; // No concurrency expected when reloading (no need for atomic compare exchange)
}
ParsedTime = FCString::Atoi(*Matcher.GetCaptureGroup(4)); // Idle5Min.
if (ParsedTime > Session.Idle5Min)
{
Session.Idle5Min = ParsedTime;
}
ParsedTime = FCString::Atoi(*Matcher.GetCaptureGroup(5)); // Idle30Min.
if (ParsedTime > Session.Idle30Min)
{
Session.Idle30Min = ParsedTime;
}
ParsedTime = FCString::Atoi(*Matcher.GetCaptureGroup(6)); // SessionDuration.
if (ParsedTime > Session.SessionDuration)
{
Session.SessionDuration = ParsedTime;
}
FDateTime ParsedTimestamp = FDateTime::FromUnixTimestamp(FCString::Atoi64(*Matcher.GetCaptureGroup(6))); // Unix timestamp (UTC)
if (ParsedTimestamp > Session.Timestamp)
{
Session.Timestamp = ParsedTimestamp;
}
}
}
return true;
});
}
static void DeleteLogEvents(const FEditorAnalyticsSession& Session)
{
// Gather the list of files
TArray<FString> SessionEventPaths;
IFileManager::Get().IterateDirectoryRecursively(*EditorAnalyticsUtils::GetSessionEventLogDir(), [&Session, &SessionEventPaths](const TCHAR* Pathname, bool bIsDir)
{
if (bIsDir)
{
if (FPaths::GetCleanFilename(Pathname).StartsWith(Session.SessionId))
{
SessionEventPaths.Emplace(Pathname);
}
}
return true; // Continue
});
// Delete the session files.
for (const FString& EventPathname : SessionEventPaths)
{
IFileManager::Get().DeleteDirectory(*EventPathname, /*RequiredExist*/false, /*Tree*/false);
}
}
// Utility macros to make it easier to check that all fields are being written to.
#define GET_STORED_STRING(FieldName) FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs:: FieldName ## StoreKey, Session.FieldName)
#define GET_STORED_INT(FieldName) \
{ \
FString FieldName ## Temp; \
FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs:: FieldName ## StoreKey, FieldName ## Temp); \
Session.FieldName = FCString::Atoi(*FieldName ## Temp); \
}
static void LoadInternal(FEditorAnalyticsSession& Session, const FString& InSessionId)
{
Session.SessionId = InSessionId;
FString SectionName = EditorAnalyticsUtils::GetSessionStorageLocation(Session.SessionId);
GET_STORED_STRING(AppId);
GET_STORED_STRING(AppVersion);
GET_STORED_STRING(UserId);
GET_STORED_STRING(ProjectName);
GET_STORED_STRING(ProjectID);
GET_STORED_STRING(ProjectDescription);
GET_STORED_STRING(ProjectVersion);
GET_STORED_STRING(EngineVersion);
GET_STORED_INT(PlatformProcessID);
GET_STORED_INT(MonitorProcessID);
{
FString ExitCodeString;
if (FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::ExitCodeStoreKey, ExitCodeString))
{
Session.ExitCode.Emplace(FCString::Atoi(*ExitCodeString));
}
}
{
FString MonitorExceptCodeString;
if (FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::MonitorExceptCodeStoreKey, MonitorExceptCodeString))
{
Session.MonitorExceptCode.Emplace(FCString::Atoi(*MonitorExceptCodeString));
}
}
{
FString MonitorExitCodeString;
if (FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::MonitorExitCodeStoreKey, MonitorExitCodeString))
{
Session.MonitorExitCode.Emplace(FCString::Atoi(*MonitorExitCodeString));
}
}
// scope is just to isolate the temporary value
{
FString StartupTimestampString;
FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::StartupTimestampStoreKey, StartupTimestampString);
Session.StartupTimestamp = EditorAnalyticsUtils::StringToTimestamp(StartupTimestampString);
}
{
FString TimestampString;
FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::TimestampStoreKey, TimestampString);
Session.Timestamp = EditorAnalyticsUtils::StringToTimestamp(TimestampString);
}
GET_STORED_INT(SessionDuration);
GET_STORED_INT(Idle1Min);
GET_STORED_INT(Idle5Min);
GET_STORED_INT(Idle30Min);
GET_STORED_INT(TotalEditorInactivitySeconds);
GET_STORED_STRING(CurrentUserActivity);
{
FString PluginsString;
FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::PluginsStoreKey, PluginsString);
PluginsString.ParseIntoArray(Session.Plugins, TEXT(","));
}
{
FString AverageFPSString;
FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::AverageFPSStoreKey, AverageFPSString);
Session.AverageFPS = FCString::Atof(*AverageFPSString);
}
GET_STORED_INT(SessionTickCount);
GET_STORED_INT(UserInteractionCount);
GET_STORED_STRING(DesktopGPUAdapter);
GET_STORED_STRING(RenderingGPUAdapter);
GET_STORED_INT(GPUVendorID);
GET_STORED_INT(GPUDeviceID);
GET_STORED_INT(GRHIDeviceRevision);
GET_STORED_STRING(GRHIAdapterInternalDriverVersion);
GET_STORED_STRING(GRHIAdapterUserDriverVersion);
GET_STORED_STRING(GRHIName);
{
FString TotalPhysicalRAMString;
FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::TotalPhysicalRAMStoreKey, TotalPhysicalRAMString);
Session.TotalPhysicalRAM = FCString::Atoi64(*TotalPhysicalRAMString);
}
GET_STORED_INT(CPUPhysicalCores);
GET_STORED_INT(CPULogicalCores);
GET_STORED_STRING(CPUVendor);
GET_STORED_STRING(CPUBrand);
GET_STORED_STRING(OSMajor);
GET_STORED_STRING(OSMinor);
GET_STORED_STRING(OSVersion);
Session.bIs64BitOS = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::bIs64BitOSStoreKey);
Session.bCrashed = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsCrashStoreKey);
Session.bGPUCrashed = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsGPUCrashStoreKey);
Session.bIsDebugger = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsDebuggerStoreKey);
Session.bWasEverDebugger = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::WasDebuggerStoreKey);
Session.bIsVanilla = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsVanillaStoreKey);
Session.bIsTerminating = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsTerminatingKey);
Session.bWasShutdown = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::WasShutdownStoreKey);
Session.bIsUserLoggingOut = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsUserLoggingOutStoreKey);
Session.bIsInPIE = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsInPIEStoreKey);
Session.bIsInVRMode = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsInVRModeStoreKey);
Session.bIsInEnterprise = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsInEnterpriseStoreKey);
Session.bIsLowDriveSpace = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsLowDriveSpaceStoreKey);
Session.bIsCrcExeMissing = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsCrcExeMissingStoreKey);
// Analyze the logged events and update corresponding fields in the session.
UpdateSessionFromLogAnalysis(Session);
}
#undef GET_STORED_INT
#undef GET_STORED_STRING
static TArray<FString> GetSessionList()
{
FString SessionListString;
FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, EditorAnalyticsDefs::SessionSummarySection, EditorAnalyticsDefs::SessionListStoreKey, SessionListString);
TArray<FString> SessionIDs;
SessionListString.ParseIntoArray(SessionIDs, TEXT(","));
return MoveTemp(SessionIDs);
}
}
FSystemWideCriticalSection* FEditorAnalyticsSession::StoredValuesLock = nullptr;
FEditorAnalyticsSession::FEditorAnalyticsSession()
{
AppId = EditorAnalyticsDefs::UnknownAppIdString;
AppVersion = EditorAnalyticsDefs::UnknownAppVersionString;
UserId = EditorAnalyticsDefs::UnknownUserIdString;
ProjectName = EditorAnalyticsDefs::UnknownProjectValueString;
PlatformProcessID = 0;
MonitorProcessID = 0;
StartupTimestamp = FDateTime::MinValue();
Timestamp = FDateTime::MinValue();
CurrentUserActivity = EditorAnalyticsDefs::DefaultUserActivity;
AverageFPS = 0;
GPUVendorID = 0;
GPUDeviceID = 0;
GRHIDeviceRevision = 0;
TotalPhysicalRAM = 0;
CPUPhysicalCores = 0;
CPULogicalCores = 0;
bIs64BitOS = false;
bCrashed = false;
bGPUCrashed = false;
bIsDebugger = false;
bWasEverDebugger = false;
bIsVanilla = false;
bIsTerminating = false;
bWasShutdown = false;
bIsUserLoggingOut = false;
bIsInPIE = false;
bIsInEnterprise = false;
bIsInVRMode = false;
bAlreadySaved = false;
bIsLowDriveSpace = false;
bIsCrcExeMissing = false;
}
bool FEditorAnalyticsSession::Lock(FTimespan Timeout)
{
if (!ensure(!IsLocked()))
{
return true;
}
StoredValuesLock = new FSystemWideCriticalSection(EditorAnalyticsDefs::GlobalLockName, Timeout);
if (!IsLocked())
{
delete StoredValuesLock;
StoredValuesLock = nullptr;
return false;
}
return true;
}
void FEditorAnalyticsSession::Unlock()
{
if (!ensure(IsLocked()))
{
return;
}
delete StoredValuesLock;
StoredValuesLock = nullptr;
}
bool FEditorAnalyticsSession::IsLocked()
{
return StoredValuesLock != nullptr && StoredValuesLock->IsValid();
}
bool FEditorAnalyticsSession::Save()
{
if (!ensure(IsLocked()))
{
return false;
}
const FString StorageLocation = EditorAnalyticsUtils::GetSessionStorageLocation(SessionId);
if (!bAlreadySaved)
{
const FString PluginsString = FString::Join(Plugins, TEXT(","));
TMap<FString, FString> KeyValues = {
{EditorAnalyticsDefs::EngineVersionStoreKey, EngineVersion},
{EditorAnalyticsDefs::PlatformProcessIDStoreKey, FString::FromInt(PlatformProcessID)},
{EditorAnalyticsDefs::MonitorProcessIDStoreKey, FString::FromInt(MonitorProcessID)},
{EditorAnalyticsDefs::DesktopGPUAdapterStoreKey, DesktopGPUAdapter},
{EditorAnalyticsDefs::RenderingGPUAdapterStoreKey, RenderingGPUAdapter},
{EditorAnalyticsDefs::GPUVendorIDStoreKey, FString::FromInt(GPUVendorID)},
{EditorAnalyticsDefs::GPUDeviceIDStoreKey, FString::FromInt(GPUDeviceID)},
{EditorAnalyticsDefs::GRHIDeviceRevisionStoreKey, FString::FromInt(GRHIDeviceRevision)},
{EditorAnalyticsDefs::GRHIAdapterInternalDriverVersionStoreKey, GRHIAdapterUserDriverVersion},
{EditorAnalyticsDefs::GRHIAdapterUserDriverVersionStoreKey, GRHIAdapterUserDriverVersion},
{EditorAnalyticsDefs::GRHINameStoreKey, GRHIName},
{EditorAnalyticsDefs::TotalPhysicalRAMStoreKey, FString::Printf(TEXT("%llu"), TotalPhysicalRAM)},
{EditorAnalyticsDefs::CPUPhysicalCoresStoreKey, FString::FromInt(CPUPhysicalCores)},
{EditorAnalyticsDefs::CPULogicalCoresStoreKey, FString::FromInt(CPULogicalCores)},
{EditorAnalyticsDefs::CPUVendorStoreKey, CPUVendor},
{EditorAnalyticsDefs::CPUBrandStoreKey, CPUBrand},
{EditorAnalyticsDefs::StartupTimestampStoreKey, EditorAnalyticsUtils::TimestampToString(StartupTimestamp)},
{EditorAnalyticsDefs::OSMajorStoreKey, OSMajor},
{EditorAnalyticsDefs::OSMinorStoreKey, OSMinor},
{EditorAnalyticsDefs::OSVersionStoreKey, OSVersion},
{EditorAnalyticsDefs::bIs64BitOSStoreKey, EditorAnalyticsUtils::BoolToStoredString(bIs64BitOS)},
{EditorAnalyticsDefs::IsCrcExeMissingStoreKey, EditorAnalyticsUtils::BoolToStoredString(bIsCrcExeMissing)},
{EditorAnalyticsDefs::PluginsStoreKey, PluginsString},
{EditorAnalyticsDefs::AppIdStoreKey, AppId},
{EditorAnalyticsDefs::AppVersionStoreKey, AppVersion},
{EditorAnalyticsDefs::UserIdStoreKey, UserId},
};
FPlatformMisc::SetStoredValues(EditorAnalyticsDefs::StoreId, StorageLocation, KeyValues);
bAlreadySaved = true;
}
{
TMap<FString, FString> KeyValues = {
{EditorAnalyticsDefs::ProjectNameStoreKey, ProjectName},
{EditorAnalyticsDefs::ProjectIDStoreKey, ProjectID},
{EditorAnalyticsDefs::ProjectDescriptionStoreKey, ProjectDescription},
{EditorAnalyticsDefs::ProjectVersionStoreKey, ProjectVersion},
{EditorAnalyticsDefs::TimestampStoreKey, EditorAnalyticsUtils::TimestampToString(Timestamp)},
{EditorAnalyticsDefs::SessionDurationStoreKey, FString::FromInt(SessionDuration)},
{EditorAnalyticsDefs::Idle1MinStoreKey, FString::FromInt(Idle1Min)},
{EditorAnalyticsDefs::Idle5MinStoreKey, FString::FromInt(Idle5Min)},
{EditorAnalyticsDefs::Idle30MinStoreKey, FString::FromInt(Idle30Min)},
{EditorAnalyticsDefs::TotalEditorInactivitySecondsStoreKey, FString::FromInt(TotalEditorInactivitySeconds)},
{EditorAnalyticsDefs::CurrentUserActivityStoreKey, CurrentUserActivity},
{EditorAnalyticsDefs::AverageFPSStoreKey, FString::SanitizeFloat(AverageFPS)},
{EditorAnalyticsDefs::SessionTickCountStoreKey, FString::FromInt(SessionTickCount)},
{EditorAnalyticsDefs::UserInteractionCountStoreKey, FString::FromInt(UserInteractionCount)},
{EditorAnalyticsDefs::IsDebuggerStoreKey, EditorAnalyticsUtils::BoolToStoredString(bIsDebugger)},
{EditorAnalyticsDefs::WasDebuggerStoreKey, EditorAnalyticsUtils::BoolToStoredString(bWasEverDebugger)},
{EditorAnalyticsDefs::IsVanillaStoreKey, EditorAnalyticsUtils::BoolToStoredString(bIsVanilla)},
{EditorAnalyticsDefs::WasShutdownStoreKey, EditorAnalyticsUtils::BoolToStoredString(bWasShutdown)},
{EditorAnalyticsDefs::IsUserLoggingOutStoreKey, EditorAnalyticsUtils::BoolToStoredString(bIsUserLoggingOut)},
{EditorAnalyticsDefs::IsInPIEStoreKey, EditorAnalyticsUtils::BoolToStoredString(bIsInPIE) },
{EditorAnalyticsDefs::IsInEnterpriseStoreKey, EditorAnalyticsUtils::BoolToStoredString(bIsInEnterprise)},
{EditorAnalyticsDefs::IsInVRModeStoreKey, EditorAnalyticsUtils::BoolToStoredString(bIsInVRMode)},
{EditorAnalyticsDefs::IsLowDriveSpaceStoreKey, EditorAnalyticsUtils::BoolToStoredString(bIsLowDriveSpace)},
};
if (ExitCode.IsSet())
{
KeyValues.Emplace(EditorAnalyticsDefs::ExitCodeStoreKey, FString::FromInt(ExitCode.GetValue()));
}
if (MonitorExceptCode.IsSet())
{
KeyValues.Emplace(EditorAnalyticsDefs::MonitorExceptCodeStoreKey, FString::FromInt(MonitorExceptCode.GetValue()));
}
if (MonitorExitCode.IsSet())
{
KeyValues.Emplace(EditorAnalyticsDefs::MonitorExitCodeStoreKey, FString::FromInt(MonitorExitCode.GetValue()));
}
FPlatformMisc::SetStoredValues(EditorAnalyticsDefs::StoreId, StorageLocation, KeyValues);
}
return true;
}
bool FEditorAnalyticsSession::Load(const FString& InSessionID)
{
if (!ensure(IsLocked()))
{
return false;
}
EditorAnalyticsUtils::LoadInternal(*this, InSessionID);
bAlreadySaved = false;
return true;
}
bool FEditorAnalyticsSession::Delete() const
{
if (!ensure(IsLocked()))
{
return false;
}
FString SectionName = EditorAnalyticsUtils::GetSessionStorageLocation(SessionId);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::AppIdStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::AppVersionStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::UserIdStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::ProjectNameStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::ProjectIDStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::ProjectDescriptionStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::ProjectVersionStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::EngineVersionStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::PlatformProcessIDStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::MonitorProcessIDStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::ExitCodeStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::MonitorExceptCodeStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::MonitorExitCodeStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::StartupTimestampStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::TimestampStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::SessionDurationStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::Idle1MinStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::Idle5MinStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::Idle30MinStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::TotalEditorInactivitySecondsStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::CurrentUserActivityStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::PluginsStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::AverageFPSStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::SessionTickCountStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::UserInteractionCountStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::DesktopGPUAdapterStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::RenderingGPUAdapterStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::GPUVendorIDStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::GPUDeviceIDStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::GRHIDeviceRevisionStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::GRHIAdapterInternalDriverVersionStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::GRHIAdapterUserDriverVersionStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::GRHINameStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::TotalPhysicalRAMStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::CPUPhysicalCoresStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::CPULogicalCoresStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::CPUVendorStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::CPUBrandStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::OSMajorStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::OSMinorStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::OSVersionStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::bIs64BitOSStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsCrashStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsGPUCrashStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsDebuggerStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::WasDebuggerStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsVanillaStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsTerminatingKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::WasShutdownStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsUserLoggingOutStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsInPIEStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsInEnterpriseStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsInVRModeStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsLowDriveSpaceStoreKey);
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsCrcExeMissingStoreKey);
// Delete the log files.
EditorAnalyticsUtils::DeleteLogEvents(*this);
return true;
}
bool FEditorAnalyticsSession::GetStoredSessionIDs(TArray<FString>& OutSessions)
{
if (!ensure(IsLocked()))
{
return false;
}
OutSessions = EditorAnalyticsUtils::GetSessionList();
return true;
}
bool FEditorAnalyticsSession::LoadAllStoredSessions(TArray<FEditorAnalyticsSession>& OutSessions)
{
if (!ensure(IsLocked()))
{
return false;
}
TArray<FString> SessionIDs = EditorAnalyticsUtils::GetSessionList();
// Retrieve all the sessions in the list from storage
for (const FString& Id : SessionIDs)
{
FEditorAnalyticsSession NewSession;
EditorAnalyticsUtils::LoadInternal(NewSession, Id);
OutSessions.Add(MoveTemp(NewSession));
}
return true;
}
bool FEditorAnalyticsSession::SaveStoredSessionIDs(const TArray<FString>& InSessions)
{
// build up a new SessionList string
FString SessionListString;
for (const FString& Session : InSessions)
{
if (!SessionListString.IsEmpty())
{
SessionListString.Append(TEXT(","));
}
SessionListString.Append(Session);
}
if (!ensure(IsLocked()))
{
return false;
}
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, EditorAnalyticsDefs::SessionSummarySection, EditorAnalyticsDefs::SessionListStoreKey, SessionListString);
return true;
}
void FEditorAnalyticsSession::CleanupOutdatedIncompatibleSessions(const FTimespan& MaxAge)
{
if (!ensure(IsLocked()))
{
return;
}
// Helper function to scan and clear sessions stored in sections corresponding to format version '1_0' and '1_1'.
auto CleanupVersionedSection = [&MaxAge](const FString& SectionVersion)
{
// Try to retreive the session list corresponding the specified session format.
FString SessionListString;
FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionVersion, EditorAnalyticsDefs::SessionListStoreKey, SessionListString);
if (!SessionListString.IsEmpty())
{
TArray<FString> SessionIDs;
SessionListString.ParseIntoArray(SessionIDs, TEXT(","));
for (const FString& SessionID : SessionIDs)
{
// All versions (1_0, 1_1 and 1_2) have a 'Timestamp' field. If it is not found, the session was partially deleted and should be cleaned up.
FString SessionSectionName = SectionVersion / SessionID;
FString TimestampStr;
if (FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SessionSectionName, EditorAnalyticsDefs::TimestampStoreKey, TimestampStr))
{
const FTimespan SessionAge = FDateTime::UtcNow() - EditorAnalyticsUtils::StringToTimestamp(TimestampStr);
if (SessionAge < MaxAge)
{
// Don't delete the section yet, it contains a session young enough that could be sent if the user launch the Editor corresponding to this session format again.
return;
}
}
}
}
// Nothing in the section is worth keeping, delete it entirely.
FPlatformMisc::DeleteStoredSection(EditorAnalyticsDefs::StoreId, SectionVersion);
};
// The current section format is 1_3. The older sections are considered incompatible and will be trimmed unless it contains a valid session young enough that would be picked up
// if an older Editor with compatible format was launched again.
CleanupVersionedSection(EditorAnalyticsDefs::SessionSummarySection_1_0);
CleanupVersionedSection(EditorAnalyticsDefs::SessionSummarySection_1_1);
CleanupVersionedSection(EditorAnalyticsDefs::SessionSummarySection_1_2);
}
void FEditorAnalyticsSession::LogEvent(EEventType InEventType, const FDateTime& InTimestamp)
{
EditorAnalyticsUtils::LogSessionEvent(*this, InEventType, InTimestamp);
}
bool FEditorAnalyticsSession::FindSession(const uint32 InSessionProcessId, FEditorAnalyticsSession& OutSession)
{
if (!ensure(IsLocked()))
{
return false;
}
TArray<FString> SessionIDs = EditorAnalyticsUtils::GetSessionList();
// Retrieve all the sessions in the list from storage
for (const FString& Id : SessionIDs)
{
FEditorAnalyticsSession Session;
EditorAnalyticsUtils::LoadInternal(Session, Id);
if (Session.PlatformProcessID == InSessionProcessId)
{
OutSession = MoveTemp(Session);
return true;
}
}
return false;
}
void FEditorAnalyticsSession::SaveExitCode(int32 InExitCode)
{
if (!ensure(IsLocked()))
{
return;
}
ExitCode.Emplace(InExitCode);
FString ExitCodeStr = FString::Printf(TFormatSpecifier<decltype(InExitCode)>::GetFormatSpecifier(), InExitCode);
const FString StorageLocation = EditorAnalyticsUtils::GetSessionStorageLocation(SessionId);
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::ExitCodeStoreKey, ExitCodeStr);
}
void FEditorAnalyticsSession::SaveMonitorExceptCode(int32 InExceptCode)
{
if (!ensure(IsLocked()))
{
return;
}
MonitorExceptCode.Emplace(InExceptCode);
FString ExceptCodeStr = FString::Printf(TFormatSpecifier<decltype(InExceptCode)>::GetFormatSpecifier(), InExceptCode);
const FString StorageLocation = EditorAnalyticsUtils::GetSessionStorageLocation(SessionId);
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::MonitorExceptCodeStoreKey, ExceptCodeStr);
}