You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Engine/Editor changes: - Split the Editor summary session in two, one summary for the Engine properties and one for the Editor specific properties. Made it easy to extend the Engine summary to create other summaries. - Made the summary sender as agnostics as possible of the keys it sends. - Fixed the system wide lock contention between the process when persisting a session. (On problem caused by the lock is UE-114315). - Fixed concurrent issue when saving the summary sessions on Linux/Mac - Fixed performance issue when saving the summary session on Linux/Mac. This enable saving at higher frequency. - Fixed cases where the same session summary is sent more than once. - Fixed Windows registry key overflow that could happens if we accumulated too many sessions (in theory, this can happen) - Made adding new properties to the summary easy and private to the implementation. - Brought the Linux/Mac implementation closer to Windows implementation. - Reduced memory allocation, especially when the session records a crash. - Improved chances to send the summary non-delayed by allowing the Editor to send the reports if CRC died unexpectedly. - Generalized the support to collect and aggregate analytics from helper processes. For example, CRC already collects analytics that is merged with the Editor summary as information supplement - Reserved the disk space required to store the summary ahead of time to prevent failing later. - Increased frequency at which the summary is persisted because saving the summary is more efficient. (About every 10 seconds rather than every minutes). - Added unit tests CrashReportClient changes: - Created a 'session summary' from the CRC point of view to merge with the Editor summary. - Moved analytics collection in a separated class to make the crash reporting code leaner and less noisy with all the analytics - Merged the CRC diagnostic logger in the class collecting CRC analytics summary and make the diagnostic log a property in the summary. - Collected analytics (on behalf of Editor) in a background thread because CRC main thread can be blocked collecting a crash, so it doesn't pay attention to other things - Added MonitorBatteryLevel and MonitorOnACPower summary properties on Windows. Collected on CRC background thread (never blocked, so we reduce changes to miss the battery running out) - Added MonitorSessionDuration summary property to track now long CRC ran. - Added MonitorQuitSignalRecv summary property to detect when CRC is soft killed like: taskkill /PID 1234 - Added MonitorIsReportingCrash summary property to track when CRC dies reporting a crash. - Added MonitorIsCollectingCrash summary property to track when CRC dies collecting a crash artifacts. - Added IsProcessingCrash summary property to track when CRC dies processing a crash. - Added MonitorCrashed summary property to track when CRC exception handler was triggered. - Added MonitorWasShutdown summary property to track when CRC summary was shutdown - Added MonitorLoggingOut summary property to track when CRC died because the user was logging out (or as result of shutting down or restarting the computer). - More accurate value for DeathTimestamp summary property because this is now captured in CRC background thread (which cannot be busy handling a crash) - Added crash processing timing to CRC diagnostic logs (how long it takes to collect/process a crash). #rb Jamie.Dale, Wes.Hunt, Johan.Berg #jira UETOOL-3500 #jira UE-114315 [CL 16324612 by Patrick Laflamme in ue5-main branch]
987 lines
47 KiB
C++
987 lines
47 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/EngineVersion.h"
|
|
#include "Misc/Guid.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.
|
|
// Version 1_4 : Added CommandLine, EngineTickCount, LastTickTimestamp, DeathTimestamp and IsDebuggerIgnored for 4.26.0.
|
|
// Version 1_5 : Added Stall Detector stats for 5.0.
|
|
// Version 1_6 : Added ProcessDiagnostics, Renamed IsDebugger as IsDebuggerPresent: This was the last version using this system before the refactor in UE 5.0.
|
|
static const FString StoreId(TEXT("Epic Games"));
|
|
static const FString SessionSummaryRoot(TEXT("Unreal Engine/Session Summary"));
|
|
static const FString DeprecatedVersions[] = { // The session format used by older versions.
|
|
SessionSummaryRoot / TEXT("1_0"),
|
|
SessionSummaryRoot / TEXT("1_1"),
|
|
SessionSummaryRoot / TEXT("1_2"),
|
|
SessionSummaryRoot / TEXT("1_3"),
|
|
SessionSummaryRoot / TEXT("1_4"),
|
|
SessionSummaryRoot / TEXT("1_5"),
|
|
SessionSummaryRoot / TEXT("1_6"),
|
|
};
|
|
static const FString SessionSummarySection = SessionSummaryRoot / TEXT("1_7"); // 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 EngineTickCountStoreKey(TEXT("EngineTickCount"));
|
|
static const FString UserInteractionCountStoreKey(TEXT("UserInteractionCount"));
|
|
static const FString CommandLineStoreKey(TEXT("CommandLine"));
|
|
|
|
// stall detector
|
|
static const FString TotalStallCountStoreKey(TEXT("TotalStallCount"));
|
|
static const FString TotalStallReportedStoreKey(TEXT("TotalStallReported"));
|
|
static const FString TopStallNameStoreKey(TEXT("TopStallName"));
|
|
static const FString TopStallBudgetSecondsStoreKey(TEXT("TopStallBudgetSeconds"));
|
|
static const FString TopStallOverageSecondsStoreKey(TEXT("TopStallOverageSeconds"));
|
|
static const FString TopStallTriggerCountStoreKey(TEXT("TopStallTriggerCount"));
|
|
|
|
// 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"));
|
|
static const FString LastTickTimestampStoreKey(TEXT("LastTickTimestamp"));
|
|
static const FString DeathTimestampStoreKey(TEXT("DeathTimestamp"));
|
|
|
|
// 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 IsDebuggerPresentStoreKey(TEXT("IsDebuggerPresent"));
|
|
static const FString IsDebuggerIgnoredStoreKey(TEXT("IsDebuggerIgnored"));
|
|
static const FString WasDebuggerStoreKey(TEXT("WasEverDebugger"));
|
|
static const FString IsVanillaStoreKey(TEXT("IsVanilla"));
|
|
static const FString IsTerminatingStoreKey(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"));
|
|
|
|
static const FString ProcessDiagnosticsStoreKey(TEXT("ProcessDiagnostics"));
|
|
}
|
|
|
|
// 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 bool IsSavingIndividualFieldFastAndThreadSafe()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
return true; // Saving individual field to registry on Windows is fast and thread safe.
|
|
#else
|
|
return false; // On other platforms, we load/update/save an entire .ini file. This is not fast and not thread safe.
|
|
#endif
|
|
}
|
|
|
|
static void LogSessionEvent(FEditorAnalyticsSession& Session, FEditorAnalyticsSession::EEventType InEventType, const FDateTime& InTimestamp)
|
|
{
|
|
// This is primary logging mechanims. It works across all platforms and is rather robust. It 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);
|
|
|
|
// As a secondary/backup mechanism, directly save the info in the session if this is fast and safe. This allocate memory, so that's not perfect in case of crash.
|
|
if (IsSavingIndividualFieldFastAndThreadSafe())
|
|
{
|
|
const FString StorageLocation = EditorAnalyticsUtils::GetSessionStorageLocation(Session.SessionId);
|
|
|
|
switch(InEventType)
|
|
{
|
|
case FEditorAnalyticsSession::EEventType::Crashed:
|
|
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::IsCrashStoreKey, EditorAnalyticsUtils::BoolToStoredString(true));
|
|
break;
|
|
|
|
case FEditorAnalyticsSession::EEventType::GpuCrashed:
|
|
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::IsGPUCrashStoreKey, EditorAnalyticsUtils::BoolToStoredString(true));
|
|
break;
|
|
|
|
case FEditorAnalyticsSession::EEventType::Terminated:
|
|
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::IsTerminatingStoreKey, EditorAnalyticsUtils::BoolToStoredString(true));
|
|
break;
|
|
|
|
case FEditorAnalyticsSession::EEventType::Shutdown:
|
|
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::WasShutdownStoreKey, EditorAnalyticsUtils::BoolToStoredString(true));
|
|
break;
|
|
|
|
case FEditorAnalyticsSession::EEventType::LogOut:
|
|
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::IsUserLoggingOutStoreKey, EditorAnalyticsUtils::BoolToStoredString(true));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Save the timestamps.
|
|
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::Idle1MinStoreKey, FString::FromInt(FPlatformAtomics::AtomicRead(&Session.Idle1Min)));
|
|
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::Idle5MinStoreKey, FString::FromInt(FPlatformAtomics::AtomicRead(&Session.Idle5Min)));
|
|
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::Idle30MinStoreKey, FString::FromInt(FPlatformAtomics::AtomicRead(&Session.Idle30Min)));
|
|
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::SessionDurationStoreKey, FString::FromInt(FPlatformAtomics::AtomicRead(&Session.SessionDuration)));
|
|
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::TimestampStoreKey, EditorAnalyticsUtils::TimestampToString(InTimestamp));
|
|
}
|
|
}
|
|
|
|
/** 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); \
|
|
}
|
|
|
|
#define GET_STORED_FLOAT(FieldName) \
|
|
{ \
|
|
FString FieldName ## Temp; \
|
|
FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs:: FieldName ## StoreKey, FieldName ## Temp); \
|
|
Session.FieldName = FCString::Atof(*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_STRING(CommandLine);
|
|
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);
|
|
}
|
|
|
|
{
|
|
FString LastTickTimestampString;
|
|
FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::LastTickTimestampStoreKey, LastTickTimestampString);
|
|
Session.LastTickTimestamp = EditorAnalyticsUtils::StringToTimestamp(LastTickTimestampString);
|
|
}
|
|
|
|
{
|
|
FString DeathTimestampString;
|
|
if (FPlatformMisc::GetStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::DeathTimestampStoreKey, DeathTimestampString))
|
|
{
|
|
Session.DeathTimestamp.Emplace(EditorAnalyticsUtils::StringToTimestamp(DeathTimestampString));
|
|
}
|
|
}
|
|
|
|
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(","));
|
|
}
|
|
|
|
GET_STORED_FLOAT(AverageFPS);
|
|
|
|
GET_STORED_INT(SessionTickCount);
|
|
GET_STORED_INT(EngineTickCount);
|
|
GET_STORED_INT(UserInteractionCount);
|
|
|
|
GET_STORED_INT(TotalStallCount);
|
|
GET_STORED_INT(TotalStallReported);
|
|
GET_STORED_STRING(TopStallName);
|
|
GET_STORED_FLOAT(TopStallBudgetSeconds);
|
|
GET_STORED_FLOAT(TopStallOverageSeconds);
|
|
GET_STORED_INT(TopStallTriggerCount);
|
|
|
|
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.bIsDebuggerPresent = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsDebuggerPresentStoreKey);
|
|
Session.bIsDebuggerIgnored = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsDebuggerIgnoredStoreKey);
|
|
Session.bWasEverDebugger = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::WasDebuggerStoreKey);
|
|
Session.bIsVanilla = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsVanillaStoreKey);
|
|
Session.bIsTerminating = EditorAnalyticsUtils::GetStoredBool(SectionName, EditorAnalyticsDefs::IsTerminatingStoreKey);
|
|
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);
|
|
|
|
GET_STORED_INT(ProcessDiagnostics);
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
TUniquePtr<FSystemWideCriticalSection> FEditorAnalyticsSession::StoredValuesLock;
|
|
std::atomic<uint64> FEditorAnalyticsSession::StoredValuesLockOwnerInfo(0);
|
|
|
|
FEditorAnalyticsSession::FEditorAnalyticsSession()
|
|
{
|
|
AppId = EditorAnalyticsDefs::UnknownAppIdString;
|
|
AppVersion = EditorAnalyticsDefs::UnknownAppVersionString;
|
|
UserId = EditorAnalyticsDefs::UnknownUserIdString;
|
|
|
|
ProjectName = EditorAnalyticsDefs::UnknownProjectValueString;
|
|
StartupTimestamp = FDateTime::MinValue();
|
|
Timestamp = FDateTime::MinValue();
|
|
CurrentUserActivity = EditorAnalyticsDefs::DefaultUserActivity;
|
|
|
|
bIs64BitOS = false;
|
|
bCrashed = false;
|
|
bGPUCrashed = false;
|
|
bIsDebuggerPresent = false;
|
|
bWasEverDebugger = false;
|
|
bIsVanilla = false;
|
|
bIsTerminating = false;
|
|
bWasShutdown = false;
|
|
bIsUserLoggingOut = false;
|
|
bIsInPIE = false;
|
|
bIsInEnterprise = false;
|
|
bIsInVRMode = false;
|
|
bAlreadySaved = false;
|
|
bIsLowDriveSpace = false;
|
|
bIsCrcExeMissing = false;
|
|
bIsDebuggerIgnored = false;
|
|
|
|
ProcessDiagnostics = 0x0;
|
|
}
|
|
|
|
bool FEditorAnalyticsSession::Lock(FTimespan Timeout)
|
|
{
|
|
// If Lock() is called recursively from a thread already owning the lock, fire an ensure. FSystemWideCriticalSection doesn't support recursive locking correctly.
|
|
if (!ensure(!IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Try to acquire the lock.
|
|
TUniquePtr<FSystemWideCriticalSection> TempLock = MakeUnique<FSystemWideCriticalSection>(EditorAnalyticsDefs::GlobalLockName, Timeout);
|
|
|
|
// If the lock is acquired.
|
|
if (TempLock && TempLock->IsValid())
|
|
{
|
|
// The lock was successfully acquired by this thread, keep it until Unlock() is called.
|
|
StoredValuesLock = MoveTemp(TempLock);
|
|
static_assert(sizeof(FPlatformTLS::GetCurrentThreadId()) == sizeof(uint32), "Encoding below assume the thread Id only uses 32 bits");
|
|
StoredValuesLockOwnerInfo.store(((1ull << (sizeof(uint32) * 8))) | FPlatformTLS::GetCurrentThreadId()); // 32 bits for thread id, 1 bit for 'is taken' so that threadId 0 is supported.
|
|
return true;
|
|
}
|
|
|
|
return false; // Failed to get the lock within the allowed time.
|
|
}
|
|
|
|
void FEditorAnalyticsSession::Unlock()
|
|
{
|
|
// If Unlock() is called from a non-owning thread or recursively from an owning thread, fire an ensure. FSystemWideCriticalSection doesn't support recursive locking correctly.
|
|
if (!ensure(IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
return;
|
|
}
|
|
|
|
StoredValuesLockOwnerInfo.store(0);
|
|
StoredValuesLock.Reset();
|
|
}
|
|
|
|
bool FEditorAnalyticsSession::IsLockedBy(uint32 ThreadId)
|
|
{
|
|
// Check if the calling thread has the lock.
|
|
uint64 LockInfo = StoredValuesLockOwnerInfo.load();
|
|
return LockInfo != 0 && (LockInfo & 0xFFFFFFFF) == ThreadId;
|
|
}
|
|
|
|
bool FEditorAnalyticsSession::Save()
|
|
{
|
|
if (!ensure(IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
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::CommandLineStoreKey, CommandLine},
|
|
{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::LastTickTimestampStoreKey, EditorAnalyticsUtils::TimestampToString(LastTickTimestamp)},
|
|
{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::EngineTickCountStoreKey, FString::FromInt(EngineTickCount)},
|
|
{EditorAnalyticsDefs::UserInteractionCountStoreKey, FString::FromInt(UserInteractionCount)},
|
|
{EditorAnalyticsDefs::TotalStallCountStoreKey, FString::FromInt(TotalStallCount)},
|
|
{EditorAnalyticsDefs::TotalStallReportedStoreKey, FString::FromInt(TotalStallReported)},
|
|
{EditorAnalyticsDefs::TopStallNameStoreKey, TopStallName},
|
|
{EditorAnalyticsDefs::TopStallBudgetSecondsStoreKey, FString::SanitizeFloat(TopStallBudgetSeconds)},
|
|
{EditorAnalyticsDefs::TopStallOverageSecondsStoreKey, FString::SanitizeFloat(TopStallOverageSeconds)},
|
|
{EditorAnalyticsDefs::TopStallTriggerCountStoreKey, FString::FromInt(TopStallTriggerCount)},
|
|
{EditorAnalyticsDefs::IsDebuggerPresentStoreKey,EditorAnalyticsUtils::BoolToStoredString(bIsDebuggerPresent)},
|
|
{EditorAnalyticsDefs::IsDebuggerIgnoredStoreKey,EditorAnalyticsUtils::BoolToStoredString(bIsDebuggerIgnored)},
|
|
{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)},
|
|
{EditorAnalyticsDefs::ProcessDiagnosticsStoreKey, FString::FromInt(ProcessDiagnostics)},
|
|
};
|
|
|
|
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()));
|
|
}
|
|
|
|
if (DeathTimestamp.IsSet())
|
|
{
|
|
KeyValues.Emplace(EditorAnalyticsDefs::DeathTimestampStoreKey, EditorAnalyticsUtils::TimestampToString(DeathTimestamp.GetValue()));
|
|
}
|
|
|
|
FPlatformMisc::SetStoredValues(EditorAnalyticsDefs::StoreId, StorageLocation, KeyValues);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FEditorAnalyticsSession::Load(const FString& InSessionID)
|
|
{
|
|
if (!ensure(IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
EditorAnalyticsUtils::LoadInternal(*this, InSessionID);
|
|
bAlreadySaved = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FEditorAnalyticsSession::Delete() const
|
|
{
|
|
if (!ensure(IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
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::LastTickTimestampStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::DeathTimestampStoreKey);
|
|
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::EngineTickCountStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::UserInteractionCountStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::CommandLineStoreKey);
|
|
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::TotalStallCountStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::TotalStallReportedStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::TopStallNameStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::TopStallBudgetSecondsStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::TopStallOverageSecondsStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::TopStallTriggerCountStoreKey);
|
|
|
|
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::IsDebuggerPresentStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsDebuggerIgnoredStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::WasDebuggerStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsVanillaStoreKey);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::IsTerminatingStoreKey);
|
|
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);
|
|
FPlatformMisc::DeleteStoredValue(EditorAnalyticsDefs::StoreId, SectionName, EditorAnalyticsDefs::ProcessDiagnosticsStoreKey);
|
|
|
|
// Delete the log files.
|
|
EditorAnalyticsUtils::DeleteLogEvents(*this);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FEditorAnalyticsSession::GetStoredSessionIDs(TArray<FString>& OutSessions)
|
|
{
|
|
if (!ensure(IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
OutSessions = EditorAnalyticsUtils::GetSessionList();
|
|
return true;
|
|
}
|
|
|
|
bool FEditorAnalyticsSession::LoadAllStoredSessions(TArray<FEditorAnalyticsSession>& OutSessions)
|
|
{
|
|
if (!ensure(IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
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(IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, EditorAnalyticsDefs::SessionSummarySection, EditorAnalyticsDefs::SessionListStoreKey, SessionListString);
|
|
return true;
|
|
}
|
|
|
|
void FEditorAnalyticsSession::CleanupOutdatedIncompatibleSessions(const FTimespan& MaxAge)
|
|
{
|
|
if (!ensure(IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Helper function to scan and clear sessions stored in sections corresponding to older versions.
|
|
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);
|
|
};
|
|
|
|
// Delete older and incompatible sections unless it contains a valid session young enough that would be picked up
|
|
// if an older Editor with compatible format was launched again.
|
|
for (int i = 0; i < UE_ARRAY_COUNT(EditorAnalyticsDefs::DeprecatedVersions); ++i)
|
|
{
|
|
CleanupVersionedSection(EditorAnalyticsDefs::DeprecatedVersions[i]);
|
|
}
|
|
}
|
|
|
|
void FEditorAnalyticsSession::LogEvent(EEventType InEventType, const FDateTime& InTimestamp)
|
|
{
|
|
EditorAnalyticsUtils::LogSessionEvent(*this, InEventType, InTimestamp);
|
|
}
|
|
|
|
bool FEditorAnalyticsSession::FindSession(const uint32 InSessionProcessId, FEditorAnalyticsSession& OutSession)
|
|
{
|
|
if (!ensure(IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
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;
|
|
}
|
|
|
|
bool FEditorAnalyticsSession::SaveExitCode(int32 InExitCode, const FDateTime& ApproximativeEditorDeathTime)
|
|
{
|
|
if (!ensure(IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ExitCode.Emplace(InExitCode);
|
|
DeathTimestamp.Emplace(ApproximativeEditorDeathTime);
|
|
|
|
const FString StorageLocation = EditorAnalyticsUtils::GetSessionStorageLocation(SessionId);
|
|
return FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::ExitCodeStoreKey, FString::FromInt(InExitCode))
|
|
&& FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::DeathTimestampStoreKey, EditorAnalyticsUtils::TimestampToString(ApproximativeEditorDeathTime));
|
|
}
|
|
|
|
bool FEditorAnalyticsSession::SaveMonitorExceptCode(int32 InExceptCode)
|
|
{
|
|
if (!ensure(IsLockedBy(FPlatformTLS::GetCurrentThreadId())))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
MonitorExceptCode.Emplace(InExceptCode);
|
|
|
|
const FString StorageLocation = EditorAnalyticsUtils::GetSessionStorageLocation(SessionId);
|
|
return FPlatformMisc::SetStoredValue(EditorAnalyticsDefs::StoreId, StorageLocation, EditorAnalyticsDefs::MonitorExceptCodeStoreKey, FString::FromInt(InExceptCode));
|
|
}
|
|
|
|
void FEditorAnalyticsSession::CreateMinimalCrashSession(const TOptional<int32>& ExitCode)
|
|
{
|
|
if (!ensure(IsLockedBy(FPlatformTLS::GetCurrentThreadId()))) // Caller needs to own the session lock.
|
|
{
|
|
return;
|
|
}
|
|
|
|
FEditorAnalyticsSession MinimalSession;
|
|
MinimalSession.SessionId = FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); // To have a unique session id used as key in the storage.
|
|
MinimalSession.bCrashed = true;
|
|
MinimalSession.ExitCode = ExitCode;
|
|
MinimalSession.StartupTimestamp = FDateTime::UtcNow();
|
|
MinimalSession.Timestamp = MinimalSession.StartupTimestamp; // To enable session 'expiration'.
|
|
MinimalSession.EngineVersion = FEngineVersion::Current().ToString(EVersionComponent::Changelist); // To account the crash for the corresponding engine version.
|
|
|
|
MinimalSession.Save();
|
|
|
|
// Update the session list.
|
|
TArray<FString> StoredSessions;
|
|
GetStoredSessionIDs(StoredSessions);
|
|
StoredSessions.Add(MinimalSession.SessionId);
|
|
SaveStoredSessionIDs(StoredSessions);
|
|
}
|
|
|
|
bool FEditorAnalyticsSession::IsMinimalCrashSession() const
|
|
{
|
|
return AppId == EditorAnalyticsDefs::UnknownAppIdString && ProjectID.IsEmpty() && Plugins.Num() == 0; // Check fields values that are not expected when the session is fully initialized.
|
|
}
|