// Copyright Epic Games, Inc. All Rights Reserved. #include "builds_cmd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../progressbar.h" #include #include #include ZEN_THIRD_PARTY_INCLUDES_START #include #include #include ZEN_THIRD_PARTY_INCLUDES_END #if ZEN_PLATFORM_WINDOWS # include #else # include # include # include # include #endif static const bool DoExtraContentVerify = false; namespace zen { using namespace std::literals; namespace builds_impl { static std::atomic AbortFlag = false; static std::atomic PauseFlag = false; static void SignalCallbackHandler(int SigNum) { if (SigNum == SIGINT) { PauseFlag = false; AbortFlag = true; } #if ZEN_PLATFORM_WINDOWS if (SigNum == SIGBREAK) { PauseFlag = false; AbortFlag = true; } #endif // ZEN_PLATFORM_WINDOWS } struct ZenStateSharedData { static constexpr uint64_t kMagicV1 = 0x3176646d636e657a; // zencmdv1 uint64_t Magic = 0; // Implies the size and layout of this struct - changes to the data requires change to Magic constant std::atomic Pid; uint8_t SessionId[12]; uint8_t Padding1[4]; std::atomic Abort; std::atomic Pause; uint8_t Padding2[2]; void Initialize() { Magic = kMagicV1; Pid.store(0); memset(SessionId, 0, sizeof(SessionId)); Padding1[0] = Padding1[1] = Padding1[2] = Padding1[3] = 0; Abort.store(0); Pause.store(0); Padding2[0] = Padding2[1] = 0; } }; struct MemMap { void* Handle = nullptr; void* Data = nullptr; size_t Size = 0; std::string Name; }; class ZenState { public: ZenState(const ZenState&) = delete; ZenState& operator=(const ZenState&) = delete; ZenState(); explicit ZenState(uint32_t Pid); ~ZenState(); const ZenStateSharedData& StateData() const { ZEN_ASSERT(m_Data); return *m_Data; } ZenStateSharedData& StateData() { ZEN_ASSERT(m_Data); return *m_Data; } private: static constexpr std::string_view MapBaseName = "UnrealEngineZenCmd_"sv; static constexpr size_t MapSize = sizeof(ZenStateSharedData); bool m_Created = false; std::unique_ptr m_MemMap; ZenStateSharedData* m_Data = nullptr; std::thread m_StateMonitor; Event m_ExitStateMonitorEvent; }; ZenState::ZenState(uint32_t Pid) { const std::string ZenStateMapName = fmt::format("{}{}", MapBaseName, Pid); if (!IsProcessRunning(Pid)) { throw std::runtime_error(fmt::format("The process {} is not running", Pid)); } std::unique_ptr MemMap = OpenSharedMemory(ZenStateMapName, MapSize, false); if (!MemMap) { throw std::runtime_error(fmt::format("The process {} is not a running zen process", Pid)); } ZenStateSharedData* data = (ZenStateSharedData*)MemMap->GetData(); if (uint64_t MemMagic = data->Magic; MemMagic != ZenStateSharedData::kMagicV1) { throw std::runtime_error(fmt::format("The mem map for process {} has an unsupported magic {:x}, expected {:x}", Pid, MemMagic, ZenStateSharedData::kMagicV1)); } if (uint32_t MemPid = data->Pid.load(); MemPid != Pid) { throw std::runtime_error( fmt::format("The mem map for process {} has an missmatching pid of {}, expected {}", Pid, MemPid, Pid)); } m_MemMap = std::move(MemMap); m_Data = data; } ZenState::ZenState() { int Pid = GetCurrentProcessId(); const std::string ZenStateMapName = fmt::format("{}{}", MapBaseName, Pid); std::unique_ptr MemMap = CreateSharedMemory(ZenStateMapName, MapSize, false); if (!MemMap) { throw std::runtime_error(fmt::format("The mem map for process {} could not be created", Pid)); } ZenStateSharedData* data = (ZenStateSharedData*)MemMap->GetData(); data->Initialize(); data->Pid.store(gsl::narrow(Pid)); const Oid SessionId = GetSessionId(); memcpy(data->SessionId, &SessionId, sizeof SessionId); m_MemMap = std::move(MemMap); m_Data = data; m_StateMonitor = std::thread([this]() { while (!m_ExitStateMonitorEvent.Wait(500)) { if (m_Data->Abort.load()) { AbortFlag.store(true); } PauseFlag.store(m_Data->Pause.load()); } }); } ZenState::~ZenState() { try { if (m_StateMonitor.joinable()) { m_ExitStateMonitorEvent.Set(); m_StateMonitor.join(); } m_MemMap.reset(); } catch (const std::exception& Ex) { ZEN_CONSOLE_ERROR("ZenState::~ZenState threw exception: {}", Ex.what()); } } const std::string ZenFolderName = ".zen"; std::filesystem::path ZenStateFilePath(const std::filesystem::path& ZenFolderPath) { return ZenFolderPath / "current_state.cbo"; } // std::filesystem::path ZenStateFileJsonPath(const std::filesystem::path& ZenFolderPath) { return ZenFolderPath / "current_state.json"; // } std::filesystem::path UploadTempDirectory(const std::filesystem::path& Path) { const std::u8string LocalPathString = Path.generic_u8string(); IoHash PathHash = IoHash::HashBuffer(LocalPathString.data(), LocalPathString.length()); return std::filesystem::temp_directory_path() / fmt::format("zen_{}", PathHash); } const std::string ZenExcludeManifestName = ".zen_exclude_manifest.txt"; const std::string UnsyncFolderName = ".unsync"; const std::string UGSFolderName = ".ugs"; const std::string LegacyZenTempFolderName = ".zen-tmp"; const std::vector DefaultExcludeFolders({UnsyncFolderName, ZenFolderName, UGSFolderName, LegacyZenTempFolderName}); const std::vector DefaultExcludeExtensions({}); const double DefaultLatency = 0; // .0010; const double DefaultDelayPerKBSec = 0; // 0.00005; const bool SingleThreaded = false; bool UseSparseFiles = false; static bool IsVerbose = false; static bool IsQuiet = false; static ProgressBar::Mode ProgressMode = ProgressBar::Mode::Pretty; #undef ZEN_CONSOLE_VERBOSE #define ZEN_CONSOLE_VERBOSE(fmtstr, ...) \ if (IsVerbose) \ { \ ZEN_CONSOLE_LOG(zen::logging::Info, fmtstr, ##__VA_ARGS__); \ } const std::string DefaultAccessTokenEnvVariableName( #if ZEN_PLATFORM_WINDOWS "UE-CloudDataCacheAccessToken"sv #endif #if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC "UE_CloudDataCacheAccessToken"sv #endif ); static uint64_t GetMaxMemoryBufferSize(size_t MaxBlockSize, bool BoostWorkerMemory) { return BoostWorkerMemory ? (MaxBlockSize + 16u * 1024u) : 1024u * 1024u; } class FilteredRate { public: FilteredRate() {} void Start() { if (StartTimeUS == (uint64_t)-1) { uint64_t Expected = (uint64_t)-1; if (StartTimeUS.compare_exchange_weak(Expected, Timer.GetElapsedTimeUs())) { LastTimeUS = StartTimeUS.load(); } } } void Stop() { if (EndTimeUS == (uint64_t)-1) { uint64_t Expected = (uint64_t)-1; EndTimeUS.compare_exchange_weak(Expected, Timer.GetElapsedTimeUs()); } } void Update(uint64_t Count) { if (LastTimeUS == (uint64_t)-1) { return; } uint64_t TimeUS = Timer.GetElapsedTimeUs(); uint64_t TimeDeltaUS = TimeUS - LastTimeUS; if (TimeDeltaUS >= 2000000) { uint64_t Delta = Count - LastCount; uint64_t PerSecond = (Delta * 1000000) / TimeDeltaUS; LastPerSecond = PerSecond; LastCount = Count; FilteredPerSecond = (PerSecond + (LastPerSecond * 7)) / 8; LastTimeUS = TimeUS; } } uint64_t GetCurrent() const // If Stopped - return total count / total time { if (LastTimeUS == (uint64_t)-1) { return 0; } return FilteredPerSecond; } uint64_t GetElapsedTimeUS() const { if (StartTimeUS == (uint64_t)-1) { return 0; } if (EndTimeUS == (uint64_t)-1) { return 0; } uint64_t TimeDeltaUS = EndTimeUS - StartTimeUS; return TimeDeltaUS; } bool IsActive() const { return (StartTimeUS != (uint64_t)-1) && (EndTimeUS == (uint64_t)-1); } private: Stopwatch Timer; std::atomic StartTimeUS = (uint64_t)-1; std::atomic EndTimeUS = (uint64_t)-1; std::atomic LastTimeUS = (uint64_t)-1; uint64_t LastCount = 0; uint64_t LastPerSecond = 0; uint64_t FilteredPerSecond = 0; }; uint64_t GetBytesPerSecond(uint64_t ElapsedWallTimeUS, uint64_t Count) { if (ElapsedWallTimeUS == 0) { return 0; } return Count * 1000000 / ElapsedWallTimeUS; } bool CleanAndRemoveDirectory(WorkerThreadPool& WorkerPool, const std::filesystem::path& Directory) { return CleanAndRemoveDirectory(WorkerPool, AbortFlag, PauseFlag, Directory); } void ValidateBuildPart(OperationLogOutput& Output, TransferThreadWorkers& Workers, BuildStorageBase& Storage, const Oid& BuildId, Oid BuildPartId, const std::string_view BuildPartName) { ZEN_TRACE_CPU("ValidateBuildPart"); ProgressBar::SetLogOperationName(ProgressMode, "Validate Part"); BuildsOperationValidateBuildPart ValidateOp(Output, Storage, AbortFlag, PauseFlag, Workers.GetIOWorkerPool(), Workers.GetNetworkPool(), BuildId, BuildPartId, BuildPartName, BuildsOperationValidateBuildPart::Options{.IsQuiet = IsQuiet, .IsVerbose = IsVerbose}); ValidateOp.Execute(); const uint64_t DownloadedCount = ValidateOp.m_DownloadStats.DownloadedChunkCount + ValidateOp.m_DownloadStats.DownloadedBlockCount; const uint64_t DownloadedByteCount = ValidateOp.m_DownloadStats.DownloadedChunkByteCount + ValidateOp.m_DownloadStats.DownloadedBlockByteCount; ZEN_CONSOLE("Verified: {:>8} ({}), {}B/sec, {}", DownloadedCount, NiceBytes(DownloadedByteCount), NiceNum(GetBytesPerSecond(ValidateOp.m_ValidateStats.ElapsedWallTimeUS, DownloadedByteCount)), NiceTimeSpanMs(ValidateOp.m_ValidateStats.ElapsedWallTimeUS / 1000)); } struct UploadFolderOptions { std::filesystem::path TempDir; uint64_t FindBlockMaxCount; uint8_t BlockReuseMinPercentLimit; bool AllowMultiparts; bool CreateBuild; bool IgnoreExistingBlocks; bool UploadToZenCache; const std::vector& ExcludeFolders = DefaultExcludeFolders; const std::vector& ExcludeExtensions = DefaultExcludeExtensions; }; std::vector> UploadFolder(OperationLogOutput& Output, TransferThreadWorkers& Workers, StorageInstance& Storage, const Oid& BuildId, const Oid& BuildPartId, const std::string_view BuildPartName, const std::filesystem::path& Path, const std::filesystem::path& ManifestPath, const CbObject& MetaData, ChunkingController& ChunkController, ChunkingCache& ChunkCache, const UploadFolderOptions& Options) { ProgressBar::SetLogOperationName(ProgressMode, "Upload Folder"); Stopwatch UploadTimer; BuildsOperationUploadFolder UploadOp( Output, Storage, AbortFlag, PauseFlag, Workers.GetIOWorkerPool(), Workers.GetNetworkPool(), BuildId, Path, Options.CreateBuild, std::move(MetaData), BuildsOperationUploadFolder::Options{.IsQuiet = IsQuiet, .IsVerbose = IsVerbose, .DoExtraContentValidation = DoExtraContentVerify, .FindBlockMaxCount = Options.FindBlockMaxCount, .BlockReuseMinPercentLimit = Options.BlockReuseMinPercentLimit, .AllowMultiparts = Options.AllowMultiparts, .IgnoreExistingBlocks = Options.IgnoreExistingBlocks, .TempDir = Options.TempDir, .ExcludeFolders = Options.ExcludeFolders, .ExcludeExtensions = Options.ExcludeExtensions, .ZenExcludeManifestName = ZenExcludeManifestName, .NonCompressableExtensions = DefaultSplitOnlyExtensions, .PopulateCache = Options.UploadToZenCache}); std::vector> UploadedParts = UploadOp.Execute(BuildPartId, BuildPartName, ManifestPath, ChunkController, ChunkCache); if (AbortFlag) { return {}; } ZEN_CONSOLE_VERBOSE( "Folder scanning stats:" "\n FoundFileCount: {}" "\n FoundFileByteCount: {}" "\n AcceptedFileCount: {}" "\n AcceptedFileByteCount: {}" "\n ElapsedWallTimeUS: {}", UploadOp.m_LocalFolderScanStats.FoundFileCount.load(), NiceBytes(UploadOp.m_LocalFolderScanStats.FoundFileByteCount.load()), UploadOp.m_LocalFolderScanStats.AcceptedFileCount.load(), NiceBytes(UploadOp.m_LocalFolderScanStats.AcceptedFileByteCount.load()), NiceLatencyNs(UploadOp.m_LocalFolderScanStats.ElapsedWallTimeUS * 1000)); ZEN_CONSOLE_VERBOSE( "Chunking stats:" "\n FilesProcessed: {}" "\n FilesChunked: {}" "\n BytesHashed: {}" "\n UniqueChunksFound: {}" "\n UniqueSequencesFound: {}" "\n UniqueBytesFound: {}" "\n FilesFoundInCache: {}" "\n ChunksFoundInCache: {}" "\n FilesStoredInCache: {}" "\n ChunksStoredInCache: {}" "\n ElapsedWallTimeUS: {}", UploadOp.m_ChunkingStats.FilesProcessed.load(), UploadOp.m_ChunkingStats.FilesChunked.load(), NiceBytes(UploadOp.m_ChunkingStats.BytesHashed.load()), UploadOp.m_ChunkingStats.UniqueChunksFound.load(), UploadOp.m_ChunkingStats.UniqueSequencesFound.load(), NiceBytes(UploadOp.m_ChunkingStats.UniqueBytesFound.load()), UploadOp.m_ChunkingStats.FilesFoundInCache.load(), UploadOp.m_ChunkingStats.ChunksFoundInCache.load(), NiceBytes(UploadOp.m_ChunkingStats.BytesFoundInCache.load()), UploadOp.m_ChunkingStats.FilesStoredInCache.load(), UploadOp.m_ChunkingStats.ChunksStoredInCache.load(), NiceBytes(UploadOp.m_ChunkingStats.BytesStoredInCache.load()), NiceLatencyNs(UploadOp.m_ChunkingStats.ElapsedWallTimeUS * 1000)); ZEN_CONSOLE_VERBOSE( "Find block stats:" "\n FindBlockTimeMS: {}" "\n PotentialChunkCount: {}" "\n PotentialChunkByteCount: {}" "\n FoundBlockCount: {}" "\n FoundBlockChunkCount: {}" "\n FoundBlockByteCount: {}" "\n AcceptedBlockCount: {}" "\n NewBlocksCount: {}" "\n NewBlocksChunkCount: {}" "\n NewBlocksChunkByteCount: {}", NiceTimeSpanMs(UploadOp.m_FindBlocksStats.FindBlockTimeMS), UploadOp.m_FindBlocksStats.PotentialChunkCount, NiceBytes(UploadOp.m_FindBlocksStats.PotentialChunkByteCount), UploadOp.m_FindBlocksStats.FoundBlockCount, UploadOp.m_FindBlocksStats.FoundBlockChunkCount, NiceBytes(UploadOp.m_FindBlocksStats.FoundBlockByteCount), UploadOp.m_FindBlocksStats.AcceptedBlockCount, UploadOp.m_FindBlocksStats.NewBlocksCount, UploadOp.m_FindBlocksStats.NewBlocksChunkCount, NiceBytes(UploadOp.m_FindBlocksStats.NewBlocksChunkByteCount)); ZEN_CONSOLE_VERBOSE( "Reuse block stats:" "\n AcceptedChunkCount: {}" "\n AcceptedByteCount: {}" "\n AcceptedRawByteCount: {}" "\n RejectedBlockCount: {}" "\n RejectedChunkCount: {}" "\n RejectedByteCount: {}" "\n AcceptedReduntantChunkCount: {}" "\n AcceptedReduntantByteCount: {}", UploadOp.m_ReuseBlocksStats.AcceptedChunkCount, NiceBytes(UploadOp.m_ReuseBlocksStats.AcceptedByteCount), NiceBytes(UploadOp.m_ReuseBlocksStats.AcceptedRawByteCount), UploadOp.m_ReuseBlocksStats.RejectedBlockCount, UploadOp.m_ReuseBlocksStats.RejectedChunkCount, NiceBytes(UploadOp.m_ReuseBlocksStats.RejectedByteCount), UploadOp.m_ReuseBlocksStats.AcceptedReduntantChunkCount, NiceBytes(UploadOp.m_ReuseBlocksStats.AcceptedReduntantByteCount)); ZEN_CONSOLE_VERBOSE( "Generate blocks stats:" "\n GeneratedBlockByteCount: {}" "\n GeneratedBlockCount: {}" "\n GenerateBlocksElapsedWallTimeUS: {}", NiceBytes(UploadOp.m_GenerateBlocksStats.GeneratedBlockByteCount.load()), UploadOp.m_GenerateBlocksStats.GeneratedBlockCount.load(), NiceLatencyNs(UploadOp.m_GenerateBlocksStats.GenerateBlocksElapsedWallTimeUS * 1000)); ZEN_CONSOLE_VERBOSE( "Generate blocks stats:" "\n ChunkCount: {}" "\n ChunkByteCount: {}" "\n CompressedChunkCount: {}" "\n CompressChunksElapsedWallTimeUS: {}", UploadOp.m_LooseChunksStats.ChunkCount, NiceBytes(UploadOp.m_LooseChunksStats.ChunkByteCount), UploadOp.m_LooseChunksStats.CompressedChunkCount.load(), NiceBytes(UploadOp.m_LooseChunksStats.CompressedChunkBytes.load()), NiceLatencyNs(UploadOp.m_LooseChunksStats.CompressChunksElapsedWallTimeUS * 1000)); ZEN_CONSOLE_VERBOSE( "Disk stats:" "\n OpenReadCount: {}" "\n OpenWriteCount: {}" "\n ReadCount: {}" "\n ReadByteCount: {}" "\n WriteCount: {} ({} cloned)" "\n WriteByteCount: {} ({} cloned)" "\n CurrentOpenFileCount: {}", UploadOp.m_DiskStats.OpenReadCount.load(), UploadOp.m_DiskStats.OpenWriteCount.load(), UploadOp.m_DiskStats.ReadCount.load(), NiceBytes(UploadOp.m_DiskStats.ReadByteCount.load()), UploadOp.m_DiskStats.WriteCount.load(), UploadOp.m_DiskStats.CloneCount.load(), NiceBytes(UploadOp.m_DiskStats.WriteByteCount.load()), NiceBytes(UploadOp.m_DiskStats.CloneByteCount.load()), UploadOp.m_DiskStats.CurrentOpenFileCount.load()); ZEN_CONSOLE_VERBOSE( "Upload stats:" "\n BlockCount: {}" "\n BlocksBytes: {}" "\n ChunkCount: {}" "\n ChunksBytes: {}" "\n ReadFromDiskBytes: {}" "\n MultipartAttachmentCount: {}" "\n ElapsedWallTimeUS: {}", UploadOp.m_UploadStats.BlockCount.load(), NiceBytes(UploadOp.m_UploadStats.BlocksBytes.load()), UploadOp.m_UploadStats.ChunkCount.load(), NiceBytes(UploadOp.m_UploadStats.ChunksBytes.load()), NiceBytes(UploadOp.m_UploadStats.ReadFromDiskBytes.load()), UploadOp.m_UploadStats.MultipartAttachmentCount.load(), NiceLatencyNs(UploadOp.m_UploadStats.ElapsedWallTimeUS * 1000)); const double DeltaByteCountPercent = UploadOp.m_ChunkingStats.BytesHashed > 0 ? (100.0 * (UploadOp.m_FindBlocksStats.NewBlocksChunkByteCount + UploadOp.m_LooseChunksStats.CompressedChunkBytes)) / (UploadOp.m_ChunkingStats.BytesHashed) : 0.0; const std::string MultipartAttachmentStats = Options.AllowMultiparts ? fmt::format(" ({} as multipart)", UploadOp.m_UploadStats.MultipartAttachmentCount.load()) : ""; if (!IsQuiet) { ZEN_CONSOLE( "Uploaded part {} ('{}') to build {}, {}\n" " Scanned files: {:>8} ({}), {}B/sec, {}\n" " New data: {:>8} ({}) {:.1f}%\n" " New blocks: {:>8} ({} -> {}), {}B/sec, {}\n" " New chunks: {:>8} ({} -> {}), {}B/sec, {}\n" " Uploaded: {:>8} ({}), {}bits/sec, {}\n" " Blocks: {:>8} ({})\n" " Chunks: {:>8} ({}){}", BuildPartId, BuildPartName, BuildId, NiceTimeSpanMs(UploadTimer.GetElapsedTimeMs()), UploadOp.m_LocalFolderScanStats.FoundFileCount.load(), NiceBytes(UploadOp.m_LocalFolderScanStats.FoundFileByteCount.load()), NiceNum(GetBytesPerSecond(UploadOp.m_ChunkingStats.ElapsedWallTimeUS, UploadOp.m_ChunkingStats.BytesHashed)), NiceTimeSpanMs(UploadOp.m_ChunkingStats.ElapsedWallTimeUS / 1000), UploadOp.m_FindBlocksStats.NewBlocksChunkCount + UploadOp.m_LooseChunksStats.CompressedChunkCount, NiceBytes(UploadOp.m_FindBlocksStats.NewBlocksChunkByteCount + UploadOp.m_LooseChunksStats.CompressedChunkBytes), DeltaByteCountPercent, UploadOp.m_GenerateBlocksStats.GeneratedBlockCount.load(), NiceBytes(UploadOp.m_FindBlocksStats.NewBlocksChunkByteCount), NiceBytes(UploadOp.m_GenerateBlocksStats.GeneratedBlockByteCount.load()), NiceNum(GetBytesPerSecond(UploadOp.m_GenerateBlocksStats.GenerateBlocksElapsedWallTimeUS, UploadOp.m_GenerateBlocksStats.GeneratedBlockByteCount)), NiceTimeSpanMs(UploadOp.m_GenerateBlocksStats.GenerateBlocksElapsedWallTimeUS / 1000), UploadOp.m_LooseChunksStats.CompressedChunkCount.load(), NiceBytes(UploadOp.m_LooseChunksStats.CompressedChunkRawBytes), NiceBytes(UploadOp.m_LooseChunksStats.CompressedChunkBytes.load()), NiceNum(GetBytesPerSecond(UploadOp.m_LooseChunksStats.CompressChunksElapsedWallTimeUS, UploadOp.m_LooseChunksStats.CompressedChunkRawBytes)), NiceTimeSpanMs(UploadOp.m_LooseChunksStats.CompressChunksElapsedWallTimeUS / 1000), UploadOp.m_UploadStats.BlockCount.load() + UploadOp.m_UploadStats.ChunkCount.load(), NiceBytes(UploadOp.m_UploadStats.BlocksBytes + UploadOp.m_UploadStats.ChunksBytes), NiceNum(GetBytesPerSecond(UploadOp.m_UploadStats.ElapsedWallTimeUS, (UploadOp.m_UploadStats.ChunksBytes + UploadOp.m_UploadStats.BlocksBytes) * 8)), NiceTimeSpanMs(UploadOp.m_UploadStats.ElapsedWallTimeUS / 1000), UploadOp.m_UploadStats.BlockCount.load(), NiceBytes(UploadOp.m_UploadStats.BlocksBytes.load()), UploadOp.m_UploadStats.ChunkCount.load(), NiceBytes(UploadOp.m_UploadStats.ChunksBytes.load()), MultipartAttachmentStats); } return UploadedParts; } struct VerifyFolderStatistics { std::atomic FilesVerified = 0; std::atomic FilesFailed = 0; std::atomic ReadBytes = 0; uint64_t VerifyElapsedWallTimeUs = 0; }; void VerifyFolder(TransferThreadWorkers& Workers, const ChunkedFolderContent& Content, const ChunkedContentLookup& Lookup, const std::filesystem::path& Path, const std::vector& ExcludeFolders, bool VerifyFileHash, VerifyFolderStatistics& VerifyFolderStats) { ZEN_TRACE_CPU("VerifyFolder"); Stopwatch Timer; ProgressBar ProgressBar(ProgressMode, "Verify Files"); WorkerThreadPool& VerifyPool = Workers.GetIOWorkerPool(); ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog); const uint32_t PathCount = gsl::narrow(Content.Paths.size()); RwLock ErrorLock; std::vector Errors; auto IsAcceptedFolder = [ExcludeFolders = ExcludeFolders](const std::string_view& RelativePath) -> bool { for (const std::string& ExcludeFolder : ExcludeFolders) { if (RelativePath.starts_with(ExcludeFolder)) { if (RelativePath.length() == ExcludeFolder.length()) { return false; } else if (RelativePath[ExcludeFolder.length()] == '/') { return false; } } } return true; }; for (uint32_t PathIndex = 0; PathIndex < PathCount; PathIndex++) { if (Work.IsAborted()) { break; } Work.ScheduleWork( VerifyPool, [&Path, &Content, &Lookup, &ErrorLock, &Errors, &VerifyFolderStats, VerifyFileHash, &IsAcceptedFolder, PathIndex]( std::atomic&) { if (!AbortFlag) { ZEN_TRACE_CPU("VerifyFile_work"); // TODO: Convert ScheduleWork body to function const std::filesystem::path TargetPath = (Path / Content.Paths[PathIndex]).make_preferred(); if (IsAcceptedFolder(TargetPath.parent_path().generic_string())) { const uint64_t ExpectedSize = Content.RawSizes[PathIndex]; if (!IsFile(TargetPath)) { ErrorLock.WithExclusiveLock([&]() { Errors.push_back(fmt::format("File {} with expected size {} does not exist", TargetPath, ExpectedSize)); }); VerifyFolderStats.FilesFailed++; } else { std::error_code Ec; uint64_t SizeOnDisk = gsl::narrow(FileSizeFromPath(TargetPath, Ec)); if (Ec) { ErrorLock.WithExclusiveLock([&]() { Errors.push_back( fmt::format("Failed to get size of file {}: {} ({})", TargetPath, Ec.message(), Ec.value())); }); VerifyFolderStats.FilesFailed++; } else if (SizeOnDisk < ExpectedSize) { ErrorLock.WithExclusiveLock([&]() { Errors.push_back(fmt::format("Size of file {} is smaller than expected. Expected: {}, Found: {}", TargetPath, ExpectedSize, SizeOnDisk)); }); VerifyFolderStats.FilesFailed++; } else if (SizeOnDisk > ExpectedSize) { ErrorLock.WithExclusiveLock([&]() { Errors.push_back(fmt::format("Size of file {} is bigger than expected. Expected: {}, Found: {}", TargetPath, ExpectedSize, SizeOnDisk)); }); VerifyFolderStats.FilesFailed++; } else if (SizeOnDisk > 0 && VerifyFileHash) { const IoHash& ExpectedRawHash = Content.RawHashes[PathIndex]; IoBuffer Buffer = IoBufferBuilder::MakeFromFile(TargetPath); IoHash RawHash = IoHash::HashBuffer(Buffer); if (RawHash != ExpectedRawHash) { uint64_t FileOffset = 0; const uint32_t SequenceIndex = Lookup.RawHashToSequenceIndex.at(ExpectedRawHash); const uint32_t OrderOffset = Lookup.SequenceIndexChunkOrderOffset[SequenceIndex]; for (uint32_t OrderIndex = OrderOffset; OrderIndex < OrderOffset + Content.ChunkedContent.ChunkCounts[SequenceIndex]; OrderIndex++) { uint32_t ChunkIndex = Content.ChunkedContent.ChunkOrders[OrderIndex]; uint64_t ChunkSize = Content.ChunkedContent.ChunkRawSizes[ChunkIndex]; IoHash ChunkHash = Content.ChunkedContent.ChunkHashes[ChunkIndex]; IoBuffer FileChunk = IoBuffer(Buffer, FileOffset, ChunkSize); if (IoHash::HashBuffer(FileChunk) != ChunkHash) { ErrorLock.WithExclusiveLock([&]() { Errors.push_back(fmt::format( "WARNING: Hash of file {} does not match expected hash. Expected: {}, Found: {}. " "Mismatch at chunk {}", TargetPath, ExpectedRawHash, RawHash, OrderIndex - OrderOffset)); }); break; } FileOffset += ChunkSize; } VerifyFolderStats.FilesFailed++; } VerifyFolderStats.ReadBytes += SizeOnDisk; } } } VerifyFolderStats.FilesVerified++; } }, [&, PathIndex](std::exception_ptr Ex, std::atomic&) { std::string Description; try { std::rethrow_exception(Ex); } catch (const std::exception& Ex) { Description = Ex.what(); } ErrorLock.WithExclusiveLock([&]() { Errors.push_back(fmt::format("Failed verifying file '{}'. Reason: {}", (Path / Content.Paths[PathIndex]).make_preferred(), Description)); }); VerifyFolderStats.FilesFailed++; }); } Work.Wait(GetUpdateDelayMS(ProgressMode), [&](bool IsAborted, bool IsPaused, std::ptrdiff_t PendingWork) { ZEN_UNUSED(PendingWork); std::string Details = fmt::format("Verified {}/{} ({}). Failed files: {}", VerifyFolderStats.FilesVerified.load(), PathCount, NiceBytes(VerifyFolderStats.ReadBytes.load()), VerifyFolderStats.FilesFailed.load()); ProgressBar.UpdateState({.Task = "Verifying files ", .Details = Details, .TotalCount = gsl::narrow(PathCount), .RemainingCount = gsl::narrow(PathCount - VerifyFolderStats.FilesVerified.load()), .Status = ProgressBar::State::CalculateStatus(IsAborted, IsPaused)}, false); }); VerifyFolderStats.VerifyElapsedWallTimeUs = Timer.GetElapsedTimeUs(); ProgressBar.Finish(); if (AbortFlag) { return; } for (const std::string& Error : Errors) { ZEN_CONSOLE_ERROR("{}", Error); } if (!Errors.empty()) { throw std::runtime_error(fmt::format("Verify failed with {} errors", Errors.size())); } } CbObject GetBuild(BuildStorageBase& Storage, const Oid& BuildId) { Stopwatch GetBuildTimer; CbObject BuildObject = Storage.GetBuild(BuildId); if (!IsQuiet) { ZEN_CONSOLE("GetBuild took {}. Name: '{}', Payload size: {}", NiceTimeSpanMs(GetBuildTimer.GetElapsedTimeMs()), BuildObject["name"sv].AsString(), NiceBytes(BuildObject.GetSize())); ZEN_CONSOLE("{}", GetCbObjectAsNiceString(BuildObject, " "sv, "\n"sv)); } return BuildObject; } std::vector GetNewPaths(const std::span KnownPaths, const std::span Paths) { tsl::robin_set KnownPathsSet; KnownPathsSet.reserve(KnownPaths.size()); for (const std::filesystem::path& LocalPath : KnownPaths) { KnownPathsSet.insert(LocalPath.generic_string()); } std::vector NewPaths; for (const std::filesystem::path& UntrackedPath : Paths) { if (!KnownPathsSet.contains(UntrackedPath.generic_string())) { NewPaths.push_back(UntrackedPath); } } return NewPaths; } BuildSaveState GetLocalStateFromPaths(TransferThreadWorkers& Workers, GetFolderContentStatistics& LocalFolderScanStats, ChunkingStatistics& ChunkingStats, const std::filesystem::path& Path, ChunkingController& ChunkController, ChunkingCache& ChunkCache, std::span PathsToCheck) { FolderContent FolderState; ChunkedFolderContent ChunkedContent; { ProgressBar ProgressBar(ProgressMode, "Check Files"); FolderState = GetValidFolderContent( Workers.GetIOWorkerPool(), LocalFolderScanStats, Path, PathsToCheck, [&ProgressBar, &LocalFolderScanStats](uint64_t PathCount, uint64_t CompletedPathCount) { std::string Details = fmt::format("{}/{} checked, {} found", CompletedPathCount, PathCount, LocalFolderScanStats.FoundFileCount.load()); ProgressBar.UpdateState({.Task = "Checking files ", .Details = Details, .TotalCount = PathCount, .RemainingCount = PathCount - CompletedPathCount, .Status = ProgressBar::State::CalculateStatus(AbortFlag, PauseFlag)}, false); }, GetUpdateDelayMS(ProgressMode), AbortFlag, PauseFlag); ProgressBar.Finish(); } if (FolderState.Paths.size() > 0) { uint64_t ByteCountToScan = 0; for (const uint64_t RawSize : FolderState.RawSizes) { ByteCountToScan += RawSize; } ProgressBar ProgressBar(ProgressMode, "Scan Files"); FilteredRate FilteredBytesHashed; FilteredBytesHashed.Start(); ChunkingStatistics LocalChunkingStats; ChunkedContent = ChunkFolderContent( LocalChunkingStats, Workers.GetIOWorkerPool(), Path, FolderState, ChunkController, ChunkCache, GetUpdateDelayMS(ProgressMode), [&](bool IsAborted, bool IsPaused, std::ptrdiff_t) { FilteredBytesHashed.Update(LocalChunkingStats.BytesHashed.load()); std::string Details = fmt::format("{}/{} ({}/{}, {}B/s) scanned, {} ({}) chunks found", LocalChunkingStats.FilesProcessed.load(), FolderState.Paths.size(), NiceBytes(LocalChunkingStats.BytesHashed.load()), NiceBytes(ByteCountToScan), NiceNum(FilteredBytesHashed.GetCurrent()), LocalChunkingStats.UniqueChunksFound.load(), NiceBytes(LocalChunkingStats.UniqueBytesFound.load())); ProgressBar.UpdateState({.Task = "Scanning files ", .Details = Details, .TotalCount = ByteCountToScan, .RemainingCount = ByteCountToScan - LocalChunkingStats.BytesHashed.load(), .Status = ProgressBar::State::CalculateStatus(IsAborted, IsPaused)}, false); }, AbortFlag, PauseFlag); ChunkingStats += LocalChunkingStats; FilteredBytesHashed.Stop(); ProgressBar.Finish(); } return BuildSaveState{.State = BuildState{.ChunkedContent = std::move(ChunkedContent)}, .FolderState = FolderState, .LocalPath = Path}; } BuildSaveState GetLocalContent(TransferThreadWorkers& Workers, GetFolderContentStatistics& LocalFolderScanStats, ChunkingStatistics& ChunkingStats, const std::filesystem::path& Path, const std::filesystem::path& StateFilePath, ChunkingController& ChunkController, ChunkingCache& ChunkCache) { Stopwatch ReadStateTimer; bool FileExists = IsFile(StateFilePath); if (!FileExists) { ZEN_CONSOLE("No known local state file in {}, falling back to scanning", Path); return {}; } BuildSaveState SavedLocalState; try { SavedLocalState = ReadBuildSaveStateFile(StateFilePath); if (!IsQuiet) { ZEN_CONSOLE("Read local state file {} in {}", StateFilePath, NiceTimeSpanMs(ReadStateTimer.GetElapsedTimeMs())); } } catch (const std::exception& Ex) { ZEN_CONSOLE_WARN("Failed reading state file {}, falling back to scannning. Reason: {}", StateFilePath, Ex.what()); return {}; } FolderContent CurrentLocalFolderState; { ProgressBar ProgressBar(ProgressMode, "Check Known Files"); CurrentLocalFolderState = GetValidFolderContent( Workers.GetIOWorkerPool(), LocalFolderScanStats, Path, SavedLocalState.FolderState.Paths, [&ProgressBar, &LocalFolderScanStats](uint64_t PathCount, uint64_t CompletedPathCount) { std::string Details = fmt::format("{}/{} checked, {} found", CompletedPathCount, PathCount, LocalFolderScanStats.FoundFileCount.load()); ProgressBar.UpdateState({.Task = "Checking files ", .Details = Details, .TotalCount = PathCount, .RemainingCount = PathCount - CompletedPathCount, .Status = ProgressBar::State::CalculateStatus(AbortFlag, PauseFlag)}, false); }, GetUpdateDelayMS(ProgressMode), AbortFlag, PauseFlag); ProgressBar.Finish(); } if (AbortFlag) { return {}; } if (!SavedLocalState.FolderState.AreKnownFilesEqual(CurrentLocalFolderState)) { const size_t LocalStatePathCount = SavedLocalState.FolderState.Paths.size(); std::vector DeletedPaths; FolderContent UpdatedContent = GetUpdatedContent(SavedLocalState.FolderState, CurrentLocalFolderState, DeletedPaths); if (!DeletedPaths.empty()) { SavedLocalState.State.ChunkedContent = DeletePathsFromChunkedContent(SavedLocalState.State.ChunkedContent, DeletedPaths); } if (!IsQuiet) { ZEN_CONSOLE("Updating state, {} local files deleted and {} local files updated out of {}", DeletedPaths.size(), UpdatedContent.Paths.size(), LocalStatePathCount); } if (UpdatedContent.Paths.size() > 0) { uint64_t ByteCountToScan = 0; for (const uint64_t RawSize : UpdatedContent.RawSizes) { ByteCountToScan += RawSize; } ProgressBar ProgressBar(ProgressMode, "Scan Known Files"); FilteredRate FilteredBytesHashed; FilteredBytesHashed.Start(); ChunkingStatistics LocalChunkingStats; ChunkedFolderContent UpdatedLocalContent = ChunkFolderContent( LocalChunkingStats, Workers.GetIOWorkerPool(), Path, UpdatedContent, ChunkController, ChunkCache, GetUpdateDelayMS(ProgressMode), [&](bool IsAborted, bool IsPaused, std::ptrdiff_t) { FilteredBytesHashed.Update(LocalChunkingStats.BytesHashed.load()); std::string Details = fmt::format("{}/{} ({}/{}, {}B/s) scanned, {} ({}) chunks found", LocalChunkingStats.FilesProcessed.load(), UpdatedContent.Paths.size(), NiceBytes(LocalChunkingStats.BytesHashed.load()), NiceBytes(ByteCountToScan), NiceNum(FilteredBytesHashed.GetCurrent()), LocalChunkingStats.UniqueChunksFound.load(), NiceBytes(LocalChunkingStats.UniqueBytesFound.load())); ProgressBar.UpdateState({.Task = "Scanning files ", .Details = Details, .TotalCount = ByteCountToScan, .RemainingCount = ByteCountToScan - LocalChunkingStats.BytesHashed.load(), .Status = ProgressBar::State::CalculateStatus(IsAborted, IsPaused)}, false); }, AbortFlag, PauseFlag); ChunkingStats += LocalChunkingStats; FilteredBytesHashed.Stop(); ProgressBar.Finish(); if (AbortFlag) { return {}; } SavedLocalState.State.ChunkedContent = MergeChunkedFolderContents(SavedLocalState.State.ChunkedContent, {{UpdatedLocalContent}}); } } else { // Remove files from LocalContent no longer in LocalFolderState tsl::robin_set LocalFolderPaths; LocalFolderPaths.reserve(SavedLocalState.FolderState.Paths.size()); for (const std::filesystem::path& LocalFolderPath : SavedLocalState.FolderState.Paths) { LocalFolderPaths.insert(LocalFolderPath.generic_string()); } std::vector DeletedPaths; for (const std::filesystem::path& LocalContentPath : SavedLocalState.State.ChunkedContent.Paths) { if (!LocalFolderPaths.contains(LocalContentPath.generic_string())) { DeletedPaths.push_back(LocalContentPath); } } if (!DeletedPaths.empty()) { SavedLocalState.State.ChunkedContent = DeletePathsFromChunkedContent(SavedLocalState.State.ChunkedContent, DeletedPaths); } } SavedLocalState.FolderState = CurrentLocalFolderState; return SavedLocalState; } ChunkedFolderContent ScanAndChunkFolder( TransferThreadWorkers& Workers, GetFolderContentStatistics& GetFolderContentStats, ChunkingStatistics& ChunkingStats, const std::filesystem::path& Path, std::function&& IsAcceptedFolder, std::function&& IsAcceptedFile, ChunkingController& ChunkController, ChunkingCache& ChunkCache) { Stopwatch Timer; ZEN_TRACE_CPU("ScanAndChunkFolder"); FolderContent Content = GetFolderContent( GetFolderContentStats, Path, std::move(IsAcceptedFolder), std::move(IsAcceptedFile), Workers.GetIOWorkerPool(), GetUpdateDelayMS(ProgressMode), [](bool, std::ptrdiff_t) {}, AbortFlag); if (AbortFlag) { return {}; } BuildState LocalContent = GetLocalContent(Workers, GetFolderContentStats, ChunkingStats, Path, ZenStateFilePath(Path / ZenFolderName), ChunkController, ChunkCache) .State; std::vector UntrackedPaths = GetNewPaths(LocalContent.ChunkedContent.Paths, Content.Paths); BuildState UntrackedLocalContent = GetLocalStateFromPaths(Workers, GetFolderContentStats, ChunkingStats, Path, ChunkController, ChunkCache, UntrackedPaths).State; ChunkedFolderContent Result = MergeChunkedFolderContents(LocalContent.ChunkedContent, std::vector{UntrackedLocalContent.ChunkedContent}); const uint64_t TotalRawSize = std::accumulate(Result.RawSizes.begin(), Result.RawSizes.end(), std::uint64_t(0)); const uint64_t ChunkedRawSize = std::accumulate(Result.ChunkedContent.ChunkRawSizes.begin(), Result.ChunkedContent.ChunkRawSizes.end(), std::uint64_t(0)); if (!IsQuiet) { ZEN_CONSOLE("Found {} ({}) files divided into {} ({}) unique chunks in '{}' in {}. Average hash rate {}B/sec", Result.Paths.size(), NiceBytes(TotalRawSize), Result.ChunkedContent.ChunkHashes.size(), NiceBytes(ChunkedRawSize), Path, NiceTimeSpanMs(Timer.GetElapsedTimeMs()), NiceNum(GetBytesPerSecond(ChunkingStats.ElapsedWallTimeUS, ChunkingStats.BytesHashed))); } return Result; }; struct DownloadOptions { 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; bool AllowFileClone = true; std::vector IncludeWildcards; std::vector ExcludeWildcards; uint64_t MaximumInMemoryPayloadSize = 512u * 1024u; bool PopulateCache = true; bool AppendNewContent = false; std::vector ExcludeFolders = DefaultExcludeFolders; }; void DownloadFolder(OperationLogOutput& Output, TransferThreadWorkers& Workers, StorageInstance& Storage, const BuildStorageCache::Statistics& StorageCacheStats, const Oid& BuildId, const std::vector& BuildPartIds, std::span BuildPartNames, const std::filesystem::path& DownloadSpecPath, const std::filesystem::path& Path, const DownloadOptions& Options) { ZEN_TRACE_CPU("DownloadFolder"); ProgressBar::SetLogOperationName(ProgressMode, "Download Folder"); enum TaskSteps : uint32_t { CheckState, CompareState, Download, Verify, Cleanup, StepCount }; auto EndProgress = MakeGuard([&]() { ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::StepCount, TaskSteps::StepCount); }); ZEN_ASSERT((!Options.PrimeCacheOnly) || (Options.PrimeCacheOnly && (Options.PartialBlockRequestMode == EPartialBlockRequestMode::Off))); Stopwatch DownloadTimer; ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::CheckState, TaskSteps::StepCount); const std::filesystem::path ZenTempFolder = ZenTempFolderPath(Options.ZenFolderPath); CreateDirectories(ZenTempFolder); std::uint64_t PreferredMultipartChunkSize = 32u * 1024u * 1024u; CbObject BuildObject = GetBuild(*Storage.BuildStorage, BuildId); std::vector> AllBuildParts = ResolveBuildPartNames(BuildObject, BuildId, BuildPartIds, BuildPartNames, PreferredMultipartChunkSize); BuildManifest Manifest; if (!DownloadSpecPath.empty()) { const std::filesystem::path AbsoluteDownloadSpecPath = DownloadSpecPath.is_relative() ? MakeSafeAbsolutePath(Path / DownloadSpecPath) : MakeSafeAbsolutePath(DownloadSpecPath); Manifest = ParseBuildManifest(DownloadSpecPath); } std::vector PartContents; std::unique_ptr ChunkController; std::vector BlockDescriptions; std::vector LooseChunkHashes; ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::CompareState, TaskSteps::StepCount); ChunkedFolderContent RemoteContent = GetRemoteContent(Output, Storage, BuildId, AllBuildParts, Manifest, Options.IncludeWildcards, Options.ExcludeWildcards, ChunkController, PartContents, BlockDescriptions, LooseChunkHashes, IsQuiet, IsVerbose, DoExtraContentVerify); const std::uint64_t LargeAttachmentSize = Options.AllowMultiparts ? PreferredMultipartChunkSize * 4u : (std::uint64_t)-1; GetFolderContentStatistics LocalFolderScanStats; ChunkingStatistics ChunkingStats; BuildSaveState LocalState; if (!Options.PrimeCacheOnly) { if (IsDir(Path)) { if (!ChunkController && !IsQuiet) { ZEN_CONSOLE_INFO("Unspecified chunking algorithm, using default"); ChunkController = CreateStandardChunkingController(StandardChunkingControllerSettings{}); } std::unique_ptr ChunkCache(CreateNullChunkingCache()); LocalState = GetLocalContent(Workers, LocalFolderScanStats, ChunkingStats, Path, ZenStateFilePath(Path / ZenFolderName), *ChunkController, *ChunkCache); std::vector UntrackedPaths = GetNewPaths(LocalState.State.ChunkedContent.Paths, RemoteContent.Paths); BuildSaveState UntrackedLocalContent = GetLocalStateFromPaths(Workers, LocalFolderScanStats, ChunkingStats, Path, *ChunkController, *ChunkCache, UntrackedPaths); if (!UntrackedLocalContent.State.ChunkedContent.Paths.empty()) { LocalState.State.ChunkedContent = MergeChunkedFolderContents(LocalState.State.ChunkedContent, std::vector{UntrackedLocalContent.State.ChunkedContent}); // TODO: Helper LocalState.FolderState.Paths.insert(LocalState.FolderState.Paths.begin(), UntrackedLocalContent.FolderState.Paths.begin(), UntrackedLocalContent.FolderState.Paths.end()); LocalState.FolderState.RawSizes.insert(LocalState.FolderState.RawSizes.begin(), UntrackedLocalContent.FolderState.RawSizes.begin(), UntrackedLocalContent.FolderState.RawSizes.end()); LocalState.FolderState.Attributes.insert(LocalState.FolderState.Attributes.begin(), UntrackedLocalContent.FolderState.Attributes.begin(), UntrackedLocalContent.FolderState.Attributes.end()); LocalState.FolderState.ModificationTicks.insert(LocalState.FolderState.ModificationTicks.begin(), UntrackedLocalContent.FolderState.ModificationTicks.begin(), UntrackedLocalContent.FolderState.ModificationTicks.end()); } if (Options.AppendNewContent) { RemoteContent = ApplyChunkedContentOverlay(LocalState.State.ChunkedContent, RemoteContent, Options.IncludeWildcards, Options.ExcludeWildcards); } #if ZEN_BUILD_DEBUG ValidateChunkedFolderContent(RemoteContent, BlockDescriptions, LooseChunkHashes, Options.IncludeWildcards, Options.ExcludeWildcards); #endif // ZEN_BUILD_DEBUG } else { CreateDirectories(Path); } } if (AbortFlag) { return; } LocalState.LocalPath = Path; { BuildsSelection::Build RemoteBuildState = {.Id = BuildId, .IncludeWildcards = Options.IncludeWildcards, .ExcludeWildcards = Options.ExcludeWildcards}; RemoteBuildState.Parts.reserve(BuildPartIds.size()); for (size_t PartIndex = 0; PartIndex < BuildPartIds.size(); PartIndex++) { RemoteBuildState.Parts.push_back( {BuildsSelection::BuildPart{.Id = BuildPartIds[PartIndex], .Name = PartIndex < BuildPartNames.size() ? BuildPartNames[PartIndex] : ""}}); } if (Options.AppendNewContent) { LocalState.State.Selection.Builds.emplace_back(std::move(RemoteBuildState)); } else { LocalState.State.Selection.Builds = std::vector{std::move(RemoteBuildState)}; } } if ((Options.EnableTargetFolderScavenging || Options.AppendNewContent) && !Options.CleanTargetFolder && CompareChunkedContent(RemoteContent, LocalState.State.ChunkedContent)) { if (!IsQuiet) { ZEN_CONSOLE("Local state is identical to build to download. All done. Completed in {}.", NiceTimeSpanMs(DownloadTimer.GetElapsedTimeMs())); } Stopwatch WriteStateTimer; CbObject StateObject = CreateBuildSaveStateObject(LocalState); CreateDirectories(ZenStateFilePath(Options.ZenFolderPath).parent_path()); TemporaryFile::SafeWriteFile(ZenStateFilePath(Options.ZenFolderPath), StateObject.GetView()); if (!IsQuiet) { ZEN_CONSOLE("Wrote local state in {}", NiceTimeSpanMs(WriteStateTimer.GetElapsedTimeMs())); } AddDownloadedPath(Options.SystemRootDir, BuildsDownloadInfo{.Selection = LocalState.State.Selection, .LocalPath = Path, .StateFilePath = ZenStateFilePath(Options.ZenFolderPath), .Iso8601Date = DateTime::Now().ToIso8601()}); } else { ExtendableStringBuilder<128> BuildPartString; for (const std::pair& BuildPart : AllBuildParts) { BuildPartString.Append(fmt::format(" {} ({})", BuildPart.second, BuildPart.first)); } uint64_t RawSize = std::accumulate(RemoteContent.RawSizes.begin(), RemoteContent.RawSizes.end(), std::uint64_t(0)); if (!IsQuiet) { ZEN_CONSOLE("Downloading build {}, parts:{} to '{}' ({})", BuildId, BuildPartString.ToView(), Path, NiceBytes(RawSize)); } Stopwatch IndexTimer; const ChunkedContentLookup LocalLookup = BuildChunkedContentLookup(LocalState.State.ChunkedContent); const ChunkedContentLookup RemoteLookup = BuildChunkedContentLookup(RemoteContent); if (!IsQuiet) { ZEN_OPERATION_LOG_INFO(Output, "Indexed local and remote content in {}", NiceTimeSpanMs(IndexTimer.GetElapsedTimeMs())); } ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::Download, TaskSteps::StepCount); BuildsOperationUpdateFolder Updater( Output, Storage, AbortFlag, PauseFlag, Workers.GetIOWorkerPool(), Workers.GetNetworkPool(), BuildId, Path, LocalState.State.ChunkedContent, LocalLookup, RemoteContent, RemoteLookup, BlockDescriptions, LooseChunkHashes, BuildsOperationUpdateFolder::Options{ .IsQuiet = IsQuiet, .IsVerbose = IsVerbose, .AllowFileClone = Options.AllowFileClone, .UseSparseFiles = UseSparseFiles, .SystemRootDir = Options.SystemRootDir, .ZenFolderPath = Options.ZenFolderPath, .LargeAttachmentSize = LargeAttachmentSize, .PreferredMultipartChunkSize = PreferredMultipartChunkSize, .PartialBlockRequestMode = Options.PartialBlockRequestMode, .WipeTargetFolder = Options.CleanTargetFolder, .PrimeCacheOnly = Options.PrimeCacheOnly, .EnableOtherDownloadsScavenging = Options.EnableOtherDownloadsScavenging, .EnableTargetFolderScavenging = Options.EnableTargetFolderScavenging || Options.AppendNewContent, .ValidateCompletedSequences = Options.PostDownloadVerify, .ExcludeFolders = Options.ExcludeFolders, .MaximumInMemoryPayloadSize = Options.MaximumInMemoryPayloadSize, .PopulateCache = Options.PopulateCache}); { ProgressBar::PushLogOperation(ProgressMode, "Download"); auto _ = MakeGuard([]() { ProgressBar::PopLogOperation(ProgressMode); }); FolderContent UpdatedLocalFolderState; Updater.Execute(UpdatedLocalFolderState); LocalState.State.ChunkedContent = RemoteContent; LocalState.FolderState = std::move(UpdatedLocalFolderState); } VerifyFolderStatistics VerifyFolderStats; if (!AbortFlag) { if (!Options.PrimeCacheOnly) { AddDownloadedPath(Options.SystemRootDir, BuildsDownloadInfo{.Selection = LocalState.State.Selection, .LocalPath = Path, .StateFilePath = ZenStateFilePath(Options.ZenFolderPath), .Iso8601Date = DateTime::Now().ToIso8601()}); ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::Verify, TaskSteps::StepCount); VerifyFolder(Workers, RemoteContent, RemoteLookup, Path, Options.ExcludeFolders, Options.PostDownloadVerify, VerifyFolderStats); Stopwatch WriteStateTimer; CbObject StateObject = CreateBuildSaveStateObject(LocalState); CreateDirectories(ZenStateFilePath(Options.ZenFolderPath).parent_path()); TemporaryFile::SafeWriteFile(ZenStateFilePath(Options.ZenFolderPath), StateObject.GetView()); if (!IsQuiet) { ZEN_CONSOLE("Wrote local state in {}", NiceTimeSpanMs(WriteStateTimer.GetElapsedTimeMs())); } #if 0 ExtendableStringBuilder<1024> SB; CompactBinaryToJson(StateObject, SB); WriteFile(ZenStateFileJsonPath(Options.ZenFolderPath), IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size())); #endif // 0 } const uint64_t DownloadCount = Updater.m_DownloadStats.DownloadedChunkCount.load() + Updater.m_DownloadStats.DownloadedBlockCount.load() + Updater.m_DownloadStats.DownloadedPartialBlockCount.load(); const uint64_t DownloadByteCount = Updater.m_DownloadStats.DownloadedChunkByteCount.load() + Updater.m_DownloadStats.DownloadedBlockByteCount.load() + Updater.m_DownloadStats.DownloadedPartialBlockByteCount.load(); const uint64_t DownloadTimeMs = DownloadTimer.GetElapsedTimeMs(); if (!IsQuiet) { std::string CloneInfo; if (Updater.m_DiskStats.CloneByteCount > 0) { CloneInfo = fmt::format(" ({} cloned)", NiceBytes(Updater.m_DiskStats.CloneByteCount.load())); } std::string DownloadDetails; { ExtendableStringBuilder<128> SB; BuildStorageBase::ExtendedStatistics ExtendedDownloadStats; if (Storage.BuildStorage->GetExtendedStatistics(ExtendedDownloadStats)) { if (!ExtendedDownloadStats.ReceivedBytesPerSource.empty()) { for (auto& It : ExtendedDownloadStats.ReceivedBytesPerSource) { if (SB.Size() > 0) { SB.Append(", "sv); } SB.Append(It.first); SB.Append(": "sv); SB.Append(NiceBytes(It.second)); } } } if (Storage.CacheStorage) { if (SB.Size() > 0) { SB.Append(", "sv); } SB.Append("Cache: "); SB.Append(NiceBytes(StorageCacheStats.TotalBytesRead.load())); } if (SB.Size() > 0) { DownloadDetails = fmt::format(" ({})", SB.ToView()); } } ZEN_CONSOLE( "Downloaded build {}, parts:{} in {}\n" " Scavenge: {} (Target: {}, Cache: {}, Others: {})\n" " Download: {} ({}) {}bits/s{}\n" " Write: {} ({}) {}B/s{}\n" " Clean: {}\n" " Finalize: {}\n" " Verify: {}", BuildId, BuildPartString.ToView(), NiceTimeSpanMs(DownloadTimeMs), NiceTimeSpanMs((Updater.m_CacheMappingStats.CacheScanElapsedWallTimeUs + Updater.m_CacheMappingStats.LocalScanElapsedWallTimeUs + Updater.m_CacheMappingStats.ScavengeElapsedWallTimeUs) / 1000), NiceTimeSpanMs(Updater.m_CacheMappingStats.LocalScanElapsedWallTimeUs / 1000), NiceTimeSpanMs(Updater.m_CacheMappingStats.CacheScanElapsedWallTimeUs / 1000), NiceTimeSpanMs(Updater.m_CacheMappingStats.ScavengeElapsedWallTimeUs / 1000), DownloadCount, NiceBytes(DownloadByteCount), NiceNum(GetBytesPerSecond(Updater.m_WriteChunkStats.DownloadTimeUs, DownloadByteCount * 8)), DownloadDetails, Updater.m_DiskStats.WriteCount.load(), NiceBytes(Updater.m_WrittenChunkByteCount.load()), NiceNum(GetBytesPerSecond(Updater.m_WriteChunkStats.WriteTimeUs, Updater.m_DiskStats.WriteByteCount.load())), CloneInfo, NiceTimeSpanMs(Updater.m_RebuildFolderStateStats.CleanFolderElapsedWallTimeUs / 1000), NiceTimeSpanMs(Updater.m_RebuildFolderStateStats.FinalizeTreeElapsedWallTimeUs / 1000), NiceTimeSpanMs(VerifyFolderStats.VerifyElapsedWallTimeUs / 1000)); } } } if (Options.PrimeCacheOnly) { if (Storage.CacheStorage) { Storage.CacheStorage->Flush(5000, [](intptr_t Remaining) { if (!IsQuiet) { if (Remaining == 0) { ZEN_CONSOLE("Build cache upload complete"); } else { ZEN_CONSOLE("Waiting for build cache to complete uploading. {} blobs remaining", Remaining); } } return !AbortFlag; }); } } ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::Cleanup, TaskSteps::StepCount); CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), ZenTempFolder); } void ListBuild(StorageInstance& Storage, const Oid& BuildId, const std::vector& BuildPartIds, std::span BuildPartNames, std::span IncludeWildcards, std::span ExcludeWildcards, CbObjectWriter* OptionalStructuredOutput) { std::uint64_t PreferredMultipartChunkSize = 32u * 1024u * 1024u; CbObject BuildObject = GetBuild(*Storage.BuildStorage, BuildId); if (OptionalStructuredOutput != nullptr) { OptionalStructuredOutput->AddObjectId("buildId"sv, BuildId); OptionalStructuredOutput->AddObject("build"sv, BuildObject); } std::vector> AllBuildParts = ResolveBuildPartNames(BuildObject, BuildId, BuildPartIds, BuildPartNames, PreferredMultipartChunkSize); if (!AllBuildParts.empty()) { Stopwatch GetBuildPartTimer; if (OptionalStructuredOutput != nullptr) { OptionalStructuredOutput->BeginArray("parts"sv); } for (size_t BuildPartIndex = 0; BuildPartIndex < AllBuildParts.size(); BuildPartIndex++) { const Oid BuildPartId = AllBuildParts[BuildPartIndex].first; const std::string_view BuildPartName = AllBuildParts[BuildPartIndex].second; CbObject BuildPartManifest = Storage.BuildStorage->GetBuildPart(BuildId, BuildPartId); if (OptionalStructuredOutput != nullptr) { OptionalStructuredOutput->BeginObject(); OptionalStructuredOutput->AddObjectId("id"sv, BuildPartId); OptionalStructuredOutput->AddString("partName"sv, BuildPartName); } { if (OptionalStructuredOutput != nullptr) { } else if (!IsQuiet) { ZEN_CONSOLE("{}Part: {} ('{}'):\n", BuildPartIndex > 0 ? "\n" : "", BuildPartId, BuildPartName, NiceTimeSpanMs(GetBuildPartTimer.GetElapsedTimeMs()), NiceBytes(BuildPartManifest.GetSize())); } std::vector Paths; std::vector RawHashes; std::vector RawSizes; std::vector Attributes; SourcePlatform Platform; std::vector SequenceRawHashes; std::vector ChunkCounts; std::vector AbsoluteChunkOrders; std::vector LooseChunkHashes; std::vector LooseChunkRawSizes; std::vector BlockRawHashes; ReadBuildContentFromCompactBinary(BuildPartManifest, Platform, Paths, RawHashes, RawSizes, Attributes, SequenceRawHashes, ChunkCounts, AbsoluteChunkOrders, LooseChunkHashes, LooseChunkRawSizes, BlockRawHashes); std::vector Order(Paths.size()); std::iota(Order.begin(), Order.end(), 0); std::sort(Order.begin(), Order.end(), [&](size_t Lhs, size_t Rhs) { const std::filesystem::path& LhsPath = Paths[Lhs]; const std::filesystem::path& RhsPath = Paths[Rhs]; return LhsPath < RhsPath; }); if (OptionalStructuredOutput != nullptr) { OptionalStructuredOutput->BeginArray("files"sv); } { for (size_t Index : Order) { const std::filesystem::path& Path = Paths[Index]; if (IncludePath(IncludeWildcards, ExcludeWildcards, ToLower(Path.generic_string()), /*CaseSensitive*/ true)) { const IoHash& RawHash = RawHashes[Index]; const uint64_t RawSize = RawSizes[Index]; const uint32_t Attribute = Attributes[Index]; if (OptionalStructuredOutput != nullptr) { OptionalStructuredOutput->BeginObject(); { OptionalStructuredOutput->AddString("path"sv, fmt::format("{}", Path)); OptionalStructuredOutput->AddInteger("rawSize"sv, RawSize); switch (Platform) { case SourcePlatform::Windows: OptionalStructuredOutput->AddInteger("attributes"sv, Attribute); break; case SourcePlatform::MacOS: case SourcePlatform::Linux: OptionalStructuredOutput->AddString("chmod"sv, fmt::format("{:#04o}", Attribute)); break; default: throw std::runtime_error(fmt::format("Unsupported platform: {}", (int)Platform)); } } OptionalStructuredOutput->EndObject(); } else { ZEN_CONSOLE("{}\t{}\t{}", Path, RawSize, RawHash); } } } } if (OptionalStructuredOutput != nullptr) { OptionalStructuredOutput->EndArray(); // "files" } } if (OptionalStructuredOutput != nullptr) { OptionalStructuredOutput->EndObject(); } } if (OptionalStructuredOutput != nullptr) { OptionalStructuredOutput->EndArray(); // parts } } } void DiffFolders(TransferThreadWorkers& Workers, const std::filesystem::path& BasePath, const std::filesystem::path& ComparePath, ChunkingController& ChunkController, ChunkingCache& ChunkCache, const std::vector& ExcludeFolders, const std::vector& ExcludeExtensions) { ZEN_TRACE_CPU("DiffFolders"); ProgressBar::SetLogOperationName(ProgressMode, "Diff Folders"); enum TaskSteps : uint32_t { CheckBase, CheckCompare, Diff, Cleanup, StepCount }; auto EndProgress = MakeGuard([&]() { ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::StepCount, TaskSteps::StepCount); }); ChunkedFolderContent BaseFolderContent; ChunkedFolderContent CompareFolderContent; { auto IsAcceptedFolder = [ExcludeFolders](const std::string_view& RelativePath) -> bool { for (const std::string& ExcludeFolder : ExcludeFolders) { if (RelativePath.starts_with(ExcludeFolder)) { if (RelativePath.length() == ExcludeFolder.length()) { return false; } else if (RelativePath[ExcludeFolder.length()] == '/') { return false; } } } return true; }; auto IsAcceptedFile = [ExcludeExtensions](const std::string_view& RelativePath, uint64_t, uint32_t) -> bool { for (const std::string& ExcludeExtension : ExcludeExtensions) { if (RelativePath.ends_with(ExcludeExtension)) { return false; } } return true; }; ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::CheckBase, TaskSteps::StepCount); GetFolderContentStatistics BaseGetFolderContentStats; ChunkingStatistics BaseChunkingStats; BaseFolderContent = ScanAndChunkFolder(Workers, BaseGetFolderContentStats, BaseChunkingStats, BasePath, IsAcceptedFolder, IsAcceptedFile, ChunkController, ChunkCache); if (AbortFlag) { return; } ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::CheckCompare, TaskSteps::StepCount); GetFolderContentStatistics CompareGetFolderContentStats; ChunkingStatistics CompareChunkingStats; CompareFolderContent = ScanAndChunkFolder(Workers, CompareGetFolderContentStats, CompareChunkingStats, ComparePath, IsAcceptedFolder, IsAcceptedFile, ChunkController, ChunkCache); if (AbortFlag) { return; } } ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::Diff, TaskSteps::StepCount); std::vector AddedHashes; std::vector RemovedHashes; uint64_t RemovedSize = 0; uint64_t AddedSize = 0; tsl::robin_map BaseRawHashLookup; for (size_t PathIndex = 0; PathIndex < BaseFolderContent.RawHashes.size(); PathIndex++) { const IoHash& RawHash = BaseFolderContent.RawHashes[PathIndex]; BaseRawHashLookup.insert_or_assign(RawHash, PathIndex); } tsl::robin_map CompareRawHashLookup; for (size_t PathIndex = 0; PathIndex < CompareFolderContent.RawHashes.size(); PathIndex++) { const IoHash& RawHash = CompareFolderContent.RawHashes[PathIndex]; if (!BaseRawHashLookup.contains(RawHash)) { AddedHashes.push_back(RawHash); AddedSize += CompareFolderContent.RawSizes[PathIndex]; } CompareRawHashLookup.insert_or_assign(RawHash, PathIndex); } for (uint32_t PathIndex = 0; PathIndex < BaseFolderContent.Paths.size(); PathIndex++) { const IoHash& RawHash = BaseFolderContent.RawHashes[PathIndex]; if (!CompareRawHashLookup.contains(RawHash)) { RemovedHashes.push_back(RawHash); RemovedSize += BaseFolderContent.RawSizes[PathIndex]; } } uint64_t BaseTotalRawSize = 0; for (uint32_t PathIndex = 0; PathIndex < BaseFolderContent.Paths.size(); PathIndex++) { BaseTotalRawSize += BaseFolderContent.RawSizes[PathIndex]; } double KeptPercent = BaseTotalRawSize > 0 ? (100.0 * (BaseTotalRawSize - RemovedSize)) / BaseTotalRawSize : 0; ZEN_CONSOLE("File diff : {} ({}) removed, {} ({}) added, {} ({} {:.1f}%) kept", RemovedHashes.size(), NiceBytes(RemovedSize), AddedHashes.size(), NiceBytes(AddedSize), BaseFolderContent.Paths.size() - RemovedHashes.size(), NiceBytes(BaseTotalRawSize - RemovedSize), KeptPercent); uint64_t CompareTotalRawSize = 0; uint64_t FoundChunkCount = 0; uint64_t FoundChunkSize = 0; uint64_t NewChunkCount = 0; uint64_t NewChunkSize = 0; const ChunkedContentLookup BaseFolderLookup = BuildChunkedContentLookup(BaseFolderContent); for (uint32_t ChunkIndex = 0; ChunkIndex < CompareFolderContent.ChunkedContent.ChunkHashes.size(); ChunkIndex++) { const IoHash& ChunkHash = CompareFolderContent.ChunkedContent.ChunkHashes[ChunkIndex]; if (BaseFolderLookup.ChunkHashToChunkIndex.contains(ChunkHash)) { FoundChunkCount++; FoundChunkSize += CompareFolderContent.ChunkedContent.ChunkRawSizes[ChunkIndex]; } else { NewChunkCount++; NewChunkSize += CompareFolderContent.ChunkedContent.ChunkRawSizes[ChunkIndex]; } CompareTotalRawSize += CompareFolderContent.ChunkedContent.ChunkRawSizes[ChunkIndex]; } double FoundPercent = CompareTotalRawSize > 0 ? (100.0 * FoundChunkSize) / CompareTotalRawSize : 0; double NewPercent = CompareTotalRawSize > 0 ? (100.0 * NewChunkSize) / CompareTotalRawSize : 0; ZEN_CONSOLE("Chunk diff: {} ({} {:.1f}%) out of {} ({}) chunks in {} ({}) base chunks. Added {} ({} {:.1f}%) chunks.", FoundChunkCount, NiceBytes(FoundChunkSize), FoundPercent, CompareFolderContent.ChunkedContent.ChunkHashes.size(), NiceBytes(CompareTotalRawSize), BaseFolderContent.ChunkedContent.ChunkHashes.size(), NiceBytes(BaseTotalRawSize), NewChunkCount, NiceBytes(NewChunkSize), NewPercent); ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::Cleanup, TaskSteps::StepCount); } } // namespace builds_impl ////////////////////////////////////////////////////////////////////////////////////////////////////// // BuildsCommand — Option-adding helpers // void BuildsCommand::AddSystemOptions(cxxopts::Options& Ops) { Ops.add_option("", "", "system-dir", "Specify system root", cxxopts::value(m_SystemRootDir), ""); Ops.add_option("", "", "use-sparse-files", "Enable use of sparse files when writing large files. Defaults to true.", cxxopts::value(m_UseSparseFiles), ""); } void BuildsCommand::AddCloudOptions(cxxopts::Options& Ops) { m_AuthOptions.AddOptions(Ops); Ops.add_option("cloud build", "", "override-host", "Cloud Builds URL", cxxopts::value(m_OverrideHost), ""); Ops.add_option("cloud build", "", "url", "Cloud Builds host url (legacy - use --override-host)", cxxopts::value(m_OverrideHost), ""); Ops.add_option("cloud build", "", "cloud-url", "Cloud Artifact URL", cxxopts::value(m_Url), ""); Ops.add_option("cloud build", "", "host", "Cloud Builds host", cxxopts::value(m_Host), ""); Ops.add_option("cloud build", "", "assume-http2", "Assume that the builds endpoint is a HTTP/2 endpoint skipping HTTP/1.1 upgrade handshake", cxxopts::value(m_AssumeHttp2), ""); Ops.add_option("cloud build", "", "verbose-http", "Enable verbose option for http client", cxxopts::value(m_VerboseHttp), ""); Ops.add_option("cloud build", "", "namespace", "Builds Storage namespace", cxxopts::value(m_Namespace), ""); Ops.add_option("cloud build", "", "bucket", "Builds Storage bucket", cxxopts::value(m_Bucket), ""); Ops.add_option("cloud build", "", "allow-redirect", "Allow redirect of requests", cxxopts::value(m_AllowRedirect), ""); } void BuildsCommand::AddFileOptions(cxxopts::Options& Ops) { Ops.add_option("filestorage", "", "storage-path", "Builds Storage Path", cxxopts::value(m_StoragePath), ""); Ops.add_option("filestorage", "", "json-metadata", "Write build, part and block metadata as .json files in addition to .cb files", cxxopts::value(m_WriteMetadataAsJson), ""); } void BuildsCommand::AddCacheOptions(cxxopts::Options& Ops) { Ops.add_option("cache", "", "zen-cache-host", "Host ip and port for zen builds cache", cxxopts::value(m_ZenCacheHost), ""); } void BuildsCommand::AddOutputOptions(cxxopts::Options& Ops) { Ops.add_option("output", "", "plain-progress", "Show progress using plain output", cxxopts::value(m_PlainProgress), ""); Ops.add_option("output", "", "log-progress", "Write @progress style progress to output", cxxopts::value(m_LogProgress), ""); Ops.add_option("output", "", "verbose", "Enable verbose console output", cxxopts::value(m_Verbose), ""); Ops.add_option("output", "", "quiet", "Suppress non-essential output", cxxopts::value(m_Quiet), ""); } void BuildsCommand::AddWorkerOptions(cxxopts::Options& Ops) { Ops.add_option("", "", "boost-worker-count", "Increase the number of worker threads - may cause computer to be less responsive", cxxopts::value(m_BoostWorkerCount), ""); Ops.add_option("", "", "boost-worker-memory", "Increase the limit where we write downloaded data to temporary storage to conserve space - may cause computer to " "be less responsive due to high memory usage", cxxopts::value(m_BoostWorkerMemory), ""); Ops.add_option("", "", "boost-workers", "Enables both 'boost-worker-count' and 'boost-worker-memory' - may cause computer to be less responsive", cxxopts::value(m_BoostWorkers), ""); } void BuildsCommand::AddZenFolderOptions(cxxopts::Options& Ops) { Ops.add_option("", "", "zen-folder-path", fmt::format("Path to zen state and temp folders. Defaults to [--local-path/]{}", builds_impl::ZenFolderName), cxxopts::value(m_ZenFolderPath), ""); } void BuildsCommand::AddChunkingCacheOptions(cxxopts::Options& Ops) { Ops.add_option("", "", "chunking-cache-path", "Path to cache for chunking information of scanned files. Default is empty resulting in no caching", cxxopts::value(m_ChunkingCachePath), ""); } void BuildsCommand::AddWildcardOptions(cxxopts::Options& Ops) { Ops.add_option("", "", "wildcard", "Windows style wildcard(s) (using * and ?) to match file paths to include, separated by ;", cxxopts::value(m_IncludeWildcard), ""); Ops.add_option("", "", "exclude-wildcard", "Windows style wildcard(s) (using * and ?) to match file paths to exclude, separated by ;. Applied after --wildcard " "include filter", cxxopts::value(m_ExcludeWildcard), ""); } void BuildsCommand::AddExcludeFolderOption(cxxopts::Options& Ops) { Ops.add_option("", "", "exclude-folders", "Names of folders to exclude, separated by ;", cxxopts::value(m_ExcludeFolders), ""); } void BuildsCommand::AddExcludeExtensionsOption(cxxopts::Options& Ops) { Ops.add_option("", "", "exclude-extensions", "Extensions to exclude, separated by ;" "include filter", cxxopts::value(m_ExcludeExtensions), ""); } void BuildsCommand::AddMultipartOptions(cxxopts::Options& Ops) { Ops.add_option("", "", "allow-multipart", "Allow large attachments to be transfered using multipart protocol. Defaults to true.", cxxopts::value(m_AllowMultiparts), ""); } void BuildsCommand::AddPartialBlockRequestOptions(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), ""); } void BuildsCommand::AddAppendNewContentOptions(cxxopts::Options& Ops) { Ops.add_option("", "", "append", "Decides if the remote data should replace or append to the current local data.\n" " false = the local content will be replaced by the remote content\n" " true = the remote data will be overlayed on top of local data\n" "Defaults to false.", cxxopts::value(m_AppendNewContent), ""); } BuildsCommand::BuildsCommand() : m_ListNamespacesSubCmd(*this) , m_ListSubCmd(*this) , m_ListBlocksSubCmd(*this) , m_UploadSubCmd(*this) , m_DownloadSubCmd(*this) , m_LsSubCmd(*this) , m_DiffSubCmd(*this) , m_FetchBlobSubCmd(*this) , m_PrimeCacheSubCmd(*this) , m_PauseSubCmd(*this) , m_ResumeSubCmd(*this) , m_AbortSubCmd(*this) , m_ValidatePartSubCmd(*this) , m_TestSubCmd(*this) , m_MultiTestDownloadSubCmd(*this) { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("__hidden__", "", "subcommand", "", cxxopts::value(m_SubCommand)->default_value(""), ""); m_Options.parse_positional({"subcommand"}); AddSubCommand(m_ListNamespacesSubCmd); AddSubCommand(m_ListSubCmd); AddSubCommand(m_ListBlocksSubCmd); AddSubCommand(m_UploadSubCmd); AddSubCommand(m_DownloadSubCmd); AddSubCommand(m_LsSubCmd); AddSubCommand(m_DiffSubCmd); AddSubCommand(m_FetchBlobSubCmd); AddSubCommand(m_PrimeCacheSubCmd); AddSubCommand(m_PauseSubCmd); AddSubCommand(m_ResumeSubCmd); AddSubCommand(m_AbortSubCmd); AddSubCommand(m_ValidatePartSubCmd); AddSubCommand(m_TestSubCmd); AddSubCommand(m_MultiTestDownloadSubCmd); } BuildsCommand::~BuildsCommand() = default; bool BuildsCommand::OnParentOptionsParsed(const ZenCliOptions& /*GlobalOptions*/) { using namespace builds_impl; signal(SIGINT, SignalCallbackHandler); #if ZEN_PLATFORM_WINDOWS signal(SIGBREAK, SignalCallbackHandler); #endif // ZEN_PLATFORM_WINDOWS // Validate output options if (m_Verbose && m_Quiet) { throw OptionParseException("'--verbose' conflicts with '--quiet'", {}); } if (m_LogProgress && m_PlainProgress) { throw OptionParseException("'--plain-progress' conflicts with '--log-progress'", {}); } if (m_LogProgress && m_Quiet) { throw OptionParseException("'--quiet' conflicts with '--log-progress'", {}); } if (m_PlainProgress && m_Quiet) { throw OptionParseException("'--quiet' conflicts with '--plain-progress'", {}); } IsVerbose = m_Verbose; IsQuiet = m_Quiet; if (m_LogProgress) { ProgressMode = ProgressBar::Mode::Log; } else if (m_PlainProgress) { ProgressMode = ProgressBar::Mode::Plain; } else if (m_Verbose) { ProgressMode = ProgressBar::Mode::Plain; } else if (IsQuiet) { ProgressMode = ProgressBar::Mode::Quiet; } else { ProgressMode = ProgressBar::Mode::Pretty; } if (m_BoostWorkers) { m_BoostWorkerCount = true; m_BoostWorkerMemory = true; } // Parse system options if (m_SystemRootDir.empty()) { m_SystemRootDir = PickDefaultSystemRootDirectory(); } MakeSafeAbsolutePathInPlace(m_SystemRootDir); UseSparseFiles = m_UseSparseFiles; return true; } void BuildsCommand::ParseStorageOptions(std::string& BuildId, bool RequireNamespace, bool RequireBucket, cxxopts::Options& SubOpts) { if (!m_Url.empty()) { if (!m_Host.empty()) { throw OptionParseException(fmt::format("'--host' ('{}') conflicts with '--url' ('{}')", m_Host, m_Url), SubOpts.help()); } if (!m_Bucket.empty()) { throw OptionParseException(fmt::format("'--bucket' ('{}') conflicts with '--url' ('{}')", m_Bucket, m_Url), SubOpts.help()); } if (!BuildId.empty()) { throw OptionParseException(fmt::format("'--buildid' ('{}') conflicts with '--url' ('{}')", BuildId, m_Url), SubOpts.help()); } if (!ParseBuildStorageUrl(m_Url, m_Host, m_Namespace, m_Bucket, BuildId)) { throw OptionParseException("'--url' ('{}') is malformed, it does not match the Cloud Artifact URL format", SubOpts.help()); } } if (!m_OverrideHost.empty() || !m_Host.empty()) { if (!m_StoragePath.empty()) { throw OptionParseException( fmt::format("'--storage-path' ('{}') conflicts with '--host'/'--url'/'--override-host' options", m_StoragePath), SubOpts.help()); } if (RequireNamespace && m_Namespace.empty()) { throw OptionParseException("'--namespace' is required", SubOpts.help()); } if (RequireBucket && m_Bucket.empty()) { throw OptionParseException("'--bucket' is required", SubOpts.help()); } } else if (m_StoragePath.empty()) { throw OptionParseException("'--host', '--url', '--override-host' or '--storage-path' is required", SubOpts.help()); } MakeSafeAbsolutePathInPlace(m_StoragePath); } StorageInstance BuildsCommand::CreateBuildStorage(BuildStorageBase::Statistics& StorageStats, BuildStorageCache::Statistics& StorageCacheStats, const std::filesystem::path& TempPath, std::string& BuildId, bool RequireNamespace, bool RequireBucket, bool BoostCacheBackgroundWorkerPool, std::unique_ptr& Auth, cxxopts::Options& SubOpts) { using namespace builds_impl; ParseStorageOptions(BuildId, RequireNamespace, RequireBucket, SubOpts); HttpClientSettings ClientSettings{.LogCategory = "httpbuildsclient", .AssumeHttp2 = m_AssumeHttp2, .AllowResume = true, .RetryCount = 2, .Verbose = m_VerboseHttp, .MaximumInMemoryDownloadSize = GetMaxMemoryBufferSize(DefaultMaxChunkBlockSize, m_BoostWorkerMemory)}; std::string StorageDescription; std::string CacheDescription; StorageInstance Result; if (!m_Host.empty() || !m_OverrideHost.empty()) { m_AuthOptions.ParseOptions(SubOpts, m_SystemRootDir, ClientSettings, m_Host.empty() ? m_OverrideHost : m_Host, Auth, IsQuiet, /*Hidden*/ false, m_Verbose); BuildStorageResolveResult ResolveRes = ResolveBuildStorage(*CreateConsoleLogOutput(ProgressMode), ClientSettings, m_Host, m_OverrideHost, m_ZenCacheHost, ZenCacheResolveMode::All, m_Verbose); if (!ResolveRes.Cloud.Address.empty()) { ClientSettings.AssumeHttp2 = ResolveRes.Cloud.AssumeHttp2; Result.BuildStorageHttp = std::make_unique(ResolveRes.Cloud.Address, ClientSettings, []() { return AbortFlag.load(); }); Result.BuildStorage = CreateJupiterBuildStorage(Log(), *Result.BuildStorageHttp, StorageStats, m_Namespace, m_Bucket, m_AllowRedirect, TempPath / "storage"); Result.BuildStorageHost = ResolveRes.Cloud; uint64_t HostLatencyNs = ResolveRes.Cloud.LatencySec >= 0 ? uint64_t(ResolveRes.Cloud.LatencySec * 1000000000.0) : 0; StorageDescription = fmt::format("Cloud {}{}. SessionId: '{}'. Namespace '{}', Bucket '{}'. Latency: {}", ResolveRes.Cloud.Name, (ResolveRes.Cloud.Address == ResolveRes.Cloud.Name) ? "" : fmt::format(" {}", ResolveRes.Cloud.Address), Result.BuildStorageHttp->GetSessionId(), m_Namespace, m_Bucket, NiceLatencyNs(HostLatencyNs)); if (!ResolveRes.Cache.Address.empty()) { Result.CacheHttp = std::make_unique( ResolveRes.Cache.Address, HttpClientSettings{ .LogCategory = "httpcacheclient", .ConnectTimeout = std::chrono::milliseconds{3000}, .Timeout = std::chrono::milliseconds{30000}, .AssumeHttp2 = ResolveRes.Cache.AssumeHttp2, .AllowResume = true, .RetryCount = 0, .Verbose = m_VerboseHttp, .MaximumInMemoryDownloadSize = GetMaxMemoryBufferSize(DefaultMaxChunkBlockSize, m_BoostWorkerMemory)}, []() { return AbortFlag.load(); }); Result.CacheStorage = CreateZenBuildStorageCache(*Result.CacheHttp, StorageCacheStats, m_Namespace, m_Bucket, TempPath / "zencache", BoostCacheBackgroundWorkerPool ? GetSmallWorkerPool(EWorkloadType::Background) : GetTinyWorkerPool(EWorkloadType::Background)); Result.CacheHost = ResolveRes.Cache; uint64_t CacheLatencyNs = ResolveRes.Cache.LatencySec >= 0 ? uint64_t(ResolveRes.Cache.LatencySec * 1000000000.0) : 0; CacheDescription = fmt::format("Zen {}{}. SessionId: '{}'. Latency: {}", ResolveRes.Cache.Name, (ResolveRes.Cache.Address == ResolveRes.Cache.Name) ? "" : fmt::format(" {}", ResolveRes.Cache.Address), Result.CacheHttp->GetSessionId(), NiceLatencyNs(CacheLatencyNs)); if (!m_Namespace.empty()) { CacheDescription += fmt::format(". Namespace '{}'", m_Namespace); } if (!m_Bucket.empty()) { CacheDescription += fmt::format(" Bucket '{}'", m_Bucket); } } } } else if (!m_StoragePath.empty()) { StorageDescription = fmt::format("folder {}", m_StoragePath); Result.BuildStorage = CreateFileBuildStorage(m_StoragePath, StorageStats, false, DefaultLatency, DefaultDelayPerKBSec); Result.BuildStorageHost = BuildStorageResolveResult::Host{.Address = m_StoragePath.generic_string(), .Name = "Disk", .LatencySec = 1.0 / 100000, // 1 us .Caps = {.MaxRangeCountPerRequest = 2048u}}; if (!m_ZenCacheHost.empty()) { ZenCacheEndpointTestResult TestResult = TestZenCacheEndpoint(m_ZenCacheHost, m_AssumeHttp2, m_VerboseHttp); if (TestResult.Success) { Result.CacheHttp = std::make_unique( m_ZenCacheHost, HttpClientSettings{ .LogCategory = "httpcacheclient", .ConnectTimeout = std::chrono::milliseconds{3000}, .Timeout = std::chrono::milliseconds{30000}, .AssumeHttp2 = m_AssumeHttp2, .AllowResume = true, .RetryCount = 0, .Verbose = m_VerboseHttp, .MaximumInMemoryDownloadSize = GetMaxMemoryBufferSize(DefaultMaxChunkBlockSize, m_BoostWorkerMemory)}, []() { return AbortFlag.load(); }); Result.CacheStorage = CreateZenBuildStorageCache(*Result.CacheHttp, StorageCacheStats, m_Namespace, m_Bucket, TempPath / "zencache", BoostCacheBackgroundWorkerPool ? GetSmallWorkerPool(EWorkloadType::Background) : GetTinyWorkerPool(EWorkloadType::Background)); Result.CacheHost = BuildStorageResolveResult::Host{.Address = m_ZenCacheHost, .Name = m_ZenCacheHost, .AssumeHttp2 = m_AssumeHttp2, .LatencySec = TestResult.LatencySeconds, .Caps = {.MaxRangeCountPerRequest = TestResult.MaxRangeCountPerRequest}}; CacheDescription = fmt::format("Zen {}. SessionId: '{}'", Result.CacheHost.Name, Result.CacheHttp->GetSessionId()); if (!m_Namespace.empty()) { CacheDescription += fmt::format(". Namespace '{}'", m_Namespace); } if (!m_Bucket.empty()) { CacheDescription += fmt::format(" Bucket '{}'", m_Bucket); } } } } else { throw OptionParseException("'--host', '--url', '--override-host' or '--storage-path' is required", SubOpts.help()); } if (!IsQuiet) { ZEN_CONSOLE("Remote: {}", StorageDescription); if (!Result.CacheHost.Name.empty()) { ZEN_CONSOLE("Cache : {}", CacheDescription); } } return Result; } Oid BuildsCommand::ParseBuildId(const std::string& BuildIdStr, cxxopts::Options& SubOpts) { if (BuildIdStr.length() != Oid::StringLength) { throw OptionParseException( fmt::format("'--build-id' ('{}') is malformed, it must be {} characters long", BuildIdStr, Oid::StringLength), SubOpts.help()); } else if (Oid BuildId = Oid::FromHexString(BuildIdStr); BuildId == Oid::Zero) { throw OptionParseException(fmt::format("'--build-id' ('{}') is invalid", BuildIdStr), SubOpts.help()); } else { return BuildId; } } Oid BuildsCommand::ParseBuildPartId(const std::string& BuildPartIdStr, cxxopts::Options& SubOpts) { if (BuildPartIdStr.length() != Oid::StringLength) { throw OptionParseException( fmt::format("'--build-id' ('{}') is malformed, it must be {} characters long", BuildPartIdStr, Oid::StringLength), SubOpts.help()); } else if (Oid BuildPartId = Oid::FromHexString(BuildPartIdStr); BuildPartId == Oid::Zero) { throw OptionParseException(fmt::format("'--build-id' ('{}') is malformed", BuildPartIdStr), SubOpts.help()); } else { return BuildPartId; } } std::vector BuildsCommand::ParseBuildPartIds(const std::vector& BuildPartIdStrs, cxxopts::Options& SubOpts) { std::vector BuildPartIds; for (const std::string& BuildPartId : BuildPartIdStrs) { BuildPartIds.push_back(Oid::TryFromHexString(RemoveQuotes(BuildPartId))); if (BuildPartIds.back() == Oid::Zero) { throw OptionParseException(fmt::format("'--build-part-id' ('{}') is malformed", BuildPartId), SubOpts.help()); } } return BuildPartIds; } std::vector BuildsCommand::ParseBuildPartNames(const std::vector& BuildPartNameStrs, cxxopts::Options& SubOpts) { std::vector BuildPartNames; for (const std::string& BuildPartName : BuildPartNameStrs) { BuildPartNames.push_back(std::string(RemoveQuotes(BuildPartName))); if (BuildPartNames.back().empty()) { throw OptionParseException(fmt::format("'--build-part-names' ('{}') is invalid", BuildPartName), SubOpts.help()); } } return BuildPartNames; } CbObject BuildsCommand::ParseBuildMetadata(bool CreateBuild, std::filesystem::path& BuildMetadataPath, const std::string& BuildMetadata, cxxopts::Options& SubOpts) { if (CreateBuild) { if (BuildMetadataPath.empty() && BuildMetadata.empty()) { throw OptionParseException("'--metadata-path' or '--metadata' is required", SubOpts.help()); } if (!BuildMetadataPath.empty() && !BuildMetadata.empty()) { throw OptionParseException( fmt::format("'--metadata-path' ('{}') conflicts with '--metadata' ('{}')", BuildMetadataPath.string(), BuildMetadata), SubOpts.help()); } if (!BuildMetadataPath.empty()) { MakeSafeAbsolutePathInPlace(BuildMetadataPath); IoBuffer MetaDataJson = ReadFile(BuildMetadataPath).Flatten(); std::string_view Json(reinterpret_cast(MetaDataJson.GetData()), MetaDataJson.GetSize()); std::string JsonError; CbObject MetaData = LoadCompactBinaryFromJson(Json, JsonError).AsObject(); if (!JsonError.empty()) { throw std::runtime_error( fmt::format("build metadata file '{}' is malformed. Reason: '{}'", BuildMetadataPath.string(), JsonError)); } return MetaData; } if (!BuildMetadata.empty()) { CbObjectWriter MetaDataWriter(1024); ForEachStrTok(BuildMetadata, ';', [&](std::string_view Pair) { size_t SplitPos = Pair.find('='); if (SplitPos == std::string::npos || SplitPos == 0) { throw std::runtime_error(fmt::format("build metadata key-value pair '{}' is malformed", Pair)); } MetaDataWriter.AddString(Pair.substr(0, SplitPos), Pair.substr(SplitPos + 1)); return true; }); return MetaDataWriter.Save(); } } else { if (!BuildMetadataPath.empty()) { throw OptionParseException("'--metadata-path' requires '--create-build'", SubOpts.help()); } if (!BuildMetadata.empty()) { throw OptionParseException("'--metadata' requires '--create-build'", SubOpts.help()); } } return {}; } void BuildsCommand::ParsePath(std::filesystem::path& Path, cxxopts::Options& SubOpts) { if (Path.empty()) { throw OptionParseException("'--local-path' is required", SubOpts.help()); } MakeSafeAbsolutePathInPlace(Path); } IoHash BuildsCommand::ParseBlobHash(const std::string& BlobHashStr, cxxopts::Options& SubOpts) { if (BlobHashStr.empty()) { throw OptionParseException("'--blob-hash' is required", SubOpts.help()); } if (BlobHashStr.length() != IoHash::StringLength) { throw OptionParseException( fmt::format("'--blob-hash' ('{}') is malformed, it must be {} characters long", BlobHashStr, IoHash::StringLength), SubOpts.help()); } IoHash BlobHash; if (!IoHash::TryParse(BlobHashStr, BlobHash)) { throw OptionParseException(fmt::format("'--blob-hash' ('{}') is malformed", BlobHashStr), SubOpts.help()); } return BlobHash; } void BuildsCommand::ParseFileFilters(std::vector& OutIncludeWildcards, std::vector& OutExcludeWildcards) { auto SplitAndAppendWildcard = [](const std::string_view Wildcard, std::vector& Output) { ForEachStrTok(Wildcard, ';', [&Output](std::string_view Wildcard) { if (!Wildcard.empty()) { std::string CleanWildcard(ToLower(Wildcard)); for (auto It = begin(CleanWildcard); It != end(CleanWildcard); It++) { if (*It == '\\') { *It = '/'; } } if (CleanWildcard.starts_with("./") || CleanWildcard.starts_with(".\\")) { CleanWildcard = CleanWildcard.substr(2); } Output.emplace_back(std::move(CleanWildcard)); } return true; }); }; SplitAndAppendWildcard(m_IncludeWildcard, OutIncludeWildcards); SplitAndAppendWildcard(m_ExcludeWildcard, OutExcludeWildcards); } void BuildsCommand::ParseExcludeFolderAndExtension(std::vector& OutExcludeFolders, std::vector& OutExcludeExtensions) { auto SplitAndAppendExclusion = [](const std::string_view Input, std::vector& Output) { ForEachStrTok(Input, ";,", [&Output](std::string_view Exclusion) { if (!Exclusion.empty()) { std::string CleanExclusion(ToLower(Exclusion)); if (CleanExclusion.length() > 2 && CleanExclusion.front() == '"' && CleanExclusion.back() == '"') { CleanExclusion = CleanExclusion.substr(1, CleanExclusion.length() - 2); } Output.emplace_back(std::move(CleanExclusion)); } return true; }); }; SplitAndAppendExclusion(m_ExcludeFolders, OutExcludeFolders); SplitAndAppendExclusion(m_ExcludeExtensions, OutExcludeExtensions); } void BuildsCommand::ResolveZenFolderPath(const std::filesystem::path& DefaultPath) { if (m_ZenFolderPath.empty()) { m_ZenFolderPath = DefaultPath; } MakeSafeAbsolutePathInPlace(m_ZenFolderPath); } EPartialBlockRequestMode BuildsCommand::ParseAllowPartialBlockRequests(bool PrimeCacheOnly, cxxopts::Options& SubOpts) { if (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), SubOpts.help()); } return Mode; } void BuildsCommand::ParseZenProcessId(int& ZenProcessId) { if (ZenProcessId == -1) { const std::filesystem::path RunningExecutablePath = GetRunningExecutablePath(); ProcessHandle RunningProcess; std::error_code Ec = FindProcess(RunningExecutablePath, RunningProcess, /*IncludeSelf*/ false); if (Ec) { throw std::runtime_error(fmt::format("Failed finding process running '{}', reason: '{}'", RunningExecutablePath, Ec.message())); } if (!RunningProcess.IsValid()) { throw std::runtime_error(fmt::format("Unable to find a running instance of the zen executable '{}'", RunningExecutablePath)); } ZenProcessId = RunningProcess.Pid(); } } ////////////////////////////////////////////////////////////////////////// // --------------------------------------------------------------------------- // Subcommand implementations // --------------------------------------------------------------------------- BuildsListNamespacesSubCmd::BuildsListNamespacesSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("list-namespaces", "List all namespaces and optionally their buckets") , m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddSystemOptions(Opts); Parent.AddCloudOptions(Opts); Parent.AddFileOptions(Opts); Parent.AddOutputOptions(Opts); Parent.AddZenFolderOptions(Opts); Opts.add_option("", "", "recursive", "Enable fetch of buckets within namespaces also", cxxopts::value(m_Recursive), ""); Opts.add_option("", "", "result-path", "Path to json (.json) or compactbinary (.cbo) to write output result to. Default is output to console", cxxopts::value(m_ResultPath), ""); Opts.parse_positional({"result-path"}); Opts.positional_help("result-path"); } void BuildsListNamespacesSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; if (!m_ResultPath.empty()) { if (!IsQuiet) { ZenCmdBase::LogExecutableVersionAndPid(); } } BuildStorageBase::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; m_Parent.ResolveZenFolderPath(std::filesystem::current_path() / ZenFolderName); CreateDirectories(m_Parent.GetZenFolderPath()); auto _ = MakeGuard([this]() { CleanAndRemoveDirectory(GetSmallWorkerPool(EWorkloadType::Burst), m_Parent.GetZenFolderPath()); }); std::unique_ptr Auth; std::string DummyBuildId; StorageInstance Storage = m_Parent.CreateBuildStorage(StorageStats, StorageCacheStats, ZenTempFolderPath(m_Parent.GetZenFolderPath()), DummyBuildId, /*RequireNamespace*/ false, /*RequireBucket*/ false, /*BoostCacheBackgroundWorkerPool*/ false, Auth, Opts); CbObject Response = Storage.BuildStorage->ListNamespaces(m_Recursive); ZEN_ASSERT(ValidateCompactBinary(Response.GetView(), CbValidateMode::Default) == CbValidateError::None); if (m_ResultPath.empty()) { ExtendableStringBuilder<1024> SB; CompactBinaryToJson(Response.GetView(), SB); ZEN_CONSOLE("{}", SB.ToView()); } else { std::filesystem::path ResultPath = MakeSafeAbsolutePath(m_ResultPath); if (ToLower(ResultPath.extension().string()) == ".cbo") { MemoryView ResponseView = Response.GetView(); WriteFile(ResultPath, IoBuffer(IoBuffer::Wrap, ResponseView.GetData(), ResponseView.GetSize())); } else { ExtendableStringBuilder<1024> SB; CompactBinaryToJson(Response.GetView(), SB); WriteFile(ResultPath, IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size())); } } } BuildsListSubCmd::BuildsListSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("list", "List builds matching a query"), m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddSystemOptions(Opts); Parent.AddCloudOptions(Opts); Parent.AddFileOptions(Opts); Parent.AddOutputOptions(Opts); Parent.AddZenFolderOptions(Opts); Opts.add_option("", "", "query-path", "Path to json or compactbinary file containing list query", cxxopts::value(m_QueryPath), ""); Opts.add_option("", "", "result-path", "Path to json (.json) or compactbinary (.cbo) to write output result to. Default is output to console", cxxopts::value(m_ResultPath), ""); Opts.parse_positional({"query-path", "result-path"}); Opts.positional_help("query-path result-path"); } void BuildsListSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; MakeSafeAbsolutePathInPlace(m_QueryPath); MakeSafeAbsolutePathInPlace(m_ResultPath); if (!m_ResultPath.empty()) { if (!IsQuiet) { ZenCmdBase::LogExecutableVersionAndPid(); } } std::string JsonQuery; if (m_QueryPath.empty()) { CbObjectWriter QueryWriter; QueryWriter.BeginObject("query"); QueryWriter.EndObject(); // query CbObject QueryObject = QueryWriter.Save(); ExtendableStringBuilder<64> SB; CompactBinaryToJson(QueryObject, SB); JsonQuery = SB.ToString(); } else { if (ToLower(m_QueryPath.extension().string()) == ".cbo") { CbObject QueryObject = LoadCompactBinaryObject(IoBufferBuilder::MakeFromFile(m_QueryPath)); ExtendableStringBuilder<64> SB; CompactBinaryToJson(QueryObject, SB); JsonQuery = SB.ToString(); } else { IoBuffer MetaDataJson = ReadFile(m_QueryPath).Flatten(); std::string_view Json(reinterpret_cast(MetaDataJson.GetData()), MetaDataJson.GetSize()); std::string JsonError; CbObject QueryObject = LoadCompactBinaryFromJson(Json, JsonError) .AsObject(); // We try to convert it so it is at least reaonably verified in format if (!JsonError.empty()) { throw std::runtime_error( fmt::format("build metadata file '{}' is malformed. Reason: '{}'", m_QueryPath.string(), JsonError)); } JsonQuery = std::string(Json); } } BuildStorageBase::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; m_Parent.ResolveZenFolderPath(std::filesystem::current_path() / ZenFolderName); CreateDirectories(m_Parent.GetZenFolderPath()); auto _ = MakeGuard([this]() { CleanAndRemoveDirectory(GetSmallWorkerPool(EWorkloadType::Burst), m_Parent.GetZenFolderPath()); }); std::unique_ptr Auth; std::string DummyBuildId; StorageInstance Storage = m_Parent.CreateBuildStorage(StorageStats, StorageCacheStats, ZenTempFolderPath(m_Parent.GetZenFolderPath()), DummyBuildId, /*RequireNamespace*/ true, /*RequireBucket*/ false, /*BoostCacheBackgroundWorkerPool*/ false, Auth, Opts); CbObject Response = Storage.BuildStorage->ListBuilds(JsonQuery); ZEN_ASSERT(ValidateCompactBinary(Response.GetView(), CbValidateMode::Default) == CbValidateError::None); if (m_ResultPath.empty()) { ExtendableStringBuilder<1024> SB; CompactBinaryToJson(Response.GetView(), SB); ZEN_CONSOLE("{}", SB.ToView()); } else { if (ToLower(m_ResultPath.extension().string()) == ".cbo") { MemoryView ResponseView = Response.GetView(); WriteFile(m_ResultPath, IoBuffer(IoBuffer::Wrap, ResponseView.GetData(), ResponseView.GetSize())); } else { ExtendableStringBuilder<1024> SB; CompactBinaryToJson(Response.GetView(), SB); WriteFile(m_ResultPath, IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size())); } } } BuildsListBlocksSubCmd::BuildsListBlocksSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("list-blocks", "List blocks for a build") , m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddSystemOptions(Opts); Parent.AddCloudOptions(Opts); Parent.AddZenFolderOptions(Opts); Opts.add_option("", "", "build-id", "Build Id", cxxopts::value(m_BuildId), ""); Opts.add_option("", "", "result-path", "Path to json (.json) or compactbinary (.cbo) to write output result to. Default is output to console", cxxopts::value(m_ResultPath), ""); Opts.add_option("", "", "max-count", "Maximum number of blocks to list", cxxopts::value(m_MaxCount), ""); Opts.parse_positional({"build-id"}); Opts.positional_help("build-id"); } void BuildsListBlocksSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; MakeSafeAbsolutePathInPlace(m_ResultPath); if (!m_ResultPath.empty()) { if (!IsQuiet) { ZenCmdBase::LogExecutableVersionAndPid(); } } if (m_MaxCount == 0) { throw OptionParseException(fmt::format("'--max-count' ('{}') is invalid", m_MaxCount), Opts.help()); } BuildStorageBase::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; m_Parent.ResolveZenFolderPath(std::filesystem::current_path() / ZenFolderName); CreateDirectories(m_Parent.GetZenFolderPath()); auto _ = MakeGuard([this]() { CleanAndRemoveDirectory(GetSmallWorkerPool(EWorkloadType::Burst), m_Parent.GetZenFolderPath()); }); std::unique_ptr Auth; StorageInstance Storage = m_Parent.CreateBuildStorage(StorageStats, StorageCacheStats, ZenTempFolderPath(m_Parent.GetZenFolderPath()), m_BuildId, /*RequireNamespace*/ true, /*RequireBucket*/ true, /*BoostCacheBackgroundWorkerPool*/ false, Auth, Opts); const Oid BuildId = m_Parent.ParseBuildId(m_BuildId, Opts); CbObject Response = Storage.BuildStorage->FindBlocks(BuildId, m_MaxCount); ZEN_ASSERT(ValidateCompactBinary(Response.GetView(), CbValidateMode::Default) == CbValidateError::None); std::vector BlockDescriptions = ParseChunkBlockDescriptionList(Response); if (!IsQuiet) { ZEN_CONSOLE("Response contains {} block", BlockDescriptions.size()); } if (m_ResultPath.empty()) { ExtendableStringBuilder<1024> SB; CompactBinaryToJson(Response.GetView(), SB); ForEachStrTok(SB.ToView(), '\n', [](std::string_view Row) { ZEN_CONSOLE("{}", Row); return true; }); } else { if (ToLower(m_ResultPath.extension().string()) == ".cbo") { MemoryView ResponseView = Response.GetView(); WriteFile(m_ResultPath, IoBuffer(IoBuffer::Wrap, ResponseView.GetData(), ResponseView.GetSize())); } else { ExtendableStringBuilder<1024> SB; CompactBinaryToJson(Response.GetView(), SB); WriteFile(m_ResultPath, IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size())); } } } BuildsUploadSubCmd::BuildsUploadSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("upload", "Upload a folder to build storage") , m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddSystemOptions(Opts); Parent.AddCloudOptions(Opts); Parent.AddFileOptions(Opts); Parent.AddOutputOptions(Opts); Parent.AddCacheOptions(Opts); Parent.AddWorkerOptions(Opts); Parent.AddZenFolderOptions(Opts); Parent.AddExcludeFolderOption(Opts); Parent.AddExcludeExtensionsOption(Opts); Parent.AddChunkingCacheOptions(Opts); Opts.add_option("", "l", "local-path", "Root file system folder for build", cxxopts::value(m_Path), ""); Opts.add_option("", "", "create-build", "Set to true to create the containing build, if unset a builds-id must be given and the build already exist", cxxopts::value(m_CreateBuild), ""); Opts.add_option("", "", "build-id", "Build Id", cxxopts::value(m_BuildId), ""); Opts.add_option("", "", "build-part-id", "Build part Id, if not given it will be auto generated", cxxopts::value(m_BuildPartId), ""); Opts.add_option("", "", "build-part-name", "Name of the build part, if not given it will be be named after the directory name at end of local-path", cxxopts::value(m_BuildPartName), ""); Opts.add_option("", "", "metadata-path", "Path to json file that holds the metadata for the build. Requires the create-build option to be set", cxxopts::value(m_BuildMetadataPath), ""); Opts.add_option( "", "", "metadata", "Key-value pairs separated by ';' with build meta data. (key1=value1;key2=value2). Requires the create-build option to be set", cxxopts::value(m_BuildMetadata), ""); Opts.add_option("", "", "clean", "Ignore existing blocks", cxxopts::value(m_Clean), ""); Opts.add_option("", "", "block-min-reuse", "Percent of an existing block that must be relevant for it to be resused. Defaults to 85.", cxxopts::value(m_BlockReuseMinPercentLimit), ""); Opts.add_option("cache", "", "zen-cache-upload", "Upload data downloaded from remote host to zen cache", cxxopts::value(m_UploadToZenCache), ""); Parent.AddMultipartOptions(Opts); Opts.add_option("", "", "manifest-path", "Path to a text file with one line of [TAB] per file to include or a " "structured .json file describing the parts", cxxopts::value(m_ManifestPath), ""); Opts.add_option("", "", "verify", "Enable post upload verify of all uploaded data", cxxopts::value(m_PostUploadVerify), ""); Opts.add_option("", "", "find-max-block-count", "The maximum number of blocks we search for in the build context", cxxopts::value(m_FindBlockMaxCount), ""); Opts.parse_positional({"local-path", "build-id"}); Opts.positional_help("local-path build-id"); } void BuildsUploadSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; if (!IsQuiet) { ZenCmdBase::LogExecutableVersionAndPid(); } TransferThreadWorkers Workers(m_Parent.m_BoostWorkerCount, SingleThreaded); if (!IsQuiet) { ZEN_CONSOLE("{}", Workers.GetWorkersInfo()); } ZenState InstanceState; m_Parent.ParsePath(m_Path, Opts); BuildStorageBase::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; m_Parent.ResolveZenFolderPath(std::filesystem::current_path() / ZenFolderName); MakeSafeAbsolutePathInPlace(m_Parent.m_ChunkingCachePath); CreateDirectories(m_Parent.GetZenFolderPath()); auto _ = MakeGuard([this, &Workers]() { CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), m_Parent.GetZenFolderPath()); }); std::unique_ptr Auth; StorageInstance Storage = m_Parent.CreateBuildStorage(StorageStats, StorageCacheStats, ZenTempFolderPath(m_Parent.GetZenFolderPath()), m_BuildId, /*RequireNamespace*/ true, /*RequireBucket*/ true, /*BoostCacheBackgroundWorkerPool*/ false, Auth, Opts); if (m_BuildPartName.empty() && m_ManifestPath.empty()) { m_BuildPartName = m_Path.filename().string(); } const Oid BuildId = m_BuildId.empty() ? Oid::NewOid() : m_Parent.ParseBuildId(m_BuildId, Opts); if (m_BuildId.empty()) { m_BuildId = BuildId.ToString(); } Oid BuildPartId; if (!m_BuildPartId.empty()) { BuildPartId = m_Parent.ParseBuildPartId(m_BuildPartId, Opts); } CbObject MetaData = m_Parent.ParseBuildMetadata(m_CreateBuild, m_BuildMetadataPath, m_BuildMetadata, Opts); const std::filesystem::path TempDir = ZenTempFolderPath(m_Parent.GetZenFolderPath()); std::vector ExcludeFolders = DefaultExcludeFolders; std::vector ExcludeExtensions = DefaultExcludeExtensions; m_Parent.ParseExcludeFolderAndExtension(ExcludeFolders, ExcludeExtensions); std::unique_ptr ChunkController = CreateStandardChunkingController(StandardChunkingControllerSettings{}); std::unique_ptr ChunkCache = m_Parent.m_ChunkingCachePath.empty() ? CreateNullChunkingCache() : CreateDiskChunkingCache(m_Parent.m_ChunkingCachePath, *ChunkController, 256u * 1024u); std::unique_ptr Output(CreateConsoleLogOutput(ProgressMode)); std::vector> UploadedParts = UploadFolder(*Output, Workers, Storage, BuildId, BuildPartId, m_BuildPartName, m_Path, m_ManifestPath, MetaData, *ChunkController, *ChunkCache, UploadFolderOptions{.TempDir = TempDir, .FindBlockMaxCount = m_FindBlockMaxCount, .BlockReuseMinPercentLimit = m_BlockReuseMinPercentLimit, .AllowMultiparts = m_Parent.m_AllowMultiparts, .CreateBuild = m_CreateBuild, .IgnoreExistingBlocks = m_Clean, .UploadToZenCache = m_UploadToZenCache, .ExcludeFolders = ExcludeFolders, .ExcludeExtensions = ExcludeExtensions}); if (!AbortFlag) { if (m_PostUploadVerify) { for (const auto& Part : UploadedParts) { ValidateBuildPart(*Output, Workers, *Storage.BuildStorage, BuildId, Part.first, Part.second); } } } if (true) { if (!IsQuiet) { ZEN_CONSOLE( "{}:\n" "Read: {}\n" "Write: {}\n" "Requests: {}\n" "Avg Request Time: {}\n" "Avg I/O Time: {}", Storage.BuildStorageHost.Name, NiceBytes(StorageStats.TotalBytesRead.load()), NiceBytes(StorageStats.TotalBytesWritten.load()), StorageStats.TotalRequestCount.load(), StorageStats.TotalExecutionTimeUs.load() > 0 ? NiceTimeSpanMs(StorageStats.TotalExecutionTimeUs.load() / 1000 / StorageStats.TotalRequestCount.load()) : 0, StorageStats.TotalRequestCount.load() > 0 ? NiceTimeSpanMs(StorageStats.TotalRequestTimeUs.load() / 1000 / StorageStats.TotalRequestCount.load()) : 0); } } if (AbortFlag) { throw std::runtime_error("Upload aborted"); } } BuildsDownloadSubCmd::BuildsDownloadSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("download", "Download a build to a local folder") , m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddSystemOptions(Opts); Parent.AddCloudOptions(Opts); Parent.AddFileOptions(Opts); Parent.AddOutputOptions(Opts); Parent.AddCacheOptions(Opts); Parent.AddZenFolderOptions(Opts); Parent.AddWorkerOptions(Opts); Parent.AddWildcardOptions(Opts); Parent.AddAppendNewContentOptions(Opts); Parent.AddExcludeFolderOption(Opts); Opts.add_option("cache", "", "cache-prime-only", "Only download blobs missing in cache and upload to cache", cxxopts::value(m_PrimeCacheOnly), ""); Opts.add_option("", "l", "local-path", "Root file system folder for build", cxxopts::value(m_Path), ""); Opts.add_option("", "", "build-id", "Build Id", cxxopts::value(m_BuildId), ""); Opts.add_option("", "", "build-part-id", "Build part Ids list separated by ',', if no build-part-ids or build-part-names are given all parts will be downloaded", cxxopts::value(m_BuildPartIds), ""); Opts.add_option("", "", "build-part-name", "Name of the build parts list separated by ',', if no build-part-ids or build-part-names are given " "all parts will be downloaded", cxxopts::value(m_BuildPartNames), ""); Opts.add_option("", "", "clean", "Delete all data in target folder that is not part of the downloaded content", cxxopts::value(m_Clean), ""); Opts.add_option("", "", "force", "Force download of all content by ignoring any existing local content", cxxopts::value(m_Force), ""); Opts.add_option("cache", "", "zen-cache-upload", "Upload data downloaded from remote host to zen cache", cxxopts::value(m_UploadToZenCache), ""); Parent.AddMultipartOptions(Opts); Parent.AddPartialBlockRequestOptions(Opts); Opts.add_option( "", "", "download-spec-path", "Path to a text file with one line of per file to include or a structured .json file describing what to download.", cxxopts::value(m_DownloadSpecPath), ""); Opts.add_option("", "", "verify", "Enable post download verify of all tracked files", cxxopts::value(m_PostDownloadVerify), ""); Opts.add_option("", "", "enable-scavenge", "Enable scavenging of data from previouse download locations", cxxopts::value(m_EnableScavenging), ""); Opts.add_option("", "", "allow-file-clone", "Enable use of block reference counting when copying files", cxxopts::value(m_AllowFileClone), ""); Opts.parse_positional({"local-path", "build-id", "build-part-name"}); Opts.positional_help("local-path build-id build-part-name"); } void BuildsDownloadSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; if (!IsQuiet) { ZenCmdBase::LogExecutableVersionAndPid(); } TransferThreadWorkers Workers(m_Parent.m_BoostWorkerCount, SingleThreaded); if (!IsQuiet) { ZEN_CONSOLE("{}", Workers.GetWorkersInfo()); } ZenState InstanceState; m_Parent.ParsePath(m_Path, Opts); std::vector IncludeWildcards; std::vector ExcludeWildcards; m_Parent.ParseFileFilters(IncludeWildcards, ExcludeWildcards); m_Parent.ResolveZenFolderPath(m_Path / ZenFolderName); BuildStorageBase::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; std::unique_ptr Auth; StorageInstance Storage = m_Parent.CreateBuildStorage(StorageStats, StorageCacheStats, ZenTempFolderPath(m_Parent.GetZenFolderPath()), m_BuildId, /*RequireNamespace*/ true, /*RequireBucket*/ true, /*BoostCacheBackgroundWorkerPool*/ m_PrimeCacheOnly, Auth, Opts); const Oid BuildId = m_Parent.ParseBuildId(m_BuildId, Opts); if (m_PostDownloadVerify && m_PrimeCacheOnly) { throw OptionParseException("'--cache-prime-only' conflicts with '--verify'", Opts.help()); } if (m_Clean && m_PrimeCacheOnly) { ZEN_CONSOLE_WARN("Ignoring '--clean' option when '--cache-prime-only' is enabled"); } if (m_Force && m_PrimeCacheOnly) { ZEN_CONSOLE_WARN("Ignoring '--force' option when '--cache-prime-only' is enabled"); } if (m_Parent.m_AllowPartialBlockRequests != "false" && m_PrimeCacheOnly) { ZEN_CONSOLE_WARN("Ignoring '--allow-partial-block-requests' option when '--cache-prime-only' is enabled"); } std::vector BuildPartIds = m_Parent.ParseBuildPartIds(m_BuildPartIds, Opts); std::vector BuildPartNames = m_Parent.ParseBuildPartNames(m_BuildPartNames, Opts); EPartialBlockRequestMode PartialBlockRequestMode = m_Parent.ParseAllowPartialBlockRequests(m_PrimeCacheOnly, Opts); if (m_Parent.m_AppendNewContent && m_Clean) { throw OptionParseException("'--append' conflicts with '--clean'", Opts.help()); } std::vector ExcludeFolders = DefaultExcludeFolders; std::vector ExcludeExtensions = DefaultExcludeExtensions; m_Parent.ParseExcludeFolderAndExtension(ExcludeFolders, ExcludeExtensions); std::unique_ptr Output(CreateConsoleLogOutput(ProgressMode)); DownloadFolder( *Output, Workers, Storage, StorageCacheStats, BuildId, BuildPartIds, BuildPartNames, m_DownloadSpecPath, m_Path, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = m_Parent.GetZenFolderPath(), .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = m_Clean, .PostDownloadVerify = m_PostDownloadVerify, .PrimeCacheOnly = m_PrimeCacheOnly, .EnableOtherDownloadsScavenging = m_EnableScavenging && !m_Force, .EnableTargetFolderScavenging = !m_Force, .AllowFileClone = m_AllowFileClone, .IncludeWildcards = IncludeWildcards, .ExcludeWildcards = ExcludeWildcards, .MaximumInMemoryPayloadSize = GetMaxMemoryBufferSize(DefaultMaxChunkBlockSize, m_Parent.m_BoostWorkerMemory), .PopulateCache = m_UploadToZenCache, .AppendNewContent = m_Parent.m_AppendNewContent, .ExcludeFolders = ExcludeFolders}); if (AbortFlag) { throw std::runtime_error("Download aborted"); } } BuildsLsSubCmd::BuildsLsSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("ls", "List files in a build"), m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddSystemOptions(Opts); Parent.AddCloudOptions(Opts); Parent.AddFileOptions(Opts); Parent.AddOutputOptions(Opts); Parent.AddCacheOptions(Opts); Parent.AddZenFolderOptions(Opts); Parent.AddWorkerOptions(Opts); Parent.AddWildcardOptions(Opts); Opts.add_option("", "", "build-id", "Build Id", cxxopts::value(m_BuildId), ""); Opts.add_option("", "", "build-part-id", "Build part Ids list separated by ',', if no build-part-ids or build-part-names are given all parts will be downloaded", cxxopts::value(m_BuildPartIds), ""); Opts.add_option("", "", "build-part-name", "Name of the build parts list separated by ',', if no build-part-ids or build-part-names are given " "all parts will be downloaded", cxxopts::value(m_BuildPartNames), ""); Opts.add_option("", "", "result-path", "Path to json (.json) or compactbinary (.cbo) to write output result to. Default is output to console", cxxopts::value(m_ResultPath), ""); Opts.add_option("", "o", "output-path", "Path to output, extension .json or .cb (compac binary). Default is output to console", cxxopts::value(m_ResultPath), ""); Opts.parse_positional({"build-id", "wildcard"}); Opts.positional_help("build-id wildcard"); } void BuildsLsSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; if (!m_ResultPath.empty()) { if (!IsQuiet) { ZenCmdBase::LogExecutableVersionAndPid(); } } ZenState InstanceState; std::vector IncludeWildcards; std::vector ExcludeWildcards; m_Parent.ParseFileFilters(IncludeWildcards, ExcludeWildcards); m_Parent.ResolveZenFolderPath(m_Parent.m_StoragePath); // ls uses storage path context BuildStorageBase::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; std::unique_ptr Auth; StorageInstance Storage = m_Parent.CreateBuildStorage(StorageStats, StorageCacheStats, ZenTempFolderPath(m_Parent.GetZenFolderPath()), m_BuildId, /*RequireNamespace*/ true, /*RequireBucket*/ true, /*BoostCacheBackgroundWorkerPool*/ false, Auth, Opts); const Oid BuildId = m_Parent.ParseBuildId(m_BuildId, Opts); std::vector BuildPartIds = m_Parent.ParseBuildPartIds(m_BuildPartIds, Opts); std::vector BuildPartNames = m_Parent.ParseBuildPartNames(m_BuildPartNames, Opts); std::unique_ptr StructuredOutput; if (!m_ResultPath.empty()) { MakeSafeAbsolutePathInPlace(m_ResultPath); StructuredOutput = std::make_unique(); } ListBuild(Storage, BuildId, BuildPartIds, BuildPartNames, IncludeWildcards, ExcludeWildcards, StructuredOutput.get()); if (StructuredOutput) { CbObject Response = StructuredOutput->Save(); if (ToLower(m_ResultPath.extension().string()) == ".cbo") { MemoryView ResponseView = Response.GetView(); WriteFile(m_ResultPath, IoBuffer(IoBuffer::Wrap, ResponseView.GetData(), ResponseView.GetSize())); } else { ExtendableStringBuilder<1024> SB; CompactBinaryToJson(Response.GetView(), SB); WriteFile(m_ResultPath, IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size())); } } if (AbortFlag) { throw std::runtime_error("List build aborted"); } } BuildsDiffSubCmd::BuildsDiffSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("diff", "Diff two local folders"), m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddOutputOptions(Opts); Parent.AddWorkerOptions(Opts); Parent.AddExcludeFolderOption(Opts); Parent.AddExcludeExtensionsOption(Opts); Parent.AddChunkingCacheOptions(Opts); Opts.add_option("", "l", "local-path", "Root file system folder used as base", cxxopts::value(m_Path), ""); Opts.add_option("", "c", "compare-path", "Root file system folder used as diff", cxxopts::value(m_DiffPath), ""); Opts.add_option("", "", "only-chunked", "Skip files from diff summation that are not processed with chunking", cxxopts::value(m_OnlyChunked), ""); Opts.parse_positional({"local-path", "compare-path"}); Opts.positional_help("local-path compare-path"); } void BuildsDiffSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; if (!IsQuiet) { ZenCmdBase::LogExecutableVersionAndPid(); } TransferThreadWorkers Workers(m_Parent.m_BoostWorkerCount, SingleThreaded); if (!IsQuiet) { ZEN_CONSOLE("{}", Workers.GetWorkersInfo()); } m_Parent.ParsePath(m_Path, Opts); if (m_DiffPath.empty()) { throw OptionParseException("'--compare-path' is required", Opts.help()); } MakeSafeAbsolutePathInPlace(m_DiffPath); MakeSafeAbsolutePathInPlace(m_Parent.m_ChunkingCachePath); std::vector ExcludeFolders = DefaultExcludeFolders; std::vector ExcludeExtensions = DefaultExcludeExtensions; m_Parent.ParseExcludeFolderAndExtension(ExcludeFolders, ExcludeExtensions); StandardChunkingControllerSettings ChunkingSettings; std::unique_ptr ChunkController = CreateStandardChunkingController(ChunkingSettings); std::unique_ptr ChunkCache = m_Parent.m_ChunkingCachePath.empty() ? CreateNullChunkingCache() : CreateDiskChunkingCache(m_Parent.m_ChunkingCachePath, *ChunkController, 256u * 1024u); if (m_OnlyChunked) { ExcludeExtensions.insert(ExcludeExtensions.end(), ChunkingSettings.SplitOnlyExtensions.begin(), ChunkingSettings.SplitOnlyExtensions.end()); ExcludeExtensions.insert(ExcludeExtensions.end(), ChunkingSettings.SplitAndCompressExtensions.begin(), ChunkingSettings.SplitAndCompressExtensions.end()); } DiffFolders(Workers, m_Path, m_DiffPath, *ChunkController, *ChunkCache, ExcludeFolders, ExcludeExtensions); if (AbortFlag) { throw std::runtime_error("Diff folders aborted"); } } BuildsFetchBlobSubCmd::BuildsFetchBlobSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("fetch-blob", "Fetch and validate a specific blob") , m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddSystemOptions(Opts); Parent.AddCloudOptions(Opts); Parent.AddFileOptions(Opts); Parent.AddOutputOptions(Opts); Parent.AddCacheOptions(Opts); Parent.AddZenFolderOptions(Opts); Opts.add_option("", "", "build-id", "Build Id", cxxopts::value(m_BuildId), ""); Opts.add_option("", "", "blob-hash", "IoHash in hex form identifying the blob to download", cxxopts::value(m_BlobHash), ""); Opts.parse_positional({"build-id", "blob-hash"}); Opts.positional_help("build-id blob-hash"); } void BuildsFetchBlobSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; if (!IsQuiet) { ZenCmdBase::LogExecutableVersionAndPid(); } TransferThreadWorkers Workers(m_Parent.m_BoostWorkerCount, SingleThreaded); if (!IsQuiet) { ZEN_CONSOLE("{}", Workers.GetWorkersInfo()); } BuildStorageBase::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; m_Parent.ResolveZenFolderPath(std::filesystem::current_path() / ZenFolderName); CreateDirectories(m_Parent.GetZenFolderPath()); auto _ = MakeGuard([this, &Workers]() { CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), m_Parent.GetZenFolderPath()); }); std::unique_ptr Auth; StorageInstance Storage = m_Parent.CreateBuildStorage(StorageStats, StorageCacheStats, ZenTempFolderPath(m_Parent.GetZenFolderPath()), m_BuildId, /*RequireNamespace*/ true, /*RequireBucket*/ true, /*BoostCacheBackgroundWorkerPool*/ false, Auth, Opts); IoHash BlobHash = m_Parent.ParseBlobHash(m_BlobHash, Opts); const Oid BuildId = Oid::FromHexString(m_BuildId); uint64_t CompressedSize; uint64_t DecompressedSize; ValidateBlob(AbortFlag, *Storage.BuildStorage, BuildId, BlobHash, CompressedSize, DecompressedSize); if (AbortFlag) { throw std::runtime_error("Fetch blob aborted"); } if (!IsQuiet) { ZEN_CONSOLE("Blob '{}' has a compressed size {} and a decompressed size of {} bytes", BlobHash, CompressedSize, DecompressedSize); } } BuildsPrimeCacheSubCmd::BuildsPrimeCacheSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("prime-cache", "Prime the zen cache with build data") , m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddSystemOptions(Opts); Parent.AddCloudOptions(Opts); Parent.AddFileOptions(Opts); Parent.AddOutputOptions(Opts); Parent.AddCacheOptions(Opts); Parent.AddWorkerOptions(Opts); Parent.AddZenFolderOptions(Opts); Opts.add_option("", "", "build-id", "Build Id", cxxopts::value(m_BuildId), ""); Opts.add_option("", "", "build-part-id", "Build part Ids list separated by ',', if no build-part-ids or build-part-names are given all parts will be downloaded", cxxopts::value(m_BuildPartIds), ""); Opts.add_option("", "", "build-part-name", "Name of the build parts list separated by ',', if no build-part-ids or build-part-names are given " "all parts will be downloaded", cxxopts::value(m_BuildPartNames), ""); Opts.add_option("", "", "force", "Force download of all blobs by ignoring any existing blobs in cache", cxxopts::value(m_Force), ""); Opts.parse_positional({"build-id"}); Opts.positional_help("build-id"); } void BuildsPrimeCacheSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; if (!IsQuiet) { ZenCmdBase::LogExecutableVersionAndPid(); } TransferThreadWorkers Workers(m_Parent.m_BoostWorkerCount, SingleThreaded); if (!IsQuiet) { ZEN_CONSOLE("{}", Workers.GetWorkersInfo()); } BuildStorageBase::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; m_Parent.ResolveZenFolderPath(std::filesystem::current_path() / ZenFolderName); CreateDirectories(m_Parent.GetZenFolderPath()); auto _ = MakeGuard([this, &Workers]() { CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), m_Parent.GetZenFolderPath()); }); std::unique_ptr Auth; StorageInstance Storage = m_Parent.CreateBuildStorage(StorageStats, StorageCacheStats, ZenTempFolderPath(m_Parent.GetZenFolderPath()), m_BuildId, /*RequireNamespace*/ true, /*RequireBucket*/ true, /*BoostCacheBackgroundWorkerPool*/ true, Auth, Opts); const Oid BuildId = m_Parent.ParseBuildId(m_BuildId, Opts); std::vector BuildPartIds = m_Parent.ParseBuildPartIds(m_BuildPartIds, Opts); std::vector BuildPartNames = m_Parent.ParseBuildPartNames(m_BuildPartNames, Opts); std::uint64_t PreferredMultipartChunkSize = 32u * 1024u * 1024u; CbObject BuildObject = GetBuild(*Storage.BuildStorage, BuildId); std::vector> AllBuildParts = ResolveBuildPartNames(BuildObject, BuildId, BuildPartIds, BuildPartNames, PreferredMultipartChunkSize); std::vector AllBuildPartIds; AllBuildPartIds.reserve(AllBuildParts.size()); for (const std::pair& BuildPart : AllBuildParts) { AllBuildPartIds.push_back(BuildPart.first); } ProgressBar::SetLogOperationName(ProgressMode, "Prime Cache"); std::unique_ptr Output(CreateConsoleLogOutput(ProgressMode)); BuildsOperationPrimeCache PrimeOp(*Output, Storage, AbortFlag, PauseFlag, Workers.GetNetworkPool(), BuildId, AllBuildPartIds, BuildsOperationPrimeCache::Options{.IsQuiet = IsQuiet, .IsVerbose = IsVerbose, .ZenFolderPath = m_Parent.GetZenFolderPath(), .LargeAttachmentSize = PreferredMultipartChunkSize * 4u, .PreferredMultipartChunkSize = PreferredMultipartChunkSize, .ForceUpload = m_Force}, StorageCacheStats); PrimeOp.Execute(); if (!IsQuiet) { if (Storage.CacheStorage) { ZEN_CONSOLE("Uploaded {} ({}) blobs to {}", StorageCacheStats.PutBlobCount.load(), NiceBytes(StorageCacheStats.PutBlobByteCount), Storage.CacheHost.Name); } } } BuildsPauseSubCmd::BuildsPauseSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("pause", "Pause a running zen process"), m_Parent(Parent) { auto& Opts = SubOptions(); Opts.add_option("", "", "process-id", "Process id of running process", cxxopts::value(m_ZenProcessId), ""); Opts.parse_positional({"process-id"}); Opts.positional_help("process-id"); } void BuildsPauseSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { using namespace builds_impl; m_Parent.ParseZenProcessId(m_ZenProcessId); ZenState RunningState(m_ZenProcessId); RunningState.StateData().Pause.store(true); } BuildsResumeSubCmd::BuildsResumeSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("resume", "Resume a paused zen process"), m_Parent(Parent) { auto& Opts = SubOptions(); Opts.add_option("", "", "process-id", "Process id of running process", cxxopts::value(m_ZenProcessId), ""); Opts.parse_positional({"process-id"}); Opts.positional_help("process-id"); } void BuildsResumeSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { using namespace builds_impl; m_Parent.ParseZenProcessId(m_ZenProcessId); ZenState RunningState(m_ZenProcessId); RunningState.StateData().Pause.store(false); } BuildsAbortSubCmd::BuildsAbortSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("abort", "Abort a running zen process"), m_Parent(Parent) { auto& Opts = SubOptions(); Opts.add_option("", "", "process-id", "Process id of running process", cxxopts::value(m_ZenProcessId), ""); Opts.parse_positional({"process-id"}); Opts.positional_help("process-id"); } void BuildsAbortSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { using namespace builds_impl; m_Parent.ParseZenProcessId(m_ZenProcessId); ZenState RunningState(m_ZenProcessId); RunningState.StateData().Abort.store(true); } BuildsValidatePartSubCmd::BuildsValidatePartSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("validate-part", "Validate a build part") , m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddSystemOptions(Opts); Parent.AddCloudOptions(Opts); Parent.AddFileOptions(Opts); Parent.AddOutputOptions(Opts); Parent.AddWorkerOptions(Opts); Parent.AddZenFolderOptions(Opts); Opts.add_option("", "", "build-id", "Build Id", cxxopts::value(m_BuildId), ""); Opts.add_option("", "", "build-part-id", "Build part Id, if not given it will be auto generated", cxxopts::value(m_BuildPartId), ""); Opts.add_option("", "", "build-part-name", "Name of the build part, if not given it will be be named after the directory name at end of local-path", cxxopts::value(m_BuildPartName), ""); Opts.parse_positional({"build-id", "build-part-id"}); Opts.positional_help("build-id build-part-id"); } void BuildsValidatePartSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; if (!IsQuiet) { ZenCmdBase::LogExecutableVersionAndPid(); } TransferThreadWorkers Workers(m_Parent.m_BoostWorkerCount, SingleThreaded); if (!IsQuiet) { ZEN_CONSOLE("{}", Workers.GetWorkersInfo()); } ZenState InstanceState; BuildStorageBase::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; m_Parent.ResolveZenFolderPath(std::filesystem::current_path() / ZenFolderName); CreateDirectories(m_Parent.GetZenFolderPath()); auto _ = MakeGuard([this, &Workers]() { CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), m_Parent.GetZenFolderPath()); }); std::unique_ptr Auth; StorageInstance Storage = m_Parent.CreateBuildStorage(StorageStats, StorageCacheStats, ZenTempFolderPath(m_Parent.GetZenFolderPath()), m_BuildId, /*RequireNamespace*/ true, /*RequireBucket*/ true, /*BoostCacheBackgroundWorkerPool*/ false, Auth, Opts); Oid BuildId = m_Parent.ParseBuildId(m_BuildId, Opts); if (!m_BuildPartName.empty() && !m_BuildPartId.empty()) { throw OptionParseException( fmt::format("'--build-part-id' ('{}') conflicts with '--build-part-name' ('{}')", m_BuildPartId, m_BuildPartName), Opts.help()); } const Oid BuildPartId = m_BuildPartName.empty() ? Oid::Zero : m_Parent.ParseBuildPartId(m_BuildPartId, Opts); std::unique_ptr Output(CreateConsoleLogOutput(ProgressMode)); ValidateBuildPart(*Output, Workers, *Storage.BuildStorage, BuildId, BuildPartId, m_BuildPartName); if (AbortFlag) { throw std::runtime_error("Validate build part failed"); } } BuildsTestSubCmd::BuildsTestSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("test", "Run an upload/download test cycle"), m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddSystemOptions(Opts); Parent.AddCloudOptions(Opts); Parent.AddFileOptions(Opts); Parent.AddOutputOptions(Opts); Parent.AddCacheOptions(Opts); Parent.AddWorkerOptions(Opts); Opts.add_option("", "l", "local-path", "Root file system folder used as base", cxxopts::value(m_Path), ""); Parent.AddMultipartOptions(Opts); Parent.AddPartialBlockRequestOptions(Opts); Parent.AddWildcardOptions(Opts); Parent.AddAppendNewContentOptions(Opts); Parent.AddChunkingCacheOptions(Opts); Opts.add_option("", "", "enable-scavenge", "Enable scavenging of data from previouse download locations", cxxopts::value(m_EnableScavenging), ""); Opts.add_option("", "", "allow-file-clone", "Enable use of block reference counting when copying files", cxxopts::value(m_AllowFileClone), ""); Opts.parse_positional({"local-path"}); Opts.positional_help("local-path"); } void BuildsTestSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; TransferThreadWorkers Workers(m_Parent.m_BoostWorkerCount, SingleThreaded); if (!IsQuiet) { ZEN_CONSOLE("{}", Workers.GetWorkersInfo()); } m_Parent.m_SystemRootDir = (GetRunningExecutablePath().parent_path() / ".tmpzensystem").make_preferred(); CreateDirectories(m_Parent.m_SystemRootDir); CleanDirectory(m_Parent.m_SystemRootDir, /*ForceRemoveReadOnlyFiles*/ true); auto SystemGuard = MakeGuard([this]() { DeleteDirectories(m_Parent.m_SystemRootDir); }); m_Parent.ParsePath(m_Path, Opts); if (m_Parent.m_OverrideHost.empty() && m_Parent.m_StoragePath.empty()) { m_Parent.m_StoragePath = (GetRunningExecutablePath().parent_path() / ".tmpstore").make_preferred(); CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), m_Parent.m_StoragePath); CreateDirectories(m_Parent.m_StoragePath); m_Parent.m_StoragePath = m_Parent.m_StoragePath.generic_string(); } auto StorageGuard = MakeGuard([this]() { if (m_Parent.m_OverrideHost.empty() && m_Parent.m_StoragePath.empty()) { DeleteDirectories(m_Parent.m_StoragePath); } }); EPartialBlockRequestMode PartialBlockRequestMode = m_Parent.ParseAllowPartialBlockRequests(false, Opts); BuildStorageBase::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; m_BuildPartName = m_Path.filename().string(); const std::filesystem::path DownloadPath = m_Path.parent_path() / (m_BuildPartName + "_test"); const std::filesystem::path DownloadPath2 = m_Path.parent_path() / (m_BuildPartName + "_test2"); const std::filesystem::path DownloadPath3 = m_Path.parent_path() / (m_BuildPartName + "_test3"); CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath); CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath2); CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath3); auto DownloadGuard = MakeGuard([&Workers, DownloadPath, DownloadPath2, DownloadPath3]() { CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath); CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath2); CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath3); }); m_Parent.ResolveZenFolderPath(m_Path / ZenFolderName); MakeSafeAbsolutePathInPlace(m_Parent.m_ChunkingCachePath); std::unique_ptr Auth; std::string TestBuildId; StorageInstance Storage = m_Parent.CreateBuildStorage(StorageStats, StorageCacheStats, ZenTempFolderPath(m_Parent.GetZenFolderPath()), TestBuildId, /*RequireNamespace*/ true, /*RequireBucket*/ true, /*BoostCacheBackgroundWorkerPool*/ false, Auth, Opts); m_BuildId = Oid::NewOid().ToString(); m_BuildPartId = Oid::NewOid().ToString(); m_CreateBuild = true; const Oid BuildId = Oid::FromHexString(m_BuildId); const Oid BuildPartId = Oid::FromHexString(m_BuildPartId); auto MakeMetaData = [](const Oid& BuildId) -> CbObject { CbObjectWriter BuildMetaDataWriter; { const uint32_t CL = BuildId.OidBits[2]; BuildMetaDataWriter.AddString("name", fmt::format("++Test+Main-CL-{}", CL)); BuildMetaDataWriter.AddString("branch", "ZenTestBuild"); BuildMetaDataWriter.AddString("baselineBranch", "ZenTestBuild"); BuildMetaDataWriter.AddString("platform", "Windows"); BuildMetaDataWriter.AddString("project", "Test"); BuildMetaDataWriter.AddInteger("changelist", CL); BuildMetaDataWriter.AddString("buildType", "test-folder"); } return BuildMetaDataWriter.Save(); }; CbObject MetaData = MakeMetaData(Oid::TryFromHexString(m_BuildId)); { ExtendableStringBuilder<256> SB; CompactBinaryToJson(MetaData, SB); ZEN_CONSOLE("Upload Build {}, Part {} ({}) from '{}'\n{}", m_BuildId, BuildPartId, m_BuildPartName, m_Path, SB.ToView()); } const std::filesystem::path UploadTempDir = UploadTempDirectory(m_Path); std::unique_ptr ChunkController = CreateStandardChunkingController(StandardChunkingControllerSettings{}); std::unique_ptr ChunkCache = m_Parent.m_ChunkingCachePath.empty() ? CreateNullChunkingCache() : CreateDiskChunkingCache(m_Parent.m_ChunkingCachePath, *ChunkController, 256u * 1024u); std::unique_ptr Output(CreateConsoleLogOutput(ProgressMode)); UploadFolder(*Output, Workers, Storage, BuildId, BuildPartId, m_BuildPartName, m_Path, {}, MetaData, *ChunkController, *ChunkCache, UploadFolderOptions{.TempDir = UploadTempDir, .FindBlockMaxCount = m_FindBlockMaxCount, .BlockReuseMinPercentLimit = m_BlockReuseMinPercentLimit, .AllowMultiparts = m_Parent.m_AllowMultiparts, .CreateBuild = true, .IgnoreExistingBlocks = false, .UploadToZenCache = m_UploadToZenCache}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Upload build)"); } { ZEN_CONSOLE("Upload Build {}, Part {} ({}) from '{}' with chunking cache", m_BuildId, BuildPartId, m_BuildPartName, m_Path); UploadFolder(*Output, Workers, Storage, Oid::NewOid(), Oid::NewOid(), m_BuildPartName, m_Path, {}, MetaData, *ChunkController, *ChunkCache, UploadFolderOptions{.TempDir = UploadTempDir, .FindBlockMaxCount = m_FindBlockMaxCount, .BlockReuseMinPercentLimit = m_BlockReuseMinPercentLimit, .AllowMultiparts = m_Parent.m_AllowMultiparts, .CreateBuild = true, .IgnoreExistingBlocks = false, .UploadToZenCache = m_UploadToZenCache}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Upload again, chunking is cached)"); } } ValidateBuildPart(*Output, Workers, *Storage.BuildStorage, BuildId, BuildPartId, m_BuildPartName); if (!m_Parent.m_IncludeWildcard.empty() || !m_Parent.m_ExcludeWildcard.empty()) { auto WcGuard = MakeGuard([&]() { CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath); }); ZEN_CONSOLE("\nDownload Filtered Build {}, Part {} ({}) to '{}'", BuildId, BuildPartId, m_BuildPartName, DownloadPath); std::vector IncludeWildcards; std::vector ExcludeWildcards; m_Parent.ParseFileFilters(IncludeWildcards, ExcludeWildcards); DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId, {BuildPartId}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = true, .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = false, .AllowFileClone = m_AllowFileClone, .IncludeWildcards = IncludeWildcards, .ExcludeWildcards = ExcludeWildcards, .AppendNewContent = false}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Download build)"); } ZEN_CONSOLE("\nDownload Filtered Out Remaining of Build {}, Part {} ({}) to '{}'", BuildId, BuildPartId, m_BuildPartName, DownloadPath); DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId, {BuildPartId}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = true, .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = true, .AllowFileClone = m_AllowFileClone, .IncludeWildcards = ExcludeWildcards, .ExcludeWildcards = IncludeWildcards, .AppendNewContent = true}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Download build)"); } ZEN_CONSOLE("\nDownload Full Build {}, Part {} ({}) to '{}'", BuildId, BuildPartId, m_BuildPartName, DownloadPath); DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId, {BuildPartId}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = true, .AllowFileClone = m_AllowFileClone, .IncludeWildcards = {}, .ExcludeWildcards = {}, .AppendNewContent = false}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Download build)"); } } ZEN_CONSOLE("\nDownload Build {}, Part {} ({}) to '{}'", BuildId, BuildPartId, m_BuildPartName, DownloadPath); DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId, {BuildPartId}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = true, .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = false, .AllowFileClone = m_AllowFileClone}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Download build)"); } ZEN_CONSOLE("\nRe-download Build {}, Part {} ({}) to '{}' (identical target)", BuildId, BuildPartId, m_BuildPartName, DownloadPath); DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId, {BuildPartId}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = true, .AllowFileClone = m_AllowFileClone}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Re-download identical target)"); } auto ScrambleDir = [&Workers, this](const std::filesystem::path& Path) { ZEN_CONSOLE("\nScrambling '{}'", Path); Stopwatch Timer; DirectoryContent DownloadContent; GetDirectoryContent( Path, DirectoryContentFlags::Recursive | DirectoryContentFlags::IncludeFiles | DirectoryContentFlags::IncludeFileSizes, DownloadContent); auto IsAcceptedFolder = [ExcludeFolders = DefaultExcludeFolders, Path](const std::filesystem::path& AbsolutePath) -> bool { std::string RelativePath = std::filesystem::relative(AbsolutePath, Path).generic_string(); for (const std::string& ExcludeFolder : ExcludeFolders) { if (RelativePath.starts_with(ExcludeFolder)) { if (RelativePath.length() == ExcludeFolder.length()) { return false; } else if (RelativePath[ExcludeFolder.length()] == '/') { return false; } } } return true; }; ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog); uint32_t Randomizer = 0; auto FileSizeIt = DownloadContent.FileSizes.begin(); for (const std::filesystem::path& FilePath : DownloadContent.Files) { if (IsAcceptedFolder(FilePath)) { uint32_t Case = (Randomizer++) % 7; switch (Case) { case 0: { uint64_t SourceSize = *FileSizeIt; if (SourceSize > 256) { Work.ScheduleWork( Workers.GetIOWorkerPool(), [SourceSize, FilePath = std::filesystem::path(FilePath)](std::atomic&) { if (!AbortFlag) { bool WasReadOnly = SetFileReadOnly(FilePath, false); { BasicFile Source(FilePath, BasicFile::Mode::kWrite); uint64_t RangeSize = Min(SourceSize / 3, 512u * 1024u); IoBuffer TempBuffer1(RangeSize); IoBuffer TempBuffer2(RangeSize); IoBuffer TempBuffer3(RangeSize); Source.Read(TempBuffer1.GetMutableView().GetData(), RangeSize, 0); Source.Read(TempBuffer2.GetMutableView().GetData(), RangeSize, SourceSize / 2); Source.Read(TempBuffer3.GetMutableView().GetData(), RangeSize, SourceSize - RangeSize); Source.Write(TempBuffer1, SourceSize / 2); Source.Write(TempBuffer2, SourceSize - RangeSize); Source.Write(TempBuffer3, SourceSize - 0); } if (WasReadOnly) { SetFileReadOnly(FilePath, true); } } }); } } break; case 1: { (void)SetFileReadOnly(FilePath, false); (void)RemoveFile(FilePath); } break; default: break; } } FileSizeIt++; } Work.Wait(5000, [&](bool IsAborted, bool IsPaused, std::ptrdiff_t PendingWork) { ZEN_UNUSED(IsAborted, IsPaused); ZEN_CONSOLE("Scrambling files, {} remaining", PendingWork); }); ZEN_ASSERT(!AbortFlag.load()); ZEN_CONSOLE("Scrambled files in {}", NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }; ScrambleDir(DownloadPath); ZEN_CONSOLE("\nRe-download Build {}, Part {} ({}) to '{}' (scrambled target)", BuildId, BuildPartId, m_BuildPartName, DownloadPath); DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId, {BuildPartId}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = true, .AllowFileClone = m_AllowFileClone}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Re-download scrambled target)"); } ScrambleDir(DownloadPath); Oid BuildId2 = Oid::NewOid(); Oid BuildPartId2 = Oid::NewOid(); CbObject MetaData2 = MakeMetaData(BuildId2); { ExtendableStringBuilder<256> SB; CompactBinaryToJson(MetaData, SB); ZEN_CONSOLE("\nUpload scrambled Build {}, Part {} ({})\n{}\n", BuildId2, BuildPartId2, m_BuildPartName, SB.ToView()); } UploadFolder(*Output, Workers, Storage, BuildId2, BuildPartId2, m_BuildPartName, DownloadPath, {}, MetaData2, *ChunkController, *ChunkCache, UploadFolderOptions{.TempDir = UploadTempDir, .FindBlockMaxCount = m_FindBlockMaxCount, .BlockReuseMinPercentLimit = m_BlockReuseMinPercentLimit, .AllowMultiparts = m_Parent.m_AllowMultiparts, .CreateBuild = true, .IgnoreExistingBlocks = false, .UploadToZenCache = m_UploadToZenCache}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Upload scrambled)"); } ValidateBuildPart(*Output, Workers, *Storage.BuildStorage, BuildId, BuildPartId, m_BuildPartName); ZEN_CONSOLE("\nDownload Build {}, Part {} ({}) to '{}' (original)", BuildId, BuildPartId, m_BuildPartName, DownloadPath); DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId, {BuildPartId}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = true, .AllowFileClone = m_AllowFileClone}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Download original)"); } ZEN_CONSOLE("\nDownload Build {}, Part {} ({}) to '{}' (scrambled)", BuildId2, BuildPartId2, m_BuildPartName, DownloadPath); DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId2, {BuildPartId2}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = true, .AllowFileClone = m_AllowFileClone}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Download scrambled)"); } ZEN_CONSOLE("\nRe-download Build {}, Part {} ({}) to '{}' (scrambled)", BuildId2, BuildPartId2, m_BuildPartName, DownloadPath); DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId2, {BuildPartId2}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = true, .AllowFileClone = m_AllowFileClone}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Re-download scrambled)"); } ZEN_CONSOLE("\nDownload Build {}, Part {} ({}) to '{}' (original)", BuildId, BuildPartId, m_BuildPartName, DownloadPath2); DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId, {BuildPartId}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, DownloadPath2, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = DownloadPath2 / ZenFolderName, .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = true, .AllowFileClone = m_AllowFileClone}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Download original)"); } ZEN_CONSOLE("\nDownload Build {}, Part {} ({}) to '{}' (original)", BuildId, BuildPartId, m_BuildPartName, DownloadPath3); DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId, {BuildPartId}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, DownloadPath3, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = DownloadPath3 / ZenFolderName, .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = false, .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = true, .AllowFileClone = m_AllowFileClone}); if (AbortFlag) { throw std::runtime_error("Test aborted. (Download original)"); } } BuildsMultiTestDownloadSubCmd::BuildsMultiTestDownloadSubCmd(BuildsCommand& Parent) : ZenSubCmdBase("multi-test-download", "Download multiple builds sequentially as a test") , m_Parent(Parent) { auto& Opts = SubOptions(); Parent.AddSystemOptions(Opts); Parent.AddCloudOptions(Opts); Parent.AddFileOptions(Opts); Parent.AddOutputOptions(Opts); Parent.AddCacheOptions(Opts); Parent.AddWorkerOptions(Opts); Opts.add_option("", "l", "local-path", "Root file system folder used as base", cxxopts::value(m_Path), ""); Opts.add_option("", "", "build-ids", "Build Ids list separated by ','", cxxopts::value(m_BuildIds), ""); Opts.add_option("", "", "enable-scavenge", "Enable scavenging of data from previouse download locations", cxxopts::value(m_EnableScavenging), ""); Opts.add_option("", "", "allow-file-clone", "Enable use of block reference counting when copying files", cxxopts::value(m_AllowFileClone), ""); Opts.parse_positional({"local-path"}); Opts.positional_help("local-path"); } void BuildsMultiTestDownloadSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/) { auto& Opts = SubOptions(); using namespace builds_impl; TransferThreadWorkers Workers(m_Parent.m_BoostWorkerCount, SingleThreaded); if (!IsQuiet) { ZEN_CONSOLE("{}", Workers.GetWorkersInfo()); } m_Parent.m_SystemRootDir = (GetRunningExecutablePath().parent_path() / ".tmpzensystem").make_preferred(); CreateDirectories(m_Parent.m_SystemRootDir); CleanDirectory(m_Parent.m_SystemRootDir, /*ForceRemoveReadOnlyFiles*/ true); auto SystemGuard = MakeGuard([this]() { DeleteDirectories(m_Parent.m_SystemRootDir); }); m_Parent.ParsePath(m_Path, Opts); m_Parent.ResolveZenFolderPath(m_Path / ZenFolderName); EPartialBlockRequestMode PartialBlockRequestMode = m_Parent.ParseAllowPartialBlockRequests(false, Opts); BuildStorageBase::Statistics StorageStats; BuildStorageCache::Statistics StorageCacheStats; std::unique_ptr Auth; std::string DummyBuildId; StorageInstance Storage = m_Parent.CreateBuildStorage(StorageStats, StorageCacheStats, ZenTempFolderPath(m_Parent.GetZenFolderPath()), DummyBuildId, /*RequireNamespace*/ true, /*RequireBucket*/ true, /*BoostCacheBackgroundWorkerPool*/ false, Auth, Opts); std::unique_ptr Output(CreateConsoleLogOutput(ProgressMode)); Stopwatch Timer; for (const std::string& BuildIdString : m_BuildIds) { Oid BuildId = Oid::FromHexString(RemoveQuotes(BuildIdString)); if (BuildId == Oid::Zero) { throw OptionParseException(fmt::format("'--build-id' ('{}') is malformed", BuildIdString), Opts.help()); } DownloadFolder(*Output, Workers, Storage, StorageCacheStats, BuildId, /*BuildPartIds,*/ {}, /*BuildPartNames*/ {}, /*ManifestPath*/ {}, m_Path, DownloadOptions{.SystemRootDir = m_Parent.m_SystemRootDir, .ZenFolderPath = m_Parent.GetZenFolderPath(), .AllowMultiparts = m_Parent.m_AllowMultiparts, .PartialBlockRequestMode = PartialBlockRequestMode, .CleanTargetFolder = BuildIdString == m_BuildIds.front(), .PostDownloadVerify = true, .PrimeCacheOnly = false, .EnableOtherDownloadsScavenging = m_EnableScavenging, .EnableTargetFolderScavenging = false, .AllowFileClone = m_AllowFileClone}); if (AbortFlag) { throw std::runtime_error("Multitest aborted"); } if (!IsQuiet) { ZEN_CONSOLE("\n"); } } if (!IsQuiet) { ZEN_CONSOLE("Completed in {}", NiceTimeSpanMs(Timer.GetElapsedTimeMs())); } } } // namespace zen