// Copyright Epic Games, Inc. All Rights Reserved. #include "Common/StatsCollector.h" #include "HAL/PlatformTime.h" #include "Misc/ScopeLock.h" #include "Misc/OutputDeviceRedirector.h" namespace BuildPatchServices { const double ToPercentage = 10000; const double FromPercentage = 1 / ToPercentage; uint64 FStatsCollector::GetCycles() { return FPlatformTime::Cycles64(); } double FStatsCollector::GetSeconds() { return CyclesToSeconds(GetCycles()); } double FStatsCollector::CyclesToSeconds(uint64 Cycles) { return FPlatformTime::GetSecondsPerCycle64() * double(Cycles); } uint64 FStatsCollector::SecondsToCycles(double Seconds) { const long double Result = Seconds / FPlatformTime::GetSecondsPerCycle64(); // We upper clamp to cover cases where Seconds is too high to represent the resulting cycles as uint64. if (Result >= double(TNumericLimits::Max())) { return TNumericLimits::Max(); } else { return Result; } } void FStatsCollector::AccumulateTimeBegin(uint64& TempValue) { TempValue = FStatsCollector::GetCycles(); } void FStatsCollector::AccumulateTimeEnd(volatile FAtomicValue* Stat, uint64& TempValue) { FPlatformAtomics::InterlockedAdd(Stat, FAtomicValue(FStatsCollector::GetCycles() - TempValue)); } void FStatsCollector::Accumulate(volatile FAtomicValue* Stat, int64 Amount) { FPlatformAtomics::InterlockedAdd(Stat, FAtomicValue(Amount)); } void FStatsCollector::Set(volatile FAtomicValue* Stat, int64 Value) { FPlatformAtomics::InterlockedExchange(Stat, FAtomicValue(Value)); } void FStatsCollector::SetAsPercentage(volatile FAtomicValue* Stat, double Value) { FPlatformAtomics::InterlockedExchange(Stat, FAtomicValue(Value * ToPercentage)); } double FStatsCollector::GetAsPercentage(volatile FAtomicValue* Stat) { return *Stat * FromPercentage; } class FStatsCollectorImpl : public FStatsCollector { public: FStatsCollectorImpl(); virtual ~FStatsCollectorImpl(); virtual volatile FAtomicValue* CreateStat(const FString& Name, EStatFormat Type, FAtomicValue InitialValue = 0) override; virtual void LogStats(float TimeBetweenLogs = 0.0f) override; private: FCriticalSection DataCS; TMap AddedStats; TMap AtomicNameMap; TMap AtomicFormatMap; uint64 LastLogged; int32 LongestName; FNumberFormattingOptions PercentageFormattingOptions; }; FStatsCollectorImpl::FStatsCollectorImpl() : LastLogged(FStatsCollector::GetCycles()) , LongestName(0) { PercentageFormattingOptions.MinimumFractionalDigits = 2; PercentageFormattingOptions.MaximumFractionalDigits = 2; } FStatsCollectorImpl::~FStatsCollectorImpl() { FScopeLock ScopeLock(&DataCS); for (auto& Stat : AddedStats) { delete Stat.Value; } AddedStats.Empty(); AtomicNameMap.Empty(); AtomicFormatMap.Empty(); } volatile FStatsCollector::FAtomicValue* FStatsCollectorImpl::CreateStat(const FString& Name, EStatFormat Type, FAtomicValue InitialValue) { FScopeLock ScopeLock(&DataCS); if (AddedStats.Contains(Name) == false) { volatile FAtomicValue* NewInteger = new volatile FAtomicValue(InitialValue); AddedStats.Add(Name, NewInteger); AtomicNameMap.Add((FAtomicValue*)NewInteger, Name + TEXT(": ")); AtomicFormatMap.Add((FAtomicValue*)NewInteger, Type); LongestName = FMath::Max(LongestName, Name.Len() + 2); for (auto& Stat : AtomicNameMap) { while (Stat.Value.Len() < LongestName) { Stat.Value += TEXT(" "); } } } return AddedStats[Name]; } void FStatsCollectorImpl::LogStats(float TimeBetweenLogs) { const uint64 Cycles = FStatsCollector::GetCycles(); if (FStatsCollector::CyclesToSeconds(Cycles - LastLogged) >= TimeBetweenLogs) { LastLogged = Cycles; FScopeLock ScopeLock(&DataCS); GLog->Log(TEXT("/-------- FStatsCollector Log ---------------------")); for (auto& Stat : AddedStats) { FAtomicValue* NameLookup = (FAtomicValue*)Stat.Value; switch (AtomicFormatMap[NameLookup]) { case EStatFormat::Timer: GLog->Logf(TEXT("| %s%s"), *AtomicNameMap[NameLookup], *FPlatformTime::PrettyTime(FStatsCollector::CyclesToSeconds(*Stat.Value))); break; case EStatFormat::DataSize: GLog->Logf(TEXT("| %s%s, %s"), *AtomicNameMap[NameLookup], *FText::AsMemory(*Stat.Value, EMemoryUnitStandard::SI).ToString(), *FText::AsMemory(*Stat.Value, EMemoryUnitStandard::IEC).ToString()); break; case EStatFormat::DataSpeed: GLog->Logf(TEXT("| %s%s/s, %s/s"), *AtomicNameMap[NameLookup], *FText::AsMemory(*Stat.Value, EMemoryUnitStandard::SI).ToString(), *FText::AsMemory(*Stat.Value, EMemoryUnitStandard::IEC).ToString()); break; case EStatFormat::Percentage: GLog->Logf(TEXT("| %s%s"), *AtomicNameMap[NameLookup], *FText::AsPercent(GetAsPercentage(Stat.Value), &PercentageFormattingOptions).ToString()); break; default: GLog->Logf(TEXT("| %s%lld"), *AtomicNameMap[NameLookup], *Stat.Value); break; } } GLog->Log(TEXT("\\--------------------------------------------------")); } } FStatsCollector* FStatsCollectorFactory::Create() { return new FStatsCollectorImpl(); } }