// Copyright Epic Games, Inc. All Rights Reserved. #include "Installer/Statistics/DownloadServiceStatistics.h" #include "Misc/ScopeLock.h" #include "Misc/Paths.h" #include "Interfaces/IHttpResponse.h" #include "Core/AsyncHelpers.h" #include "Common/StatsCollector.h" #include "Installer/InstallerAnalytics.h" #include "BuildPatchProgress.h" #include "BuildPatchManifest.h" #include "BuildPatchUtil.h" #include "Containers/Queue.h" namespace BuildPatchServices { class FDownloadServiceStatistics : public IDownloadServiceStatistics { public: FDownloadServiceStatistics(ISpeedRecorder* SpeedRecorder, IDataSizeProvider* DataSizeProvider, IInstallerAnalytics* InstallerAnalytics); // IDownloadServiceStat interface begin. virtual void OnDownloadStarted(int32 RequestId, const FString& Uri) override; virtual void OnDownloadProgress(int32 RequestId, uint64 BytesReceived) override; virtual void OnDownloadComplete(const FDownloadRecord& DownloadRecord) override; // IDownloadServiceStat interface end. // IDownloadServiceStatistics interface begin. virtual uint64 GetBytesDownloaded() const override; virtual int32 GetNumSuccessfulChunkDownloads() const override; virtual int32 GetNumFailedChunkDownloads() const override; virtual int32 GetNumCurrentDownloads() const override; virtual TArray GetCurrentDownloads() const override; virtual TPair GetImmediateAverageSpeedPerRequest(uint32 MinCount) override; virtual void Reset() override; // IDownloadServiceStatistics interface end. private: ISpeedRecorder* SpeedRecorder; // AddRecord is safe, but SpeedRecorder is ticked IDataSizeProvider* DataSizeProvider; IInstallerAnalytics* InstallerAnalytics; std::atomic TotalBytesReceived; std::atomic NumSuccessfulDownloads; std::atomic NumFailedDownloads; typedef TTuple FDownloadTuple; TMap Downloads; mutable FCriticalSection DownloadsCriticalSection; double AccumulatedRequestSpeed; uint32 AverageSpeedSampleCount; mutable FCriticalSection AverageSpeedCriticalSection; }; FDownloadServiceStatistics::FDownloadServiceStatistics(ISpeedRecorder* InSpeedRecorder, IDataSizeProvider* InDataSizeProvider, IInstallerAnalytics* InInstallerAnalytics) : SpeedRecorder(InSpeedRecorder) , DataSizeProvider(InDataSizeProvider) , InstallerAnalytics(InInstallerAnalytics) , TotalBytesReceived(0) , NumSuccessfulDownloads(0) , NumFailedDownloads(0) , AccumulatedRequestSpeed(0.0) , AverageSpeedSampleCount(0U) { } void FDownloadServiceStatistics::OnDownloadStarted(int32 RequestId, const FString& Uri) { FScopeLock Lock(&DownloadsCriticalSection); FDownloadTuple& DownloadTuple = Downloads.FindOrAdd(RequestId); DownloadTuple.Get<0>() = Uri; DownloadTuple.Get<1>() = 0; } void FDownloadServiceStatistics::OnDownloadProgress(int32 RequestId, uint64 BytesReceived) { FScopeLock Lock(&DownloadsCriticalSection); FDownloadTuple& DownloadTuple = Downloads.FindOrAdd(RequestId); DownloadTuple.Get<1>() = BytesReceived; } void FDownloadServiceStatistics::OnDownloadComplete(const FDownloadRecord& DownloadRecord) { { FScopeLock Lock(&DownloadsCriticalSection); Downloads.Remove(DownloadRecord.RequestId); } if (DownloadRecord.bSuccess && EHttpResponseCodes::IsOk(DownloadRecord.ResponseCode)) { TotalBytesReceived += DownloadRecord.SpeedRecord.Size; NumSuccessfulDownloads++; SpeedRecorder->AddRecord(DownloadRecord.SpeedRecord); // update average speed const uint64 Cycles = DownloadRecord.SpeedRecord.CyclesEnd - DownloadRecord.SpeedRecord.CyclesStart; double Speed = Cycles > 0U ? DownloadRecord.SpeedRecord.Size / FStatsCollector::CyclesToSeconds(Cycles) : 0.0; FScopeLock Lock(&AverageSpeedCriticalSection); AccumulatedRequestSpeed += Speed; AverageSpeedSampleCount += 1; } else { NumFailedDownloads++; InstallerAnalytics->RecordChunkDownloadError(DownloadRecord.Uri, DownloadRecord.ResponseCode, TEXT("DownloadFail")); } } uint64 FDownloadServiceStatistics::GetBytesDownloaded() const { return TotalBytesReceived; } int32 FDownloadServiceStatistics::GetNumSuccessfulChunkDownloads() const { return NumSuccessfulDownloads; } int32 FDownloadServiceStatistics::GetNumFailedChunkDownloads() const { return NumFailedDownloads; } int32 FDownloadServiceStatistics::GetNumCurrentDownloads() const { FScopeLock Lock(&DownloadsCriticalSection); return Downloads.Num(); } TArray FDownloadServiceStatistics::GetCurrentDownloads() const { TArray DownloadData; TArray DownloadSizes; TArray Result; { FScopeLock Lock(&DownloadsCriticalSection); DownloadData.Empty(Downloads.Num()); Result.Empty(Downloads.Num()); for (const TPair& Download : Downloads) { DownloadData.Emplace(Download.Value.Get<0>()); Result.AddDefaulted(); FDownload& Element = Result.Last(); Element.Received = Download.Value.Get<1>(); } } DataSizeProvider->GetDownloadSize(DownloadData, DownloadSizes); check(DownloadSizes.Num() == Result.Num()); for (int32 Index = 0; FDownload& Element : Result) { Element.Data = MoveTemp(DownloadData[Index]); Element.Size = DownloadSizes[Index]; ++Index; } return Result; } TPair FDownloadServiceStatistics::GetImmediateAverageSpeedPerRequest(uint32 MinCount) { uint32 Count = 0U; double Result = 0.0L; FScopeLock Lock(&AverageSpeedCriticalSection); if (AverageSpeedSampleCount >= MinCount) { Result = AccumulatedRequestSpeed / AverageSpeedSampleCount; Count = AverageSpeedSampleCount; AccumulatedRequestSpeed = 0.0L; AverageSpeedSampleCount = 0; } return TPair(Result, Count); } void FDownloadServiceStatistics::Reset() { checkSlow(IsInGameThread()); TotalBytesReceived = 0; NumSuccessfulDownloads = 0; NumFailedDownloads = 0; { FScopeLock Lock(&DownloadsCriticalSection); Downloads.Empty(); } { FScopeLock Lock(&AverageSpeedCriticalSection); AccumulatedRequestSpeed = 0; AverageSpeedSampleCount = 0; } } IDownloadServiceStatistics* FDownloadServiceStatisticsFactory::Create(ISpeedRecorder* SpeedRecorder, IDataSizeProvider* DataSizeProvider, IInstallerAnalytics* InstallerAnalytics) { check(SpeedRecorder != nullptr); check(DataSizeProvider != nullptr); check(InstallerAnalytics != nullptr); return new FDownloadServiceStatistics(SpeedRecorder, DataSizeProvider, InstallerAnalytics); } };