// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "UnrealFrontendPrivatePCH.h" #include "StatsConvertCommand.h" void FStatsConvertCommand::WriteString( FArchive& Writer, const ANSICHAR* Format, ... ) { ANSICHAR Array[1024]; va_list ArgPtr; va_start(ArgPtr,Format); // Build the string int32 Result = FCStringAnsi::GetVarArgs(Array,ARRAY_COUNT(Array),ARRAY_COUNT(Array)-1,Format,ArgPtr); // Now write that to the file Writer.Serialize((void*)Array,Result); } void FStatsConvertCommand::InternalRun() { #if STATS // get the target file FString TargetFile; FParse::Value(FCommandLine::Get(), TEXT("-INFILE="), TargetFile); FString OutFile; FParse::Value(FCommandLine::Get(), TEXT("-OUTFILE="), OutFile); FString StatListString; FParse::Value(FCommandLine::Get(), TEXT("-STATLIST="), StatListString); // get the list of stats TArray StatArrayString; if (StatListString.ParseIntoArray(StatArrayString, TEXT("+"), true) == 0) { StatArrayString.Add(TEXT("STAT_FrameTime")); } // convert to FNames for faster compare for( const FString& It : StatArrayString ) { StatList.Add(*It); } // open a csv file for write TAutoPtr FileWriter( IFileManager::Get().CreateFileWriter( *OutFile ) ); if (!FileWriter) { UE_LOG( LogStats, Error, TEXT( "Could not open output file: %s" ), *OutFile ); return; } // @TODO yrx 2014-03-24 move to function // attempt to read the data and convert to csv const int64 Size = IFileManager::Get().FileSize( *TargetFile ); if( Size < 4 ) { UE_LOG( LogStats, Error, TEXT( "Could not open input file: %s" ), *TargetFile ); return; } TAutoPtr FileReader( IFileManager::Get().CreateFileReader( *TargetFile ) ); if( !FileReader ) { UE_LOG( LogStats, Error, TEXT( "Could not open input file: %s" ), *TargetFile ); return; } if( !Stream.ReadHeader( *FileReader ) ) { UE_LOG( LogStats, Error, TEXT( "Could not open input file, bad magic: %s" ), *TargetFile ); return; } // This is not supported yet. if (Stream.Header.bRawStatsFile) { UE_LOG( LogStats, Error, TEXT( "Could not open input file, not supported type (raw): %s" ), *TargetFile ); return; } const bool bIsFinalized = Stream.Header.IsFinalized(); if( bIsFinalized ) { // Read metadata. TArray MetadataMessages; Stream.ReadFNamesAndMetadataMessages( *FileReader, MetadataMessages ); ThreadState.ProcessMetaDataOnly( MetadataMessages ); // Read frames offsets. Stream.ReadFramesOffsets( *FileReader ); FileReader->Seek( Stream.FramesInfo[0].FrameFileOffset ); } if( Stream.Header.HasCompressedData() ) { UE_CLOG( !bIsFinalized, LogStats, Fatal, TEXT( "Compressed stats file has to be finalized" ) ); } ReadAndConvertStatMessages( *FileReader, *FileWriter ); #endif // STATS } void FStatsConvertCommand::ReadAndConvertStatMessages( FArchive& Reader, FArchive& Writer ) { // output the csv header WriteString( Writer, "Frame,Name,Value\r\n" ); uint64 ReadMessages = 0; TArray Messages; // Buffer used to store the compressed and decompressed data. TArray SrcData; TArray DestData; const bool bHasCompressedData = Stream.Header.HasCompressedData(); const bool bIsFinalized = Stream.Header.IsFinalized(); float DataLoadingProgress = 0.0f; // Sanity checks. check( bHasCompressedData ); while( Reader.Tell() < Reader.TotalSize() ) { // Read the compressed data. FCompressedStatsData UncompressedData( SrcData, DestData ); Reader << UncompressedData; if( UncompressedData.HasReachedEndOfCompressedData() ) { return; } FMemoryReader MemoryReader( DestData, true ); while( MemoryReader.Tell() < MemoryReader.TotalSize() ) { // read the message FStatMessage Message( Stream.ReadMessage( MemoryReader, bIsFinalized ) ); ReadMessages++; if( ReadMessages % 32768 == 0 ) { UE_LOG( LogStats, Log, TEXT( "StatsConvertCommand progress: %.1f%%" ), DataLoadingProgress ); } if( Message.NameAndInfo.GetShortName() != TEXT( "Unknown FName" ) ) { if( Message.NameAndInfo.GetField() == EStatOperation::AdvanceFrameEventGameThread && ReadMessages > 2 ) { new (Messages) FStatMessage( Message ); ThreadState.AddMessages( Messages ); Messages.Reset(); CollectAndWriteStatsValues( Writer ); DataLoadingProgress = (double)Reader.Tell() / (double)Reader.TotalSize() * 100.0f; } new (Messages) FStatMessage( Message ); } else { break; } } } } void FStatsConvertCommand::CollectAndWriteStatsValues( FArchive& Writer ) { // get the thread stats TArray Stats; ThreadState.GetInclusiveAggregateStackStats(ThreadState.CurrentGameFrame, Stats); // The tick rate for different platforms will be different. We cannot use the Windows tick rate accurately here. We will pull it from the stats, // but until we encounter one our best bet is to leave values as full ticks that can be analysed by hand. double MillisecondsPerCycle = 1.0f; for (int32 Index = 0; Index < Stats.Num(); ++Index) { FStatMessage const& Meta = Stats[Index]; //UE_LOG(LogTemp, Display, TEXT("Stat: %s"), *Meta.NameAndInfo.GetShortName().ToString()); if (Meta.NameAndInfo.GetShortName() == FStatConstants::NAME_SecondsPerCycle) { // SecondsPerCycle may vary over time, so we update it here MillisecondsPerCycle = Meta.GetValue_double() * 1000.0f; } for (int32 Jndex = 0; Jndex < StatList.Num(); ++Jndex) { if (Meta.NameAndInfo.GetShortName() == StatList[Jndex]) { double StatValue = 0.0f; if (Meta.NameAndInfo.GetFlag(EStatMetaFlags::IsPackedCCAndDuration)) { StatValue = MillisecondsPerCycle * FromPackedCallCountDuration_Duration(Meta.GetValue_int64()); } else if (Meta.NameAndInfo.GetFlag(EStatMetaFlags::IsCycle)) { StatValue = MillisecondsPerCycle * Meta.GetValue_int64(); } else { StatValue = Meta.GetValue_int64(); } // write out to the csv file WriteString( Writer, "%d,%S,%f\r\n", ThreadState.CurrentGameFrame, *StatList[Jndex].ToString(), StatValue ); } } } }