Files
UnrealEngine/Engine/Source/Runtime/Experimental/IoStore/OnDemand/Private/Statistics.cpp
2025-05-18 13:04:45 +08:00

1315 lines
44 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Statistics.h"
#include "Async/Mutex.h"
#include "Async/UniqueLock.h"
#include "Algo/MaxElement.h"
#include "AnalyticsEventAttribute.h"
#include "HAL/IConsoleManager.h"
#include "HAL/PlatformTime.h"
#include "IO/IoStatus.h"
#include "IO/IoStoreOnDemand.h"
#include "IasHostGroup.h"
#include "Internationalization/Internationalization.h"
#include "Math/UnrealMathUtility.h"
#include "Misc/CoreDelegates.h"
#include "OnDemandHttpThread.h"
#include "Templates/Requires.h"
LLM_DEFINE_TAG(Ias);
#if IAS_WITH_STATISTICS
#define UE_ENABLE_ANALYTICS_RECORDING 0
#define UE_ENABLE_ONSCREEN_STATISTICS !UE_BUILD_SHIPPING
namespace UE::IoStore
{
float GIasStatisticsLogInterval = 30.f;
static FAutoConsoleVariableRef CVar_StatisticsLogInterval(
TEXT("ias.StatisticsLogInterval"),
GIasStatisticsLogInterval,
TEXT("Enables and sets interval for periodic logging of statistics"));
bool GIasReportHttpAnalyticsEnabled = true;
static FAutoConsoleVariableRef CVar_ReportHttpAnalytics(
TEXT("ias.ReportHttpAnalytics"),
GIasReportHttpAnalyticsEnabled,
TEXT("Enables reporting statics on our http traffic to the analytics system"));
bool GIasReportCacheAnalyticsEnabled = true;
static FAutoConsoleVariableRef CVar_ReportCacheAnalytics(
TEXT("ias.ReportCacheAnalytics"),
GIasReportCacheAnalyticsEnabled,
TEXT("Enables reporting statics on our file cache usage to the analytics system"));
bool GIadReportAnalyticsEnabled = true;
static FAutoConsoleVariableRef CVar_ReportIadAnalyticsEnabled(
TEXT("iad.ReportAnalytics"),
GIadReportAnalyticsEnabled,
TEXT("Enables reporting analytics for individual asset downloads."));
#if UE_ENABLE_ONSCREEN_STATISTICS
bool GIasDisplayOnScreenStatistics = false;
static FAutoConsoleVariableRef CVar_DisplayOnScreenStatistics(
TEXT("ias.DisplayOnScreenStatistics"),
GIasDisplayOnScreenStatistics,
TEXT("Enables display of Ias on screen statistics"));
#endif // UE_ENABLE_ONSCREEN_STATISTICS
////////////////////////////////////////////////////////////////////////////////
static int32 BytesToApproxMB(uint64 Bytes) { return int32(Bytes >> 20); }
static int32 BytesToApproxKB(uint64 Bytes) { return int32(Bytes >> 10); }
/**
* Code taken from SummarizeTraceCommandlet.cpp pending discussion on moving it
* somewhere for general use.
* Currently not thread safe!
*/
class FIncrementalVariance
{
public:
FIncrementalVariance()
: Count(0)
, Mean(0.0)
, VarianceAccumulator(0.0)
{
}
uint64 GetCount() const
{
return Count;
}
double GetMean() const
{
return Mean;
}
/**
* Compute the variance given Welford's accumulator and the overall count
*
* @return The variance in sample units squared
*/
double GetVariance() const
{
double Result = 0.0;
if (Count > 1)
{
// Welford's final step, dependent on sample count
Result = VarianceAccumulator / double(Count - 1);
}
return Result;
}
/**
* Compute the standard deviation given Welford's accumulator and the overall count
*
* @return The standard deviation in sample units
*/
double GetDeviation() const
{
double Result = 0.0;
if (Count > 1)
{
// Welford's final step, dependent on sample count
double DeviationSqrd = VarianceAccumulator / double(Count - 1);
// stddev is sqrt of variance, to restore to units (vs. units squared)
Result = sqrt(DeviationSqrd);
}
return Result;
}
/**
* Perform an increment of work for Welford's variance, from which we can compute variation and standard deviation
*
* @param InSample The new sample value to operate on
*/
void Increment(const double InSample)
{
Count++;
const double OldMean = Mean;
Mean += ((InSample - Mean) / double(Count));
VarianceAccumulator += ((InSample - Mean) * (InSample - OldMean));
}
/**
* Merge with another IncrementalVariance series in progress
*
* @param Other The other variance incremented from another mutually exclusive population of analogous data.
*/
void Merge(const FIncrementalVariance& Other)
{
// empty other, nothing to do
if (Other.Count == 0)
{
return;
}
// empty this, just copy other
if (Count == 0)
{
Count = Other.Count;
Mean = Other.Mean;
VarianceAccumulator = Other.VarianceAccumulator;
return;
}
const double TotalPopulation = static_cast<double>(Count + Other.Count);
const double MeanDifference = Mean - Other.Mean;
const double A = (double(Count - 1) * GetVariance()) + (double(Other.Count - 1) * Other.GetVariance());
const double B = (MeanDifference) * (MeanDifference) * (double(Count) * double(Other.Count) / TotalPopulation);
const double MergedVariance = (A + B) / (TotalPopulation - 1);
const uint64 NewCount = Count + Other.Count;
const double NewMean = ((Mean * double(Count)) + (Other.Mean * double(Other.Count))) / double(NewCount);
const double NewVarianceAccumulator = MergedVariance * double(NewCount - 1);
Count = NewCount;
Mean = NewMean;
VarianceAccumulator = NewVarianceAccumulator;
}
/**
* Reset state back to initialized.
*/
void Reset()
{
Count = 0;
Mean = 0.0;
VarianceAccumulator = 0.0;
}
private:
uint64 Count;
double Mean;
double VarianceAccumulator;
};
class FDeltaTracking
{
public:
int64 Get(FStringView Name, int64 Value)
{
if (int64* PrevValue = IntTotals.FindByHash(GetTypeHash(Name), Name))
{
const int64 Delta = Value - *PrevValue;
*PrevValue = Value;
return Delta;
}
else
{
IntTotals.Add(FString(Name), Value);
return Value;
}
}
uint32 Get(FStringView Name, uint32 Value)
{
return static_cast<uint32>(Get(Name, static_cast<int64>(Value)));
}
double Get(FStringView Name, double Value)
{
if (double* PrevValue = RealTotals.FindByHash(GetTypeHash(Name), Name))
{
const double Delta = Value - *PrevValue;
*PrevValue = Value;
return Delta;
}
else
{
RealTotals.Add(FString(Name), 0.0);
return Value;
}
}
private:
TMap<FString, int64> IntTotals;
TMap<FString, double> RealTotals;
} static GDeltaTracking;
////////////////////////////////////////////////////////////////////////////////
// TRACE STATS
#if COUNTERSTRACE_ENABLED
using FCounterInt = FCountersTrace::FCounterInt;
using FCounterAtomicInt = FCountersTrace::FCounterAtomicInt;
#else
template <typename Type>
struct TCounterInt
{
TCounterInt(...) {}
void Set(int64 i) { V = i; }
void Add(int64 d) { V += d; }
void Increment() { V++; }
void Decrement() { --V; }
int64 Get() const { return V;}
Type V = 0;
};
using FCounterInt = TCounterInt<int64>;
using FCounterAtomicInt = TCounterInt<std::atomic<int64>>;
#endif
#define UE_IAX_COUNTER(Name, Type) Type G##Name[(uint8)EHttpRequestType::NUM_SOURCES] = {{TEXT(UE_STRINGIZE(Ias/Name)), TraceCounterDisplayHint_None}, {TEXT(UE_STRINGIZE(Iad/Name)), TraceCounterDisplayHint_None}};
#define UE_IAX_MEMORY_COUNTER(Name, Type) Type G##Name[(uint8)EHttpRequestType::NUM_SOURCES] = {{TEXT(UE_STRINGIZE(Ias/Name)), TraceCounterDisplayHint_None}, {TEXT(UE_STRINGIZE(Iad/Name)), TraceCounterDisplayHint_Memory}};
// iorequest stats
FCounterInt GIoRequestCount(TEXT("Ias/IoRequestCount"), TraceCounterDisplayHint_None);
FCounterAtomicInt GIoRequestReadCount(TEXT("Ias/IoRequestReadCount"), TraceCounterDisplayHint_None);
FCounterAtomicInt GIoRequestReadBytes(TEXT("Ias/IoRequestReadBytes"), TraceCounterDisplayHint_Memory);
FCounterInt GIoRequestCancelCount(TEXT("Ias/IoRequestCancelCount"), TraceCounterDisplayHint_None);
FCounterAtomicInt GIoRequestErrorCount(TEXT("Ias/IoRequestErrorCount"), TraceCounterDisplayHint_None);
// cache stats
FCounterAtomicInt GCacheErrorCount(TEXT("Ias/CacheErrorCount"), TraceCounterDisplayHint_None);
FCounterAtomicInt GCacheDecodeErrorCount(TEXT("Ias/CacheDecodeErrorCount"), TraceCounterDisplayHint_None);
FCounterAtomicInt GCacheGetCount(TEXT("Ias/CacheGetCount"), TraceCounterDisplayHint_None);
FCounterAtomicInt GCachePutCount(TEXT("Ias/CachePutCount"), TraceCounterDisplayHint_None);
FCounterAtomicInt GCachePutExistingCount(TEXT("Ias/CachePutExistingCount"), TraceCounterDisplayHint_None);
FCounterAtomicInt GCachePutRejectCount(TEXT("Ias/CachePutRejectCount"), TraceCounterDisplayHint_None);
FCounterAtomicInt GCacheCachedBytes(TEXT("Ias/CacheCachedBytes"), TraceCounterDisplayHint_Memory);
FCounterAtomicInt GCacheWrittenBytes(TEXT("Ias/CacheWrittenBytes"), TraceCounterDisplayHint_Memory);
int64 GCacheMaxBytes = 0;
FCounterAtomicInt GCachePendingBytes(TEXT("Ias/CachePendingBytes"), TraceCounterDisplayHint_Memory);
FCounterAtomicInt GCacheReadBytes(TEXT("Ias/CacheReadBytes"), TraceCounterDisplayHint_Memory);
FCounterAtomicInt GCacheRejectBytes(TEXT("Ias/CachePutRejectBytes"), TraceCounterDisplayHint_Memory);
// http stats
bool GHttpDistributedEndpointResolved = false;
FCounterInt GHttpConnectCount(TEXT("Ias/HttpConnectCount"), TraceCounterDisplayHint_None);
FCounterInt GHttpDisconnectCount(TEXT("Ias/HttpDisconnectCount"), TraceCounterDisplayHint_None);
UE_IAX_COUNTER(HttpGetCount, FCounterInt);
UE_IAX_COUNTER(HttpErrorCount, FCounterInt);
UE_IAX_COUNTER(HttpDecodeErrorCount, FCounterAtomicInt);
UE_IAX_COUNTER(HttpRetryCount, FCounterInt);
UE_IAX_COUNTER(HttpCancelCount, FCounterInt);
UE_IAX_COUNTER(HttpPendingCount, FCounterAtomicInt);
UE_IAX_COUNTER(HttpInflightCount, FCounterInt);
UE_IAX_MEMORY_COUNTER(HttpDownloadedBytes, FCounterInt);
UE_IAX_COUNTER(HttpDurationMs, FCounterInt);
UE_IAX_COUNTER(HttpBandwidthMbps, FCounterInt);
double GHttpDurationMsAvg[(uint8)EHttpRequestType::NUM_SOURCES] = { 0.0 };
int32 GHttpDurationMsMax[(uint8)EHttpRequestType::NUM_SOURCES] = { 0 };
int64 GHttpDurationMsSum[(uint8)EHttpRequestType::NUM_SOURCES] = { 0 };
struct FHttpRecentHistoryStatistics
{
static const int64 HistoryCount = 16;
int64 Duration[HistoryCount] = {};
int64 Bytes[HistoryCount] = {};
int64 TotalDuration = 0;
int64 MaxDuration = 0;
int64 TotalBytes = 0;
int64 Index = 0;
void OnGet(uint64 SizeBytes, uint64 DurationMs)
{
int64 OldDuration = Duration[Index];
int64 NewDuration = (int64)DurationMs;
TotalDuration -= OldDuration;
TotalDuration += NewDuration;
Duration[Index] = NewDuration;
TotalBytes -= Bytes[Index];
TotalBytes += SizeBytes;
Bytes[Index] = SizeBytes;
MaxDuration = FMath::Max(NewDuration, MaxDuration);
Index = (Index + 1) % HistoryCount;
}
int64 GetBandwidthMbps() const
{
return (TotalBytes * 8) / (TotalDuration + 1) / 1000;
}
double GetAverage() const
{
return static_cast<double>(TotalDuration) / static_cast<double>(HistoryCount);
}
int64 GetMaxDuration() const
{
return MaxDuration;
}
};
FHttpRecentHistoryStatistics GHttpHistory[(uint8)EHttpRequestType::NUM_SOURCES];
// Experimental Http Stats
static uint32 GHttpCdnCacheHit = 0;
static uint32 GHttpCdnCacheMiss = 0;
static uint32 GHttpCdnCacheUnknown = 0;
////////////////////////////////////////////////////////////////////////////////
// CSV STATS
#if CSV_PROFILER && !CSV_PROFILER_MINIMAL
#define UE_IAX_CSV_DEFINE_STAT(StatName) FCsvDeclaredStat _GCsvStat_##StatName[(uint8)EHttpRequestType::NUM_SOURCES] = {{TEXT(#StatName),CSV_CATEGORY_INDEX(Ias)},{TEXT(#StatName), CSV_CATEGORY_INDEX(Iad)}}
#define UE_IAX_CSV_CUSTOM_STAT_DEFINED(RequestType, StatName, Value, Op) FCsvProfiler::RecordCustomStat(_GCsvStat_##StatName[(uint8)RequestType].Name, _GCsvStat_##StatName[(uint8)RequestType].CategoryIndex, Value, Op);
#else
#define UE_IAX_CSV_DEFINE_STAT(StatName)
#define UE_IAX_CSV_CUSTOM_STAT_DEFINED(RequestType, StatName, Value, Op)
#endif
CSV_DEFINE_CATEGORY(Ias, true);
CSV_DEFINE_CATEGORY(Iad, true);
// iorequest per frame stats
CSV_DEFINE_STAT(Ias, FrameIoRequestCount);
CSV_DEFINE_STAT(Ias, FrameIoRequestReadCount);
CSV_DEFINE_STAT(Ias, FrameIoRequestReadMB);
CSV_DEFINE_STAT(Ias, FrameIoRequestCancelCount);
CSV_DEFINE_STAT(Ias, FrameIoRequestErrorCount);
// cache stat totals
CSV_DEFINE_STAT(Ias, CacheGetCount);
CSV_DEFINE_STAT(Ias, CacheErrorCount);
CSV_DEFINE_STAT(Ias, CachePutCount);
CSV_DEFINE_STAT(Ias, CachePutExistingCount);
CSV_DEFINE_STAT(Ias, CachePutRejectCount);
CSV_DEFINE_STAT(Ias, CacheCachedMB);
CSV_DEFINE_STAT(Ias, CacheWrittenMB);
CSV_DEFINE_STAT(Ias, CacheReadMB);
CSV_DEFINE_STAT(Ias, CacheRejectedMB);
// http stat totals
UE_IAX_CSV_DEFINE_STAT(HttpGetCount);
UE_IAX_CSV_DEFINE_STAT(HttpRetryCount);
UE_IAX_CSV_DEFINE_STAT(HttpCancelCount);
UE_IAX_CSV_DEFINE_STAT(HttpErrorCount);
UE_IAX_CSV_DEFINE_STAT(HttpPendingCount);
UE_IAX_CSV_DEFINE_STAT(HttpDownloadedMB);
UE_IAX_CSV_DEFINE_STAT(HttpBandwidthMpbs);
UE_IAX_CSV_DEFINE_STAT(HttpDurationMsAvg);
UE_IAX_CSV_DEFINE_STAT(HttpDurationMsMax);
////////////////////////////////////////////////////////////////////////////////
#if COUNTERSTRACE_ENABLED
struct FInstallerTraceCounters
{
using FCounterFloat = FCountersTrace::FCounterFloat;
void Lock() { Mutex.Lock(); }
void Unlock() { Mutex.Unlock(); }
static FMutex Mutex;
FInstallerTraceCounters()
: InstallCount(TEXT("Iad/InstallCount"), TraceCounterDisplayHint_None)
, InflightInstallCount(TEXT("Iad/InflightInstallCount"), TraceCounterDisplayHint_None)
, DownloadedBytes(TEXT("Iad/DownloadedBytes"), TraceCounterDisplayHint_Memory)
, AvgInstallDurationMs(TEXT("Iad/AvgInstallDurationMs"), TraceCounterDisplayHint_None)
, AvgCacheHitRatio(TEXT("Iad/AvgCacheHitRatio"), TraceCounterDisplayHint_None)
{ }
FCounterInt InstallCount;
FCounterInt InflightInstallCount;
FCounterInt DownloadedBytes;
FCounterFloat AvgInstallDurationMs;
FCounterFloat AvgCacheHitRatio;
FIncrementalVariance InstallDurationMs;
FIncrementalVariance CacheHitRatio;
};
FMutex FInstallerTraceCounters::Mutex;
FInstallerTraceCounters InstallerTraceCounters;
#endif // COUNTERSTRACE_ENABLED
////////////////////////////////////////////////////////////////////////////////
struct FInstallerAnalytics
{
void Lock() { Mutex.Lock(); }
void Unlock() { Mutex.Unlock(); }
static FMutex Mutex;
uint64 InstallCount = 0;
uint64 InstallErrorCount = 0;
uint64 DownloadedBytes = 0;
uint64 TotalInstallDurationMs = 0;
double TotalCacheHitRatio = 0;
};
FMutex FInstallerAnalytics::Mutex;
static FInstallerAnalytics InstallerAnalytics;
////////////////////////////////////////////////////////////////////////////////
struct FInstallCacheAnalytics
{
void Lock() { Mutex.Lock(); }
void Unlock() { Mutex.Unlock(); }
static FMutex Mutex;
uint64 VerificationRemovedBlockCount = 0;
uint64 StartupErrorCount = 0;
uint64 FlushCount = 0;
uint64 FlushErrorCount = 0;
uint64 FlushedBytes = 0;
uint64 PurgedBytes = 0;
uint64 PurgeCount = 0;
uint64 PurgeErrorCount = 0;
uint64 FragmentedBytes = 0;
uint64 DefragCount = 0;
uint64 DefragErrorCount = 0;
uint64 JournalCommitCount = 0;
uint64 JournalCommitErrorCount = 0;
uint64 MaxCacheSize = 0;
uint64 MaxCacheUsageSize = 0;
uint64 MaxReferencedBlockSize = 0;
uint64 MaxReferencedSize = 0;
uint64 MaxFragmentedSize = 0;
int64 OldestBlockAccess = FDateTime::MaxValue().GetTicks();
};
FMutex FInstallCacheAnalytics::Mutex;
static FInstallCacheAnalytics InstallCacheAnalytics;
////////////////////////////////////////////////////////////////////////////////
static FOnDemandIoBackendStats* GStatistics = nullptr;
static FDelegateHandle GStatisticsEndFrameDelegateHandle;
static FDelegateHandle GStatisticsOnScreenDelegateHandle;
FOnDemandIoBackendStats::FOnDemandIoBackendStats(FBackendStatus& InStatus)
: BackendStatus(InStatus)
{
static constexpr float OneOver1024 = 1.0f / 1024.0f;
check(GStatistics == nullptr);
GStatistics = this;
GStatisticsEndFrameDelegateHandle = FCoreDelegates::OnEndFrame.AddLambda([this]()
{
// cache stat totals
int32 CGetCount = int32(GCacheGetCount.Get());
int32 CErrorCount = int32(GCacheErrorCount.Get());
int32 CPutCount = int32(GCachePutCount.Get());
int32 CPutExistingCount = int32(GCachePutExistingCount.Get());
int32 CPutRejectCount = int32(GCachePutRejectCount.Get());
float CCachedKiB = (float)GCacheCachedBytes.Get() * OneOver1024;
float CWrittenKiB = (float)GCacheWrittenBytes.Get() * OneOver1024;
float CReadKiB = (float)GCacheReadBytes.Get() * OneOver1024;
float CRejectedKiB = (float)GCacheRejectBytes.Get() * OneOver1024;
CSV_CUSTOM_STAT_DEFINED(CacheGetCount, CGetCount, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(CacheErrorCount, CErrorCount, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(CachePutCount, CPutCount, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(CachePutExistingCount, CPutExistingCount, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(CachePutRejectCount, CPutRejectCount, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(CacheCachedMB, CCachedKiB * OneOver1024, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(CacheWrittenMB, CWrittenKiB * OneOver1024, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(CacheReadMB, CReadKiB * OneOver1024, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(CacheRejectedMB, CRejectedKiB * OneOver1024, ECsvCustomStatOp::Set);
// http stat totals
auto HttpCsv = [](EHttpRequestType Type)
{
UE_IAX_CSV_CUSTOM_STAT_DEFINED(Type, HttpGetCount, (int32)GHttpGetCount[(uint8)Type].Get(), ECsvCustomStatOp::Set);
UE_IAX_CSV_CUSTOM_STAT_DEFINED(Type, HttpCancelCount, (int32)GHttpCancelCount[(uint8)Type].Get(), ECsvCustomStatOp::Set);
UE_IAX_CSV_CUSTOM_STAT_DEFINED(Type, HttpErrorCount, (int32)GHttpErrorCount[(uint8)Type].Get(), ECsvCustomStatOp::Set);
UE_IAX_CSV_CUSTOM_STAT_DEFINED(Type, HttpPendingCount, (int32)GHttpPendingCount[(uint8)Type].Get(), ECsvCustomStatOp::Set);
UE_IAX_CSV_CUSTOM_STAT_DEFINED(Type, HttpDownloadedMB, (float)GHttpDownloadedBytes[(uint8)Type].Get() * OneOver1024 * OneOver1024, ECsvCustomStatOp::Set);
UE_IAX_CSV_CUSTOM_STAT_DEFINED(Type, HttpBandwidthMpbs, (int32)GHttpBandwidthMbps[(uint8)Type].Get(), ECsvCustomStatOp::Set);
UE_IAX_CSV_CUSTOM_STAT_DEFINED(Type, HttpDurationMsAvg, (int32)GHttpDurationMsAvg[(uint8)Type], ECsvCustomStatOp::Set);
UE_IAX_CSV_CUSTOM_STAT_DEFINED(Type, HttpDurationMsMax, GHttpDurationMsMax[(uint8)Type], ECsvCustomStatOp::Set);
};
HttpCsv(EHttpRequestType::Streaming);
HttpCsv(EHttpRequestType::Installed);
if (GIasStatisticsLogInterval > 0.f)
{
static double LastLogTime = 0.0;
if (double Time = FPlatformTime::Seconds(); Time - LastLogTime > (double)GIasStatisticsLogInterval)
{
if (BackendStatus.IsCacheEnabled())
{
UE_LOG(LogIas, Log, TEXT("CacheStats: CachedKiB=%d, WrittenKiB=%d, ReadKiB=%d, RejectedKiB=%d, Get=%d, Error=%d, Put=%d, PutReject=%d, PutExisting=%d"),
(int32)CCachedKiB, (int32)CWrittenKiB, (int32)CReadKiB, (int32)CRejectedKiB, CGetCount, CErrorCount, CPutCount, CPutRejectCount, CPutExistingCount);
}
else
{
UE_LOG(LogIas, Log, TEXT("CacheStats: Disabled"));
}
auto HttpLog = [](const TCHAR* Title, EHttpRequestType Type)
{
UE_LOG(LogIas, Log, TEXT("%s - HttpStats: DownloadedKiB=%d, Get=%d, Retry=%d, Cancel=%d, Error=%d, CurPending=%d, CurDurationMsAvg=%d, CurDurationMsMax=%d"),
Title,
(int32)((float)GHttpDownloadedBytes[(uint8)Type].Get() * OneOver1024),
(int32)GHttpGetCount[(uint8)Type].Get(),
(int32)GHttpRetryCount[(uint8)Type].Get(),
(int32)GHttpCancelCount[(uint8)Type].Get(),
(int32)GHttpErrorCount[(uint8)Type].Get(),
(int32)GHttpPendingCount[(uint8)Type].Get(),
(int32)GHttpDurationMsAvg[(uint8)Type],
GHttpDurationMsMax[(uint8)Type]);
};
HttpLog(TEXT("IAS"), EHttpRequestType::Streaming);
HttpLog(TEXT("IAD"), EHttpRequestType::Installed);
#if COUNTERSTRACE_ENABLED
{
TUniqueLock Lock(InstallerTraceCounters);
UE_LOG(LogIoStoreOnDemand, Log, TEXT("IadStats: InstallCount=%llu, Downloaded=%d KiB, AvgInstallDuration=%dms, AvgCacheHitRatio=%d%%"),
InstallerTraceCounters.InstallCount.Get(),
(int32(InstallerTraceCounters.DownloadedBytes.Get()) / 1024),
int32(InstallerTraceCounters.AvgInstallDurationMs.Get()),
int32(InstallerTraceCounters.CacheHitRatio.GetMean() * 100.0));
}
#endif // COUNTERSTRACE_ENABLED
LastLogTime = Time;
}
}
});
#if UE_ENABLE_ONSCREEN_STATISTICS
GStatisticsOnScreenDelegateHandle = FCoreDelegates::OnGetOnScreenMessages.AddLambda(
[this] (FCoreDelegates::FSeverityMessageMap& Out)
{
if (!GIasDisplayOnScreenStatistics)
{
return;
}
{
TStringBuilder<256> BackendStatusText;
BackendStatusText << TEXT("IAS Backend Status: ");
BackendStatus.ToString(BackendStatusText);
Out.Add(FCoreDelegates::EOnScreenMessageSeverity::Info, FText::FromStringView(BackendStatusText.ToView()));
}
FHostGroupManager::Get().ForEachHostGroup([&Out](const FIASHostGroup& HostGroup)
{
FCoreDelegates::EOnScreenMessageSeverity Verbosity = FCoreDelegates::EOnScreenMessageSeverity::Info;
TStringBuilder<256> Text;
Text << TEXT("IAS HostGroup [") << HostGroup.GetName() << TEXT("] ");
if (HostGroup.IsConnected())
{
Text << HostGroup.GetPrimaryHostUrl();
Text << TEXT(" (") << HostGroup.GetPrimaryHostIndex() << TEXT("/") << HostGroup.GetHostUrls().Num() << TEXT(")");
}
else if (HostGroup.IsResolved())
{
Text << TEXT("Resolving...");
}
else
{
Text << TEXT("Disconnected");
Verbosity = FCoreDelegates::EOnScreenMessageSeverity::Error;
}
Out.Add(Verbosity, FText::FromStringView(Text.ToView()));
});
{
TStringBuilder<256> CachingText;
CachingText << TEXT("IAS Cache Stats: ");
if (BackendStatus.IsCacheEnabled())
{
CachingText << TEXT("Cached: ") << FText::AsMemory(GCacheCachedBytes.Get()).ToString();
CachingText << TEXT(" | Rejected: ") << FText::AsMemory(GCacheRejectBytes.Get()).ToString();
CachingText << TEXT(" | Read: ") << FText::AsMemory(GCacheReadBytes.Get()).ToString();
CachingText << TEXT(" (") << GCacheGetCount.Get() << TEXT(")");
}
else
{
CachingText << TEXTVIEW("Caching Disabled");
}
Out.Add(FCoreDelegates::EOnScreenMessageSeverity::Info, FText::FromStringView(CachingText.ToView()));
}
{
auto HttpStats = [&Out](const TCHAR* Title, EHttpRequestType Type)
{
TStringBuilder<256> Builder;
Builder.Appendf(
TEXT("%s Backend Stats: Downloaded: %s (%d) Avg %d ms | Retries: %d | Pending: %d"),
Title,
*FText::AsMemory(GHttpDownloadedBytes[(uint8)Type].Get()).ToString(),
GHttpGetCount[(uint8)Type].Get(),
(int32)GHttpDurationMsAvg[(uint8)Type],
GHttpRetryCount[(uint8)Type].Get(),
GHttpPendingCount[(uint8)Type].Get()
);
Out.Add(FCoreDelegates::EOnScreenMessageSeverity::Info, FText::FromStringView(Builder.ToView()));
};
HttpStats(TEXT("IAS"), EHttpRequestType::Streaming);
HttpStats(TEXT("IAD"), EHttpRequestType::Installed);
}
{
TStringBuilder<256> Builder;
Builder << TEXT("IAS CDN: Hit/Miss/NoHdr: ");
Builder << GHttpCdnCacheHit;
Builder << TEXT("/") << GHttpCdnCacheMiss;
Builder << TEXT("/") << GHttpCdnCacheUnknown;
if (uint32 Total = GHttpCdnCacheHit + GHttpCdnCacheMiss + GHttpCdnCacheUnknown; Total)
{
auto AsPercent = [Total] (uint32 Value) { return (Value * 100 + (Total >> 1)) / Total; };
Builder << TEXT(" - ") << AsPercent(GHttpCdnCacheHit);
Builder << TEXT("%/") << AsPercent(GHttpCdnCacheMiss);
Builder << TEXT("%/") << AsPercent(GHttpCdnCacheUnknown);
Builder << TEXT("%");
}
Out.Add(FCoreDelegates::EOnScreenMessageSeverity::Info, FText::FromStringView(Builder.ToView()));
}
if (GHttpDecodeErrorCount[(uint8)EHttpRequestType::Streaming].Get() > 0 || GCacheDecodeErrorCount.Get() > 0 || GHttpErrorCount[(uint8)EHttpRequestType::Streaming].Get() > 0)
{
TStringBuilder<256> Builder;
Builder.Appendf(TEXT("IAS Errors: Cache Decode: %d | Http Decode: %d | Http: %d"),
GCacheDecodeErrorCount.Get(),
GHttpDecodeErrorCount[(uint8)EHttpRequestType::Streaming].Get(),
GHttpErrorCount[(uint8)EHttpRequestType::Streaming].Get()
);
Out.Add(FCoreDelegates::EOnScreenMessageSeverity::Error, FText::FromStringView(Builder.ToView()));
}
});
#endif // UE_ENABLE_ONSCREEN_STATISTICS
}
FOnDemandIoBackendStats::~FOnDemandIoBackendStats()
{
FCoreDelegates::OnEndFrame.Remove(GStatisticsEndFrameDelegateHandle);
FCoreDelegates::OnGetOnScreenMessages.Remove(GStatisticsOnScreenDelegateHandle);
GStatistics = nullptr;
}
FOnDemandIoBackendStats* FOnDemandIoBackendStats::Get()
{
return GStatistics;
}
void FOnDemandIoBackendStats::ReportGeneralAnalytics(TArray<FAnalyticsEventAttribute>& OutAnalyticsArray) const
{
// Note that this analytics section is not optional, if we are reporting analytics then we report this section
// first. This means we can use the values here to determine if an analytics payload contains ondemand data or
// not since with the current system we are unable to specify our own analytics payload.
AppendAnalyticsEventAttributeArray(OutAnalyticsArray
, TEXT("IasHttpDistributedEndpointResolved"), GHttpDistributedEndpointResolved
, TEXT("IasHttpHasEverConnected"), GHttpConnectCount.Get() > 0 // Report if the system has ever actually managed to make a connection
);
}
void FOnDemandIoBackendStats::ReportEndPointAnalytics(TArray<FAnalyticsEventAttribute>& OutAnalyticsArray) const
{
if (GIasReportHttpAnalyticsEnabled)
{
auto ReportHttpStats = [&OutAnalyticsArray](const TCHAR* Prefix, EHttpRequestType Type)
{
#define UE_PREFIX(Name) WriteToString<128>(Prefix, Name)
#define UE_TRACK_DELTA(Name, Value) *Name, GDeltaTracking.Get(Name, Value)
const int64 ByteCount = GDeltaTracking.Get(UE_PREFIX(TEXT("HttpDownloadedBytes")), GHttpDownloadedBytes[(uint8)Type].Get());
const int64 GetCount = GDeltaTracking.Get(UE_PREFIX(TEXT("HttpGetCount")), GHttpGetCount[(uint8)Type].Get());
const double DataRateBPS = GHttpDurationMsSum[(uint8)Type] > 0 ? static_cast<double>(ByteCount) / (static_cast<double>(GHttpDurationMsSum[(uint8)Type]) / 1000.0) : 0.0;
const double DurationMean = GetCount ? static_cast<double>(GHttpDurationMsSum[(uint8)Type]) / static_cast<double>(GetCount) : 0.0;
AppendAnalyticsEventAttributeArray(OutAnalyticsArray
, UE_TRACK_DELTA(UE_PREFIX("HttpErrorCount"), GHttpErrorCount[(uint8)Type].Get())
, UE_TRACK_DELTA(UE_PREFIX("HttpDecodeErrors"), GHttpDecodeErrorCount[(uint8)Type].Get())
, UE_TRACK_DELTA(UE_PREFIX("HttpRetryCount"), GHttpRetryCount[(uint8)Type].Get())
, *UE_PREFIX(TEXT("HttpGetCount")), GetCount
, *UE_PREFIX(TEXT("HttpDownloadedBytes")), ByteCount
, *UE_PREFIX(TEXT("HttpDurationMean")), DurationMean
, *UE_PREFIX(TEXT("HttpDurationSum")), GHttpDurationMsSum[(uint8)Type]
, *UE_PREFIX(TEXT("HttpDataRateMean")), DataRateBPS
);
// These values we can just reset as they are only being used with analytics
GHttpDurationMsSum[(uint8)Type] = 0;
#undef UE_TRACK_DELTA
#undef UE_PREFIX
};
ReportHttpStats(TEXT("Ias"), EHttpRequestType::Streaming);
ReportHttpStats(TEXT("Iad"), EHttpRequestType::Installed);
}
if (GIasReportCacheAnalyticsEnabled)
{
#define UE_TRACK_DELTA(Name, Value) TEXT(Name), GDeltaTracking.Get(TEXTVIEW(Name), Value)
const int64 CacheTotalCount = GCacheGetCount.Get() + GCachePutCount.Get();
const float CacheUsagePercent = GCacheMaxBytes > 0 ? 100.f * (float(GCacheCachedBytes.Get()) / float(GCacheMaxBytes)) : 0.f;
AppendAnalyticsEventAttributeArray(OutAnalyticsArray
, TEXT("IasCacheEnabled"), BackendStatus.IsCacheEnabled()
, UE_TRACK_DELTA("IasCacheTotalCount", CacheTotalCount)
, UE_TRACK_DELTA("IasCacheErrorCount", GCacheErrorCount.Get())
, UE_TRACK_DELTA("IasCacheDecodeErrors", GCacheDecodeErrorCount.Get())
, UE_TRACK_DELTA("IasCacheGetCount", GCacheGetCount.Get())
, UE_TRACK_DELTA("IasCachePutCount", GCachePutCount.Get())
, TEXT("IasCacheCachedBytes"), GCacheCachedBytes.Get()
, TEXT("IasCacheMaxBytes"), GCacheMaxBytes
, TEXT("IasCacheUsagePercent"), CacheUsagePercent
, UE_TRACK_DELTA("IasCacheWriteBytes", GCacheWrittenBytes.Get())
, UE_TRACK_DELTA("IasCacheReadBytes", GCacheReadBytes.Get())
, UE_TRACK_DELTA("IasCacheRejectBytes", GCacheRejectBytes.Get())
);
#undef UE_TRACK_DELTA
}
}
#if UE_ENABLE_ANALYTICS_RECORDING
class FAnalyticsRecording : public IAnalyticsRecording
{
public:
FAnalyticsRecording() = delete;
FAnalyticsRecording(const FBackendStatus& InBackendStatus)
: BackendStatus(InBackendStatus)
{
}
virtual ~FAnalyticsRecording() = default;
private:
void StopRecording() override
{
if (!bRecording)
{
return;
}
Http.ErrorCount.Stop();
Http.DecodeErrorCount.Stop();
Http.RetryCount.Stop();
Http.GetCount.Stop();
Http.DownloadedBytes.Stop();
Http.TotalDuration.Stop();
Cache.ErrorCount.Stop();
Cache.DecodeErrorCount.Stop();
Cache.GetCount.Stop();
Cache.PutCount.Stop();
Cache.WrittenBytes.Stop();
Cache.ReadBytes.Stop();
Cache.RejectBytes.Stop();
bRecording = false;
}
void Report(TArray<FAnalyticsEventAttribute>& OutAnalyticsArray) const override
{
// Report if the system has ever actually managed to make a connection
AppendAnalyticsEventAttributeArray(OutAnalyticsArray
,TEXT("IasHttpDistributedEndpointResolved"), GHttpDistributedEndpointResolved
,TEXT("IasHttpHasEverConnected"), GHttpConnectCount.Get() > 0
);
if (GIasReportHttpAnalyticsEnabled)
{
const double DataRateBPS = Http.TotalDuration.GetValue() > 0 ? static_cast<double>(Http.DownloadedBytes.GetValue()) / (static_cast<double>(Http.TotalDuration.GetValue()) / 1000.0) : 0.0;
const double DurationMean = Http.GetCount.GetValue() ? static_cast<double>(Http.TotalDuration.GetValue()) / static_cast<double>(Http.GetCount.GetValue()) : 0.0;
AppendAnalyticsEventAttributeArray(OutAnalyticsArray
, TEXT("IasHttpErrorCount"), Http.ErrorCount.GetValue()
, TEXT("IasHttpDecodeErrorCount"), Http.DecodeErrorCount.GetValue()
, TEXT("IasHttpRetryCount"), Http.RetryCount.GetValue()
, TEXT("IasHttpGetCount"), Http.GetCount.GetValue()
, TEXT("IasHttpDownloadedBytes"), Http.DownloadedBytes.GetValue()
, TEXT("IasHttpDurationMean"), DurationMean
, TEXT("IasHttpDurationSum"), Http.TotalDuration.GetValue()
, TEXT("IasHttpDataRateMean"), DataRateBPS
);
}
if (GIasReportCacheAnalyticsEnabled)
{
const float CacheUsagePercent = GCacheMaxBytes > 0 ? 100.f * (float(GCacheCachedBytes.Get()) / float(GCacheMaxBytes)) : 0.f;
AppendAnalyticsEventAttributeArray(OutAnalyticsArray
, TEXT("IasCacheEnabled"), this->BackendStatus.IsCacheEnabled()
, TEXT("IasCacheMaxBytes"), GCacheMaxBytes
, TEXT("IasCacheCachedBytes"), GCacheCachedBytes.Get()
, TEXT("IasCacheUsagePercent"), CacheUsagePercent
, TEXT("IasCacheErrorCount"), Cache.ErrorCount.GetValue()
, TEXT("IasCacheDecodeErrorCount"), Cache.DecodeErrorCount.GetValue()
, TEXT("IasCacheGetCount"), Cache.GetCount.GetValue()
, TEXT("IasCachePutCount"), Cache.PutCount.GetValue()
, TEXT("IasCacheWriteBytes"), Cache.WrittenBytes.GetValue()
, TEXT("IasCacheReadBytes"), Cache.ReadBytes.GetValue()
, TEXT("IasCacheRejectBytes"), Cache.RejectBytes.GetValue()
);
}
}
private:
/** This wrapper class allows us to treat raw integer types and FCounter types as the same thing */
//template<typename CounterType, CounterType& Counter, int32 INDEX = -1>
template<typename CounterType, const CounterType& Counter, int32 INDEX = -1>
struct TTrackedValue
{
TTrackedValue()
{
Value = GetCurrentValue();
}
void Stop()
{
Value = GetCurrentValue() - Value;
bRecording = false;
}
int64 GetValue() const
{
if (bRecording)
{
return GetCurrentValue() - Value;
}
else
{
return Value;
}
}
private:
int64 GetCurrentValue() const
{
if constexpr (std::is_integral_v<CounterType>)
{
return Counter;
}
else
{
return Counter.Get();
}
}
int64 Value;
bool bRecording = true;
};
/** Http Stats */
struct FHttpStats
{
TTrackedValue<FCounterInt, GHttpErrorCount[0]> ErrorCount;
TTrackedValue<FCounterAtomicInt, GHttpDecodeErrorCount[0]> DecodeErrorCount;
TTrackedValue<FCounterInt, GHttpRetryCount[0]> RetryCount;
TTrackedValue<FCounterInt, GHttpGetCount[0]> GetCount;
TTrackedValue<FCounterInt, GHttpDownloadedBytes[0]> DownloadedBytes;
TTrackedValue<int64, GHttpDurationMsSum[0]> TotalDuration;
} Http;
/** Cache Stats */
struct FCacheStats
{
TTrackedValue<FCounterAtomicInt, GCacheErrorCount> ErrorCount;
TTrackedValue<FCounterAtomicInt, GCacheDecodeErrorCount> DecodeErrorCount;
TTrackedValue<FCounterAtomicInt, GCacheGetCount> GetCount;
TTrackedValue<FCounterAtomicInt, GCachePutCount> PutCount;
TTrackedValue<FCounterAtomicInt, GCacheWrittenBytes> WrittenBytes;
TTrackedValue<FCounterAtomicInt, GCacheReadBytes> ReadBytes;
TTrackedValue<FCounterAtomicInt, GCacheRejectBytes> RejectBytes;
} Cache;
const FBackendStatus& BackendStatus;
bool bRecording = true;
};
#endif //UE_ENABLE_ANALYTICS_RECORDING
TUniquePtr<IAnalyticsRecording> FOnDemandIoBackendStats::StartAnalyticsRecording() const
{
#if UE_ENABLE_ANALYTICS_RECORDING
TUniquePtr<FAnalyticsRecording> Recording = MakeUnique<FAnalyticsRecording>(BackendStatus);
return Recording;
#else
return nullptr;
#endif // UE_ENABLE_ANALYTICS_RECORDING
}
void FOnDemandIoBackendStats::OnIoRequestEnqueue()
{
GIoRequestCount.Increment();
CSV_CUSTOM_STAT_DEFINED(FrameIoRequestCount, int32(GIoRequestCount.Get()), ECsvCustomStatOp::Set);
}
void FOnDemandIoBackendStats::OnIoRequestComplete(uint64 Size, uint64 Duration)
{
GIoRequestReadCount.Increment();
GIoRequestReadBytes.Add(Size);
CSV_CUSTOM_STAT_DEFINED(FrameIoRequestReadCount, int32(GIoRequestReadCount.Get()), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(FrameIoRequestReadMB, BytesToApproxMB(GIoRequestReadBytes.Get()), ECsvCustomStatOp::Set);
}
void FOnDemandIoBackendStats::OnIoRequestCancel()
{
GIoRequestCancelCount.Increment();
CSV_CUSTOM_STAT_DEFINED(FrameIoRequestCancelCount, int32(GIoRequestCancelCount.Get()), ECsvCustomStatOp::Set);
}
void FOnDemandIoBackendStats::OnIoRequestError()
{
GIoRequestErrorCount.Increment();
CSV_CUSTOM_STAT_DEFINED(FrameIoRequestErrorCount, int32(GIoRequestErrorCount.Get()), ECsvCustomStatOp::Set);
}
void FOnDemandIoBackendStats::OnCacheError()
{
GCacheErrorCount.Increment();
}
void FOnDemandIoBackendStats::OnCacheDecodeError()
{
GCacheDecodeErrorCount.Increment();
}
void FOnDemandIoBackendStats::OnCacheGet(uint64 DataSize)
{
GCacheGetCount.Increment();
GCacheReadBytes.Add(DataSize);
}
void FOnDemandIoBackendStats::OnCachePut()
{
GCachePutCount.Increment();
}
void FOnDemandIoBackendStats::OnCachePutExisting(uint64 /*DataSize*/)
{
GCachePutExistingCount.Increment();
}
void FOnDemandIoBackendStats::OnCachePutReject(uint64 DataSize)
{
GCachePutRejectCount.Increment();
GCacheRejectBytes.Add(DataSize);
}
void FOnDemandIoBackendStats::OnCachePendingBytes(uint64 TotalSize)
{
GCachePendingBytes.Set(TotalSize);
}
void FOnDemandIoBackendStats::OnCachePersistedBytes(uint64 TotalSize)
{
GCacheCachedBytes.Set(TotalSize);
}
void FOnDemandIoBackendStats::OnCacheWriteBytes(uint64 WriteSize)
{
GCacheWrittenBytes.Add(WriteSize);
}
void FOnDemandIoBackendStats::OnCacheSetMaxBytes(uint64 TotalSize)
{
GCacheMaxBytes = TotalSize;
}
void FOnDemandIoBackendStats::OnHttpDistributedEndpointResolved()
{
GHttpDistributedEndpointResolved = true;
}
void FOnDemandIoBackendStats::OnHttpConnected()
{
GHttpConnectCount.Increment();
}
void FOnDemandIoBackendStats::OnHttpDisconnected()
{
GHttpDisconnectCount.Increment();
}
void FOnDemandIoBackendStats::OnHttpEnqueue(EHttpRequestType Type)
{
GHttpPendingCount[(uint8)Type].Increment();
}
void FOnDemandIoBackendStats::OnHttpDequeue(EHttpRequestType Type)
{
GHttpInflightCount[(uint8)Type].Increment();
}
void FOnDemandIoBackendStats::OnHttpGet(EHttpRequestType Type, uint64 SizeBytes, uint64 DurationMs)
{
GHttpPendingCount[(uint8)Type].Decrement();
GHttpInflightCount[(uint8)Type].Decrement();
GHttpGetCount[(uint8)Type].Increment();
GHttpDownloadedBytes[(uint8)Type].Add(SizeBytes);
GHttpDurationMsSum[(uint8)Type] += DurationMs;
GHttpDurationMs[(uint8)Type].Set(DurationMs);
GHttpHistory[(uint8)Type].OnGet(SizeBytes, DurationMs);
GHttpBandwidthMbps[(uint8)Type].Set(GHttpHistory[(uint8)Type].GetBandwidthMbps());
GHttpDurationMsAvg[(uint8)Type] = GHttpHistory[(uint8)Type].GetAverage();
GHttpDurationMsMax[(uint8)Type] = (int32)GHttpHistory[(uint8)Type].GetMaxDuration();
}
void FOnDemandIoBackendStats::OnHttpCancel(EHttpRequestType Type)
{
GHttpInflightCount[(uint8)Type].Decrement();
GHttpPendingCount[(uint8)Type].Decrement();
GHttpCancelCount[(uint8)Type].Increment();
}
void FOnDemandIoBackendStats::OnHttpRetry(EHttpRequestType Type)
{
GHttpRetryCount[(uint8)Type].Increment();
}
void FOnDemandIoBackendStats::OnHttpError(EHttpRequestType Type)
{
GHttpPendingCount[(uint8)Type].Decrement();
GHttpInflightCount[(uint8)Type].Decrement();
GHttpErrorCount[(uint8)Type].Increment();
}
void FOnDemandIoBackendStats::OnHttpDecodeError(EHttpRequestType Type)
{
GHttpDecodeErrorCount[(uint8)Type].Increment();
}
void FOnDemandIoBackendStats::OnHttpCdnCacheReply(EHttpRequestType Type, int32 Reply)
{
switch (Reply)
{
case -1: GHttpCdnCacheUnknown += 1; break;
case 0: GHttpCdnCacheMiss += 1; break;
default: GHttpCdnCacheHit += 1; break;
}
}
////////////////////////////////////////////////////////////////////////////////
void FOnDemandContentInstallerStats::OnRequestEnqueued()
{
#if COUNTERSTRACE_ENABLED
TUniqueLock Lock(InstallerTraceCounters);
InstallerTraceCounters.InflightInstallCount.Increment();
#endif
}
void FOnDemandContentInstallerStats::OnRequestCompleted(
uint64 RequestedChunkCount,
uint64 RequestedBytes,
uint64 DownloadedChunkCount,
uint64 DownloadedBytes,
double CacheHitRatio,
uint64 DurationCycles,
EIoErrorCode ErrorCode)
{
if (RequestedChunkCount == 0)
{
return;
}
#if COUNTERSTRACE_ENABLED
{
TUniqueLock Lock(InstallerTraceCounters);
InstallerTraceCounters.InstallCount.Increment();
InstallerTraceCounters.InflightInstallCount.Decrement();
InstallerTraceCounters.DownloadedBytes.Add(DownloadedBytes);
InstallerTraceCounters.InstallDurationMs.Increment(FPlatformTime::ToMilliseconds64(DurationCycles));
InstallerTraceCounters.CacheHitRatio.Increment(CacheHitRatio);
InstallerTraceCounters.AvgInstallDurationMs.Set(InstallerTraceCounters.InstallDurationMs.GetMean());
InstallerTraceCounters.AvgCacheHitRatio.Set(InstallerTraceCounters.CacheHitRatio.GetMean());
}
#endif // COUNTERSTRACE_ENABLED
{
TUniqueLock Lock(InstallerAnalytics);
if (ErrorCode != EIoErrorCode::Ok && ErrorCode != EIoErrorCode::Cancelled)
{
InstallerAnalytics.InstallErrorCount++;
}
InstallerAnalytics.InstallCount++;
InstallerAnalytics.DownloadedBytes += DownloadedBytes;
InstallerAnalytics.TotalInstallDurationMs += uint64(FPlatformTime::ToMilliseconds64(DurationCycles));
InstallerAnalytics.TotalCacheHitRatio += CacheHitRatio;
}
}
void FOnDemandContentInstallerStats::ReportAnalytics(TArray<FAnalyticsEventAttribute>& OutAnalyticsArray)
{
if (GIadReportAnalyticsEnabled == false)
{
return;
}
FInstallerAnalytics CurrentInstallerAnalytics;
{
TUniqueLock Lock(InstallerAnalytics);
CurrentInstallerAnalytics = InstallerAnalytics;
InstallerAnalytics = FInstallerAnalytics();
}
FInstallCacheAnalytics CurrentInstallCacheAnalytics;
{
TUniqueLock Lock(InstallCacheAnalytics);
CurrentInstallCacheAnalytics = InstallCacheAnalytics;
InstallCacheAnalytics = FInstallCacheAnalytics();
}
const FDateTime Now = FDateTime::UtcNow();
const FDateTime OldestBlockAccess(CurrentInstallCacheAnalytics.OldestBlockAccess);
const FTimespan OldestBlockAge = (Now >= OldestBlockAccess) ? (Now - OldestBlockAccess) : FTimespan::MaxValue();
AppendAnalyticsEventAttributeArray(OutAnalyticsArray
, TEXT("IadTotalInstallCount"), CurrentInstallerAnalytics.InstallCount
, TEXT("IadTotalInstallErrorCount"), CurrentInstallerAnalytics.InstallErrorCount
, TEXT("IadTotalDownloadedBytes"), CurrentInstallerAnalytics.DownloadedBytes
, TEXT("IadTotalInstallDurationMs"), CurrentInstallerAnalytics.TotalInstallDurationMs
, TEXT("IadAvgInstallDurationMs"), (double(CurrentInstallerAnalytics.TotalInstallDurationMs) / double(CurrentInstallerAnalytics.InstallCount))
, TEXT("IadAvgCacheHitRatio"), (CurrentInstallerAnalytics.TotalCacheHitRatio / double(CurrentInstallerAnalytics.InstallCount))
, TEXT("IadInstallCacheStartupErrorCount"), CurrentInstallCacheAnalytics.StartupErrorCount
, TEXT("IadInstallCacheVerificationRemovedBlockCount"), CurrentInstallCacheAnalytics.VerificationRemovedBlockCount
, TEXT("IadInstallCacheFlushCount"), CurrentInstallCacheAnalytics.FlushCount
, TEXT("IadInstallCacheFlushErrorCount"), CurrentInstallCacheAnalytics.FlushErrorCount
, TEXT("IadInstallCacheFlushedBytes"), CurrentInstallCacheAnalytics.FlushedBytes
, TEXT("IadInstallCachePurgeCount"), CurrentInstallCacheAnalytics.PurgeCount
, TEXT("IadInstallCachePurgeErrorCount"), CurrentInstallCacheAnalytics.PurgeErrorCount
, TEXT("IadInstallCacheDefragCount"), CurrentInstallCacheAnalytics.DefragCount
, TEXT("IadInstallCacheDefragErrorCount"), CurrentInstallCacheAnalytics.DefragErrorCount
, TEXT("IadInstallCacheJournalCommitCount"), CurrentInstallCacheAnalytics.JournalCommitCount
, TEXT("IadInstallCacheJournalCommitErrorCount"), CurrentInstallCacheAnalytics.JournalCommitErrorCount
, TEXT("IadInstallCacheMaxSize"), CurrentInstallCacheAnalytics.MaxCacheSize
, TEXT("IadInstallCacheMaxUsageSize"), CurrentInstallCacheAnalytics.MaxCacheUsageSize
, TEXT("IadInstallCacheMaxReferencedBlockSize"), CurrentInstallCacheAnalytics.MaxReferencedBlockSize
, TEXT("IadInstallCacheMaxReferencedSize"), CurrentInstallCacheAnalytics.MaxReferencedSize
, TEXT("IadInstallCacheMaxFragmentedSize"), CurrentInstallCacheAnalytics.MaxFragmentedSize
, TEXT("IadInstallCacheOldestBlockAgeMinutes"), OldestBlockAge.GetTotalMinutes()
);
}
////////////////////////////////////////////////////////////////////////////////
void FOnDemandInstallCacheStats::OnStartupError(EIoErrorCode ErrorCode)
{
UE::TUniqueLock Lock(InstallCacheAnalytics);
InstallCacheAnalytics.StartupErrorCount++;
}
void FOnDemandInstallCacheStats::OnFlush(EIoErrorCode ErrorCode, int64 ByteCount)
{
if (ErrorCode == EIoErrorCode::Cancelled)
{
return;
}
UE::TUniqueLock Lock(InstallCacheAnalytics);
if (ErrorCode != EIoErrorCode::Ok)
{
InstallCacheAnalytics.FlushErrorCount++;
}
InstallCacheAnalytics.FlushCount++;
InstallCacheAnalytics.FlushedBytes += ByteCount;
}
void FOnDemandInstallCacheStats::OnJournalCommit(EIoErrorCode ErrorCode, int64 ByteCount)
{
if (ErrorCode == EIoErrorCode::Cancelled)
{
return;
}
UE::TUniqueLock Lock(InstallCacheAnalytics);
if (ErrorCode != EIoErrorCode::Ok)
{
InstallCacheAnalytics.JournalCommitErrorCount++;
}
InstallCacheAnalytics.JournalCommitCount++;
}
void FOnDemandInstallCacheStats::OnCasVerificationError(int32 RemoveChunks)
{
UE::TUniqueLock Lock(InstallCacheAnalytics);
InstallCacheAnalytics.VerificationRemovedBlockCount += RemoveChunks;
}
void FOnDemandInstallCacheStats::OnPurge(
EIoErrorCode ErrorCode,
uint64 MaxCacheSize,
uint64 NewCacheSize,
uint64 BytesToPurge,
uint64 PurgedBytes)
{
if (ErrorCode == EIoErrorCode::Cancelled)
{
return;
}
UE::TUniqueLock Lock(InstallCacheAnalytics);
if (ErrorCode != EIoErrorCode::Ok)
{
InstallCacheAnalytics.PurgeErrorCount++;
}
InstallCacheAnalytics.PurgeCount++;
InstallCacheAnalytics.PurgedBytes += PurgedBytes;
}
void FOnDemandInstallCacheStats::OnDefrag(EIoErrorCode ErrorCode, uint64 FragmentedBytes)
{
if (ErrorCode == EIoErrorCode::Cancelled)
{
return;
}
UE::TUniqueLock Lock(InstallCacheAnalytics);
if (ErrorCode != EIoErrorCode::Ok)
{
InstallCacheAnalytics.DefragErrorCount++;
}
InstallCacheAnalytics.DefragCount++;
InstallCacheAnalytics.FragmentedBytes += FragmentedBytes;
}
void FOnDemandInstallCacheStats::OnCacheUsage(
uint64 MaxCacheSize,
uint64 CacheSize,
uint64 ReferencedBlockSize,
uint64 ReferencedSize,
uint64 FragmentedSize,
int64 OldestBlockAccess)
{
UE::TUniqueLock Lock(InstallCacheAnalytics);
InstallCacheAnalytics.MaxCacheSize = MaxCacheSize;
InstallCacheAnalytics.MaxCacheUsageSize = FMath::Max(InstallCacheAnalytics.MaxCacheUsageSize, CacheSize);
InstallCacheAnalytics.MaxReferencedBlockSize = FMath::Max(InstallCacheAnalytics.MaxReferencedBlockSize, ReferencedBlockSize);
InstallCacheAnalytics.MaxReferencedSize = FMath::Max(InstallCacheAnalytics.MaxReferencedSize, ReferencedSize);
InstallCacheAnalytics.MaxFragmentedSize = FMath::Max(InstallCacheAnalytics.MaxFragmentedSize, FragmentedSize);
InstallCacheAnalytics.OldestBlockAccess = FMath::Min(InstallCacheAnalytics.OldestBlockAccess, OldestBlockAccess);
}
} // namespace UE::IoStore
#undef UE_ENABLE_ONSCREEN_STATISTICS
#endif // IAS_WITH_STATISTICS