2019-12-26 15:32:37 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-10-03 16:26:48 -04:00
# include "EditorAnalyticsSession.h"
# include "Modules/ModuleManager.h"
2020-03-20 17:10:14 -04:00
# include "Internationalization/Regex.h"
2021-06-01 17:01:37 -04:00
# include "Containers/UnrealString.h"
# include "GenericPlatform/GenericPlatformFile.h"
# include "HAL/CriticalSection.h"
2020-03-20 17:25:16 -04:00
# include "HAL/PlatformProcess.h"
2021-06-01 17:01:37 -04:00
# include "HAL/PlatformMisc.h"
2020-03-20 17:25:16 -04:00
# include "HAL/FileManager.h"
2021-06-01 17:01:37 -04:00
# include "Misc/DateTime.h"
2021-01-18 10:45:38 -04:00
# include "Misc/EngineVersion.h"
2021-01-20 13:41:51 -04:00
# include "Misc/Guid.h"
2020-03-20 17:25:16 -04:00
# include "Misc/Paths.h"
2019-10-03 16:26:48 -04:00
IMPLEMENT_MODULE ( FEditorAnalyticsSessionModule , EditorAnalyticsSession ) ;
namespace EditorAnalyticsDefs
{
2020-06-23 18:40:00 -04:00
// 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).
2020-08-11 01:36:57 -04:00
// 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.
2020-09-24 00:43:27 -04:00
// Version 1_3 : Added SessionTickCount, UserInteractionCount, IsCrcExeMissing, IsUserLoggingOut, MonitorExitCode and readded lost code to save/load/delete IsLowDriveSpace for 4.26.0.
2020-11-24 18:42:39 -04:00
// Version 1_4 : Added CommandLine, EngineTickCount, LastTickTimestamp, DeathTimestamp and IsDebuggerIgnored for 4.26.0.
2021-02-02 16:47:04 -04:00
// Version 1_5 : Added Stall Detector stats for 5.0.
2021-05-13 21:58:20 -04:00
// Version 1_6 : Added ProcessDiagnostics, Renamed IsDebugger as IsDebuggerPresent: This was the last version using this system before the refactor in UE 5.0.
2019-10-03 16:26:48 -04:00
static const FString StoreId ( TEXT ( " Epic Games " ) ) ;
2020-08-11 01:36:57 -04:00
static const FString SessionSummaryRoot ( TEXT ( " Unreal Engine/Session Summary " ) ) ;
2021-02-25 18:14:09 -04:00
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 " ) ,
2021-05-13 21:58:20 -04:00
SessionSummaryRoot / TEXT ( " 1_6 " ) ,
2021-02-25 18:14:09 -04:00
} ;
2021-05-13 21:58:20 -04:00
static const FString SessionSummarySection = SessionSummaryRoot / TEXT ( " 1_7 " ) ; // The current session format.
2019-10-03 16:26:48 -04:00
static const FString GlobalLockName ( TEXT ( " UE4_SessionSummary_Lock " ) ) ;
static const FString SessionListStoreKey ( TEXT ( " SessionList " ) ) ;
static const FString TimestampStoreKey ( TEXT ( " Timestamp " ) ) ;
}
// Utilities for writing to stored values
namespace EditorAnalyticsUtils
{
static FDateTime StringToTimestamp ( FString InString )
{
int64 TimestampUnix ;
if ( LexTryParseString ( TimestampUnix , * InString ) )
{
return FDateTime : : FromUnixTimestamp ( TimestampUnix ) ;
}
return FDateTime : : MinValue ( ) ;
}
2020-03-20 17:10:14 -04:00
static FString GetSessionEventLogDir ( )
{
return FString : : Printf ( TEXT ( " %sAnalytics " ) , FPlatformProcess : : ApplicationSettingsDir ( ) ) ;
}
2021-06-01 17:01:37 -04:00
static void DeleteLogEvents ( const FString & SessionId )
2020-03-20 17:10:14 -04:00
{
// Gather the list of files
TArray < FString > SessionEventPaths ;
2021-06-01 17:01:37 -04:00
IFileManager : : Get ( ) . IterateDirectoryRecursively ( * EditorAnalyticsUtils : : GetSessionEventLogDir ( ) , [ & SessionId , & SessionEventPaths ] ( const TCHAR * Pathname , bool bIsDir )
2020-03-20 17:10:14 -04:00
{
if ( bIsDir )
{
2021-06-01 17:01:37 -04:00
if ( FPaths : : GetCleanFilename ( Pathname ) . StartsWith ( SessionId ) )
2020-03-20 17:10:14 -04:00
{
SessionEventPaths . Emplace ( Pathname ) ;
}
}
return true ; // Continue
} ) ;
// Delete the session files.
for ( const FString & EventPathname : SessionEventPaths )
{
IFileManager : : Get ( ) . DeleteDirectory ( * EventPathname , /*RequiredExist*/ false , /*Tree*/ false ) ;
}
}
2019-10-03 16:26:48 -04:00
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 ) ;
}
}
2021-06-01 17:01:37 -04:00
void CleanupDeprecatedAnalyticSessions ( const FTimespan & MaxAge )
2019-10-03 16:26:48 -04:00
{
2021-06-01 17:01:37 -04:00
FSystemWideCriticalSection SysWideLock ( EditorAnalyticsDefs : : GlobalLockName , FTimespan : : Zero ( ) ) ;
if ( ! SysWideLock . IsValid ( ) )
2019-10-03 16:26:48 -04:00
{
2021-06-01 17:01:37 -04:00
return ; // Failed to lock, don't bother, this is just for cleaning old deprecated stuff, will do next time.
2020-08-11 01:36:57 -04:00
}
2021-02-25 18:14:09 -04:00
// Helper function to scan and clear sessions stored in sections corresponding to older versions.
2020-08-11 01:36:57 -04:00
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 )
{
2021-06-01 17:01:37 -04:00
// All versions had a 'Timestamp' field. If it is not found, the session was partially deleted and should be cleaned up.
2020-08-11 01:36:57 -04:00
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 ;
}
2021-06-01 17:01:37 -04:00
// Clean up the log events (if any) left-over by this session.
EditorAnalyticsUtils : : DeleteLogEvents ( SessionID ) ;
2020-08-11 01:36:57 -04:00
}
}
}
// Nothing in the section is worth keeping, delete it entirely.
FPlatformMisc : : DeleteStoredSection ( EditorAnalyticsDefs : : StoreId , SectionVersion ) ;
} ;
2021-06-01 17:01:37 -04:00
if ( IFileManager : : Get ( ) . DirectoryExists ( * EditorAnalyticsUtils : : GetSessionEventLogDir ( ) ) )
{
int32 FileCount = 0 ;
// Find the 'log events' directory that could be left over from previous executions.
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 ( ) . IterateDirectory ( * EditorAnalyticsUtils : : GetSessionEventLogDir ( ) , [ & Pattern , & MaxAge , & FileCount ] ( const TCHAR * Pathname , bool bIsDir )
{
+ + FileCount ;
if ( bIsDir ) // Log events were encoded in the directory name.
{
FRegexMatcher Matcher ( Pattern , FPaths : : GetCleanFilename ( Pathname ) ) ;
if ( Matcher . FindNext ( ) )
{
FFileStatData DirStats = IFileManager : : Get ( ) . GetStatData ( Pathname ) ;
if ( ( FDateTime : : UtcNow ( ) - DirStats . CreationTime ) . GetTotalSeconds ( ) > = MaxAge . GetTotalSeconds ( ) )
{
if ( IFileManager : : Get ( ) . DeleteDirectory ( Pathname ) )
{
- - FileCount ;
}
}
}
}
return true ; // Continue.
} ) ;
if ( FileCount = = 0 )
{
IFileManager : : Get ( ) . DeleteDirectory ( * EditorAnalyticsUtils : : GetSessionEventLogDir ( ) ) ;
}
}
2021-02-25 18:14:09 -04:00
// Delete older and incompatible sections unless it contains a valid session young enough that would be picked up
2020-08-11 01:36:57 -04:00
// if an older Editor with compatible format was launched again.
2021-02-25 18:14:09 -04:00
for ( int i = 0 ; i < UE_ARRAY_COUNT ( EditorAnalyticsDefs : : DeprecatedVersions ) ; + + i )
{
CleanupVersionedSection ( EditorAnalyticsDefs : : DeprecatedVersions [ i ] ) ;
}
2020-08-11 01:36:57 -04:00
}