diff options
| author | Dan Engelbrecht <[email protected]> | 2025-09-22 10:40:22 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2025-09-22 10:40:22 +0200 |
| commit | 8ec953bc17e4c13ff1811f4df26d722e5913e571 (patch) | |
| tree | 68940b439a492db78e6e0f550f161b4735ee7a7f /src | |
| parent | fix quoted wildcard options (#500) (diff) | |
| download | zen-8ec953bc17e4c13ff1811f4df26d722e5913e571.tar.xz zen-8ec953bc17e4c13ff1811f4df26d722e5913e571.zip | |
improve builds download partial logic (#501)
- Improvement: `zen command` help now uses available horizontal space for output
- Improvement: Partial block request analisys has been improved with reduced download size at lower number of requests
- Improvement: Validate that `--zen-cache-host` exists and is responsive before begin used
- Feature: Options `--allow-partial-block-requests` for `zen builds download` command has been augmented with two new modes, `zencacheonly` and `mixed`. Defaults to `mixed`.
- `false` only full block requests allowed
- `mixed` multiple partial block ranges requests per block allowed to zen cache, single partial block range request per block to host
- `zencacheonly` multiple partial block ranges requests per block allowed to zen cache, only full block requests allowed to host
- `true` multiple partial block ranges requests per block allowed to zen cache and host
Diffstat (limited to 'src')
| -rw-r--r-- | src/zen/cmds/builds_cmd.cpp | 877 | ||||
| -rw-r--r-- | src/zen/cmds/builds_cmd.h | 2 | ||||
| -rw-r--r-- | src/zen/zen.cpp | 168 |
3 files changed, 671 insertions, 376 deletions
diff --git a/src/zen/cmds/builds_cmd.cpp b/src/zen/cmds/builds_cmd.cpp index 949e40085..1188f7cfd 100644 --- a/src/zen/cmds/builds_cmd.cpp +++ b/src/zen/cmds/builds_cmd.cpp @@ -1721,6 +1721,275 @@ namespace { return Result; } + struct BlockRangeDescriptor + { + uint32_t BlockIndex = (uint32_t)-1; + uint64_t RangeStart = 0; + uint64_t RangeLength = 0; + uint32_t ChunkBlockIndexStart = 0; + uint32_t ChunkBlockIndexCount = 0; + }; + + BlockRangeDescriptor MergeBlockRanges(std::span<const BlockRangeDescriptor> Ranges) + { + ZEN_ASSERT(Ranges.size() > 1); + const BlockRangeDescriptor& First = Ranges.front(); + const BlockRangeDescriptor& Last = Ranges.back(); + + return BlockRangeDescriptor{ + .BlockIndex = First.BlockIndex, + .RangeStart = First.RangeStart, + .RangeLength = Last.RangeStart + Last.RangeLength - First.RangeStart, + .ChunkBlockIndexStart = First.ChunkBlockIndexStart, + .ChunkBlockIndexCount = Last.ChunkBlockIndexStart + Last.ChunkBlockIndexCount - First.ChunkBlockIndexStart}; + } + + std::optional<std::vector<BlockRangeDescriptor>> MakeOptionalBlockRangeVector(uint64_t TotalBlockSize, + const BlockRangeDescriptor& Range) + { + if (Range.RangeLength == TotalBlockSize) + { + return {}; + } + else + { + return std::vector<BlockRangeDescriptor>{Range}; + } + }; + + struct BlockRangeLimit + { + uint16_t SizePercent; + uint16_t MaxRangeCount; + }; + + static const uint16_t FullBlockRangePercentLimit = 95; + + static const std::vector<BlockRangeLimit> ForceMergeLimits = {{.SizePercent = FullBlockRangePercentLimit, .MaxRangeCount = 1}, + {.SizePercent = 90, .MaxRangeCount = 2}, + {.SizePercent = 85, .MaxRangeCount = 8}, + {.SizePercent = 80, .MaxRangeCount = 16}, + {.SizePercent = 70, .MaxRangeCount = 32}, + {.SizePercent = 60, .MaxRangeCount = 48}, + {.SizePercent = 2, .MaxRangeCount = 56}, + {.SizePercent = 0, .MaxRangeCount = 64}}; + + const BlockRangeLimit* GetBlockRangeLimitForRange(std::span<const BlockRangeLimit> Limits, + uint64_t TotalBlockSize, + std::span<const BlockRangeDescriptor> Ranges) + { + if (Ranges.size() > 1) + { + const std::uint64_t WantedSize = + std::accumulate(Ranges.begin(), Ranges.end(), uint64_t(0), [](uint64_t Current, const BlockRangeDescriptor& Range) { + return Current + Range.RangeLength; + }); + + const double RangeRequestedPercent = (WantedSize * 100.0) / TotalBlockSize; + + for (const BlockRangeLimit& Limit : Limits) + { + if (RangeRequestedPercent >= Limit.SizePercent && Ranges.size() > Limit.MaxRangeCount) + { + return &Limit; + } + } + } + return nullptr; + }; + + std::vector<BlockRangeDescriptor> CollapseBlockRanges(const uint64_t AlwaysAcceptableGap, + std::span<const BlockRangeDescriptor> BlockRanges) + { + ZEN_ASSERT(BlockRanges.size() > 1); + std::vector<BlockRangeDescriptor> CollapsedBlockRanges; + + auto BlockRangesIt = BlockRanges.begin(); + CollapsedBlockRanges.push_back(*BlockRangesIt++); + for (; BlockRangesIt != BlockRanges.end(); BlockRangesIt++) + { + BlockRangeDescriptor& LastRange = CollapsedBlockRanges.back(); + + const uint64_t BothRangeSize = BlockRangesIt->RangeLength + LastRange.RangeLength; + + const uint64_t Gap = BlockRangesIt->RangeStart - (LastRange.RangeStart + LastRange.RangeLength); + if (Gap <= Max(BothRangeSize / 16, AlwaysAcceptableGap)) + { + LastRange.ChunkBlockIndexCount = + (BlockRangesIt->ChunkBlockIndexStart + BlockRangesIt->ChunkBlockIndexCount) - LastRange.ChunkBlockIndexStart; + LastRange.RangeLength = (BlockRangesIt->RangeStart + BlockRangesIt->RangeLength) - LastRange.RangeStart; + } + else + { + CollapsedBlockRanges.push_back(*BlockRangesIt); + } + } + + return CollapsedBlockRanges; + }; + + uint64_t CalculateNextGap(std::span<const BlockRangeDescriptor> BlockRanges) + { + ZEN_ASSERT(BlockRanges.size() > 1); + uint64_t AcceptableGap = (uint64_t)-1; + for (size_t RangeIndex = 0; RangeIndex < BlockRanges.size() - 1; RangeIndex++) + { + const BlockRangeDescriptor& Range = BlockRanges[RangeIndex]; + const BlockRangeDescriptor& NextRange = BlockRanges[RangeIndex + 1]; + + const uint64_t Gap = NextRange.RangeStart - (Range.RangeStart + Range.RangeLength); + AcceptableGap = Min(Gap, AcceptableGap); + } + AcceptableGap = RoundUp(AcceptableGap, 16u * 1024u); + return AcceptableGap; + }; + + std::optional<std::vector<BlockRangeDescriptor>> CalculateBlockRanges(uint32_t BlockIndex, + const ChunkBlockDescription& BlockDescription, + std::span<const uint32_t> BlockChunkIndexNeeded, + bool LimitToSingleRange, + const uint64_t ChunkStartOffsetInBlock, + const uint64_t TotalBlockSize, + uint64_t& OutTotalWantedChunksSize) + { + ZEN_TRACE_CPU("UpdateFolder_HandleBlocks_PartialAnalysis"); + + std::vector<BlockRangeDescriptor> BlockRanges; + { + uint64_t CurrentOffset = ChunkStartOffsetInBlock; + uint32_t ChunkBlockIndex = 0; + uint32_t NeedBlockChunkIndexOffset = 0; + BlockRangeDescriptor NextRange{.BlockIndex = BlockIndex}; + while (NeedBlockChunkIndexOffset < BlockChunkIndexNeeded.size() && ChunkBlockIndex < BlockDescription.ChunkRawHashes.size()) + { + const uint32_t ChunkCompressedLength = BlockDescription.ChunkCompressedLengths[ChunkBlockIndex]; + if (ChunkBlockIndex < BlockChunkIndexNeeded[NeedBlockChunkIndexOffset]) + { + if (NextRange.RangeLength > 0) + { + BlockRanges.push_back(NextRange); + NextRange = {.BlockIndex = BlockIndex}; + } + ChunkBlockIndex++; + CurrentOffset += ChunkCompressedLength; + } + else if (ChunkBlockIndex == BlockChunkIndexNeeded[NeedBlockChunkIndexOffset]) + { + if (NextRange.RangeLength == 0) + { + NextRange.RangeStart = CurrentOffset; + NextRange.ChunkBlockIndexStart = ChunkBlockIndex; + } + NextRange.RangeLength += ChunkCompressedLength; + NextRange.ChunkBlockIndexCount++; + ChunkBlockIndex++; + CurrentOffset += ChunkCompressedLength; + NeedBlockChunkIndexOffset++; + } + else + { + ZEN_ASSERT(false); + } + } + if (NextRange.RangeLength > 0) + { + BlockRanges.push_back(NextRange); + } + } + ZEN_ASSERT(!BlockRanges.empty()); + + OutTotalWantedChunksSize = + std::accumulate(BlockRanges.begin(), BlockRanges.end(), uint64_t(0), [](uint64_t Current, const BlockRangeDescriptor& Range) { + return Current + Range.RangeLength; + }); + + double RangeWantedPercent = (OutTotalWantedChunksSize * 100.0) / TotalBlockSize; + + if (BlockRanges.size() == 1) + { + ZEN_CONSOLE_VERBOSE("Range request of {} ({:.2f}%) using single range from block {} ({}) as is", + NiceBytes(OutTotalWantedChunksSize), + RangeWantedPercent, + BlockDescription.BlockHash, + NiceBytes(TotalBlockSize)); + return BlockRanges; + } + + if (LimitToSingleRange) + { + const BlockRangeDescriptor MergedRange = MergeBlockRanges(BlockRanges); + const double RangeRequestedPercent = (MergedRange.RangeLength * 100.0) / TotalBlockSize; + const double WastedPercent = ((MergedRange.RangeLength - OutTotalWantedChunksSize) * 100.0) / MergedRange.RangeLength; + ZEN_CONSOLE_VERBOSE( + "Range request of {} ({:.2f}%) using {} ranges from block {} ({}) limited to single block range {} ({:.2f}%) wasting " + "{:.2f}% ({})", + NiceBytes(OutTotalWantedChunksSize), + RangeWantedPercent, + BlockRanges.size(), + BlockDescription.BlockHash, + NiceBytes(TotalBlockSize), + NiceBytes(MergedRange.RangeLength), + RangeRequestedPercent, + WastedPercent, + NiceBytes(MergedRange.RangeLength - OutTotalWantedChunksSize)); + return MakeOptionalBlockRangeVector(TotalBlockSize, MergedRange); + } + + if (RangeWantedPercent > FullBlockRangePercentLimit) + { + const BlockRangeDescriptor MergedRange = MergeBlockRanges(BlockRanges); + const double RangeRequestedPercent = (MergedRange.RangeLength * 100.0) / TotalBlockSize; + const double WastedPercent = ((MergedRange.RangeLength - OutTotalWantedChunksSize) * 100.0) / MergedRange.RangeLength; + ZEN_CONSOLE_VERBOSE( + "Range request of {} ({:.2f}%) using {} ranges from block {} ({}) exceeds {}%. Merged to single block range {} " + "({:.2f}%) wasting {:.2f}% ({})", + NiceBytes(OutTotalWantedChunksSize), + RangeWantedPercent, + BlockRanges.size(), + BlockDescription.BlockHash, + NiceBytes(TotalBlockSize), + FullBlockRangePercentLimit, + NiceBytes(MergedRange.RangeLength), + RangeRequestedPercent, + WastedPercent, + NiceBytes(MergedRange.RangeLength - OutTotalWantedChunksSize)); + return MakeOptionalBlockRangeVector(TotalBlockSize, MergedRange); + } + + std::vector<BlockRangeDescriptor> CollapsedBlockRanges = CollapseBlockRanges(16u * 1024u, BlockRanges); + while (GetBlockRangeLimitForRange(ForceMergeLimits, TotalBlockSize, CollapsedBlockRanges)) + { + CollapsedBlockRanges = CollapseBlockRanges(CalculateNextGap(CollapsedBlockRanges), CollapsedBlockRanges); + } + + const std::uint64_t WantedCollapsedSize = + std::accumulate(CollapsedBlockRanges.begin(), + CollapsedBlockRanges.end(), + uint64_t(0), + [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; }); + + const double CollapsedRangeRequestedPercent = (WantedCollapsedSize * 100.0) / TotalBlockSize; + + { + const double WastedPercent = ((WantedCollapsedSize - OutTotalWantedChunksSize) * 100.0) / WantedCollapsedSize; + + ZEN_CONSOLE_VERBOSE( + "Range request of {} ({:.2f}%) using {} ranges from block {} ({}) collapsed to {} {:.2f}% using {} ranges wasting {:.2f}% " + "({})", + NiceBytes(OutTotalWantedChunksSize), + RangeWantedPercent, + BlockRanges.size(), + BlockDescription.BlockHash, + NiceBytes(TotalBlockSize), + NiceBytes(WantedCollapsedSize), + CollapsedRangeRequestedPercent, + CollapsedBlockRanges.size(), + WastedPercent, + NiceBytes(WantedCollapsedSize - OutTotalWantedChunksSize)); + return CollapsedBlockRanges; + } + }; + class BufferedOpenFile { public: @@ -5758,17 +6027,42 @@ namespace { return Result; } + enum EPartialBlockRequestMode + { + Off, + ZenCacheOnly, + Mixed, + All, + Invalid + }; + static EPartialBlockRequestMode PartialBlockRequestModeFromString(const std::string_view ModeString) + { + switch (HashStringAsLowerDjb2(ModeString)) + { + case HashStringDjb2("false"): + return EPartialBlockRequestMode::Off; + case HashStringDjb2("zencacheonly"): + return EPartialBlockRequestMode::ZenCacheOnly; + case HashStringDjb2("mixed"): + return EPartialBlockRequestMode::Mixed; + case HashStringDjb2("true"): + return EPartialBlockRequestMode::All; + default: + return EPartialBlockRequestMode::Invalid; + } + } + struct UpdateOptions { - std::filesystem::path SystemRootDir; - std::filesystem::path ZenFolderPath; - std::uint64_t LargeAttachmentSize = DefaultPreferredMultipartChunkSize * 4u; - std::uint64_t PreferredMultipartChunkSize = DefaultPreferredMultipartChunkSize; - bool AllowPartialBlockRequests = true; - bool WipeTargetFolder = false; - bool PrimeCacheOnly = false; - bool EnableOtherDownloadsScavenging = true; - bool EnableTargetFolderScavenging = true; + std::filesystem::path SystemRootDir; + std::filesystem::path ZenFolderPath; + std::uint64_t LargeAttachmentSize = DefaultPreferredMultipartChunkSize * 4u; + std::uint64_t PreferredMultipartChunkSize = DefaultPreferredMultipartChunkSize; + EPartialBlockRequestMode PartialBlockRequestMode = EPartialBlockRequestMode::Mixed; + bool WipeTargetFolder = false; + bool PrimeCacheOnly = false; + bool EnableOtherDownloadsScavenging = true; + bool EnableTargetFolderScavenging = true; }; void UpdateFolder(StorageInstance& Storage, @@ -5788,7 +6082,8 @@ namespace { { ZEN_TRACE_CPU("UpdateFolder"); - ZEN_ASSERT((!Options.PrimeCacheOnly) || (Options.PrimeCacheOnly && (!Options.AllowPartialBlockRequests))); + ZEN_ASSERT((!Options.PrimeCacheOnly) || + (Options.PrimeCacheOnly && (Options.PartialBlockRequestMode == EPartialBlockRequestMode::Off))); Stopwatch IndexTimer; @@ -6558,33 +6853,20 @@ namespace { return NeededBlockChunkIndexes; }; - std::vector<uint32_t> CachedChunkBlockIndexes; - - struct BlockRangeDescriptor - { - uint32_t BlockIndex = (uint32_t)-1; - uint64_t RangeStart = 0; - uint64_t RangeLength = 0; - uint32_t ChunkBlockIndexStart = 0; - uint32_t ChunkBlockIndexCount = 0; - }; - std::vector<BlockRangeDescriptor> BlockRangeWorks; - - std::vector<uint32_t> FullBlockWorks; + std::vector<uint32_t> CachedChunkBlockIndexes; + std::vector<uint32_t> FetchBlockIndexes; + std::vector<std::vector<uint32_t>> AllBlockChunkIndexNeeded; for (uint32_t BlockIndex = 0; BlockIndex < BlockCount; BlockIndex++) { const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex]; - const std::vector<uint32_t> BlockChunkIndexNeeded = GetNeededChunkBlockIndexes(BlockDescription); + std::vector<uint32_t> BlockChunkIndexNeeded = GetNeededChunkBlockIndexes(BlockDescription); if (!BlockChunkIndexNeeded.empty()) { if (Options.PrimeCacheOnly) { - TotalRequestCount++; - TotalPartWriteCount++; - - FullBlockWorks.push_back(BlockIndex); + FetchBlockIndexes.push_back(BlockIndex); } else { @@ -6603,188 +6885,13 @@ namespace { UsingCachedBlock = true; } } - if (!UsingCachedBlock) { - bool WantsToDoPartialBlockDownload = BlockChunkIndexNeeded.size() < BlockDescription.ChunkRawHashes.size(); - bool CanDoPartialBlockDownload = - (BlockDescription.HeaderSize > 0) && - (BlockDescription.ChunkCompressedLengths.size() == BlockDescription.ChunkRawHashes.size()); - if (Options.AllowPartialBlockRequests && WantsToDoPartialBlockDownload && CanDoPartialBlockDownload) - { - std::vector<BlockRangeDescriptor> BlockRanges; - - ZEN_TRACE_CPU("UpdateFolder_HandleBlocks_PartialAnalysis"); - - uint32_t NeedBlockChunkIndexOffset = 0; - uint32_t ChunkBlockIndex = 0; - uint32_t CurrentOffset = - gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize); - - const uint64_t TotalBlockSize = std::accumulate(BlockDescription.ChunkCompressedLengths.begin(), - BlockDescription.ChunkCompressedLengths.end(), - std::uint64_t(CurrentOffset)); - BlockRangeDescriptor NextRange{.BlockIndex = BlockIndex}; - while (NeedBlockChunkIndexOffset < BlockChunkIndexNeeded.size() && - ChunkBlockIndex < BlockDescription.ChunkRawHashes.size()) - { - const uint32_t ChunkCompressedLength = BlockDescription.ChunkCompressedLengths[ChunkBlockIndex]; - if (ChunkBlockIndex < BlockChunkIndexNeeded[NeedBlockChunkIndexOffset]) - { - if (NextRange.RangeLength > 0) - { - BlockRanges.push_back(NextRange); - NextRange = {.BlockIndex = BlockIndex}; - } - ChunkBlockIndex++; - CurrentOffset += ChunkCompressedLength; - } - else if (ChunkBlockIndex == BlockChunkIndexNeeded[NeedBlockChunkIndexOffset]) - { - if (NextRange.RangeLength == 0) - { - NextRange.RangeStart = CurrentOffset; - NextRange.ChunkBlockIndexStart = ChunkBlockIndex; - } - NextRange.RangeLength += ChunkCompressedLength; - NextRange.ChunkBlockIndexCount++; - ChunkBlockIndex++; - CurrentOffset += ChunkCompressedLength; - NeedBlockChunkIndexOffset++; - } - else - { - ZEN_ASSERT(false); - } - } - if (NextRange.RangeLength > 0) - { - BlockRanges.push_back(NextRange); - } - - ZEN_ASSERT(!BlockRanges.empty()); - - std::vector<BlockRangeDescriptor> CollapsedBlockRanges; - auto It = BlockRanges.begin(); - CollapsedBlockRanges.push_back(*It++); - while (It != BlockRanges.end()) - { - BlockRangeDescriptor& LastRange = CollapsedBlockRanges.back(); - uint64_t Slack = It->RangeStart - (LastRange.RangeStart + LastRange.RangeLength); - uint64_t BothRangeSize = It->RangeLength + LastRange.RangeLength; - if (Slack <= Max(BothRangeSize / 8, 64u * 1024u)) // Made up heuristic - we'll see how it pans out - { - LastRange.ChunkBlockIndexCount = - (It->ChunkBlockIndexStart + It->ChunkBlockIndexCount) - LastRange.ChunkBlockIndexStart; - LastRange.RangeLength = (It->RangeStart + It->RangeLength) - LastRange.RangeStart; - } - else - { - CollapsedBlockRanges.push_back(*It); - } - ++It; - } - - const std::uint64_t WantedSize = std::accumulate( - CollapsedBlockRanges.begin(), - CollapsedBlockRanges.end(), - uint64_t(0), - [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; }); - ZEN_ASSERT(WantedSize <= TotalBlockSize); - if (WantedSize > ((TotalBlockSize * 95) / 100)) - { - ZEN_CONSOLE_VERBOSE("Using more than 95% ({}) of block {} ({}), requesting full block", - NiceBytes(WantedSize), - BlockDescription.BlockHash, - NiceBytes(TotalBlockSize)); - TotalRequestCount++; - TotalPartWriteCount++; - - FullBlockWorks.push_back(BlockIndex); - } - else if ((WantedSize > ((TotalBlockSize * 9) / 10)) && CollapsedBlockRanges.size() > 1) - { - ZEN_CONSOLE_VERBOSE( - "Using more than 90% ({}) of block {} ({}) using {} requests, requesting full block", - NiceBytes(WantedSize), - BlockDescription.BlockHash, - NiceBytes(TotalBlockSize), - CollapsedBlockRanges.size()); - TotalRequestCount++; - TotalPartWriteCount++; - - FullBlockWorks.push_back(BlockIndex); - } - else if ((WantedSize > ((TotalBlockSize * 8) / 10)) && (CollapsedBlockRanges.size() > 16)) - { - ZEN_CONSOLE_VERBOSE( - "Using more than 80% ({}) of block {} ({}) using {} requests, requesting full block", - NiceBytes(WantedSize), - BlockDescription.BlockHash, - NiceBytes(TotalBlockSize), - CollapsedBlockRanges.size()); - TotalRequestCount++; - TotalPartWriteCount++; - - FullBlockWorks.push_back(BlockIndex); - } - else if ((WantedSize > ((TotalBlockSize * 7) / 10)) && (CollapsedBlockRanges.size() > 48)) - { - ZEN_CONSOLE_VERBOSE( - "Using more than 70% ({}) of block {} ({}) using {} requests, requesting full block", - NiceBytes(WantedSize), - BlockDescription.BlockHash, - NiceBytes(TotalBlockSize), - CollapsedBlockRanges.size()); - TotalRequestCount++; - TotalPartWriteCount++; - - FullBlockWorks.push_back(BlockIndex); - } - else if ((WantedSize > ((TotalBlockSize * 6) / 10)) && (CollapsedBlockRanges.size() > 64)) - { - ZEN_CONSOLE_VERBOSE( - "Using more than 60% ({}) of block {} ({}) using {} requests, requesting full block", - NiceBytes(WantedSize), - BlockDescription.BlockHash, - NiceBytes(TotalBlockSize), - CollapsedBlockRanges.size()); - TotalRequestCount++; - TotalPartWriteCount++; - - FullBlockWorks.push_back(BlockIndex); - } - else - { - if (WantedSize > ((TotalBlockSize * 5) / 10)) - { - ZEN_CONSOLE_VERBOSE("Using {}% ({}) of block {} ({}) using {} requests, requesting partial block", - (WantedSize * 100) / TotalBlockSize, - NiceBytes(WantedSize), - BlockDescription.BlockHash, - NiceBytes(TotalBlockSize), - CollapsedBlockRanges.size()); - } - TotalRequestCount += CollapsedBlockRanges.size(); - TotalPartWriteCount += CollapsedBlockRanges.size(); - - BlockRangeWorks.insert(BlockRangeWorks.end(), CollapsedBlockRanges.begin(), CollapsedBlockRanges.end()); - } - } - else - { - TotalRequestCount++; - TotalPartWriteCount++; - - FullBlockWorks.push_back(BlockIndex); - } + FetchBlockIndexes.push_back(BlockIndex); } } } - else - { - ZEN_CONSOLE_VERBOSE("Skipping block {} due to cache reuse", BlockDescriptions[BlockIndex].BlockHash); - } + AllBlockChunkIndexNeeded.emplace_back(std::move(BlockChunkIndexNeeded)); } struct BlobsExistsResult @@ -6802,18 +6909,12 @@ namespace { tsl::robin_set<IoHash> BlobHashesSet; - BlobHashesSet.reserve(LooseChunkHashWorks.size() + FullBlockWorks.size()); + BlobHashesSet.reserve(LooseChunkHashWorks.size() + FetchBlockIndexes.size()); for (LooseChunkHashWorkData& LooseChunkHashWork : LooseChunkHashWorks) { BlobHashesSet.insert(RemoteContent.ChunkedContent.ChunkHashes[LooseChunkHashWork.RemoteChunkIndex]); } - for (const BlockRangeDescriptor& BlockRange : BlockRangeWorks) - { - const uint32_t BlockIndex = BlockRange.BlockIndex; - const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex]; - BlobHashesSet.insert(BlockDescription.BlockHash); - } - for (uint32_t BlockIndex : FullBlockWorks) + for (uint32_t BlockIndex : FetchBlockIndexes) { const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex]; BlobHashesSet.insert(BlockDescription.BlockHash); @@ -6847,6 +6948,162 @@ namespace { } } + std::vector<BlockRangeDescriptor> BlockRangeWorks; + std::vector<uint32_t> FullBlockWorks; + { + Stopwatch Timer; + + std::vector<uint32_t> PartialBlockIndexes; + + for (uint32_t BlockIndex : FetchBlockIndexes) + { + const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex]; + + const std::vector<uint32_t> BlockChunkIndexNeeded = std::move(AllBlockChunkIndexNeeded[BlockIndex]); + if (!BlockChunkIndexNeeded.empty()) + { + bool WantsToDoPartialBlockDownload = BlockChunkIndexNeeded.size() < BlockDescription.ChunkRawHashes.size(); + bool CanDoPartialBlockDownload = + (BlockDescription.HeaderSize > 0) && + (BlockDescription.ChunkCompressedLengths.size() == BlockDescription.ChunkRawHashes.size()); + + bool AllowedToDoPartialRequest = false; + bool BlockExistInCache = ExistsResult.ExistingBlobs.contains(BlockDescription.BlockHash); + switch (Options.PartialBlockRequestMode) + { + case EPartialBlockRequestMode::Off: + break; + case EPartialBlockRequestMode::ZenCacheOnly: + AllowedToDoPartialRequest = BlockExistInCache; + break; + case EPartialBlockRequestMode::Mixed: + case EPartialBlockRequestMode::All: + AllowedToDoPartialRequest = true; + break; + default: + ZEN_ASSERT(false); + break; + } + + const uint32_t ChunkStartOffsetInBlock = + gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize); + + const uint64_t TotalBlockSize = std::accumulate(BlockDescription.ChunkCompressedLengths.begin(), + BlockDescription.ChunkCompressedLengths.end(), + std::uint64_t(ChunkStartOffsetInBlock)); + + if (AllowedToDoPartialRequest && WantsToDoPartialBlockDownload && CanDoPartialBlockDownload) + { + ZEN_TRACE_CPU("UpdateFolder_HandleBlocks_PartialAnalysis"); + + bool LimitToSingleRange = + BlockExistInCache ? false : Options.PartialBlockRequestMode == EPartialBlockRequestMode::Mixed; + uint64_t TotalWantedChunksSize = 0; + std::optional<std::vector<BlockRangeDescriptor>> MaybeBlockRanges = + CalculateBlockRanges(BlockIndex, + BlockDescription, + BlockChunkIndexNeeded, + LimitToSingleRange, + ChunkStartOffsetInBlock, + TotalBlockSize, + TotalWantedChunksSize); + ZEN_ASSERT(TotalWantedChunksSize <= TotalBlockSize); + + if (MaybeBlockRanges.has_value()) + { + const std::vector<BlockRangeDescriptor>& BlockRanges = MaybeBlockRanges.value(); + ZEN_ASSERT(!BlockRanges.empty()); + BlockRangeWorks.insert(BlockRangeWorks.end(), BlockRanges.begin(), BlockRanges.end()); + TotalRequestCount += BlockRanges.size(); + TotalPartWriteCount += BlockRanges.size(); + + uint64_t RequestedSize = std::accumulate( + BlockRanges.begin(), + BlockRanges.end(), + uint64_t(0), + [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; }); + PartialBlockIndexes.push_back(BlockIndex); + + if (RequestedSize > TotalWantedChunksSize) + { + ZEN_CONSOLE_VERBOSE("Requesting {} chunks ({}) from block {} ({}) using {} requests (extra bytes {})", + BlockChunkIndexNeeded.size(), + NiceBytes(RequestedSize), + BlockDescription.BlockHash, + NiceBytes(TotalBlockSize), + BlockRanges.size(), + NiceBytes(RequestedSize - TotalWantedChunksSize)); + } + } + else + { + FullBlockWorks.push_back(BlockIndex); + TotalRequestCount++; + TotalPartWriteCount++; + } + } + else + { + FullBlockWorks.push_back(BlockIndex); + TotalRequestCount++; + TotalPartWriteCount++; + } + } + } + + if (!PartialBlockIndexes.empty()) + { + uint64_t TotalFullBlockRequestBytes = 0; + for (uint32_t BlockIndex : FullBlockWorks) + { + const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex]; + uint32_t CurrentOffset = + gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize); + + TotalFullBlockRequestBytes += std::accumulate(BlockDescription.ChunkCompressedLengths.begin(), + BlockDescription.ChunkCompressedLengths.end(), + std::uint64_t(CurrentOffset)); + } + + uint64_t TotalPartialBlockBytes = 0; + for (uint32_t BlockIndex : PartialBlockIndexes) + { + const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex]; + uint32_t CurrentOffset = + gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize); + + TotalPartialBlockBytes += std::accumulate(BlockDescription.ChunkCompressedLengths.begin(), + BlockDescription.ChunkCompressedLengths.end(), + std::uint64_t(CurrentOffset)); + } + + uint64_t NonPartialTotalBlockBytes = TotalFullBlockRequestBytes + TotalPartialBlockBytes; + + const uint64_t TotalPartialBlockRequestBytes = + std::accumulate(BlockRangeWorks.begin(), + BlockRangeWorks.end(), + uint64_t(0), + [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; }); + uint64_t TotalExtraPartialBlocksRequests = BlockRangeWorks.size() - PartialBlockIndexes.size(); + + uint64_t TotalSavedBlocksSize = TotalPartialBlockBytes - TotalPartialBlockRequestBytes; + double SavedSizePercent = (TotalSavedBlocksSize * 100.0) / NonPartialTotalBlockBytes; + + if (!IsQuiet) + { + ZEN_CONSOLE( + "Analisys of partial block requests saves download of {} out of {} ({:.1f}%) using {} extra " + "requests. Completed in {}", + NiceBytes(TotalSavedBlocksSize), + NiceBytes(NonPartialTotalBlockBytes), + SavedSizePercent, + TotalExtraPartialBlocksRequests, + NiceTimeSpanMs(ExistsResult.ElapsedTimeMs)); + } + // exit(0); + } + } + BufferedWriteFileCache WriteCache; for (uint32_t ScavengeOpIndex = 0; ScavengeOpIndex < ScavengeCopyOperations.size(); ScavengeOpIndex++) @@ -7204,7 +7461,7 @@ namespace { Storage.BuildCacheStorage->PutBuildBlob( BuildId, ChunkHash, - BuildBlob.GetContentType(), + ZenContentType::kCompressedBinary, CompositeBuffer(SharedBuffer(BuildBlob))); } if (!BuildBlob) @@ -7792,7 +8049,7 @@ namespace { { Storage.BuildCacheStorage->PutBuildBlob(BuildId, BlockDescription.BlockHash, - BlockBuffer.GetContentType(), + ZenContentType::kCompressedBinary, CompositeBuffer(SharedBuffer(BlockBuffer))); } } @@ -9396,17 +9653,17 @@ namespace { struct DownloadOptions { - std::filesystem::path SystemRootDir; - std::filesystem::path ZenFolderPath; - bool AllowMultiparts = true; - bool AllowPartialBlockRequests = true; - bool CleanTargetFolder = false; - bool PostDownloadVerify = false; - bool PrimeCacheOnly = false; - bool EnableOtherDownloadsScavenging = true; - bool EnableTargetFolderScavenging = true; - std::string IncludeWildcard; - std::string ExcludeWildcard; + std::filesystem::path SystemRootDir; + std::filesystem::path ZenFolderPath; + bool AllowMultiparts = true; + EPartialBlockRequestMode PartialBlockRequestMode = EPartialBlockRequestMode::Mixed; + bool CleanTargetFolder = false; + bool PostDownloadVerify = false; + bool PrimeCacheOnly = false; + bool EnableOtherDownloadsScavenging = true; + bool EnableTargetFolderScavenging = true; + std::string IncludeWildcard; + std::string ExcludeWildcard; }; void DownloadFolder(StorageInstance& Storage, @@ -9433,7 +9690,8 @@ namespace { auto EndProgress = MakeGuard([&]() { ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::StepCount, TaskSteps::StepCount); }); - ZEN_ASSERT((!Options.PrimeCacheOnly) || (Options.PrimeCacheOnly && (!Options.AllowPartialBlockRequests))); + ZEN_ASSERT((!Options.PrimeCacheOnly) || + (Options.PrimeCacheOnly && (Options.PartialBlockRequestMode == EPartialBlockRequestMode::Off))); Stopwatch DownloadTimer; @@ -9601,7 +9859,7 @@ namespace { .ZenFolderPath = Options.ZenFolderPath, .LargeAttachmentSize = LargeAttachmentSize, .PreferredMultipartChunkSize = PreferredMultipartChunkSize, - .AllowPartialBlockRequests = Options.AllowPartialBlockRequests, + .PartialBlockRequestMode = Options.PartialBlockRequestMode, .WipeTargetFolder = Options.CleanTargetFolder, .PrimeCacheOnly = Options.PrimeCacheOnly, .EnableOtherDownloadsScavenging = Options.EnableOtherDownloadsScavenging, @@ -10154,6 +10412,31 @@ BuildsCommand::BuildsCommand() "<excludewildcard>"); }; + auto AddMultipartOptions = [this](cxxopts::Options& Ops) { + Ops.add_option("", + "", + "allow-multipart", + "Allow large attachments to be transfered using multipart protocol. Defaults to true.", + cxxopts::value(m_AllowMultiparts), + "<allowmultipart>"); + }; + + auto AddPartialBlockRequestOptions = [this](cxxopts::Options& Ops) { + Ops.add_option("", + "", + "allow-partial-block-requests", + "Allow request for partial chunk blocks.\n" + " false = only full block requests allowed\n" + " mixed = multiple partial block ranges requests per block allowed to zen cache, single partial block range " + "request per block to host\n" + " zencacheonly = multiple partial block ranges requests per block allowed to zen cache, only full block requests " + "allowed to host\n" + " true = multiple partial block ranges requests per block allowed to zen cache and host\n" + "Defaults to 'mixed'.", + cxxopts::value(m_AllowPartialBlockRequests), + "<allowpartialblockrequests>"); + }; + m_Options.add_option("", "v", "verb", @@ -10256,12 +10539,9 @@ BuildsCommand::BuildsCommand() "Percent of an existing block that must be relevant for it to be resused. Defaults to 85.", cxxopts::value(m_BlockReuseMinPercentLimit), "<minreuse>"); - m_UploadOptions.add_option("", - "", - "allow-multipart", - "Allow large attachments to be transfered using multipart protocol. Defaults to true.", - cxxopts::value(m_AllowMultiparts), - "<allowmultipart>"); + + AddMultipartOptions(m_UploadOptions); + m_UploadOptions.add_option("", "", "manifest-path", @@ -10326,18 +10606,9 @@ BuildsCommand::BuildsCommand() "Force download of all content by ignoring any existing local content", cxxopts::value(m_Force), "<force>"); - m_DownloadOptions.add_option("", - "", - "allow-multipart", - "Allow large attachments to be transfered using multipart protocol. Defaults to true.", - cxxopts::value(m_AllowMultiparts), - "<allowmultipart>"); - m_DownloadOptions.add_option("", - "", - "allow-partial-block-requests", - "Allow request for partial chunk blocks. Defaults to true.", - cxxopts::value(m_AllowPartialBlockRequests), - "<allowpartialblockrequests>"); + AddMultipartOptions(m_DownloadOptions); + + AddPartialBlockRequestOptions(m_DownloadOptions); m_DownloadOptions .add_option("", "", "verify", "Enable post download verify of all tracked files", cxxopts::value(m_PostDownloadVerify), "<verify>"); @@ -10404,18 +10675,8 @@ BuildsCommand::BuildsCommand() AddWorkerOptions(m_TestOptions); m_TestOptions.add_options()("h,help", "Print help"); m_TestOptions.add_option("", "l", "local-path", "Root file system folder used as base", cxxopts::value(m_Path), "<local-path>"); - m_TestOptions.add_option("", - "", - "allow-multipart", - "Allow large attachments to be transfered using multipart protocol. Defaults to true.", - cxxopts::value(m_AllowMultiparts), - "<allowmultipart>"); - m_TestOptions.add_option("", - "", - "allow-partial-block-requests", - "Allow request for partial chunk blocks. Defaults to true.", - cxxopts::value(m_AllowPartialBlockRequests), - "<allowpartialblockrequests>"); + AddMultipartOptions(m_TestOptions); + AddPartialBlockRequestOptions(m_TestOptions); m_TestOptions.add_option("", "", "enable-scavenge", @@ -10929,8 +11190,13 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) else { ZEN_CONSOLE_WARN("Unable to reach cache host {}. Reason: {}", m_ZenCacheHost, TestResult.second); + m_ZenCacheHost = ""; } } + else if (!m_OverrideHost.empty()) + { + CloudHost = m_OverrideHost; + } } else { @@ -10967,33 +11233,40 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } if (!m_ZenCacheHost.empty()) { - Result.CacheHttp = std::make_unique<HttpClient>(m_ZenCacheHost, - HttpClientSettings{.LogCategory = "httpcacheclient", - .ConnectTimeout = std::chrono::milliseconds{3000}, - .Timeout = std::chrono::milliseconds{30000}, - .AssumeHttp2 = CacheAssumeHttp2, - .AllowResume = true, - .RetryCount = 0}); - Result.BuildCacheStorage = CreateZenBuildStorageCache(*Result.CacheHttp, - StorageCacheStats, - m_Namespace, - m_Bucket, - TempPath / "zencache", - m_PrimeCacheOnly); - CacheDescription = fmt::format("Zen {}{}. SessionId: '{}'", - BuildCacheName.empty() ? "" : fmt::format("{}, ", BuildCacheName), - m_ZenCacheHost, - Result.CacheHttp->GetSessionId()); - - if (!m_Namespace.empty()) - { - CacheDescription += fmt::format(". Namespace '{}'", m_Namespace); + if (auto TestResult = TestCacheEndpoint(m_ZenCacheHost, false); TestResult.first) + { + Result.CacheHttp = std::make_unique<HttpClient>(m_ZenCacheHost, + HttpClientSettings{.LogCategory = "httpcacheclient", + .ConnectTimeout = std::chrono::milliseconds{3000}, + .Timeout = std::chrono::milliseconds{30000}, + .AssumeHttp2 = CacheAssumeHttp2, + .AllowResume = true, + .RetryCount = 0}); + Result.BuildCacheStorage = CreateZenBuildStorageCache(*Result.CacheHttp, + StorageCacheStats, + m_Namespace, + m_Bucket, + TempPath / "zencache", + m_PrimeCacheOnly); + CacheDescription = fmt::format("Zen {}{}. SessionId: '{}'", + BuildCacheName.empty() ? "" : fmt::format("{}, ", BuildCacheName), + m_ZenCacheHost, + Result.CacheHttp->GetSessionId()); + + if (!m_Namespace.empty()) + { + CacheDescription += fmt::format(". Namespace '{}'", m_Namespace); + } + if (!m_Bucket.empty()) + { + CacheDescription += fmt::format(" Bucket '{}'", m_Bucket); + } + Result.CacheName = BuildCacheName.empty() ? m_ZenCacheHost : BuildCacheName; } - if (!m_Bucket.empty()) + else { - CacheDescription += fmt::format(" Bucket '{}'", m_Bucket); + ZEN_CONSOLE_WARN("Unable to reach cache host {}. Reason: {}", m_ZenCacheHost, TestResult.second); } - Result.CacheName = BuildCacheName.empty() ? m_ZenCacheHost : BuildCacheName; } if (!IsQuiet) { @@ -11433,6 +11706,20 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } } + auto ParseAllowPartialBlockRequests = [&]() -> EPartialBlockRequestMode { + if (m_PrimeCacheOnly) + { + return EPartialBlockRequestMode::Off; + } + EPartialBlockRequestMode Mode = PartialBlockRequestModeFromString(m_AllowPartialBlockRequests); + if (Mode == EPartialBlockRequestMode::Invalid) + { + throw OptionParseException(fmt::format("'--allow-partial-block-requests' ('{}') is invalid", m_AllowPartialBlockRequests), + SubOption->help()); + } + return Mode; + }; + if (SubOption == &m_DownloadOptions) { if (!IsQuiet) @@ -11477,7 +11764,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) ZEN_CONSOLE_WARN("Ignoring '--force' option when '--cache-prime-only' is enabled"); } - if (m_AllowPartialBlockRequests && m_PrimeCacheOnly) + if (m_AllowPartialBlockRequests != "false" && m_PrimeCacheOnly) { ZEN_CONSOLE_WARN("Ignoring '--allow-partial-block-requests' option when '--cache-prime-only' is enabled"); } @@ -11485,6 +11772,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) std::vector<Oid> BuildPartIds = ParseBuildPartIds(); std::vector<std::string> BuildPartNames = ParseBuildPartNames(); + EPartialBlockRequestMode PartialBlockRequestMode = ParseAllowPartialBlockRequests(); + DownloadFolder(Storage, BuildId, BuildPartIds, @@ -11493,7 +11782,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = m_ZenFolderPath, .AllowMultiparts = m_AllowMultiparts, - .AllowPartialBlockRequests = m_AllowPartialBlockRequests && !m_PrimeCacheOnly, + .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = m_Clean, .PostDownloadVerify = m_PostDownloadVerify, .PrimeCacheOnly = m_PrimeCacheOnly, @@ -11680,6 +11969,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } MakeSafeAbsolutePathÍnPlace(m_ZenFolderPath); + EPartialBlockRequestMode PartialBlockRequestMode = ParseAllowPartialBlockRequests(); + BuildStorage::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; @@ -11705,7 +11996,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = m_ZenFolderPath, .AllowMultiparts = m_AllowMultiparts, - .AllowPartialBlockRequests = m_AllowPartialBlockRequests, + .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = BuildIdString == m_BuildIds.front(), .PostDownloadVerify = true, .PrimeCacheOnly = false, @@ -11791,6 +12082,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } }); + EPartialBlockRequestMode PartialBlockRequestMode = ParseAllowPartialBlockRequests(); + BuildStorage::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; @@ -11877,7 +12170,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_AllowMultiparts, - .AllowPartialBlockRequests = m_AllowPartialBlockRequests, + .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = true, .PostDownloadVerify = true, .PrimeCacheOnly = false, @@ -11898,7 +12191,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_AllowMultiparts, - .AllowPartialBlockRequests = m_AllowPartialBlockRequests, + .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, @@ -12011,7 +12304,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_AllowMultiparts, - .AllowPartialBlockRequests = m_AllowPartialBlockRequests, + .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, @@ -12063,7 +12356,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_AllowMultiparts, - .AllowPartialBlockRequests = m_AllowPartialBlockRequests, + .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, @@ -12083,7 +12376,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_AllowMultiparts, - .AllowPartialBlockRequests = m_AllowPartialBlockRequests, + .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, @@ -12103,7 +12396,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_AllowMultiparts, - .AllowPartialBlockRequests = m_AllowPartialBlockRequests, + .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, @@ -12123,7 +12416,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath2 / ZenFolderName, .AllowMultiparts = m_AllowMultiparts, - .AllowPartialBlockRequests = m_AllowPartialBlockRequests, + .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, diff --git a/src/zen/cmds/builds_cmd.h b/src/zen/cmds/builds_cmd.h index 26f804fa5..be0422210 100644 --- a/src/zen/cmds/builds_cmd.h +++ b/src/zen/cmds/builds_cmd.h @@ -64,7 +64,7 @@ private: bool m_Force = false; uint8_t m_BlockReuseMinPercentLimit = 85; bool m_AllowMultiparts = true; - bool m_AllowPartialBlockRequests = true; + std::string m_AllowPartialBlockRequests = "mixed"; std::string m_ManifestPath; // Not a std::filesystem::path since it can be relative to m_Path // Direct access token (may expire) diff --git a/src/zen/zen.cpp b/src/zen/zen.cpp index 200c8e4b4..2721433b0 100644 --- a/src/zen/zen.cpp +++ b/src/zen/zen.cpp @@ -72,6 +72,88 @@ ZEN_THIRD_PARTY_INCLUDES_END namespace zen { +#if ZEN_PLATFORM_WINDOWS +static HANDLE +GetConsoleHandle() +{ + static HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + return hStdOut; +} +#endif + +static bool +CheckStdoutTty() +{ +#if ZEN_PLATFORM_WINDOWS + HANDLE hStdOut = GetConsoleHandle(); + DWORD dwMode = 0; + static bool IsConsole = ::GetConsoleMode(hStdOut, &dwMode); + return IsConsole; +#else + return isatty(fileno(stdout)); +#endif +} + +static bool +IsStdoutTty() +{ + static bool StdoutIsTty = CheckStdoutTty(); + return StdoutIsTty; +} + +static void +OutputToConsoleRaw(const char* String, size_t Length) +{ +#if ZEN_PLATFORM_WINDOWS + HANDLE hStdOut = GetConsoleHandle(); +#endif + +#if ZEN_PLATFORM_WINDOWS + if (IsStdoutTty()) + { + WriteConsoleA(hStdOut, String, (DWORD)Length, 0, 0); + } + else + { + ::WriteFile(hStdOut, (LPCVOID)String, (DWORD)Length, 0, 0); + } +#else + fwrite(String, 1, Length, stdout); +#endif +} + +static void +OutputToConsoleRaw(const std::string& String) +{ + OutputToConsoleRaw(String.c_str(), String.length()); +} + +static void +OutputToConsoleRaw(const StringBuilderBase& SB) +{ + OutputToConsoleRaw(SB.c_str(), SB.Size()); +} + +static uint32_t +GetConsoleColumns(uint32_t Default) +{ +#if ZEN_PLATFORM_WINDOWS + HANDLE hStdOut = GetConsoleHandle(); + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (GetConsoleScreenBufferInfo(hStdOut, &csbi) == TRUE) + { + return (uint32_t)(csbi.srWindow.Right - csbi.srWindow.Left + 1); + } +#else + struct winsize w; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) + { + return (uint32_t)w.ws_col; + } +#endif + return Default; +} + enum class ReturnCode : std::int8_t { kSuccess = 0, @@ -122,6 +204,8 @@ ZenCmdBase::ParseOptions(int argc, char** argv) bool ZenCmdBase::ParseOptions(cxxopts::Options& CmdOptions, int argc, char** argv) { + CmdOptions.set_width(GetConsoleColumns(80)); + cxxopts::ParseResult Result; try @@ -308,88 +392,6 @@ ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec) return ResolveTargetHostSpec(InHostSpec, /* out */ Dummy); } -#if ZEN_PLATFORM_WINDOWS -static HANDLE -GetConsoleHandle() -{ - static HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); - return hStdOut; -} -#endif - -static bool -CheckStdoutTty() -{ -#if ZEN_PLATFORM_WINDOWS - HANDLE hStdOut = GetConsoleHandle(); - DWORD dwMode = 0; - static bool IsConsole = ::GetConsoleMode(hStdOut, &dwMode); - return IsConsole; -#else - return isatty(fileno(stdout)); -#endif -} - -static bool -IsStdoutTty() -{ - static bool StdoutIsTty = CheckStdoutTty(); - return StdoutIsTty; -} - -static void -OutputToConsoleRaw(const char* String, size_t Length) -{ -#if ZEN_PLATFORM_WINDOWS - HANDLE hStdOut = GetConsoleHandle(); -#endif - -#if ZEN_PLATFORM_WINDOWS - if (IsStdoutTty()) - { - WriteConsoleA(hStdOut, String, (DWORD)Length, 0, 0); - } - else - { - ::WriteFile(hStdOut, (LPCVOID)String, (DWORD)Length, 0, 0); - } -#else - fwrite(String, 1, Length, stdout); -#endif -} - -static void -OutputToConsoleRaw(const std::string& String) -{ - OutputToConsoleRaw(String.c_str(), String.length()); -} - -static void -OutputToConsoleRaw(const StringBuilderBase& SB) -{ - OutputToConsoleRaw(SB.c_str(), SB.Size()); -} - -static uint32_t -GetConsoleColumns() -{ -#if ZEN_PLATFORM_WINDOWS - HANDLE hStdOut = GetConsoleHandle(); - CONSOLE_SCREEN_BUFFER_INFO csbi; - if (GetConsoleScreenBufferInfo(hStdOut, &csbi) == TRUE) - { - return (uint32_t)(csbi.srWindow.Right - csbi.srWindow.Left + 1); - } -#else - struct winsize w; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) - { - return (uint32_t)w.ws_col; - } -#endif - return 1024; -} - void ProgressBar::SetLogOperationName(Mode InMode, std::string_view Name) { @@ -504,7 +506,7 @@ ProgressBar::UpdateState(const State& NewState, bool DoLinebreak) uint64_t ETAMS = (NewState.Status == State::EStatus::Running) && (PercentDone > 5) ? (ETAElapsedMS * NewState.RemainingCount) / Completed : 0; - uint32_t ConsoleColumns = GetConsoleColumns(); + uint32_t ConsoleColumns = GetConsoleColumns(1024); const std::string PercentString = fmt::format("{:#3}%", PercentDone); |