diff options
| author | Dan Engelbrecht <[email protected]> | 2024-04-10 13:50:09 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2024-04-10 13:50:09 +0200 |
| commit | 1143af52ae1924b6e2cad347924bad578dda5391 (patch) | |
| tree | 3caa74260f0ef753c4b0da4bf17153d0017e6b2c /src | |
| parent | changelog (diff) | |
| download | zen-1143af52ae1924b6e2cad347924bad578dda5391.tar.xz zen-1143af52ae1924b6e2cad347924bad578dda5391.zip | |
remote project store stats (#44)
* add remote oplog store statistics
* block chunking when uploading oplog to zenserver (mirroring)
* make sure we can move temporary dechunked file into cas store
Diffstat (limited to 'src')
| -rw-r--r-- | src/zencore/include/zencore/thread.h | 17 | ||||
| -rw-r--r-- | src/zenserver-test/zenserver-test.cpp | 153 | ||||
| -rw-r--r-- | src/zenserver/projectstore/fileremoteprojectstore.cpp | 41 | ||||
| -rw-r--r-- | src/zenserver/projectstore/jupiterremoteprojectstore.cpp | 51 | ||||
| -rw-r--r-- | src/zenserver/projectstore/projectstore.cpp | 1 | ||||
| -rw-r--r-- | src/zenserver/projectstore/remoteprojectstore.cpp | 75 | ||||
| -rw-r--r-- | src/zenserver/projectstore/remoteprojectstore.h | 16 | ||||
| -rw-r--r-- | src/zenserver/projectstore/zenremoteprojectstore.cpp | 50 | ||||
| -rw-r--r-- | src/zenserver/upstream/jupiter.cpp | 11 | ||||
| -rw-r--r-- | src/zenserver/upstream/jupiter.h | 3 | ||||
| -rw-r--r-- | src/zenserver/upstream/upstreamcache.cpp | 24 |
11 files changed, 338 insertions, 104 deletions
diff --git a/src/zencore/include/zencore/thread.h b/src/zencore/include/zencore/thread.h index 2d0ef7396..bae630db9 100644 --- a/src/zencore/include/zencore/thread.h +++ b/src/zencore/include/zencore/thread.h @@ -206,6 +206,23 @@ private: Event Complete; }; +inline void +SetAtomicMax(std::atomic_uint64_t& Max, uint64_t Value) +{ + while (true) + { + uint64_t CurrentMax = Max.load(); + if (Value <= CurrentMax) + { + return; + } + if (Max.compare_exchange_strong(CurrentMax, Value)) + { + return; + } + } +} + ZENCORE_API int GetCurrentThreadId(); ZENCORE_API void Sleep(int ms); diff --git a/src/zenserver-test/zenserver-test.cpp b/src/zenserver-test/zenserver-test.cpp index 325b15e3f..3cf96f1cc 100644 --- a/src/zenserver-test/zenserver-test.cpp +++ b/src/zenserver-test/zenserver-test.cpp @@ -490,6 +490,52 @@ namespace utils { void SpawnServer(ZenServerInstance& Server, ZenConfig& Cfg) { Cfg.Spawn(Server); } + CompressedBuffer CreateSemiRandomBlob(size_t AttachmentSize, OodleCompressionLevel CompressionLevel = OodleCompressionLevel::VeryFast) + { + // Convoluted way to get a compressed buffer whose result it large enough to be a separate file + // but also does actually compress + const size_t PartCount = (AttachmentSize / (1u * 1024u * 64)) + 1; + const size_t PartSize = AttachmentSize / PartCount; + auto Part = SharedBuffer(CreateRandomBlob(PartSize)); + std::vector<SharedBuffer> Parts(PartCount, Part); + size_t RemainPartSize = AttachmentSize - (PartSize * PartCount); + if (RemainPartSize > 0) + { + Parts.push_back(SharedBuffer(CreateRandomBlob(RemainPartSize))); + } + CompressedBuffer Value = CompressedBuffer::Compress(CompositeBuffer(std::move(Parts)), OodleCompressor::Mermaid, CompressionLevel); + if (CompressionLevel != OodleCompressionLevel::VeryFast) + { + ZEN_INFO("{}", Value.GetCompressedSize()); + } + return Value; + }; + + std::vector<std::pair<Oid, CompressedBuffer>> CreateAttachments(const std::span<const size_t>& Sizes) + { + std::vector<std::pair<Oid, CompressedBuffer>> Result; + Result.reserve(Sizes.size()); + for (size_t Size : Sizes) + { + CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer(CreateRandomBlob(Size))); + Result.emplace_back(std::pair<Oid, CompressedBuffer>(Oid::NewOid(), Compressed)); + } + return Result; + } + + std::vector<std::pair<Oid, CompressedBuffer>> CreateSemiRandomAttachments(const std::span<const size_t>& Sizes) + { + std::vector<std::pair<Oid, CompressedBuffer>> Result; + Result.reserve(Sizes.size()); + for (size_t Size : Sizes) + { + CompressedBuffer Compressed = + CreateSemiRandomBlob(Size, Size > 1024u * 1024u ? OodleCompressionLevel::None : OodleCompressionLevel::VeryFast); + Result.emplace_back(std::pair<Oid, CompressedBuffer>(Oid::NewOid(), Compressed)); + } + return Result; + } + } // namespace utils TEST_CASE("zcache.basic") @@ -1788,27 +1834,11 @@ TEST_CASE("zcache.rpc.partialchunks") return CacheKey::Create(Bucket, KeyHash); }; - auto CreateSemiRandomBlob = [](size_t AttachmentSize) -> CompressedBuffer { - // Convoluted way to get a compressed buffer whose result it large enough to be a separate file - // but also does actually compress - const size_t PartCount = (AttachmentSize / (1u * 1024u * 64)) + 1; - const size_t PartSize = AttachmentSize / PartCount; - auto Part = SharedBuffer(CreateRandomBlob(PartSize)); - std::vector<SharedBuffer> Parts(PartCount, Part); - size_t RemainPartSize = AttachmentSize - (PartSize * PartCount); - if (RemainPartSize > 0) - { - Parts.push_back(SharedBuffer(CreateRandomBlob(RemainPartSize))); - } - CompressedBuffer Value = CompressedBuffer::Compress(CompositeBuffer(std::move(Parts))); - return Value; - }; - - auto AppendCacheRecord = [&CreateSemiRandomBlob](cacherequests::PutCacheRecordsRequest& Request, - const CacheKey& CacheKey, - size_t AttachmentCount, - size_t AttachmentsSize, - CachePolicy RecordPolicy) -> std::vector<std::pair<Oid, CompressedBuffer>> { + auto AppendCacheRecord = [](cacherequests::PutCacheRecordsRequest& Request, + const CacheKey& CacheKey, + size_t AttachmentCount, + size_t AttachmentsSize, + CachePolicy RecordPolicy) -> std::vector<std::pair<Oid, CompressedBuffer>> { std::vector<std::pair<Oid, CompressedBuffer>> AttachmentBuffers; std::vector<cacherequests::PutCacheRecordRequestValue> Attachments; for (size_t AttachmentIndex = 0; AttachmentIndex < AttachmentCount; AttachmentIndex++) @@ -2651,19 +2681,6 @@ CreateOplogPackage(const Oid& Id, const std::span<const std::pair<Oid, Compresse return Package; }; -std::vector<std::pair<Oid, CompressedBuffer>> -CreateAttachments(const std::span<const size_t>& Sizes) -{ - std::vector<std::pair<Oid, CompressedBuffer>> Result; - Result.reserve(Sizes.size()); - for (size_t Size : Sizes) - { - CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer(CreateRandomBlob(Size))); - Result.emplace_back(std::pair<Oid, CompressedBuffer>(Oid::NewOid(), Compressed)); - } - return Result; -} - cpr::Body AsBody(const IoBuffer& Payload) { @@ -2704,48 +2721,50 @@ operator<<(CbWriter& Writer, CbWriterMeta Meta) TEST_CASE("project.remote") { using namespace std::literals; + using namespace utils; ZenServerTestHelper Servers("remote", 3); Servers.SpawnServers("--debug"); std::vector<Oid> OpIds; - OpIds.reserve(24); - for (size_t I = 0; I < 24; ++I) + const size_t OpCount = 24; + OpIds.reserve(OpCount); + for (size_t I = 0; I < OpCount; ++I) { OpIds.emplace_back(Oid::NewOid()); } std::unordered_map<Oid, std::vector<std::pair<Oid, CompressedBuffer>>, Oid::Hasher> Attachments; { - std::vector<std::size_t> AttachmentSizes({7633, 6825, 5738, 8031, 7225, 566, 3656, 6006, 24, 3466, 1093, 4269, - 2257, 3685, 3489, 7194, 6151, 5482, 6217, 3511, 6738, 5061, 7537, 2759, - 1916, 8210, 2235, 4024, 1582, 5251, 491, 5464, 4607, 8135, 3767, 4045, - 4415, 5007, 8876, 6761, 3359, 8526, 4097, 4855, 8225}); - auto It = AttachmentSizes.begin(); - Attachments[OpIds[0]] = {}; - Attachments[OpIds[1]] = CreateAttachments(std::initializer_list<size_t>{*It++}); - Attachments[OpIds[2]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++}); - Attachments[OpIds[3]] = CreateAttachments(std::initializer_list<size_t>{*It++}); - Attachments[OpIds[4]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++, *It++}); - Attachments[OpIds[5]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++}); - Attachments[OpIds[6]] = CreateAttachments(std::initializer_list<size_t>{*It++}); - Attachments[OpIds[7]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++}); - Attachments[OpIds[8]] = CreateAttachments(std::initializer_list<size_t>{}); - Attachments[OpIds[9]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++}); - Attachments[OpIds[10]] = CreateAttachments(std::initializer_list<size_t>{*It++}); - Attachments[OpIds[11]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++, *It++}); - Attachments[OpIds[12]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++}); - Attachments[OpIds[13]] = CreateAttachments(std::initializer_list<size_t>{*It++}); - Attachments[OpIds[14]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++}); - Attachments[OpIds[15]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++}); - Attachments[OpIds[16]] = CreateAttachments(std::initializer_list<size_t>{}); - Attachments[OpIds[17]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++}); - Attachments[OpIds[18]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++}); - Attachments[OpIds[19]] = CreateAttachments(std::initializer_list<size_t>{}); - Attachments[OpIds[20]] = CreateAttachments(std::initializer_list<size_t>{*It++}); - Attachments[OpIds[21]] = CreateAttachments(std::initializer_list<size_t>{*It++}); - Attachments[OpIds[22]] = CreateAttachments(std::initializer_list<size_t>{*It++, *It++, *It++}); - Attachments[OpIds[23]] = CreateAttachments(std::initializer_list<size_t>{*It++}); + std::vector<std::size_t> AttachmentSizes( + {7633, 6825, 5738, 8031, 7225, 566, 3656, 6006, 24, 33466, 1093, 4269, 2257, 3685, 13489, 97194, + 6151, 5482, 6217, 3511, 6738, 5061, 7537, 2759, 1916, 8210, 2235, 224024, 51582, 5251, 491, 2u * 1024u * 1024u + 124u, + 74607, 18135, 3767, 154045, 4415, 5007, 8876, 96761, 3359, 8526, 4097, 4855, 48225}); + auto It = AttachmentSizes.begin(); + Attachments[OpIds[0]] = {}; + Attachments[OpIds[1]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++}); + Attachments[OpIds[2]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++}); + Attachments[OpIds[3]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++}); + Attachments[OpIds[4]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++}); + Attachments[OpIds[5]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++}); + Attachments[OpIds[6]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++}); + Attachments[OpIds[7]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++}); + Attachments[OpIds[8]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{}); + Attachments[OpIds[9]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++}); + Attachments[OpIds[10]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++}); + Attachments[OpIds[11]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++}); + Attachments[OpIds[12]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++}); + Attachments[OpIds[13]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++}); + Attachments[OpIds[14]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++}); + Attachments[OpIds[15]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++}); + Attachments[OpIds[16]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{}); + Attachments[OpIds[17]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++}); + Attachments[OpIds[18]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++}); + Attachments[OpIds[19]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{}); + Attachments[OpIds[20]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++}); + Attachments[OpIds[21]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++}); + Attachments[OpIds[22]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++}); + Attachments[OpIds[23]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++}); ZEN_ASSERT(It == AttachmentSizes.end()); } @@ -2914,6 +2933,7 @@ TEST_CASE("project.remote") { Writer << "maxblocksize"sv << 3072u; Writer << "maxchunkembedsize"sv << 1296u; + Writer << "chunkfilesizelimit"sv << 5u * 1024u; Writer << "force"sv << false; Writer << "file"sv << BeginObject; { @@ -2977,6 +2997,7 @@ TEST_CASE("project.remote") { Writer << "maxblocksize"sv << 3072u; Writer << "maxchunkembedsize"sv << 1296u; + Writer << "chunkfilesizelimit"sv << 5u * 1024u; Writer << "force"sv << false; Writer << "file"sv << BeginObject; { @@ -3038,6 +3059,7 @@ TEST_CASE("project.remote") { Writer << "maxblocksize"sv << 3072u; Writer << "maxchunkembedsize"sv << 1296u; + Writer << "chunkfilesizelimit"sv << 5u * 1024u; Writer << "force"sv << false; Writer << "file"sv << BeginObject; { @@ -3105,6 +3127,7 @@ TEST_CASE("project.remote") { Writer << "maxblocksize"sv << 3072u; Writer << "maxchunkembedsize"sv << 1296u; + Writer << "chunkfilesizelimit"sv << 5u * 1024u; Writer << "force"sv << false; Writer << "zen"sv << BeginObject; { diff --git a/src/zenserver/projectstore/fileremoteprojectstore.cpp b/src/zenserver/projectstore/fileremoteprojectstore.cpp index 764bea355..5c7a5536a 100644 --- a/src/zenserver/projectstore/fileremoteprojectstore.cpp +++ b/src/zenserver/projectstore/fileremoteprojectstore.cpp @@ -39,12 +39,24 @@ public: return { .CreateBlocks = m_EnableBlocks, .UseTempBlockFiles = m_UseTempBlocks, + .AllowChunking = true, .ContainerName = m_Name, .BaseContainerName = m_OptionalBaseName, .Description = fmt::format("[file] {}/{}{}{}"sv, m_OutputPath, m_Name, m_OptionalBaseName.empty() ? "" : " Base: ", m_OptionalBaseName)}; } + virtual Stats GetStats() const override + { + return {.m_SentBytes = m_SentBytes.load(), + .m_ReceivedBytes = m_ReceivedBytes.load(), + .m_RequestTimeNS = m_RequestTimeNS.load(), + .m_RequestCount = m_RequestCount.load(), + .m_PeakSentBytes = m_PeakSentBytes.load(), + .m_PeakReceivedBytes = m_PeakReceivedBytes.load(), + .m_PeakBytesPerSec = m_PeakBytesPerSec.load()}; + } + virtual SaveResult SaveContainer(const IoBuffer& Payload) override { Stopwatch Timer; @@ -84,6 +96,7 @@ public: Result.ErrorCode = gsl::narrow<int32_t>(HttpResponseCode::InternalServerError); Result.Reason = fmt::format("Failed saving oplog container to '{}'. Reason: {}", ContainerPath, Ex.what()); } + AddStats(Payload.GetSize(), 0, Timer.GetElapsedTimeUs() * 1000); Result.ElapsedSeconds = Timer.GetElapsedTimeMs() / 1000.0; return Result; } @@ -114,6 +127,7 @@ public: Result.Reason = fmt::format("Failed saving oplog attachment to '{}'. Reason: {}", ChunkPath, Ex.what()); } } + AddStats(Payload.GetSize(), 0, Timer.GetElapsedTimeUs() * 1000); Result.ElapsedSeconds = Timer.GetElapsedTimeMs() / 1000.0; return Result; } @@ -161,6 +175,7 @@ public: Result.Needs.insert(RawHash); } } + AddStats(0, 0, Timer.GetElapsedTimeUs() * 1000); Result.ElapsedSeconds = Timer.GetElapsedTimeMs() / 1000.0; return Result; } @@ -182,6 +197,7 @@ public: ChunkFile.Open(ChunkPath, BasicFile::Mode::kRead); Result.Bytes = ChunkFile.ReadAll(); } + AddStats(0, Result.Bytes.GetSize(), Timer.GetElapsedTimeUs() * 1000); Result.ElapsedSeconds = Timer.GetElapsedTimeMs() / 1000.0; return Result; } @@ -225,6 +241,7 @@ private: ContainerFile.Open(SourcePath, BasicFile::Mode::kRead); ContainerPayload = ContainerFile.ReadAll(); } + AddStats(0, ContainerPayload.GetSize(), Timer.GetElapsedTimeUs() * 1000); Result.ContainerObject = LoadCompactBinaryObject(ContainerPayload); if (!Result.ContainerObject) { @@ -256,11 +273,35 @@ private: return ShardedPath.ToPath(); } + void AddStats(uint64_t UploadedBytes, uint64_t DownloadedBytes, uint64_t ElapsedNS) + { + m_SentBytes.fetch_add(UploadedBytes); + m_ReceivedBytes.fetch_add(DownloadedBytes); + m_RequestTimeNS.fetch_add(ElapsedNS); + SetAtomicMax(m_PeakSentBytes, UploadedBytes); + SetAtomicMax(m_PeakReceivedBytes, DownloadedBytes); + if (ElapsedNS > 0) + { + uint64_t BytesPerSec = (gsl::narrow<uint64_t>(UploadedBytes + DownloadedBytes) * 1000000) / ElapsedNS; + SetAtomicMax(m_PeakBytesPerSec, BytesPerSec); + } + + m_RequestCount.fetch_add(1); + } + const std::string m_Name; const std::string m_OptionalBaseName; const std::filesystem::path m_OutputPath; bool m_EnableBlocks = true; bool m_UseTempBlocks = false; + + std::atomic_uint64_t m_SentBytes; + std::atomic_uint64_t m_ReceivedBytes; + std::atomic_uint64_t m_RequestTimeNS; + std::atomic_uint64_t m_RequestCount; + std::atomic_uint64_t m_PeakSentBytes; + std::atomic_uint64_t m_PeakReceivedBytes; + std::atomic_uint64_t m_PeakBytesPerSec; }; std::shared_ptr<RemoteProjectStore> diff --git a/src/zenserver/projectstore/jupiterremoteprojectstore.cpp b/src/zenserver/projectstore/jupiterremoteprojectstore.cpp index e68eb2823..bede3abb4 100644 --- a/src/zenserver/projectstore/jupiterremoteprojectstore.cpp +++ b/src/zenserver/projectstore/jupiterremoteprojectstore.cpp @@ -44,6 +44,7 @@ public: { return {.CreateBlocks = m_EnableBlocks, .UseTempBlockFiles = m_UseTempBlocks, + .AllowChunking = true, .ContainerName = fmt::format("{}/{}/{}", m_Namespace, m_Bucket, m_Key), .BaseContainerName = m_OptionalBaseKey == IoHash::Zero ? "" : fmt::format("{}/{}/{}", m_Namespace, m_Bucket, m_Key), .Description = fmt::format("[cloud] {} as {}/{}/{}{}"sv, @@ -54,10 +55,22 @@ public: m_OptionalBaseKey == IoHash::Zero ? "" : fmt::format(" Base {}", m_OptionalBaseKey))}; } + virtual Stats GetStats() const override + { + return {.m_SentBytes = m_SentBytes.load(), + .m_ReceivedBytes = m_ReceivedBytes.load(), + .m_RequestTimeNS = m_RequestTimeNS.load(), + .m_RequestCount = m_RequestCount.load(), + .m_PeakSentBytes = m_PeakSentBytes.load(), + .m_PeakReceivedBytes = m_PeakReceivedBytes.load(), + .m_PeakBytesPerSec = m_PeakBytesPerSec.load()}; + } + virtual SaveResult SaveContainer(const IoBuffer& Payload) override { CloudCacheSession Session(m_CloudClient.Get()); PutRefResult PutResult = Session.PutRef(m_Namespace, m_Bucket, m_Key, Payload, ZenContentType::kCbObject); + AddStats(PutResult); SaveResult Result{ConvertResult(PutResult), {PutResult.Needs.begin(), PutResult.Needs.end()}, PutResult.RawHash}; if (Result.ErrorCode) @@ -76,6 +89,7 @@ public: { CloudCacheSession Session(m_CloudClient.Get()); CloudCacheResult PutResult = Session.PutCompressedBlob(m_Namespace, RawHash, Payload); + AddStats(PutResult); SaveAttachmentResult Result{ConvertResult(PutResult)}; if (Result.ErrorCode) @@ -108,7 +122,9 @@ public: { CloudCacheSession Session(m_CloudClient.Get()); FinalizeRefResult FinalizeRefResult = Session.FinalizeRef(m_Namespace, m_Bucket, m_Key, RawHash); - FinalizeResult Result{ConvertResult(FinalizeRefResult), {FinalizeRefResult.Needs.begin(), FinalizeRefResult.Needs.end()}}; + AddStats(FinalizeRefResult); + + FinalizeResult Result{ConvertResult(FinalizeRefResult), {FinalizeRefResult.Needs.begin(), FinalizeRefResult.Needs.end()}}; if (Result.ErrorCode) { Result.Reason = fmt::format("Failed finalizing oplog container to {}/{}/{}/{}. Reason: '{}'", @@ -137,6 +153,8 @@ public: CloudCacheSession Session(m_CloudClient.Get()); CloudCacheExistsResult ExistsResult = Session.CompressedBlobExists(m_Namespace, std::set<IoHash>(RawHashes.begin(), RawHashes.end())); + AddStats(ExistsResult); + HasAttachmentsResult Result{ConvertResult(ExistsResult), std::unordered_set<IoHash, IoHash::Hasher>(ExistsResult.Needs.begin(), ExistsResult.Needs.end())}; if (ExistsResult.ErrorCode) @@ -151,8 +169,10 @@ public: virtual LoadAttachmentResult LoadAttachment(const IoHash& RawHash) override { - CloudCacheSession Session(m_CloudClient.Get()); - CloudCacheResult GetResult = Session.GetCompressedBlob(m_Namespace, RawHash, m_TempFilePath); + CloudCacheSession Session(m_CloudClient.Get()); + CloudCacheResult GetResult = Session.GetCompressedBlob(m_Namespace, RawHash, m_TempFilePath); + AddStats(GetResult); + LoadAttachmentResult Result{ConvertResult(GetResult), std::move(GetResult.Response)}; if (GetResult.ErrorCode) { @@ -187,6 +207,7 @@ private: { CloudCacheSession Session(m_CloudClient.Get()); CloudCacheResult GetResult = Session.GetRef(m_Namespace, m_Bucket, Key, ZenContentType::kCbObject); + AddStats(GetResult); if (GetResult.ErrorCode || !GetResult.Success) { LoadContainerResult Result{ConvertResult(GetResult)}; @@ -215,6 +236,22 @@ private: return LoadContainerResult{ConvertResult(GetResult), std::move(ContainerObject)}; } + void AddStats(const CloudCacheResult& Result) + { + m_SentBytes.fetch_add(gsl::narrow<uint64_t>(Result.SentBytes)); + m_ReceivedBytes.fetch_add(gsl::narrow<uint64_t>(Result.ReceivedBytes)); + m_RequestTimeNS.fetch_add(static_cast<uint64_t>(Result.ElapsedSeconds * 1000000000)); + SetAtomicMax(m_PeakSentBytes, Result.SentBytes); + SetAtomicMax(m_PeakReceivedBytes, Result.ReceivedBytes); + if (Result.ElapsedSeconds > 0.0) + { + uint64_t BytesPerSec = static_cast<uint64_t>((Result.SentBytes + Result.ReceivedBytes) / Result.ElapsedSeconds); + SetAtomicMax(m_PeakBytesPerSec, BytesPerSec); + } + + m_RequestCount.fetch_add(1); + } + static Result ConvertResult(const CloudCacheResult& Response) { std::string Text; @@ -260,6 +297,14 @@ private: std::filesystem::path m_TempFilePath; bool m_EnableBlocks = true; bool m_UseTempBlocks = true; + + std::atomic_uint64_t m_SentBytes; + std::atomic_uint64_t m_ReceivedBytes; + std::atomic_uint64_t m_RequestTimeNS; + std::atomic_uint64_t m_RequestCount; + std::atomic_uint64_t m_PeakSentBytes; + std::atomic_uint64_t m_PeakReceivedBytes; + std::atomic_uint64_t m_PeakBytesPerSec; }; std::shared_ptr<RemoteProjectStore> diff --git a/src/zenserver/projectstore/projectstore.cpp b/src/zenserver/projectstore/projectstore.cpp index 84ed6f842..65d2730d8 100644 --- a/src/zenserver/projectstore/projectstore.cpp +++ b/src/zenserver/projectstore/projectstore.cpp @@ -3150,6 +3150,7 @@ ProjectStore::ReadOplog(const std::string_view ProjectId, ChunkFileSizeLimit, /* BuildBlocks */ false, /* IgnoreMissingAttachments */ false, + /* AllowChunking*/ false, [](CompressedBuffer&&, const IoHash&) {}, [](const IoHash&, TGetAttachmentBufferFunc&&) {}, [](std::vector<std::pair<IoHash, FetchChunkFunc>>&&) {}, diff --git a/src/zenserver/projectstore/remoteprojectstore.cpp b/src/zenserver/projectstore/remoteprojectstore.cpp index 8efb92e6b..ba3ff6499 100644 --- a/src/zenserver/projectstore/remoteprojectstore.cpp +++ b/src/zenserver/projectstore/remoteprojectstore.cpp @@ -113,6 +113,34 @@ IsCancelled(JobContext* OptionalContext) return OptionalContext->IsCancelled(); } +void +ReportRemoteStoreStats(JobContext* OptionalContext, + const RemoteProjectStore::RemoteStoreInfo& RemoteStoreInfo, + const RemoteProjectStore::Stats& Stats) +{ + ReportMessage( + OptionalContext, + fmt::format( + "Remote store stats for '{}'\n" + "Requests: {} (avg {})\n" + "Sent: {} (avg {}, {}/s)\n" + "Received: {} (avg {}, {}/s)\n" + "Peak: Send {}, Receive {} ({}/s)", + RemoteStoreInfo.Description, + Stats.m_RequestCount, + NiceLatencyNs(Stats.m_RequestCount > 0 ? (Stats.m_RequestTimeNS / Stats.m_RequestCount) : 0u), + NiceBytes(Stats.m_SentBytes), + NiceBytes(Stats.m_RequestCount > 0u ? static_cast<uint64_t>((Stats.m_SentBytes) / Stats.m_RequestCount) : 0u), + NiceBytes(Stats.m_RequestTimeNS > 0u ? static_cast<uint64_t>((Stats.m_SentBytes * 1000000000) / Stats.m_RequestTimeNS) : 0u), + NiceBytes(Stats.m_ReceivedBytes), + NiceBytes(Stats.m_RequestCount > 0u ? static_cast<uint64_t>((Stats.m_ReceivedBytes) / Stats.m_RequestCount) : 0u), + NiceBytes(Stats.m_RequestTimeNS > 0u ? static_cast<uint64_t>((Stats.m_ReceivedBytes * 1000000000) / Stats.m_RequestTimeNS) + : 0u), + NiceBytes(Stats.m_PeakSentBytes), + NiceBytes(Stats.m_PeakReceivedBytes), + NiceBytes(Stats.m_PeakBytesPerSec))); +} + bool IterateBlock(IoBuffer&& CompressedBlock, std::function<void(CompressedBuffer&& Chunk, const IoHash& AttachmentHash)> Visitor) { @@ -292,6 +320,7 @@ BuildContainer(CidStore& ChunkStore, size_t ChunkFileSizeLimit, bool BuildBlocks, bool IgnoreMissingAttachments, + bool AllowChunking, const std::vector<Block>& KnownBlocks, WorkerThreadPool& WorkerPool, const std::function<void(CompressedBuffer&&, const IoHash&)>& AsyncOnBlock, @@ -619,7 +648,8 @@ BuildContainer(CidStore& ChunkStore, auto __ = MakeGuard([&SourceFile]() { SourceFile.Detach(); }); Chunked.Chunked = ChunkData(SourceFile, Offset, Size, UShaderByteCodeParams); - Chunked.Source = RawData; + ZEN_ASSERT(Chunked.Chunked.Info.RawHash == RawHash); + Chunked.Source = RawData; ZEN_INFO("Chunked large attachment '{}' {} into {} chunks in {}", RawHash, @@ -667,6 +697,7 @@ BuildContainer(CidStore& ChunkStore, &ChunkedFiles, MaxChunkEmbedSize, ChunkFileSizeLimit, + AllowChunking, &RemoteResult, OptionalContext]() { auto _ = MakeGuard([&ResolveAttachmentsLatch] { ResolveAttachmentsLatch.CountDown(); }); @@ -804,7 +835,7 @@ BuildContainer(CidStore& ChunkStore, }; IoBufferFileReference FileRef; - if (GetForChunking(ChunkFileSizeLimit, Data, FileRef)) + if (AllowChunking && GetForChunking(ChunkFileSizeLimit, Data, FileRef)) { ChunkedFile Chunked = ChunkFile(RawHash, Data, FileRef, OptionalContext); ResolveLock.WithExclusiveLock( @@ -1357,6 +1388,7 @@ BuildContainer(CidStore& ChunkStore, size_t ChunkFileSizeLimit, bool BuildBlocks, bool IgnoreMissingAttachments, + bool AllowChunking, const std::function<void(CompressedBuffer&&, const IoHash&)>& AsyncOnBlock, const std::function<void(const IoHash&, TGetAttachmentBufferFunc&&)>& OnLargeAttachment, const std::function<void(std::vector<std::pair<IoHash, FetchChunkFunc>>&&)>& OnBlockChunks, @@ -1373,6 +1405,7 @@ BuildContainer(CidStore& ChunkStore, ChunkFileSizeLimit, BuildBlocks, IgnoreMissingAttachments, + AllowChunking, {}, WorkerPool, AsyncOnBlock, @@ -1880,6 +1913,8 @@ SaveOplog(CidStore& ChunkStore, ChunkFileSizeLimit, RemoteStoreInfo.CreateBlocks, IgnoreMissingAttachments, + RemoteStoreInfo.AllowChunking, + KnownBlocks, WorkerPool, OnBlock, @@ -2039,6 +2074,8 @@ SaveOplog(CidStore& ChunkStore, Info.AttachmentsUploaded.load(), NiceBytes(Info.AttachmentBytesUploaded.load()))); + ReportRemoteStoreStats(OptionalContext, RemoteStoreInfo, RemoteStore.GetStats()); + return Result; }; @@ -2548,7 +2585,18 @@ LoadOplog(CidStore& ChunkStore, IgnoreMissingAttachments, &Info, OptionalContext]() { - auto _ = MakeGuard([&DechunkLatch] { DechunkLatch.CountDown(); }); + auto _ = MakeGuard([&DechunkLatch, &TempFileName] { + std::error_code Ec; + if (std::filesystem::exists(TempFileName, Ec)) + { + std::filesystem::remove(TempFileName, Ec); + if (Ec) + { + ZEN_INFO("Failed to remove temporary file '{}'. Reason: {}", TempFileName, Ec.message()); + } + } + DechunkLatch.CountDown(); + }); if (RemoteResult.IsError()) { return; @@ -2574,14 +2622,6 @@ LoadOplog(CidStore& ChunkStore, // We only add 1 as the resulting missing count will be 1 for the dechunked file Info.MissingAttachmentCount.fetch_add(1); - TmpFile.Close(); - std::error_code Ec; - std::filesystem::remove(TempFileName, Ec); - if (Ec) - { - ZEN_INFO("Failed to remove temporary file '{}'. Reason: {}", TempFileName, Ec.message()); - } - if (!IgnoreMissingAttachments) { RemoteResult.SetError( @@ -2606,14 +2646,8 @@ LoadOplog(CidStore& ChunkStore, UniqueBuffer Header = CompressedBuffer::CreateHeaderForNoneEncoder(Chunked.RawSize, RawHash); TmpWriter.Write(Header.GetData(), Header.GetSize(), 0); } - TmpFile.Flush(); - uint64_t TmpFileSize = TmpFile.FileSize(); - TmpBuffer = IoBuffer(IoBuffer::File, TmpFile.Detach(), 0, TmpFileSize, /*IsWholeFile*/ true); - IoHash ValidateRawHash; - uint64_t ValidateRawSize = 0; - ZEN_ASSERT(CompressedBuffer::ValidateCompressedHeader(TmpBuffer, ValidateRawHash, ValidateRawSize)); - ZEN_ASSERT(ValidateRawHash == Chunked.RawHash); - ZEN_ASSERT(ValidateRawSize == Chunked.RawSize); + TmpFile.Close(); + TmpBuffer = IoBufferBuilder::MakeFromTemporaryFile(TempFileName); } CidStore::InsertResult InsertResult = ChunkStore.AddChunk(TmpBuffer, Chunked.RawHash, CidStore::InsertMode::kMayBeMovedInPlace); @@ -2668,6 +2702,9 @@ LoadOplog(CidStore& ChunkStore, Info.AttachmentsStored.load(), NiceBytes(Info.AttachmentBytesStored.load()), Info.MissingAttachmentCount.load())); + + ReportRemoteStoreStats(OptionalContext, RemoteStoreInfo, RemoteStore.GetStats()); + return Result; } diff --git a/src/zenserver/projectstore/remoteprojectstore.h b/src/zenserver/projectstore/remoteprojectstore.h index 020069441..6b83e526c 100644 --- a/src/zenserver/projectstore/remoteprojectstore.h +++ b/src/zenserver/projectstore/remoteprojectstore.h @@ -67,15 +67,28 @@ public: { bool CreateBlocks; bool UseTempBlockFiles; + bool AllowChunking; std::string ContainerName; std::string BaseContainerName; std::string Description; }; + struct Stats + { + std::uint64_t m_SentBytes; + std::uint64_t m_ReceivedBytes; + std::uint64_t m_RequestTimeNS; + std::uint64_t m_RequestCount; + std::uint64_t m_PeakSentBytes; + std::uint64_t m_PeakReceivedBytes; + std::uint64_t m_PeakBytesPerSec; + }; + RemoteProjectStore(); virtual ~RemoteProjectStore(); - virtual RemoteStoreInfo GetInfo() const = 0; + virtual RemoteStoreInfo GetInfo() const = 0; + virtual Stats GetStats() const = 0; virtual SaveResult SaveContainer(const IoBuffer& Payload) = 0; virtual SaveAttachmentResult SaveAttachment(const CompositeBuffer& Payload, const IoHash& RawHash) = 0; @@ -112,6 +125,7 @@ RemoteProjectStore::LoadContainerResult BuildContainer( size_t ChunkFileSizeLimit, bool BuildBlocks, bool IgnoreMissingAttachments, + bool AllowChunking, const std::function<void(CompressedBuffer&&, const IoHash&)>& AsyncOnBlock, const std::function<void(const IoHash&, TGetAttachmentBufferFunc&&)>& OnLargeAttachment, const std::function<void(std::vector<std::pair<IoHash, FetchChunkFunc>>&&)>& OnBlockChunks, diff --git a/src/zenserver/projectstore/zenremoteprojectstore.cpp b/src/zenserver/projectstore/zenremoteprojectstore.cpp index 600338843..cec68111f 100644 --- a/src/zenserver/projectstore/zenremoteprojectstore.cpp +++ b/src/zenserver/projectstore/zenremoteprojectstore.cpp @@ -34,16 +34,29 @@ public: { return {.CreateBlocks = false, .UseTempBlockFiles = false, + .AllowChunking = false, .ContainerName = fmt::format("{}/{}", m_Project, m_Oplog), .BaseContainerName = "", .Description = fmt::format("[zen] {}"sv, m_HostAddress)}; } + virtual Stats GetStats() const override + { + return {.m_SentBytes = m_SentBytes.load(), + .m_ReceivedBytes = m_ReceivedBytes.load(), + .m_RequestTimeNS = m_RequestTimeNS.load(), + .m_RequestCount = m_RequestCount.load(), + .m_PeakSentBytes = m_PeakSentBytes.load(), + .m_PeakReceivedBytes = m_PeakReceivedBytes.load(), + .m_PeakBytesPerSec = m_PeakBytesPerSec.load()}; + } + virtual SaveResult SaveContainer(const IoBuffer& Payload) override { std::string SaveRequest = fmt::format("/{}/oplog/{}/save"sv, m_Project, m_Oplog); HttpClient::Response Response = m_Client.Post(SaveRequest, Payload, ZenContentType::kCbObject); - SaveResult Result = SaveResult{ConvertResult(Response)}; + AddStats(Response); + SaveResult Result = SaveResult{ConvertResult(Response)}; if (Result.ErrorCode) { @@ -79,7 +92,8 @@ public: { std::string SaveRequest = fmt::format("/{}/oplog/{}/{}"sv, m_Project, m_Oplog, RawHash); HttpClient::Response Response = m_Client.Post(SaveRequest, Payload, ZenContentType::kCompressedBinary); - SaveAttachmentResult Result = SaveAttachmentResult{ConvertResult(Response)}; + AddStats(Response); + SaveAttachmentResult Result = SaveAttachmentResult{ConvertResult(Response)}; if (Result.ErrorCode) { Result.Reason = fmt::format("Failed saving oplog attachment to {}/{}/{}/{}. Reason: '{}'", @@ -114,6 +128,7 @@ public: } std::string SaveRequest = fmt::format("/{}/oplog/{}/rpc"sv, m_Project, m_Oplog); HttpClient::Response Response = m_Client.Post(SaveRequest, RequestPackage); + AddStats(Response); SaveAttachmentsResult Result = SaveAttachmentsResult{ConvertResult(Response)}; if (Result.ErrorCode) @@ -148,6 +163,7 @@ public: } HttpClient::Response Response = m_Client.Post(LoadRequest, Request, HttpClient::Accept(ZenContentType::kCbPackage)); + AddStats(Response); LoadAttachmentsResult Result = LoadAttachmentsResult{ConvertResult(Response)}; if (Result.ErrorCode) @@ -180,7 +196,9 @@ public: std::string LoadRequest = fmt::format("/{}/oplog/{}/load"sv, m_Project, m_Oplog); HttpClient::Response Response = m_Client.Get(LoadRequest, HttpClient::Accept(ZenContentType::kCbObject)); - LoadContainerResult Result = LoadContainerResult{ConvertResult(Response)}; + AddStats(Response); + + LoadContainerResult Result = LoadContainerResult{ConvertResult(Response)}; if (Result.ErrorCode) { Result.Reason = fmt::format("Failed fetching oplog container from {}/{}/{}. Reason: '{}'", @@ -221,6 +239,8 @@ public: std::string LoadRequest = fmt::format("/{}/oplog/{}/{}"sv, m_Project, m_Oplog, RawHash); HttpClient::Response Response = m_Client.Download(LoadRequest, m_TempFilePath, HttpClient::Accept(ZenContentType::kCompressedBinary)); + AddStats(Response); + LoadAttachmentResult Result = LoadAttachmentResult{ConvertResult(Response)}; if (!Result.ErrorCode) { @@ -240,6 +260,22 @@ public: } private: + void AddStats(const HttpClient::Response& Result) + { + m_SentBytes.fetch_add(gsl::narrow<uint64_t>(Result.UploadedBytes)); + m_ReceivedBytes.fetch_add(gsl::narrow<uint64_t>(Result.DownloadedBytes)); + m_RequestTimeNS.fetch_add(static_cast<uint64_t>(Result.ElapsedSeconds * 1000000000)); + SetAtomicMax(m_PeakSentBytes, Result.UploadedBytes); + SetAtomicMax(m_PeakReceivedBytes, Result.DownloadedBytes); + if (Result.ElapsedSeconds > 0.0) + { + uint64_t BytesPerSec = static_cast<uint64_t>((Result.UploadedBytes + Result.DownloadedBytes) / Result.ElapsedSeconds); + SetAtomicMax(m_PeakBytesPerSec, BytesPerSec); + } + + m_RequestCount.fetch_add(1); + } + static Result ConvertResult(const HttpClient::Response& Response, const std::string_view ErrorPrefix = ""sv) { if (Response.Error) @@ -266,6 +302,14 @@ private: const std::filesystem::path m_TempFilePath; HttpClient m_Client; + + std::atomic_uint64_t m_SentBytes; + std::atomic_uint64_t m_ReceivedBytes; + std::atomic_uint64_t m_RequestTimeNS; + std::atomic_uint64_t m_RequestCount; + std::atomic_uint64_t m_PeakSentBytes; + std::atomic_uint64_t m_PeakReceivedBytes; + std::atomic_uint64_t m_PeakBytesPerSec; }; std::shared_ptr<RemoteProjectStore> diff --git a/src/zenserver/upstream/jupiter.cpp b/src/zenserver/upstream/jupiter.cpp index c77834657..3e477f053 100644 --- a/src/zenserver/upstream/jupiter.cpp +++ b/src/zenserver/upstream/jupiter.cpp @@ -35,20 +35,25 @@ namespace detail { { if (Response.Error) { - return {.ElapsedSeconds = Response.ElapsedSeconds, + return {.SentBytes = gsl::narrow<uint64_t>(Response.UploadedBytes), + .ReceivedBytes = gsl::narrow<uint64_t>(Response.DownloadedBytes), + .ElapsedSeconds = Response.ElapsedSeconds, .ErrorCode = Response.Error.value().ErrorCode, .Reason = Response.ErrorMessage(ErrorPrefix), .Success = false}; } if (!Response.IsSuccess()) { - return {.ElapsedSeconds = Response.ElapsedSeconds, + return {.SentBytes = gsl::narrow<uint64_t>(Response.UploadedBytes), + .ReceivedBytes = gsl::narrow<uint64_t>(Response.DownloadedBytes), + .ElapsedSeconds = Response.ElapsedSeconds, .ErrorCode = static_cast<int32_t>(Response.StatusCode), .Reason = Response.ErrorMessage(ErrorPrefix), .Success = false}; } return {.Response = Response.ResponsePayload, - .Bytes = Response.DownloadedBytes, + .SentBytes = gsl::narrow<uint64_t>(Response.UploadedBytes), + .ReceivedBytes = gsl::narrow<uint64_t>(Response.DownloadedBytes), .ElapsedSeconds = Response.ElapsedSeconds, .ErrorCode = 0, .Success = true}; diff --git a/src/zenserver/upstream/jupiter.h b/src/zenserver/upstream/jupiter.h index cfe8f6186..00ba55bad 100644 --- a/src/zenserver/upstream/jupiter.h +++ b/src/zenserver/upstream/jupiter.h @@ -52,7 +52,8 @@ struct CloudCacheAccessToken struct CloudCacheResult { IoBuffer Response; - int64_t Bytes{}; + uint64_t SentBytes{}; + uint64_t ReceivedBytes{}; double ElapsedSeconds{}; int32_t ErrorCode{}; std::string Reason; diff --git a/src/zenserver/upstream/upstreamcache.cpp b/src/zenserver/upstream/upstreamcache.cpp index 6d1d026cc..ab8fe8704 100644 --- a/src/zenserver/upstream/upstreamcache.cpp +++ b/src/zenserver/upstream/upstreamcache.cpp @@ -210,7 +210,8 @@ namespace detail { CacheRecord.IterateAttachments([&](CbFieldView AttachmentHash) { CloudCacheResult AttachmentResult = Session.GetCompressedBlob(BlobStoreNamespace, AttachmentHash.AsHash()); - Result.Bytes += AttachmentResult.Bytes; + Result.ReceivedBytes += AttachmentResult.ReceivedBytes; + Result.SentBytes += AttachmentResult.SentBytes; Result.ElapsedSeconds += AttachmentResult.ElapsedSeconds; Result.ErrorCode = AttachmentResult.ErrorCode; @@ -249,7 +250,8 @@ namespace detail { CacheRecord.IterateAttachments([&](CbFieldView AttachmentHash) { CloudCacheResult AttachmentResult = Session.GetCompressedBlob(BlobStoreNamespace, AttachmentHash.AsHash()); - Result.Bytes += AttachmentResult.Bytes; + Result.ReceivedBytes += AttachmentResult.ReceivedBytes; + Result.SentBytes += AttachmentResult.SentBytes; Result.ElapsedSeconds += AttachmentResult.ElapsedSeconds; Result.ErrorCode = AttachmentResult.ErrorCode; @@ -283,7 +285,9 @@ namespace detail { if (Result.ErrorCode == 0) { - return {.Status = {.Bytes = Result.Bytes, .ElapsedSeconds = Result.ElapsedSeconds, .Success = Result.Success}, + return {.Status = {.Bytes = gsl::narrow<int64_t>(Result.ReceivedBytes), + .ElapsedSeconds = Result.ElapsedSeconds, + .Success = Result.Success}, .Value = Result.Response, .Source = &m_Info}; } @@ -379,7 +383,9 @@ namespace detail { if (Result.ErrorCode == 0) { - return {.Status = {.Bytes = Result.Bytes, .ElapsedSeconds = Result.ElapsedSeconds, .Success = Result.Success}, + return {.Status = {.Bytes = gsl::narrow<int64_t>(Result.ReceivedBytes), + .ElapsedSeconds = Result.ElapsedSeconds, + .Success = Result.Success}, .Value = Result.Response, .Source = &m_Info}; } @@ -555,7 +561,7 @@ namespace detail { m_Status.SetFromErrorCode(Result.ErrorCode, Result.Reason); return {.Reason = std::move(Result.Reason), - .Bytes = Result.Bytes, + .Bytes = gsl::narrow<int64_t>(Result.ReceivedBytes), .ElapsedSeconds = Result.ElapsedSeconds, .Success = Result.Success}; } @@ -629,7 +635,7 @@ namespace detail { static void AppendResult(const CloudCacheResult& Result, GetUpstreamCacheResult& Out) { Out.Success &= Result.Success; - Out.Bytes += Result.Bytes; + Out.Bytes += gsl::narrow<int64_t>(Result.ReceivedBytes); Out.ElapsedSeconds += Result.ElapsedSeconds; if (Result.ErrorCode) @@ -673,7 +679,7 @@ namespace detail { return false; } - TotalBytes += BlobResult.Bytes; + TotalBytes += gsl::narrow<int64_t>(BlobResult.ReceivedBytes); TotalElapsedSeconds += BlobResult.ElapsedSeconds; } @@ -694,7 +700,7 @@ namespace detail { .Success = false}; } - TotalBytes += RefResult.Bytes; + TotalBytes += gsl::narrow<int64_t>(RefResult.ReceivedBytes); TotalElapsedSeconds += RefResult.ElapsedSeconds; std::string Reason; @@ -746,7 +752,7 @@ namespace detail { } } - TotalBytes += FinalizeResult.Bytes; + TotalBytes += gsl::narrow<int64_t>(FinalizeResult.ReceivedBytes); TotalElapsedSeconds += FinalizeResult.ElapsedSeconds; return {.Bytes = TotalBytes, .ElapsedSeconds = TotalElapsedSeconds, .Success = true}; |