diff options
| author | Dan Engelbrecht <[email protected]> | 2023-10-30 18:29:09 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-10-30 18:29:09 +0100 |
| commit | cbdda104ada38108700f9da5b192867d83074119 (patch) | |
| tree | 98c04b344e041c156fdc1a5c393672bef743be34 /src | |
| parent | fix changelog (diff) | |
| download | zen-cbdda104ada38108700f9da5b192867d83074119.tar.xz zen-cbdda104ada38108700f9da5b192867d83074119.zip | |
individual gc stats (#506)
- Feature: New parameter for endpoint `admin/gc` (GET) `details=true` which gives details stats on GC operation when using GC V2
- Feature: New options for zen command `gc-status`
- `--details` that enables the detailed output from the last GC operation when using GC V2
Diffstat (limited to 'src')
| -rw-r--r-- | src/zen/cmds/admin_cmd.cpp | 5 | ||||
| -rw-r--r-- | src/zen/cmds/admin_cmd.h | 1 | ||||
| -rw-r--r-- | src/zenserver/admin/admin.cpp | 130 | ||||
| -rw-r--r-- | src/zenserver/cache/cachedisklayer.cpp | 65 | ||||
| -rw-r--r-- | src/zenserver/cache/cachedisklayer.h | 3 | ||||
| -rw-r--r-- | src/zenserver/cache/structuredcachestore.cpp | 306 | ||||
| -rw-r--r-- | src/zenserver/projectstore/projectstore.cpp | 91 | ||||
| -rw-r--r-- | src/zenserver/projectstore/projectstore.h | 3 | ||||
| -rw-r--r-- | src/zenstore/compactcas.cpp | 89 | ||||
| -rw-r--r-- | src/zenstore/compactcas.h | 3 | ||||
| -rw-r--r-- | src/zenstore/filecas.cpp | 99 | ||||
| -rw-r--r-- | src/zenstore/filecas.h | 3 | ||||
| -rw-r--r-- | src/zenstore/gc.cpp | 631 | ||||
| -rw-r--r-- | src/zenstore/include/zenstore/gc.h | 90 |
14 files changed, 966 insertions, 553 deletions
diff --git a/src/zen/cmds/admin_cmd.cpp b/src/zen/cmds/admin_cmd.cpp index 209390e2a..8b8b416ba 100644 --- a/src/zen/cmds/admin_cmd.cpp +++ b/src/zen/cmds/admin_cmd.cpp @@ -183,6 +183,7 @@ GcStatusCommand::GcStatusCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "d", "details", "Show detailed GC report", cxxopts::value(m_Details)->default_value("false"), "<details>"); } GcStatusCommand::~GcStatusCommand() @@ -209,6 +210,10 @@ GcStatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) cpr::Session Session; Session.SetHeader(cpr::Header{{"Accept", "application/json"}}); Session.SetUrl({fmt::format("{}/admin/gc", m_HostName)}); + if (m_Details) + { + Session.SetParameters({{"details", "true"}}); + } cpr::Response Result = Session.Get(); diff --git a/src/zen/cmds/admin_cmd.h b/src/zen/cmds/admin_cmd.h index 4a9de89e2..4b57eccdb 100644 --- a/src/zen/cmds/admin_cmd.h +++ b/src/zen/cmds/admin_cmd.h @@ -57,6 +57,7 @@ public: private: cxxopts::Options m_Options{"gc-status", "Garbage collect zen storage status check"}; std::string m_HostName; + bool m_Details = false; }; //////////////////////////////////////////// diff --git a/src/zenserver/admin/admin.cpp b/src/zenserver/admin/admin.cpp index fb376f238..83f33d4ea 100644 --- a/src/zenserver/admin/admin.cpp +++ b/src/zenserver/admin/admin.cpp @@ -199,6 +199,14 @@ HttpAdminService::HttpAdminService(GcScheduler& Scheduler, [this](HttpRouterRequest& Req) { const GcSchedulerState State = m_GcScheduler.GetState(); + const HttpServerRequest::QueryParams Params = Req.ServerRequest().GetQueryParams(); + + bool Details = false; + if (auto Param = Params.GetValue("details"); Param == "true") + { + Details = true; + } + auto SecondsToString = [](std::chrono::seconds Secs) { return NiceTimeSpanMs(uint64_t(std::chrono::milliseconds(Secs).count())); }; @@ -226,6 +234,101 @@ HttpAdminService::HttpAdminService(GcScheduler& Scheduler, Response << "DiskUsed" << NiceBytes(State.DiskUsed); Response << "DiskFree" << NiceBytes(State.DiskFree); + auto WriteReferencerStats = [&](CbObjectWriter& Response, const GcReferencerStats& Stats) { + if (Stats.Count == 0) + { + return; + } + Response << "Count" << Stats.Count; + Response << "Expired" << Stats.Expired; + Response << "Deleted" << Stats.Deleted; + + Response << "RemovedDisk" << NiceBytes(Stats.RemovedDisk); + Response << "RemovedMemory" << NiceBytes(Stats.RemovedMemory); + + Response << "RemoveExpiredData" << NiceTimeSpanMs(Stats.RemoveExpiredDataMS.count()); + Response << "CreateReferenceCheckers" << NiceTimeSpanMs(Stats.CreateReferenceCheckersMS.count()); + Response << "LockState" << NiceTimeSpanMs(Stats.LockStateMS.count()); + Response << "Elapsed" << NiceTimeSpanMs(Stats.ElapsedMS.count()); + }; + + auto WriteReferenceStoreStats = [&](CbObjectWriter& Response, const GcReferenceStoreStats& Stats) { + if (Stats.Count == 0) + { + return; + } + Response << "Count" << Stats.Count; + Response << "Pruned" << Stats.Pruned; + Response << "Compacted" << Stats.Compacted; + + Response << "RemovedDisk" << NiceBytes(Stats.RemovedDisk); + Response << "RemovedMemory" << NiceBytes(Stats.RemovedMemory); + + Response << "CreateReferencePruner" << NiceTimeSpanMs(Stats.CreateReferencePrunerMS.count()); + Response << "RemoveUnreferencedData" << NiceTimeSpanMs(Stats.RemoveUnreferencedDataMS.count()); + Response << "CompactReferenceStore" << NiceTimeSpanMs(Stats.CompactReferenceStoreMS.count()); + Response << "Elapsed" << NiceTimeSpanMs(Stats.ElapsedMS.count()); + }; + + auto WriteGCResult = [&](CbObjectWriter& Response, const GcResult& Result) { + Response << "RemovedDisk" << NiceBytes(Result.RemovedDisk); + Response << "RemovedMemory" << NiceBytes(Result.RemovedMemory); + Response << "WriteBlock" << NiceTimeSpanMs(Result.WriteBlockMS.count()); + Response << "Elapsed" << NiceTimeSpanMs(Result.ElapsedMS.count()); + + if (!Details) + { + return; + } + + Response << "RemoveExpiredData" << NiceTimeSpanMs(Result.RemoveExpiredDataMS.count()); + Response << "CreateReferenceCheckers" << NiceTimeSpanMs(Result.CreateReferenceCheckersMS.count()); + Response << "LockState" << NiceTimeSpanMs(Result.LockStateMS.count()); + + Response << "CreateReferencePruner" << NiceTimeSpanMs(Result.CreateReferencePrunerMS.count()); + Response << "RemoveUnreferencedData" << NiceTimeSpanMs(Result.RemoveUnreferencedDataMS.count()); + Response << "CompactReferenceStore" << NiceTimeSpanMs(Result.CompactReferenceStoreMS.count()); + + Response.BeginObject("ReferencerStats"); + { + WriteReferencerStats(Response, Result.ReferencerStat); + } + Response.EndObject(); + + Response.BeginObject("ReferenceStoreStats"); + { + WriteReferenceStoreStats(Response, Result.ReferenceStoreStat); + } + Response.EndObject(); + + if (!Result.ReferencerStats.empty()) + { + Response.BeginArray("Referencers"); + { + for (const std::pair<std::string, GcReferencerStats>& It : Result.ReferencerStats) + { + Response.BeginObject(); + Response << "Name" << It.first; + WriteReferencerStats(Response, It.second); + Response.EndObject(); + } + } + Response.EndArray(); + } + if (!Result.ReferenceStoreStats.empty()) + { + Response.BeginArray("ReferenceStores"); + for (const std::pair<std::string, GcReferenceStoreStats>& It : Result.ReferenceStoreStats) + { + Response.BeginObject(); + Response << "Name" << It.first; + WriteReferenceStoreStats(Response, It.second); + Response.EndObject(); + } + Response.EndArray(); + } + }; + Response.BeginObject("FullGC"); { Response << "LastTime" << fmt::format("{}", State.LastFullGcTime); @@ -234,18 +337,33 @@ HttpAdminService::HttpAdminService(GcScheduler& Scheduler, { Response << "SpaceToNext" << NiceBytes(State.RemainingSpaceUntilFullGC); } - Response << "LastDuration" << NiceTimeSpanMs(State.LastFullGcDuration.count()); - Response << "LastDiskFreed" << NiceBytes(State.LastFullGCDiff.DiskSize); - Response << "LastMemoryFreed" << NiceBytes(State.LastFullGCDiff.MemorySize); + if (State.LastFullGCV2Result) + { + WriteGCResult(Response, State.LastFullGCV2Result.value()); + } + else + { + Response << "LastDuration" << NiceTimeSpanMs(State.LastFullGcDuration.count()); + Response << "LastDiskFreed" << NiceBytes(State.LastFullGCDiff.DiskSize); + Response << "LastMemoryFreed" << NiceBytes(State.LastFullGCDiff.MemorySize); + } } Response.EndObject(); Response.BeginObject("LightweightGC"); { Response << "LastTime" << fmt::format("{}", State.LastLightweightGcTime); Response << "TimeToNext" << SecondsToString(State.RemainingTimeUntilLightweightGc); - Response << "LastDuration" << NiceTimeSpanMs(State.LastLightweightGcDuration.count()); - Response << "LastDiskFreed" << NiceBytes(State.LastLightweightGCDiff.DiskSize); - Response << "LastMemoryFreed" << NiceBytes(State.LastLightweightGCDiff.MemorySize); + + if (State.LastLightweightGCV2Result) + { + WriteGCResult(Response, State.LastLightweightGCV2Result.value()); + } + else + { + Response << "LastDuration" << NiceTimeSpanMs(State.LastLightweightGcDuration.count()); + Response << "LastDiskFreed" << NiceBytes(State.LastLightweightGCDiff.DiskSize); + Response << "LastMemoryFreed" << NiceBytes(State.LastLightweightGCDiff.MemorySize); + } } Response.EndObject(); Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Response.Save()); diff --git a/src/zenserver/cache/cachedisklayer.cpp b/src/zenserver/cache/cachedisklayer.cpp index 38cbf3a93..fa80ed414 100644 --- a/src/zenserver/cache/cachedisklayer.cpp +++ b/src/zenserver/cache/cachedisklayer.cpp @@ -2213,8 +2213,14 @@ ZenCacheDiskLayer::CacheBucket::PutInlineCacheValue(const IoHash& HashKey, const }); } +std::string +ZenCacheDiskLayer::CacheBucket::GetGcName(GcCtx&) +{ + return fmt::format("cachebucket:'{}'", m_BucketDir.string()); +} + void -ZenCacheDiskLayer::CacheBucket::RemoveExpiredData(GcCtx& Ctx) +ZenCacheDiskLayer::CacheBucket::RemoveExpiredData(GcCtx& Ctx, GcReferencerStats& Stats) { size_t TotalEntries = 0; tsl::robin_set<IoHash, IoHash::Hasher> ExpiredInlineKeys; @@ -2222,11 +2228,18 @@ ZenCacheDiskLayer::CacheBucket::RemoveExpiredData(GcCtx& Ctx) Stopwatch Timer; const auto _ = MakeGuard([&] { - ZEN_DEBUG("gc cache bucket '{}': removed {} expired keys out of {} in {}", - m_BucketDir, - ExpiredStandaloneKeys.size() + ExpiredInlineKeys.size(), - TotalEntries, - NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: cachebucket [REMOVE EXPIRED] '{}': Count: {}, Expired: {}, Deleted: {}, RemovedDisk: {}, RemovedMemory: {} in {}", + m_BucketDir, + Stats.Count, + Stats.Expired, + Stats.Deleted, + NiceBytes(Stats.RemovedDisk), + NiceBytes(Stats.RemovedMemory), + NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }); const GcClock::Tick ExpireTicks = Ctx.Settings.CacheExpireTime.time_since_epoch().count(); @@ -2278,7 +2291,7 @@ ZenCacheDiskLayer::CacheBucket::RemoveExpiredData(GcCtx& Ctx) } } - Ctx.ExpiredItems.fetch_add(ExpiredStandaloneKeys.size() + ExpiredInlineKeys.size()); + Stats.Expired += ExpiredStandaloneKeys.size() + ExpiredInlineKeys.size(); // Get all locations we need to keep for affected blocks if (Ctx.Settings.CollectSmallObjects && !ExpiredInlineKeys.empty()) @@ -2312,7 +2325,7 @@ ZenCacheDiskLayer::CacheBucket::RemoveExpiredData(GcCtx& Ctx) if (m_Configuration.MemCacheSizeThreshold > 0 && m_CachedPayloads[It->second]) { size_t PayloadSize = m_CachedPayloads[It->second].GetSize(); - Ctx.RemovedMemory.fetch_add(PayloadSize); + Stats.RemovedMemory += PayloadSize; RemoveMemCacheUsage(PayloadSize); } m_Index.erase(It); @@ -2321,7 +2334,7 @@ ZenCacheDiskLayer::CacheBucket::RemoveExpiredData(GcCtx& Ctx) m_StandaloneSize.fetch_sub(RemovedStandaloneSize, std::memory_order::relaxed); } } - Ctx.Items.fetch_add(TotalEntries); + Stats.Count += TotalEntries; if (ExpiredEntries.empty()) { @@ -2333,7 +2346,7 @@ ZenCacheDiskLayer::CacheBucket::RemoveExpiredData(GcCtx& Ctx) return; } - Ctx.DeletedItems.fetch_add(ExpiredEntries.size()); + Stats.Deleted += ExpiredEntries.size(); // Compact standalone items ExtendablePathBuilder<256> Path; @@ -2370,7 +2383,7 @@ ZenCacheDiskLayer::CacheBucket::RemoveExpiredData(GcCtx& Ctx) Ec.message()); continue; } - Ctx.RemovedDiskSpace.fetch_add(ExpiredKey.second); + Stats.RemovedDisk += ExpiredKey.second; } if (Ctx.Settings.CollectSmallObjects && !ExpiredInlineKeys.empty()) @@ -2405,7 +2418,7 @@ ZenCacheDiskLayer::CacheBucket::RemoveExpiredData(GcCtx& Ctx) } } m_SlogFile.Append(MovedEntries); - Ctx.RemovedDiskSpace.fetch_add(FreedDiskSpace); + Stats.RemovedDisk += FreedDiskSpace; }, [&]() { return 0; }); } @@ -2436,14 +2449,18 @@ public: } } - virtual void LockState(GcCtx&) override + virtual void LockState(GcCtx& Ctx) override { Stopwatch Timer; const auto _ = MakeGuard([&] { - ZEN_DEBUG("gc cache bucket '{}': found {} references in {}", - m_CacheBucket.m_BucketDir, - m_CacheBucket.m_ReferenceCount + m_UncachedReferences.size(), - NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: cachebucket [LOCKSTATE] '{}': found {} references in {}", + m_CacheBucket.m_BucketDir, + m_CacheBucket.m_ReferenceCount + m_UncachedReferences.size(), + NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }); m_IndexLock = std::make_unique<RwLock::SharedLockScope>(m_CacheBucket.m_IndexLock); @@ -2503,11 +2520,19 @@ public: }; std::vector<GcReferenceChecker*> -ZenCacheDiskLayer::CacheBucket::CreateReferenceCheckers(GcCtx&) +ZenCacheDiskLayer::CacheBucket::CreateReferenceCheckers(GcCtx& Ctx) { Stopwatch Timer; - const auto _ = MakeGuard( - [&] { ZEN_DEBUG("gc cache bucket '{}': refreshed reference cache in {}", m_BucketDir, NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }); + const auto _ = MakeGuard([&] { + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: cachebucket [CREATE CHECKERS] '{}': found {} references in {}", + m_BucketDir, + m_ReferenceCount, + NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + }); std::vector<IoHash> UpdateKeys; std::vector<IoHash> StandaloneKeys; diff --git a/src/zenserver/cache/cachedisklayer.h b/src/zenserver/cache/cachedisklayer.h index d8f51c398..ded8f2e70 100644 --- a/src/zenserver/cache/cachedisklayer.h +++ b/src/zenserver/cache/cachedisklayer.h @@ -267,7 +267,8 @@ private: std::atomic_uint64_t m_StandaloneSize{}; std::atomic_uint64_t m_MemCachedSize{}; - virtual void RemoveExpiredData(GcCtx& Ctx) override; + virtual std::string GetGcName(GcCtx& Ctx) override; + virtual void RemoveExpiredData(GcCtx& Ctx, GcReferencerStats& Stats) override; virtual std::vector<GcReferenceChecker*> CreateReferenceCheckers(GcCtx& Ctx) override; void BuildPath(PathBuilderBase& Path, const IoHash& HashKey) const; diff --git a/src/zenserver/cache/structuredcachestore.cpp b/src/zenserver/cache/structuredcachestore.cpp index 516532528..55ab6bf31 100644 --- a/src/zenserver/cache/structuredcachestore.cpp +++ b/src/zenserver/cache/structuredcachestore.cpp @@ -1982,18 +1982,20 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() - std::chrono::hours(1), .ProjectStoreExpireTime = GcClock::Now() - std::chrono::hours(1), .CollectSmallObjects = false, - .IsDeleteMode = false}); - CHECK_EQ(7u, Result.Items); - CHECK_EQ(0u, Result.ExpiredItems); - CHECK_EQ(0u, Result.DeletedItems); - CHECK_EQ(5u, Result.References); - CHECK_EQ(0u, Result.PrunedReferences); - CHECK_EQ(0u, Result.CompactedReferences); - CHECK_EQ(0u, Result.RemovedDiskSpace); + .IsDeleteMode = false, + .Verbose = true}); + CHECK_EQ(7u, Result.ReferencerStat.Count); + CHECK_EQ(0u, Result.ReferencerStat.Expired); + CHECK_EQ(0u, Result.ReferencerStat.Deleted); + CHECK_EQ(5u, Result.ReferenceStoreStat.Count); + CHECK_EQ(0u, Result.ReferenceStoreStat.Pruned); + CHECK_EQ(0u, Result.ReferenceStoreStat.Compacted); + CHECK_EQ(0u, Result.RemovedDisk); CHECK_EQ(0u, Result.RemovedMemory); CHECK(ValidateCacheEntry(Zcs, CidStore, TearDrinkerBucket, CacheRecords[0], true, true)); @@ -2014,18 +2016,20 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); - GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::minutes(1), - .ProjectStoreExpireTime = GcClock::Now() + std::chrono::minutes(1), + GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::hours(1), + .ProjectStoreExpireTime = GcClock::Now() + std::chrono::hours(1), .CollectSmallObjects = false, - .IsDeleteMode = false}); - CHECK_EQ(7u, Result.Items); - CHECK_EQ(1u, Result.ExpiredItems); - CHECK_EQ(0u, Result.DeletedItems); - CHECK_EQ(5u, Result.References); - CHECK_EQ(0u, Result.PrunedReferences); - CHECK_EQ(0u, Result.CompactedReferences); - CHECK_EQ(0u, Result.RemovedDiskSpace); + .IsDeleteMode = false, + .Verbose = true}); + CHECK_EQ(7u, Result.ReferencerStat.Count); + CHECK_EQ(1u, Result.ReferencerStat.Expired); + CHECK_EQ(0u, Result.ReferencerStat.Deleted); + CHECK_EQ(5u, Result.ReferenceStoreStat.Count); + CHECK_EQ(0u, Result.ReferenceStoreStat.Pruned); + CHECK_EQ(0u, Result.ReferenceStoreStat.Compacted); + CHECK_EQ(0u, Result.RemovedDisk); CHECK_EQ(0u, Result.RemovedMemory); CHECK(ValidateCacheEntry(Zcs, CidStore, TearDrinkerBucket, CacheRecords[0], true, true)); @@ -2046,18 +2050,20 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); - GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::minutes(1), - .ProjectStoreExpireTime = GcClock::Now() + std::chrono::minutes(1), + GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::hours(1), + .ProjectStoreExpireTime = GcClock::Now() + std::chrono::hours(1), .CollectSmallObjects = true, - .IsDeleteMode = false}); - CHECK_EQ(7u, Result.Items); - CHECK_EQ(7u, Result.ExpiredItems); - CHECK_EQ(0u, Result.DeletedItems); - CHECK_EQ(5u, Result.References); - CHECK_EQ(0u, Result.PrunedReferences); - CHECK_EQ(0u, Result.CompactedReferences); - CHECK_EQ(0u, Result.RemovedDiskSpace); + .IsDeleteMode = false, + .Verbose = true}); + CHECK_EQ(7u, Result.ReferencerStat.Count); + CHECK_EQ(7u, Result.ReferencerStat.Expired); + CHECK_EQ(0u, Result.ReferencerStat.Deleted); + CHECK_EQ(5u, Result.ReferenceStoreStat.Count); + CHECK_EQ(0u, Result.ReferenceStoreStat.Pruned); + CHECK_EQ(0u, Result.ReferenceStoreStat.Compacted); + CHECK_EQ(0u, Result.RemovedDisk); CHECK_EQ(0u, Result.RemovedMemory); CHECK(ValidateCacheEntry(Zcs, CidStore, TearDrinkerBucket, CacheRecords[0], true, true)); @@ -2078,19 +2084,21 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); - GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::minutes(1), - .ProjectStoreExpireTime = GcClock::Now() + std::chrono::minutes(1), + GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::hours(1), + .ProjectStoreExpireTime = GcClock::Now() + std::chrono::hours(1), .CollectSmallObjects = false, .IsDeleteMode = true, - .SkipCidDelete = true}); - CHECK_EQ(7u, Result.Items); - CHECK_EQ(1u, Result.ExpiredItems); - CHECK_EQ(1u, Result.DeletedItems); - CHECK_EQ(0u, Result.References); - CHECK_EQ(0u, Result.PrunedReferences); - CHECK_EQ(0u, Result.CompactedReferences); - CHECK_EQ(CacheEntries[UnstructuredCacheValues[2]].Data.GetSize(), Result.RemovedDiskSpace); + .SkipCidDelete = true, + .Verbose = true}); + CHECK_EQ(7u, Result.ReferencerStat.Count); + CHECK_EQ(1u, Result.ReferencerStat.Expired); + CHECK_EQ(1u, Result.ReferencerStat.Deleted); + CHECK_EQ(0u, Result.ReferenceStoreStat.Count); + CHECK_EQ(0u, Result.ReferenceStoreStat.Pruned); + CHECK_EQ(0u, Result.ReferenceStoreStat.Compacted); + CHECK_EQ(CacheEntries[UnstructuredCacheValues[2]].Data.GetSize(), Result.RemovedDisk); CHECK_EQ(0u, Result.RemovedMemory); CHECK(ValidateCacheEntry(Zcs, CidStore, TearDrinkerBucket, CacheRecords[0], true, true)); @@ -2111,19 +2119,21 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); - GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::minutes(1), - .ProjectStoreExpireTime = GcClock::Now() + std::chrono::minutes(1), + GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::hours(1), + .ProjectStoreExpireTime = GcClock::Now() + std::chrono::hours(1), .CollectSmallObjects = true, .IsDeleteMode = true, - .SkipCidDelete = true}); - CHECK_EQ(7u, Result.Items); - CHECK_EQ(7u, Result.ExpiredItems); - CHECK_EQ(7u, Result.DeletedItems); - CHECK_EQ(0u, Result.References); - CHECK_EQ(0u, Result.PrunedReferences); - CHECK_EQ(0u, Result.CompactedReferences); - CHECK_GE(Result.RemovedDiskSpace, 0); + .SkipCidDelete = true, + .Verbose = true}); + CHECK_EQ(7u, Result.ReferencerStat.Count); + CHECK_EQ(7u, Result.ReferencerStat.Expired); + CHECK_EQ(7u, Result.ReferencerStat.Deleted); + CHECK_EQ(0u, Result.ReferenceStoreStat.Count); + CHECK_EQ(0u, Result.ReferenceStoreStat.Pruned); + CHECK_EQ(0u, Result.ReferenceStoreStat.Compacted); + CHECK_GE(Result.RemovedDisk, 0); CHECK_EQ(0u, Result.RemovedMemory); CHECK(ValidateCacheEntry(Zcs, CidStore, TearDrinkerBucket, CacheRecords[0], false, true)); @@ -2144,21 +2154,24 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); - GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::minutes(1), - .ProjectStoreExpireTime = GcClock::Now() + std::chrono::minutes(1), + GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::hours(1), + .ProjectStoreExpireTime = GcClock::Now() + std::chrono::hours(1), .CollectSmallObjects = false, .IsDeleteMode = true, - .SkipCidDelete = false}); - CHECK_EQ(7u, Result.Items); - CHECK_EQ(1u, Result.ExpiredItems); // Only one cache value is pruned/deleted as that is the only large item in the cache (all other - // large items as in cas) - CHECK_EQ(1u, Result.DeletedItems); - CHECK_EQ(5u, Result.References); + .SkipCidDelete = false, + .Verbose = true}); + CHECK_EQ(7u, Result.ReferencerStat.Count); + CHECK_EQ(1u, Result.ReferencerStat.Expired); // Only one cache value is pruned/deleted as that is the only large item in the cache + // (all other large items as in cas) + CHECK_EQ(1u, Result.ReferencerStat.Deleted); + CHECK_EQ(5u, Result.ReferenceStoreStat.Count); CHECK_EQ(0u, - Result.PrunedReferences); // We won't remove any references since all referencers are small which retains all references - CHECK_EQ(0u, Result.CompactedReferences); - CHECK_EQ(CacheEntries[UnstructuredCacheValues[2]].Data.GetSize(), Result.RemovedDiskSpace); + Result.ReferenceStoreStat + .Pruned); // We won't remove any references since all referencers are small which retains all references + CHECK_EQ(0u, Result.ReferenceStoreStat.Compacted); + CHECK_EQ(CacheEntries[UnstructuredCacheValues[2]].Data.GetSize(), Result.RemovedDisk); CHECK_EQ(0u, Result.RemovedMemory); CHECK(ValidateCacheEntry(Zcs, CidStore, TearDrinkerBucket, CacheRecords[0], true, true)); @@ -2179,18 +2192,21 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); - GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::minutes(1), - .ProjectStoreExpireTime = GcClock::Now() + std::chrono::minutes(1), - .CollectSmallObjects = true, - .IsDeleteMode = true, - .SkipCidDelete = false}); - CHECK_EQ(7u, Result.Items); - CHECK_EQ(7u, Result.ExpiredItems); - CHECK_EQ(7u, Result.DeletedItems); - CHECK_EQ(5u, Result.References); - CHECK_EQ(5u, Result.PrunedReferences); - CHECK_EQ(5u, Result.CompactedReferences); - CHECK_GT(Result.RemovedDiskSpace, 0); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); + + GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::hours(1), + .ProjectStoreExpireTime = GcClock::Now() + std::chrono::hours(1), + .CollectSmallObjects = true, + .IsDeleteMode = true, + .SkipCidDelete = false, + .Verbose = true}); + CHECK_EQ(7u, Result.ReferencerStat.Count); + CHECK_EQ(7u, Result.ReferencerStat.Expired); + CHECK_EQ(7u, Result.ReferencerStat.Deleted); + CHECK_EQ(5u, Result.ReferenceStoreStat.Count); + CHECK_EQ(5u, Result.ReferenceStoreStat.Pruned); + CHECK_EQ(5u, Result.ReferenceStoreStat.Compacted); + CHECK_GT(Result.RemovedDisk, 0); CHECK_EQ(0u, Result.RemovedMemory); CHECK(ValidateCacheEntry(Zcs, CidStore, TearDrinkerBucket, CacheRecords[0], false, false)); @@ -2212,20 +2228,23 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); - Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[0], GcClock::Now() + std::chrono::minutes(2)); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); - GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::minutes(1), - .ProjectStoreExpireTime = GcClock::Now() + std::chrono::minutes(1), + Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[0], GcClock::Now() + std::chrono::hours(2)); + + GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::hours(1), + .ProjectStoreExpireTime = GcClock::Now() + std::chrono::hours(1), .CollectSmallObjects = true, .IsDeleteMode = true, - .SkipCidDelete = true}); - CHECK_EQ(7u, Result.Items); - CHECK_EQ(6u, Result.ExpiredItems); - CHECK_EQ(6u, Result.DeletedItems); - CHECK_EQ(0u, Result.References); - CHECK_EQ(0u, Result.PrunedReferences); - CHECK_EQ(0u, Result.CompactedReferences); - CHECK_GT(Result.RemovedDiskSpace, 0); + .SkipCidDelete = true, + .Verbose = true}); + CHECK_EQ(7u, Result.ReferencerStat.Count); + CHECK_EQ(6u, Result.ReferencerStat.Expired); + CHECK_EQ(6u, Result.ReferencerStat.Deleted); + CHECK_EQ(0u, Result.ReferenceStoreStat.Count); + CHECK_EQ(0u, Result.ReferenceStoreStat.Pruned); + CHECK_EQ(0u, Result.ReferenceStoreStat.Compacted); + CHECK_GT(Result.RemovedDisk, 0); CHECK_EQ(0u, Result.RemovedMemory); CHECK(ValidateCacheEntry(Zcs, CidStore, TearDrinkerBucket, CacheRecords[0], true, true)); @@ -2247,21 +2266,24 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); - Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[0], GcClock::Now() + std::chrono::minutes(2)); - Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[1], GcClock::Now() + std::chrono::minutes(2)); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); + + Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[0], GcClock::Now() + std::chrono::hours(2)); + Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[1], GcClock::Now() + std::chrono::hours(2)); - GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::minutes(1), - .ProjectStoreExpireTime = GcClock::Now() + std::chrono::minutes(1), + GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::hours(1), + .ProjectStoreExpireTime = GcClock::Now() + std::chrono::hours(1), .CollectSmallObjects = true, .IsDeleteMode = true, - .SkipCidDelete = false}); - CHECK_EQ(7u, Result.Items); - CHECK_EQ(5u, Result.ExpiredItems); - CHECK_EQ(5u, Result.DeletedItems); - CHECK_EQ(5u, Result.References); - CHECK_EQ(0u, Result.PrunedReferences); - CHECK_EQ(0u, Result.CompactedReferences); - CHECK_GT(Result.RemovedDiskSpace, 0); + .SkipCidDelete = false, + .Verbose = true}); + CHECK_EQ(7u, Result.ReferencerStat.Count); + CHECK_EQ(5u, Result.ReferencerStat.Expired); + CHECK_EQ(5u, Result.ReferencerStat.Deleted); + CHECK_EQ(5u, Result.ReferenceStoreStat.Count); + CHECK_EQ(0u, Result.ReferenceStoreStat.Pruned); + CHECK_EQ(0u, Result.ReferenceStoreStat.Compacted); + CHECK_GT(Result.RemovedDisk, 0); CHECK_EQ(0u, Result.RemovedMemory); CHECK(ValidateCacheEntry(Zcs, CidStore, TearDrinkerBucket, CacheRecords[0], true, true)); @@ -2283,22 +2305,25 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); - Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[1], GcClock::Now() + std::chrono::minutes(2)); - Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[2], GcClock::Now() + std::chrono::minutes(2)); - Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[3], GcClock::Now() + std::chrono::minutes(2)); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); + + Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[1], GcClock::Now() + std::chrono::hours(2)); + Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[2], GcClock::Now() + std::chrono::hours(2)); + Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[3], GcClock::Now() + std::chrono::hours(2)); - GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::minutes(1), - .ProjectStoreExpireTime = GcClock::Now() + std::chrono::minutes(1), + GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::hours(1), + .ProjectStoreExpireTime = GcClock::Now() + std::chrono::hours(1), .CollectSmallObjects = true, .IsDeleteMode = true, - .SkipCidDelete = false}); - CHECK_EQ(7u, Result.Items); - CHECK_EQ(4u, Result.ExpiredItems); - CHECK_EQ(4u, Result.DeletedItems); - CHECK_EQ(5u, Result.References); - CHECK_EQ(5u, Result.PrunedReferences); - CHECK_EQ(5u, Result.CompactedReferences); - CHECK_GT(Result.RemovedDiskSpace, 0); + .SkipCidDelete = false, + .Verbose = true}); + CHECK_EQ(7u, Result.ReferencerStat.Count); + CHECK_EQ(4u, Result.ReferencerStat.Expired); + CHECK_EQ(4u, Result.ReferencerStat.Deleted); + CHECK_EQ(5u, Result.ReferenceStoreStat.Count); + CHECK_EQ(5u, Result.ReferenceStoreStat.Pruned); + CHECK_EQ(5u, Result.ReferenceStoreStat.Compacted); + CHECK_GT(Result.RemovedDisk, 0); CHECK_EQ(0u, Result.RemovedMemory); CHECK(ValidateCacheEntry(Zcs, CidStore, TearDrinkerBucket, CacheRecords[0], false, false)); @@ -2320,6 +2345,7 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); // Prime so we can check GC of memory layer ZenCacheValue Dummy; @@ -2331,22 +2357,23 @@ TEST_CASE("z$.newgc.basics") Zcs.Get(TearDrinkerBucket, UnstructuredCacheValues[2], Dummy); Zcs.Get(TearDrinkerBucket, UnstructuredCacheValues[3], Dummy); - Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[1], GcClock::Now() + std::chrono::minutes(2)); - Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[2], GcClock::Now() + std::chrono::minutes(2)); - Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[3], GcClock::Now() + std::chrono::minutes(2)); + Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[1], GcClock::Now() + std::chrono::hours(2)); + Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[2], GcClock::Now() + std::chrono::hours(2)); + Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[3], GcClock::Now() + std::chrono::hours(2)); - GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::minutes(1), - .ProjectStoreExpireTime = GcClock::Now() + std::chrono::minutes(1), + GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::hours(1), + .ProjectStoreExpireTime = GcClock::Now() + std::chrono::hours(1), .CollectSmallObjects = true, .IsDeleteMode = true, - .SkipCidDelete = true}); - CHECK_EQ(7u, Result.Items); - CHECK_EQ(4u, Result.ExpiredItems); - CHECK_EQ(4u, Result.DeletedItems); - CHECK_EQ(0u, Result.References); - CHECK_EQ(0u, Result.PrunedReferences); - CHECK_EQ(0u, Result.CompactedReferences); - CHECK_GT(Result.RemovedDiskSpace, 0); + .SkipCidDelete = true, + .Verbose = true}); + CHECK_EQ(7u, Result.ReferencerStat.Count); + CHECK_EQ(4u, Result.ReferencerStat.Expired); + CHECK_EQ(4u, Result.ReferencerStat.Deleted); + CHECK_EQ(0u, Result.ReferenceStoreStat.Count); + CHECK_EQ(0u, Result.ReferenceStoreStat.Pruned); + CHECK_EQ(0u, Result.ReferenceStoreStat.Compacted); + CHECK_GT(Result.RemovedDisk, 0); uint64_t MemoryClean = CacheEntries[CacheRecords[0]].Data.GetSize() + CacheEntries[CacheRecords[1]].Data.GetSize() + CacheEntries[CacheRecords[2]].Data.GetSize() + CacheEntries[UnstructuredCacheValues[0]].Data.GetSize(); CHECK_EQ(MemoryClean, Result.RemovedMemory); @@ -2370,35 +2397,36 @@ TEST_CASE("z$.newgc.basics") *JobQueue, TempDir.Path() / "cache", {.DiskLayerConfig = {.BucketConfig = {.EnableReferenceCaching = true}}}); + CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount); auto Attachments = CreateCompressedAttachment(CidStore, std::vector<size_t>{177, 1024 * 1024 * 2 + 31, 8999, 1024 * 1024 * 2 + 187}); IoHash CacheRecord = CreateCacheRecord(Zcs, CidStore, TearDrinkerBucket, Attachments); - Zcs.SetAccessTime(TearDrinkerBucket, CacheRecord, GcClock::Now() - std::chrono::minutes(2)); + Zcs.SetAccessTime(TearDrinkerBucket, CacheRecord, GcClock::Now() - std::chrono::hours(2)); - Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[0], GcClock::Now() + std::chrono::minutes(2)); - Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[1], GcClock::Now() + std::chrono::minutes(2)); - Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[2], GcClock::Now() + std::chrono::minutes(2)); + Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[0], GcClock::Now() + std::chrono::hours(2)); + Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[1], GcClock::Now() + std::chrono::hours(2)); + Zcs.SetAccessTime(TearDrinkerBucket, CacheRecords[2], GcClock::Now() + std::chrono::hours(2)); - Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[0], GcClock::Now() + std::chrono::minutes(2)); - Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[1], GcClock::Now() + std::chrono::minutes(2)); - Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[2], GcClock::Now() + std::chrono::minutes(2)); - Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[3], GcClock::Now() + std::chrono::minutes(2)); + Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[0], GcClock::Now() + std::chrono::hours(2)); + Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[1], GcClock::Now() + std::chrono::hours(2)); + Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[2], GcClock::Now() + std::chrono::hours(2)); + Zcs.SetAccessTime(TearDrinkerBucket, UnstructuredCacheValues[3], GcClock::Now() + std::chrono::hours(2)); - GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::minutes(1), - .ProjectStoreExpireTime = GcClock::Now() + std::chrono::minutes(1), + GcResult Result = Gc.CollectGarbage(GcSettings{.CacheExpireTime = GcClock::Now() + std::chrono::hours(1), + .ProjectStoreExpireTime = GcClock::Now() + std::chrono::hours(1), .CollectSmallObjects = true, .IsDeleteMode = true, - .SkipCidDelete = false}); - CHECK_EQ(8u, Result.Items); - CHECK_EQ(1u, Result.ExpiredItems); - CHECK_EQ(1u, Result.DeletedItems); - CHECK_EQ(9u, Result.References); - CHECK_EQ(4u, Result.PrunedReferences); - CHECK_EQ(4u, Result.CompactedReferences); - CHECK_EQ(Attachments[1].second.GetCompressed().GetSize() + Attachments[3].second.GetCompressed().GetSize(), - Result.RemovedDiskSpace); + .SkipCidDelete = false, + .Verbose = true}); + CHECK_EQ(8u, Result.ReferencerStat.Count); + CHECK_EQ(1u, Result.ReferencerStat.Expired); + CHECK_EQ(1u, Result.ReferencerStat.Deleted); + CHECK_EQ(9u, Result.ReferenceStoreStat.Count); + CHECK_EQ(4u, Result.ReferenceStoreStat.Pruned); + CHECK_EQ(4u, Result.ReferenceStoreStat.Compacted); + CHECK_EQ(Attachments[1].second.GetCompressed().GetSize() + Attachments[3].second.GetCompressed().GetSize(), Result.RemovedDisk); uint64_t MemoryClean = CacheEntries[CacheRecord].Data.GetSize(); CHECK_EQ(MemoryClean, Result.RemovedMemory); } diff --git a/src/zenserver/projectstore/projectstore.cpp b/src/zenserver/projectstore/projectstore.cpp index 274876123..7da2f7695 100644 --- a/src/zenserver/projectstore/projectstore.cpp +++ b/src/zenserver/projectstore/projectstore.cpp @@ -3036,8 +3036,14 @@ ProjectStore::AreDiskWritesAllowed() const return (m_DiskWriteBlocker == nullptr || m_DiskWriteBlocker->AreDiskWritesAllowed()); } +std::string +ProjectStore::GetGcName(GcCtx&) +{ + return fmt::format("projectstore:'{}'", m_ProjectBasePath.string()); +} + void -ProjectStore::RemoveExpiredData(GcCtx& Ctx) +ProjectStore::RemoveExpiredData(GcCtx& Ctx, GcReferencerStats& Stats) { size_t ProjectCount = 0; size_t ExpiredProjectCount = 0; @@ -3045,13 +3051,18 @@ ProjectStore::RemoveExpiredData(GcCtx& Ctx) size_t ExpiredOplogCount = 0; Stopwatch Timer; const auto _ = MakeGuard([&] { - ZEN_DEBUG("gc project store '{}': removed {} expired projects out of {}, {} expired oplogs out of {} in {}", - m_ProjectBasePath, - ExpiredProjectCount, - ProjectCount, - ExpiredOplogCount, - OplogCount, - NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: projectstore [REMOVE EXPIRED] '{}': Count: {}, Expired: {}, Deleted: {}, RemovedDisk: {}, RemovedMemory: {} in {}", + m_ProjectBasePath, + Stats.Count, + Stats.Expired, + Stats.Deleted, + NiceBytes(Stats.RemovedDisk), + NiceBytes(Stats.RemovedMemory), + NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }); std::vector<Ref<Project>> ExpiredProjects; @@ -3097,14 +3108,14 @@ ProjectStore::RemoveExpiredData(GcCtx& Ctx) OplogId, Project->Identifier); Project->DeleteOplog(OplogId); - Ctx.RemovedDiskSpace.fetch_add(OplogSize); + Stats.RemovedDisk += OplogSize; } - Ctx.DeletedItems.fetch_add(ExpiredOplogs.size()); + Stats.Deleted += ExpiredOplogs.size(); Project->Flush(); } } ProjectCount = Projects.size(); - Ctx.Items.fetch_add(ProjectCount + OplogCount); + Stats.Count += ProjectCount + OplogCount; ExpiredProjectCount = ExpiredProjects.size(); if (ExpiredProjects.empty()) @@ -3150,20 +3161,34 @@ ProjectStore::RemoveExpiredData(GcCtx& Ctx) DeleteDirectories(PathToRemove); } - Ctx.DeletedItems.fetch_add(ExpiredProjects.size()); + Stats.Deleted += ExpiredProjects.size(); } - Ctx.ExpiredItems.fetch_add(ExpiredOplogCount + ExpiredProjectCount); + Stats.Expired += ExpiredOplogCount + ExpiredProjectCount; } class ProjectStoreReferenceChecker : public GcReferenceChecker { public: - ProjectStoreReferenceChecker(ProjectStore::Oplog& Owner, bool PreCache) : m_Oplog(Owner) + ProjectStoreReferenceChecker(GcCtx& Ctx, ProjectStore::Oplog& Owner, bool PreCache) : m_Oplog(Owner) { if (PreCache) { - RwLock::SharedLockScope _(m_Oplog.m_OplogLock); + Stopwatch Timer; + const auto _ = MakeGuard([&] { + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: projectstore [LOCKSTATE] '{}': precached {} references in {} from {}/{}", + m_Oplog.m_BasePath, + m_UncachedReferences.size(), + NiceTimeSpanMs(Timer.GetElapsedTimeMs()), + m_Oplog.m_OuterProject->Identifier, + m_Oplog.OplogId()); + }); + + RwLock::SharedLockScope __(m_Oplog.m_OplogLock); m_Oplog.IterateOplog([&](CbObjectView Op) { Op.IterateAttachments([&](CbFieldView Visitor) { m_UncachedReferences.insert(Visitor.AsAttachment()); }); }); @@ -3173,16 +3198,20 @@ public: virtual ~ProjectStoreReferenceChecker() {} - virtual void LockState(GcCtx&) override + virtual void LockState(GcCtx& Ctx) override { Stopwatch Timer; const auto _ = MakeGuard([&] { - ZEN_DEBUG("gc project oplog '{}': found {} references in {} from {}/{}", - m_Oplog.m_BasePath, - m_UncachedReferences.size(), - NiceTimeSpanMs(Timer.GetElapsedTimeMs()), - m_Oplog.m_OuterProject->Identifier, - m_Oplog.OplogId()); + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: projectstore [LOCKSTATE] '{}': found {} references in {} from {}/{}", + m_Oplog.m_BasePath, + m_UncachedReferences.size(), + NiceTimeSpanMs(Timer.GetElapsedTimeMs()), + m_Oplog.m_OuterProject->Identifier, + m_Oplog.OplogId()); }); m_OplogLock = std::make_unique<RwLock::SharedLockScope>(m_Oplog.m_OplogLock); @@ -3211,18 +3240,22 @@ public: }; std::vector<GcReferenceChecker*> -ProjectStore::CreateReferenceCheckers(GcCtx&) +ProjectStore::CreateReferenceCheckers(GcCtx& Ctx) { size_t ProjectCount = 0; size_t OplogCount = 0; Stopwatch Timer; const auto _ = MakeGuard([&] { - ZEN_DEBUG("gc project store '{}': opened {} projects and {} oplogs in {}", - m_ProjectBasePath, - ProjectCount, - OplogCount, - NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: projectstore [CREATE CHECKERS] '{}': opened {} projects and {} oplogs in {}", + m_ProjectBasePath, + ProjectCount, + OplogCount, + NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }); DiscoverProjects(); @@ -3250,7 +3283,7 @@ ProjectStore::CreateReferenceCheckers(GcCtx&) ProjectStore::Oplog* Oplog = Project->OpenOplog(OpLogId); GcClock::TimePoint Now = GcClock::Now(); bool TryPreCache = Project->LastOplogAccessTime(OpLogId) < (Now - std::chrono::minutes(5)); - Checkers.emplace_back(new ProjectStoreReferenceChecker(*Oplog, TryPreCache)); + Checkers.emplace_back(new ProjectStoreReferenceChecker(Ctx, *Oplog, TryPreCache)); } OplogCount += OpLogs.size(); } diff --git a/src/zenserver/projectstore/projectstore.h b/src/zenserver/projectstore/projectstore.h index 94e697278..fbff1444d 100644 --- a/src/zenserver/projectstore/projectstore.h +++ b/src/zenserver/projectstore/projectstore.h @@ -293,7 +293,8 @@ public: virtual void CollectGarbage(GcContext& GcCtx) override; virtual GcStorageSize StorageSize() const override; - virtual void RemoveExpiredData(GcCtx& Ctx) override; + virtual std::string GetGcName(GcCtx& Ctx) override; + virtual void RemoveExpiredData(GcCtx& Ctx, GcReferencerStats& Stats) override; virtual std::vector<GcReferenceChecker*> CreateReferenceCheckers(GcCtx& Ctx) override; CbArray GetProjectsList(); diff --git a/src/zenstore/compactcas.cpp b/src/zenstore/compactcas.cpp index f93dafa21..655c0558d 100644 --- a/src/zenstore/compactcas.cpp +++ b/src/zenstore/compactcas.cpp @@ -567,15 +567,22 @@ public: { } - virtual void CompactReferenceStore(GcCtx& Ctx) + virtual void CompactReferenceStore(GcCtx& Ctx, GcReferenceStoreStats& Stats) { - size_t CompactedCount = 0; Stopwatch Timer; const auto _ = MakeGuard([&] { - ZEN_DEBUG("gc block store '{}': compacted {} cids in {}", - m_CasContainerStrategy.m_RootDirectory / m_CasContainerStrategy.m_ContainerBaseName, - CompactedCount, - NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: compactcas [COMPACT] '{}': Count: {}, Pruned: {}, Compacted: {}, RemovedDisk: {}, RemovedMemory: {} in {}", + m_CasContainerStrategy.m_RootDirectory / m_CasContainerStrategy.m_ContainerBaseName, + Stats.Count, + Stats.Pruned, + Stats.Compacted, + NiceBytes(Stats.RemovedDisk), + NiceBytes(Stats.RemovedMemory), + NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }); if (Ctx.Settings.IsDeleteMode && Ctx.Settings.CollectSmallObjects) @@ -609,13 +616,12 @@ public: } } m_CasContainerStrategy.m_CasLog.Append(MovedEntries); - Ctx.RemovedDiskSpace.fetch_add(FreedDiskSpace); + Stats.RemovedDisk += FreedDiskSpace; }, [&]() { return 0; }); - CompactedCount = m_PrunedKeys.size(); - Ctx.CompactedReferences.fetch_add( - CompactedCount); // Slightly missleading, it might not be compacted if the block is the currently writing block + Stats.Compacted += + m_PrunedKeys.size(); // Slightly missleading, it might not be compacted if the block is the currently writing block } } @@ -634,21 +640,27 @@ public: { } - virtual GcReferenceStoreCompactor* RemoveUnreferencedData(GcCtx& Ctx, const GetUnusedReferencesFunc& GetUnusedReferences) + virtual GcReferenceStoreCompactor* RemoveUnreferencedData(GcCtx& Ctx, + GcReferenceStoreStats& Stats, + const GetUnusedReferencesFunc& GetUnusedReferences) { - size_t TotalCount = m_Cids.size(); - size_t PruneCount = 0; Stopwatch Timer; const auto _ = MakeGuard([&] { - ZEN_DEBUG("gc block store '{}': removed {} unused cid out of {} in {}", - m_CasContainerStrategy.m_RootDirectory / m_CasContainerStrategy.m_ContainerBaseName, - PruneCount, - TotalCount, - NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: compactcas [PRUNE] '{}': Count: {}, Pruned: {}, Compacted: {}, RemovedDisk: {}, RemovedMemory: {} in {}", + m_CasContainerStrategy.m_RootDirectory / m_CasContainerStrategy.m_ContainerBaseName, + Stats.Count, + Stats.Pruned, + Stats.Compacted, + NiceBytes(Stats.RemovedDisk), + NiceBytes(Stats.RemovedMemory), + NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }); std::vector<IoHash> UnusedCids = GetUnusedReferences(m_Cids); - m_Cids.clear(); if (UnusedCids.empty()) { @@ -725,8 +737,7 @@ public: } } - PruneCount = UnusedKeys.size(); - Ctx.PrunedReferences.fetch_add(PruneCount); + Stats.Pruned += UnusedKeys.size(); return new CasContainerStoreCompactor(m_CasContainerStrategy, std::move(CompactState), std::move(CompactStateKeys), @@ -738,33 +749,45 @@ private: std::vector<IoHash> m_Cids; }; +std::string +CasContainerStrategy::GetGcName(GcCtx&) +{ + return fmt::format("compactcas:'{}'", (m_RootDirectory / m_ContainerBaseName).string()); +} + GcReferencePruner* -CasContainerStrategy::CreateReferencePruner(GcCtx& Ctx) +CasContainerStrategy::CreateReferencePruner(GcCtx& Ctx, GcReferenceStoreStats& Stats) { - size_t TotalCount = 0; Stopwatch Timer; const auto _ = MakeGuard([&] { - ZEN_DEBUG("gc block store '{}': found {} cid keys to check in {}", - m_RootDirectory / m_ContainerBaseName, - TotalCount, - NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: compactcas [CREATE PRUNERS] '{}': Count: {}, Pruned: {}, Compacted: {}, RemovedDisk: {}, RemovedMemory: {} in {}", + m_RootDirectory / m_ContainerBaseName, + Stats.Count, + Stats.Pruned, + Stats.Compacted, + NiceBytes(Stats.RemovedDisk), + NiceBytes(Stats.RemovedMemory), + NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }); std::vector<IoHash> CidsToCheck; { RwLock::SharedLockScope __(m_LocationMapLock); + if (m_LocationMap.empty()) + { + return {}; + } CidsToCheck.reserve(m_LocationMap.size()); for (const auto& It : m_LocationMap) { CidsToCheck.push_back(It.first); } } - TotalCount = CidsToCheck.size(); - if (TotalCount == 0) - { - return {}; - } - Ctx.References.fetch_add(TotalCount); + Stats.Count += CidsToCheck.size(); return new CasContainerReferencePruner(*this, std::move(CidsToCheck)); } diff --git a/src/zenstore/compactcas.h b/src/zenstore/compactcas.h index 9ff4ae4fc..9f3aa6b07 100644 --- a/src/zenstore/compactcas.h +++ b/src/zenstore/compactcas.h @@ -71,7 +71,8 @@ struct CasContainerStrategy final : public GcStorage, public GcReferenceStore virtual void CollectGarbage(GcContext& GcCtx) override; virtual GcStorageSize StorageSize() const override; - virtual GcReferencePruner* CreateReferencePruner(GcCtx& Ctx) override; + virtual std::string GetGcName(GcCtx& Ctx) override; + virtual GcReferencePruner* CreateReferencePruner(GcCtx& Ctx, GcReferenceStoreStats& Stats) override; private: CasStore::InsertResult InsertChunk(const void* ChunkData, size_t ChunkSize, const IoHash& ChunkHash); diff --git a/src/zenstore/filecas.cpp b/src/zenstore/filecas.cpp index e28e0dea4..c021e0e21 100644 --- a/src/zenstore/filecas.cpp +++ b/src/zenstore/filecas.cpp @@ -1340,15 +1340,22 @@ public: { } - virtual void CompactReferenceStore(GcCtx& Ctx) + virtual void CompactReferenceStore(GcCtx& Ctx, GcReferenceStoreStats& Stats) { - size_t CompactedCount = 0; Stopwatch Timer; const auto _ = MakeGuard([&] { - ZEN_DEBUG("gc file store '{}': removed data for {} unused cids in {}", - m_FileCasStrategy.m_RootDirectory, - CompactedCount, - NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: filecas [COMPACT] '{}': Count: {}, Pruned: {}, Compacted: {}, RemovedDisk: {}, RemovedMemory: {} in {}", + m_FileCasStrategy.m_RootDirectory, + Stats.Count, + Stats.Pruned, + Stats.Compacted, + NiceBytes(Stats.RemovedDisk), + NiceBytes(Stats.RemovedMemory), + NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }); std::vector<IoHash> ReferencedCleaned; ReferencedCleaned.reserve(m_ReferencesToClean.size()); @@ -1382,7 +1389,7 @@ public: { continue; } - Ctx.RemovedDiskSpace.fetch_add(SizeOnDisk); + Stats.RemovedDisk += SizeOnDisk; } else { @@ -1401,8 +1408,7 @@ public: ReferencedCleaned.push_back(ChunkHash); } } - CompactedCount = ReferencedCleaned.size(); - Ctx.CompactedReferences.fetch_add(ReferencedCleaned.size()); + Stats.Compacted += ReferencedCleaned.size(); } private: @@ -1415,27 +1421,38 @@ class FileCasReferencePruner : public GcReferencePruner public: FileCasReferencePruner(FileCasStrategy& Owner, std::vector<IoHash>&& Cids) : m_FileCasStrategy(Owner), m_Cids(std::move(Cids)) {} - virtual GcReferenceStoreCompactor* RemoveUnreferencedData(GcCtx& Ctx, const GetUnusedReferencesFunc& GetUnusedReferences) + virtual GcReferenceStoreCompactor* RemoveUnreferencedData(GcCtx& Ctx, + GcReferenceStoreStats& Stats, + const GetUnusedReferencesFunc& GetUnusedReferences) { - size_t TotalCount = m_Cids.size(); - size_t PruneCount = 0; Stopwatch Timer; const auto _ = MakeGuard([&] { - ZEN_DEBUG("gc file store '{}': removed {} unused cid out of {} in {}", - m_FileCasStrategy.m_RootDirectory, - PruneCount, - TotalCount, - NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: filecas [PRUNE] '{}': Count: {}, Pruned: {}, Compacted: {}, RemovedDisk: {}, RemovedMemory: {} in {}", + m_FileCasStrategy.m_RootDirectory, + Stats.Count, + Stats.Pruned, + Stats.Compacted, + NiceBytes(Stats.RemovedDisk), + NiceBytes(Stats.RemovedMemory), + NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }); - std::vector<IoHash> UnusedReferences = GetUnusedReferences(m_Cids); - m_Cids.clear(); + std::vector<IoHash> UnusedCids = GetUnusedReferences(m_Cids); + if (UnusedCids.empty()) + { + // Nothing to collect + return nullptr; + } std::vector<IoHash> PrunedReferences; - PrunedReferences.reserve(UnusedReferences.size()); + PrunedReferences.reserve(UnusedCids.size()); { RwLock::ExclusiveLockScope __(m_FileCasStrategy.m_Lock); - for (const IoHash& ChunkHash : UnusedReferences) + for (const IoHash& ChunkHash : UnusedCids) { auto It = m_FileCasStrategy.m_Index.find(ChunkHash); if (It == m_FileCasStrategy.m_Index.end()) @@ -1454,8 +1471,7 @@ public: } } - PruneCount = PrunedReferences.size(); - Ctx.PrunedReferences.fetch_add(PruneCount); + Stats.Pruned += PrunedReferences.size(); return new FileCasStoreCompactor(m_FileCasStrategy, std::move(PrunedReferences)); } @@ -1464,33 +1480,44 @@ private: std::vector<IoHash> m_Cids; }; +std::string +FileCasStrategy::GetGcName(GcCtx&) +{ + return fmt::format("filecas:'{}'", m_RootDirectory.string()); +} + GcReferencePruner* -FileCasStrategy::CreateReferencePruner(GcCtx& Ctx) +FileCasStrategy::CreateReferencePruner(GcCtx& Ctx, GcReferenceStoreStats& Stats) { - // TODO - std::size_t TotalCount = 0; Stopwatch Timer; const auto _ = MakeGuard([&] { - ZEN_DEBUG("gc file store '{}': found {} cid keys to check in {}", - m_RootDirectory, - TotalCount, - NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + if (!Ctx.Settings.Verbose) + { + return; + } + ZEN_INFO("GCV2: filecas [CREATE PRUNERS] '{}': Count: {}, Pruned: {}, Compacted: {}, RemovedDisk: {}, RemovedMemory: {} in {}", + m_RootDirectory, + Stats.Count, + Stats.Pruned, + Stats.Compacted, + NiceBytes(Stats.RemovedDisk), + NiceBytes(Stats.RemovedMemory), + NiceTimeSpanMs(Timer.GetElapsedTimeMs())); }); std::vector<IoHash> CidsToCheck; { RwLock::SharedLockScope __(m_Lock); + if (m_Index.empty()) + { + return {}; + } CidsToCheck.reserve(m_Index.size()); for (const auto& It : m_Index) { CidsToCheck.push_back(It.first); } } - TotalCount = CidsToCheck.size(); - if (TotalCount == 0) - { - return {}; - } - Ctx.References.fetch_add(TotalCount); + Stats.Count += CidsToCheck.size(); return new FileCasReferencePruner(*this, std::move(CidsToCheck)); } diff --git a/src/zenstore/filecas.h b/src/zenstore/filecas.h index 2e9a1d5dc..c39a39bb7 100644 --- a/src/zenstore/filecas.h +++ b/src/zenstore/filecas.h @@ -44,7 +44,8 @@ struct FileCasStrategy final : public GcStorage, public GcReferenceStore virtual void CollectGarbage(GcContext& GcCtx) override; virtual GcStorageSize StorageSize() const override; - virtual GcReferencePruner* CreateReferencePruner(GcCtx& Ctx) override; + virtual std::string GetGcName(GcCtx& Ctx) override; + virtual GcReferencePruner* CreateReferencePruner(GcCtx& Ctx, GcReferenceStoreStats& Stats) override; private: void MakeIndexSnapshot(); diff --git a/src/zenstore/gc.cpp b/src/zenstore/gc.cpp index e09f46063..4d146c16c 100644 --- a/src/zenstore/gc.cpp +++ b/src/zenstore/gc.cpp @@ -330,6 +330,47 @@ GcManager::~GcManager() //////// Begin New GC WIP void +GcResult::Sum() +{ + for (std::pair<std::string, GcReferencerStats>& Referencer : ReferencerStats) + { + GcReferencerStats& SubStat = Referencer.second; + ReferencerStat.Count += SubStat.Count; + ReferencerStat.Expired += SubStat.Expired; + ReferencerStat.Deleted += SubStat.Deleted; + ReferencerStat.RemovedDisk += SubStat.RemovedDisk; + ReferencerStat.RemovedMemory += SubStat.RemovedMemory; + SubStat.ElapsedMS = SubStat.RemoveExpiredDataMS + SubStat.CreateReferenceCheckersMS + SubStat.LockStateMS; + + ReferencerStat.RemoveExpiredDataMS += SubStat.RemoveExpiredDataMS; + ReferencerStat.CreateReferenceCheckersMS += SubStat.CreateReferenceCheckersMS; + ReferencerStat.LockStateMS += SubStat.LockStateMS; + ReferencerStat.ElapsedMS += SubStat.ElapsedMS; + + RemovedDisk += SubStat.RemovedDisk; + RemovedMemory += SubStat.RemovedMemory; + } + for (std::pair<std::string, GcReferenceStoreStats>& ReferenceStore : ReferenceStoreStats) + { + GcReferenceStoreStats& SubStat = ReferenceStore.second; + ReferenceStoreStat.Count += SubStat.Count; + ReferenceStoreStat.Pruned += SubStat.Pruned; + ReferenceStoreStat.Compacted += SubStat.Compacted; + ReferenceStoreStat.RemovedDisk += SubStat.RemovedDisk; + ReferenceStoreStat.RemovedMemory += SubStat.RemovedMemory; + SubStat.ElapsedMS = SubStat.CreateReferencePrunerMS + SubStat.RemoveUnreferencedDataMS + SubStat.CompactReferenceStoreMS; + + ReferenceStoreStat.CreateReferencePrunerMS += SubStat.CreateReferencePrunerMS; + ReferenceStoreStat.RemoveUnreferencedDataMS += SubStat.RemoveUnreferencedDataMS; + ReferenceStoreStat.CompactReferenceStoreMS += SubStat.CompactReferenceStoreMS; + ReferenceStoreStat.ElapsedMS += SubStat.ElapsedMS; + + RemovedDisk += SubStat.RemovedDisk; + RemovedMemory += SubStat.RemovedMemory; + } +} + +void GcManager::AddGcReferencer(GcReferencer& Referencer) { RwLock::ExclusiveLockScope _(m_Lock); @@ -358,245 +399,270 @@ GcManager::RemoveGcReferenceStore(GcReferenceStore& ReferenceStore) GcResult GcManager::CollectGarbage(const GcSettings& Settings) { - GcCtx Ctx{.Settings = Settings}; - - Stopwatch TotalTimer; - auto __ = MakeGuard([&]() { - ZEN_INFO( - "GC: Removed {} items out of {}, deleted {} out of {}. Pruned {} Cid entries out of {}, compacted {} Cid entries out of {}, " - "freed " - "{} on disk and {} of memory in {}", - Ctx.ExpiredItems.load(), - Ctx.Items.load(), - Ctx.DeletedItems.load(), - Ctx.ExpiredItems.load(), - Ctx.PrunedReferences.load(), - Ctx.References.load(), - Ctx.CompactedReferences.load(), - Ctx.PrunedReferences.load(), - NiceBytes(Ctx.RemovedDiskSpace.load()), - NiceBytes(Ctx.RemovedMemory.load()), - NiceTimeSpanMs(TotalTimer.GetElapsedTimeMs())); - }); - - RwLock::SharedLockScope GcLock(m_Lock); - - static const bool SingleThread = -#if ZEN_BUILD_DEBUG - true -#else - false -#endif - ; - WorkerThreadPool ThreadPool(SingleThread ? 0 : 8); - - if (!m_GcReferencers.empty()) - { - Latch WorkLeft(1); - // First remove any cache keys that may own references - Stopwatch Timer; - auto _ = MakeGuard([&]() { ZEN_INFO("GC: Removed expired data in {}", NiceTimeSpanMs(Timer.GetElapsedTimeMs())) }); - for (GcReferencer* Owner : m_GcReferencers) - { - WorkLeft.AddCount(1); - ThreadPool.ScheduleWork([&Ctx, Owner, &WorkLeft]() { - auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); - Owner->RemoveExpiredData(Ctx); - }); - } - WorkLeft.CountDown(); - WorkLeft.Wait(); - } + GcCtx Ctx{.Settings = Settings}; + GcResult Result; - if (Ctx.Settings.SkipCidDelete) { - return GcResult{.Items = Ctx.Items.load(), - .ExpiredItems = Ctx.ExpiredItems.load(), - .DeletedItems = Ctx.DeletedItems.load(), - .References = Ctx.References.load(), - .PrunedReferences = Ctx.PrunedReferences.load(), - .CompactedReferences = Ctx.CompactedReferences.load(), - .RemovedDiskSpace = Ctx.RemovedDiskSpace.load(), - .RemovedMemory = Ctx.RemovedMemory.load()}; - } + Stopwatch TotalTimer; + auto __ = MakeGuard([&]() { Result.ElapsedMS = std::chrono::milliseconds(TotalTimer.GetElapsedTimeMs()); }); - std::vector<std::unique_ptr<GcReferencePruner>> ReferencePruners; - if (!m_GcReferenceStores.empty()) - { - ReferencePruners.reserve(m_GcReferenceStores.size()); - Latch WorkLeft(1); - RwLock ReferencePrunersLock; - // Easy to go wide, CreateReferencePruner is usually not very heavy but big data sets change that - Stopwatch Timer; - auto _ = MakeGuard([&]() { ZEN_INFO("GC: Created Cid pruners in {}", NiceTimeSpanMs(Timer.GetElapsedTimeMs())) }); - for (GcReferenceStore* CidStore : m_GcReferenceStores) - { - WorkLeft.AddCount(1); - ThreadPool.ScheduleWork([&Ctx, CidStore, &WorkLeft, &ReferencePrunersLock, &ReferencePruners]() { - auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); - // The CidStore will pick a list of CId entries to check, returning a collector - std::unique_ptr<GcReferencePruner> ReferencePruner(CidStore->CreateReferencePruner(Ctx)); - if (ReferencePruner) - { - RwLock::ExclusiveLockScope __(ReferencePrunersLock); - ReferencePruners.emplace_back(std::move(ReferencePruner)); - } - }); - } - WorkLeft.CountDown(); - WorkLeft.Wait(); - } + RwLock::SharedLockScope GcLock(m_Lock); - std::vector<std::unique_ptr<GcReferenceChecker>> ReferenceCheckers; - if (!m_GcReferencers.empty()) - { - ReferenceCheckers.reserve(m_GcReferencers.size()); - Latch WorkLeft(1); - RwLock ReferenceCheckersLock; - Stopwatch Timer; - auto _ = MakeGuard([&]() { ZEN_INFO("GC: Created Cid checkers in {}", NiceTimeSpanMs(Timer.GetElapsedTimeMs())) }); - // Easy to go wide, CreateReferenceCheckers is potentially heavy - // Lock all reference owners from changing the reference data and get access to check for referenced data - for (GcReferencer* Referencer : m_GcReferencers) - { - WorkLeft.AddCount(1); - ThreadPool.ScheduleWork([&Ctx, &WorkLeft, Referencer, &ReferenceCheckersLock, &ReferenceCheckers]() { - auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); - // The Referencer will create a reference checker that guarrantees that the references do not change as long as it lives - std::vector<GcReferenceChecker*> Checkers = Referencer->CreateReferenceCheckers(Ctx); - try - { - if (!Checkers.empty()) - { - RwLock::ExclusiveLockScope __(ReferenceCheckersLock); - for (auto& Checker : Checkers) - { - ReferenceCheckers.emplace_back(std::unique_ptr<GcReferenceChecker>(Checker)); - Checker = nullptr; - } - } - } - catch (std::exception&) - { - while (!Checkers.empty()) - { - delete Checkers.back(); - Checkers.pop_back(); - } - throw; - } - }); - } - WorkLeft.CountDown(); - WorkLeft.Wait(); - } - - Stopwatch LockStateTimer; - if (!ReferenceCheckers.empty()) - { - // Easy to go wide, locking all references checkers so we hafve a stead state of which references are used - // From this point we have block all writes to all References (DiskBucket/ProjectStore) until we do delete the ReferenceCheckers - Latch WorkLeft(1); - - Stopwatch Timer; - auto _ = MakeGuard([&]() { ZEN_INFO("GC: Locked Cid checkers in {}", NiceTimeSpanMs(Timer.GetElapsedTimeMs())) }); - for (std::unique_ptr<GcReferenceChecker>& ReferenceChecker : ReferenceCheckers) - { - GcReferenceChecker* Checker = ReferenceChecker.get(); - WorkLeft.AddCount(1); - ThreadPool.ScheduleWork([&Ctx, Checker, &WorkLeft, &ReferenceCheckers]() { - auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); - Checker->LockState(Ctx); - }); - } - WorkLeft.CountDown(); - WorkLeft.Wait(); - } - - std::vector<std::unique_ptr<GcReferenceStoreCompactor>> ReferenceStoreCompactors; - ReferenceStoreCompactors.reserve(ReferencePruners.size()); - if (!ReferencePruners.empty()) - { - const auto GetUnusedReferences = [&ReferenceCheckers, &Ctx](std::span<IoHash> References) -> std::vector<IoHash> { - HashSet UnusedCids(References.begin(), References.end()); - for (const std::unique_ptr<GcReferenceChecker>& ReferenceChecker : ReferenceCheckers) - { - ReferenceChecker->RemoveUsedReferencesFromSet(Ctx, UnusedCids); - if (UnusedCids.empty()) - { - return {}; - } - } - return std::vector<IoHash>(UnusedCids.begin(), UnusedCids.end()); - }; - - // Easy to go wide, checking all Cids agains references in cache - // Ask stores to remove data that the ReferenceCheckers says are not references - this should be a lightweight operation that - // only updates in-memory index, actual disk changes should be done by the ReferenceStoreCompactors - - Latch WorkLeft(1); - RwLock ReferenceStoreCompactorsLock; - - Stopwatch Timer; - auto _ = MakeGuard([&]() { ZEN_INFO("GC: Pruned unreferenced Cid data in {}", NiceTimeSpanMs(Timer.GetElapsedTimeMs())) }); - for (std::unique_ptr<GcReferencePruner>& ReferencePruner : ReferencePruners) - { - GcReferencePruner* Pruner = ReferencePruner.get(); - WorkLeft.AddCount(1); - ThreadPool.ScheduleWork( - [&Ctx, Pruner, &WorkLeft, &GetUnusedReferences, &ReferenceStoreCompactorsLock, &ReferenceStoreCompactors]() { - auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); - // Go through all the ReferenceCheckers to see if the list of Cids the collector selected are referenced or not. - std::unique_ptr<GcReferenceStoreCompactor> ReferenceCompactor(Pruner->RemoveUnreferencedData(Ctx, GetUnusedReferences)); - if (ReferenceCompactor) - { - RwLock::ExclusiveLockScope __(ReferenceStoreCompactorsLock); - ReferenceStoreCompactors.emplace_back(std::move(ReferenceCompactor)); - } - }); - } - WorkLeft.CountDown(); - WorkLeft.Wait(); - } - // Let the GcReferencers add new data, we will only change on-disk data at this point, adding new data is allowed - ReferenceCheckers.clear(); - ZEN_INFO("GC: Writes blocked for {}", NiceTimeSpanMs(LockStateTimer.GetElapsedTimeMs())) - - // Let go of the pruners - ReferencePruners.clear(); - - if (!ReferenceStoreCompactors.empty()) - { - Latch WorkLeft(1); - - // Easy to go wide - // Remove the stuff we deemed unreferenced from disk - may be heavy operation - Stopwatch Timer; - auto _ = MakeGuard([&]() { ZEN_INFO("GC: Compacted Cid stores in {}", NiceTimeSpanMs(Timer.GetElapsedTimeMs())) }); - for (std::unique_ptr<GcReferenceStoreCompactor>& StoreCompactor : ReferenceStoreCompactors) - { - GcReferenceStoreCompactor* Compactor = StoreCompactor.get(); - WorkLeft.AddCount(1); - ThreadPool.ScheduleWork([&Ctx, Compactor, &WorkLeft]() { - auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); - // Go through all the ReferenceCheckers to see if the list of Cids the collector selected are referenced or not. - Compactor->CompactReferenceStore(Ctx); - }); - } - WorkLeft.CountDown(); - WorkLeft.Wait(); - } - - ReferenceStoreCompactors.clear(); - - return GcResult{.Items = Ctx.Items.load(), - .ExpiredItems = Ctx.ExpiredItems.load(), - .DeletedItems = Ctx.DeletedItems.load(), - .References = Ctx.References.load(), - .PrunedReferences = Ctx.PrunedReferences.load(), - .CompactedReferences = Ctx.CompactedReferences.load(), - .RemovedDiskSpace = Ctx.RemovedDiskSpace.load(), - .RemovedMemory = Ctx.RemovedMemory.load()}; + static const bool SingleThread = +#if ZEN_BUILD_DEBUG + true +#else + false +#endif + ; + +#define SCOPED_TIMER(closure) \ + Stopwatch $Timer##__LINE__; \ + auto $Guard##__LINE = MakeGuard([&, &Timer = $Timer##__LINE__]() { closure }) + + Result.ReferencerStats.resize(m_GcReferencers.size()); + Result.ReferenceStoreStats.resize(m_GcReferenceStores.size()); + + WorkerThreadPool ThreadPool(SingleThread ? 0 : 8); + + ZEN_INFO("GCV2: Removing expired data from {} referencers", m_GcReferencers.size()); + if (!m_GcReferencers.empty()) + { + Latch WorkLeft(1); + // First remove any cache keys that may own references + SCOPED_TIMER(Result.RemoveExpiredDataMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + for (size_t Index = 0; Index < m_GcReferencers.size(); Index++) + { + GcReferencer* Owner = m_GcReferencers[Index]; + std::pair<std::string, GcReferencerStats>& Stats = Result.ReferencerStats[Index]; + WorkLeft.AddCount(1); + ThreadPool.ScheduleWork([&Ctx, Owner, &Stats, &WorkLeft]() { + auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); + Stats.first = Owner->GetGcName(Ctx); + SCOPED_TIMER(Stats.second.RemoveExpiredDataMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + Owner->RemoveExpiredData(Ctx, Stats.second); + }); + } + WorkLeft.CountDown(); + WorkLeft.Wait(); + } + + if (Ctx.Settings.SkipCidDelete) + { + Result.Sum(); + return Result; + } + + ZEN_INFO("GCV2: Creating reference pruners from {} reference stores", m_GcReferenceStores.size()); + std::unordered_map<size_t, std::unique_ptr<GcReferencePruner>> ReferencePruners; + if (!m_GcReferenceStores.empty()) + { + ReferencePruners.reserve(m_GcReferenceStores.size()); + Latch WorkLeft(1); + RwLock ReferencePrunersLock; + // CreateReferencePruner is usually not very heavy but big data sets change that + SCOPED_TIMER(Result.CreateReferencePrunerMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + for (size_t Index = 0; Index < m_GcReferenceStores.size(); Index++) + { + GcReferenceStore* ReferenceStore = m_GcReferenceStores[Index]; + std::pair<std::string, GcReferenceStoreStats>& Stats = Result.ReferenceStoreStats[Index]; + WorkLeft.AddCount(1); + ThreadPool.ScheduleWork([&Ctx, ReferenceStore, &Stats, Index, &WorkLeft, &ReferencePrunersLock, &ReferencePruners]() { + auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); + Stats.first = ReferenceStore->GetGcName(Ctx); + std::unique_ptr<GcReferencePruner> ReferencePruner; + { + SCOPED_TIMER(Stats.second.CreateReferencePrunerMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + // The ReferenceStore will pick a list of CId entries to check, returning a collector + ReferencePruner = std::unique_ptr<GcReferencePruner>(ReferenceStore->CreateReferencePruner(Ctx, Stats.second)); + } + if (ReferencePruner) + { + RwLock::ExclusiveLockScope __(ReferencePrunersLock); + ReferencePruners.insert_or_assign(Index, std::move(ReferencePruner)); + } + }); + } + WorkLeft.CountDown(); + WorkLeft.Wait(); + } + + ZEN_INFO("GCV2: Creating reference checkers from {} referencers", m_GcReferencers.size()); + std::unordered_map<std::unique_ptr<GcReferenceChecker>, size_t> ReferenceCheckers; + if (!m_GcReferencers.empty()) + { + ReferenceCheckers.reserve(m_GcReferencers.size()); + Latch WorkLeft(1); + RwLock ReferenceCheckersLock; + SCOPED_TIMER(Result.CreateReferenceCheckersMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + // Lock all reference owners from changing the reference data and get access to check for referenced data + for (size_t Index = 0; Index < m_GcReferencers.size(); Index++) + { + GcReferencer* Referencer = m_GcReferencers[Index]; + std::pair<std::string, GcReferencerStats>& Stats = Result.ReferencerStats[Index]; + WorkLeft.AddCount(1); + ThreadPool.ScheduleWork([&Ctx, &WorkLeft, Referencer, Index, &Stats, &ReferenceCheckersLock, &ReferenceCheckers]() { + auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); + // The Referencer will create a reference checker that guarrantees that the references do not change as long as it lives + std::vector<GcReferenceChecker*> Checkers; + { + SCOPED_TIMER(Stats.second.CreateReferenceCheckersMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + Checkers = Referencer->CreateReferenceCheckers(Ctx); + } + try + { + if (!Checkers.empty()) + { + RwLock::ExclusiveLockScope __(ReferenceCheckersLock); + for (auto& Checker : Checkers) + { + ReferenceCheckers.insert_or_assign(std::unique_ptr<GcReferenceChecker>(Checker), Index); + Checker = nullptr; + } + } + } + catch (std::exception&) + { + while (!Checkers.empty()) + { + delete Checkers.back(); + Checkers.pop_back(); + } + throw; + } + }); + } + WorkLeft.CountDown(); + WorkLeft.Wait(); + } + + std::unordered_map<std::unique_ptr<GcReferenceStoreCompactor>, size_t> ReferenceStoreCompactors; + ReferenceStoreCompactors.reserve(ReferencePruners.size()); + + ZEN_INFO("GCV2: Locking state for {} reference checkers", ReferenceCheckers.size()); + { + SCOPED_TIMER(uint64_t ElapsedMS = Timer.GetElapsedTimeMs(); Result.WriteBlockMS = std::chrono::milliseconds(ElapsedMS); + ZEN_INFO("GCV2: Writes blocked for {}", NiceTimeSpanMs(ElapsedMS))); + if (!ReferenceCheckers.empty()) + { + // Locking all references checkers so we have a steady state of which references are used + // From this point we have blocked all writes to all References (DiskBucket/ProjectStore) until + // we delete the ReferenceCheckers + Latch WorkLeft(1); + + SCOPED_TIMER(Result.LockStateMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + for (auto& It : ReferenceCheckers) + { + GcReferenceChecker* Checker = It.first.get(); + size_t Index = It.second; + std::pair<std::string, GcReferencerStats>& Stats = Result.ReferencerStats[Index]; + WorkLeft.AddCount(1); + ThreadPool.ScheduleWork([&Ctx, Checker, Index, &Stats, &WorkLeft]() { + auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); + SCOPED_TIMER(Stats.second.LockStateMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + Checker->LockState(Ctx); + }); + } + WorkLeft.CountDown(); + WorkLeft.Wait(); + } + + ZEN_INFO("GCV2: Removing unreferenced data for {} reference pruners", ReferencePruners.size()); + if (!ReferencePruners.empty()) + { + const auto GetUnusedReferences = [&ReferenceCheckers, &Ctx](std::span<IoHash> References) -> std::vector<IoHash> { + HashSet UnusedCids(References.begin(), References.end()); + for (const auto& It : ReferenceCheckers) + { + GcReferenceChecker* ReferenceChecker = It.first.get(); + ReferenceChecker->RemoveUsedReferencesFromSet(Ctx, UnusedCids); + if (UnusedCids.empty()) + { + return {}; + } + } + return std::vector<IoHash>(UnusedCids.begin(), UnusedCids.end()); + }; + + // checking all Cids agains references in cache + // Ask stores to remove data that the ReferenceCheckers says are not referenced - this should be a lightweight operation + // that only updates in-memory index, actual disk changes should be done by the ReferenceStoreCompactors + + Latch WorkLeft(1); + RwLock ReferenceStoreCompactorsLock; + + SCOPED_TIMER(Result.RemoveUnreferencedDataMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + for (auto& It : ReferencePruners) + { + GcReferencePruner* Pruner = It.second.get(); + size_t Index = It.first; + GcReferenceStoreStats& Stats = Result.ReferenceStoreStats[Index].second; + WorkLeft.AddCount(1); + ThreadPool.ScheduleWork([&Ctx, + Pruner, + &Stats, + &WorkLeft, + Index, + &GetUnusedReferences, + &ReferenceStoreCompactorsLock, + &ReferenceStoreCompactors]() { + auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); + // Go through all the ReferenceCheckers to see if the list of Cids the collector selected are referenced or not. + std::unique_ptr<GcReferenceStoreCompactor> ReferenceCompactor; + { + SCOPED_TIMER(Stats.RemoveUnreferencedDataMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + ReferenceCompactor = + std::unique_ptr<GcReferenceStoreCompactor>(Pruner->RemoveUnreferencedData(Ctx, Stats, GetUnusedReferences)); + } + if (ReferenceCompactor) + { + RwLock::ExclusiveLockScope __(ReferenceStoreCompactorsLock); + ReferenceStoreCompactors.insert_or_assign(std::move(ReferenceCompactor), Index); + } + }); + } + WorkLeft.CountDown(); + WorkLeft.Wait(); + } + // Let the GcReferencers add new data, we will only change on-disk data at this point, adding new data is allowed + ReferenceCheckers.clear(); + } + + // Let go of the pruners + ReferencePruners.clear(); + + ZEN_INFO("GCV2: Compacting reference stores for {} reference store compactors", ReferenceStoreCompactors.size()); + if (!ReferenceStoreCompactors.empty()) + { + Latch WorkLeft(1); + + // Remove the stuff we deemed unreferenced from disk - may be heavy operation + SCOPED_TIMER(Result.CompactReferenceStoreMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + for (auto& It : ReferenceStoreCompactors) + { + GcReferenceStoreCompactor* Compactor = It.first.get(); + size_t Index = It.second; + GcReferenceStoreStats& Stats = Result.ReferenceStoreStats[Index].second; + WorkLeft.AddCount(1); + ThreadPool.ScheduleWork([&Ctx, Compactor, &Stats, &WorkLeft]() { + auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); }); + // Go through all the ReferenceCheckers to see if the list of Cids the collector selected are referenced or not. + SCOPED_TIMER(Stats.CompactReferenceStoreMS = std::chrono::milliseconds(Timer.GetElapsedTimeMs());); + Compactor->CompactReferenceStore(Ctx, Stats); + }); + } + WorkLeft.CountDown(); + WorkLeft.Wait(); + } + + ReferenceStoreCompactors.clear(); + + ZEN_INFO("GCV2: Completed in {}", NiceTimeSpanMs(TotalTimer.GetElapsedTimeMs())); + } + + Result.Sum(); + return Result; +#undef SCOPED_TIMER } //////// End New GC WIP @@ -998,15 +1064,6 @@ GcScheduler::GetState() const GcSchedulerState Result{.Status = Status(), .Config = m_Config, .AreDiskWritesBlocked = m_AreDiskWritesBlocked.load()}; - { - std::unique_lock Lock(m_GcMutex); - Result.LastFullGcTime = m_LastGcTime; - Result.LastFullGCDiff = m_LastFullGCDiff; - Result.LastFullGcDuration = m_LastFullGcDuration; - Result.LastLightweightGcTime = m_LastLightweightGcTime; - Result.LastLightweightGCDiff = m_LastLightweightGCDiff; - Result.LastLightweightGcDuration = m_LastLightweightGcDuration; - } std::error_code Ec; DiskSpace Space = DiskSpaceInfo(Result.Config.RootDirectory, Ec); if (!Ec) @@ -1026,30 +1083,40 @@ GcScheduler::GetState() const Result.HasDiskReserve = std::filesystem::is_regular_file(Result.Config.RootDirectory / "reserve.gc", Ec) && !Ec; } - GcClock::TimePoint CacheExpireTime = - Result.Config.MaxCacheDuration == GcClock::Duration::max() ? GcClock::TimePoint::min() : Now - Result.Config.MaxCacheDuration; - GcClock::TimePoint ProjectStoreExpireTime = Result.Config.MaxProjectStoreDuration == GcClock::Duration::max() - ? GcClock::TimePoint::min() - : Now - Result.Config.MaxProjectStoreDuration; + if (Result.Status != GcSchedulerStatus::kRunning) + { + { + std::unique_lock Lock(m_GcMutex); + Result.LastFullGcTime = m_LastGcTime; + Result.LastFullGCDiff = m_LastFullGCDiff; + Result.LastFullGcDuration = m_LastFullGcDuration; + Result.LastLightweightGcTime = m_LastLightweightGcTime; + Result.LastLightweightGCDiff = m_LastLightweightGCDiff; + Result.LastLightweightGcDuration = m_LastLightweightGcDuration; + + Result.LastLightweightGCV2Result = m_LastLightweightGCV2Result; + Result.LastFullGCV2Result = m_LastFullGCV2Result; + } - Result.RemainingTimeUntilFullGc = - Result.Config.Interval.count() == 0 - ? std::chrono::seconds::max() - : std::chrono::duration_cast<std::chrono::seconds>(Result.LastFullGcTime + Result.Config.Interval - Now); + Result.RemainingTimeUntilFullGc = + Result.Config.Interval.count() == 0 + ? std::chrono::seconds::max() + : std::chrono::duration_cast<std::chrono::seconds>(Result.LastFullGcTime + Result.Config.Interval - Now); - if (Result.RemainingTimeUntilFullGc < std::chrono::seconds::zero()) - { - Result.RemainingTimeUntilFullGc = std::chrono::seconds::zero(); - } + if (Result.RemainingTimeUntilFullGc < std::chrono::seconds::zero()) + { + Result.RemainingTimeUntilFullGc = std::chrono::seconds::zero(); + } - Result.RemainingTimeUntilLightweightGc = - Result.Config.LightweightInterval.count() == 0 - ? std::chrono::seconds::max() - : std::chrono::duration_cast<std::chrono::seconds>(Result.LastLightweightGcTime + Result.Config.LightweightInterval - Now); + Result.RemainingTimeUntilLightweightGc = + Result.Config.LightweightInterval.count() == 0 + ? std::chrono::seconds::max() + : std::chrono::duration_cast<std::chrono::seconds>(Result.LastLightweightGcTime + Result.Config.LightweightInterval - Now); - if (Result.RemainingTimeUntilLightweightGc < std::chrono::seconds::zero()) - { - Result.RemainingTimeUntilLightweightGc = std::chrono::seconds::zero(); + if (Result.RemainingTimeUntilLightweightGc < std::chrono::seconds::zero()) + { + Result.RemainingTimeUntilLightweightGc = std::chrono::seconds::zero(); + } } return Result; @@ -1474,6 +1541,14 @@ GcScheduler::CollectGarbage(const GcClock::TimePoint& CacheExpireTime, { case GcVersion::kV1: Diff = m_GcManager.CollectGarbage(GcCtx); + if (SkipCid) + { + m_LastLightweightGCV2Result.reset(); + } + else + { + m_LastFullGCV2Result.reset(); + } break; case GcVersion::kV2: { @@ -1482,7 +1557,33 @@ GcScheduler::CollectGarbage(const GcClock::TimePoint& CacheExpireTime, .CollectSmallObjects = CollectSmallObjects, .IsDeleteMode = Delete, .SkipCidDelete = SkipCid}); - Diff.DiskSize = Result.RemovedDiskSpace; + + ZEN_INFO( + "GCV2: Removed {} items out of {}, deleted {} out of {}. Pruned {} Cid entries out of {}, compacted {} Cid entries " + "out of {}, " + "freed " + "{} on disk and {} of memory in {}", + Result.ReferencerStat.Expired, + Result.ReferencerStat.Count, + Result.ReferencerStat.Deleted, + Result.ReferencerStat.Expired, + Result.ReferenceStoreStat.Pruned, + Result.ReferenceStoreStat.Count, + Result.ReferenceStoreStat.Compacted, + Result.ReferenceStoreStat.Pruned, + NiceBytes(Result.RemovedDisk), + NiceBytes(Result.RemovedMemory), + NiceTimeSpanMs(Result.ElapsedMS.count())); + + if (SkipCid) + { + m_LastLightweightGCV2Result = Result; + } + else + { + m_LastFullGCV2Result = Result; + } + Diff.DiskSize = Result.RemovedDisk; Diff.MemorySize = Result.RemovedMemory; } break; diff --git a/src/zenstore/include/zenstore/gc.h b/src/zenstore/include/zenstore/gc.h index fa7dce331..e2e99d5a4 100644 --- a/src/zenstore/include/zenstore/gc.h +++ b/src/zenstore/include/zenstore/gc.h @@ -61,31 +61,67 @@ struct GcSettings bool CollectSmallObjects = false; bool IsDeleteMode = false; bool SkipCidDelete = false; + bool Verbose = false; +}; + +struct GcReferencerStats +{ + std::uint64_t Count = 0; + std::uint64_t Expired = 0; + std::uint64_t Deleted = 0; + std::uint64_t RemovedDisk = 0; + std::uint64_t RemovedMemory = 0; + + std::chrono::milliseconds RemoveExpiredDataMS = {}; + std::chrono::milliseconds CreateReferenceCheckersMS = {}; + std::chrono::milliseconds LockStateMS = {}; + std::chrono::milliseconds ElapsedMS = {}; +}; + +struct GcReferenceStoreStats +{ + std::uint64_t Count = 0; + std::uint64_t Pruned = 0; + std::uint64_t Compacted = 0; + std::uint64_t RemovedDisk = 0; + std::uint64_t RemovedMemory = 0; + + std::chrono::milliseconds CreateReferencePrunerMS = {}; + std::chrono::milliseconds RemoveUnreferencedDataMS = {}; + std::chrono::milliseconds CompactReferenceStoreMS = {}; + std::chrono::milliseconds ElapsedMS = {}; }; struct GcResult { - uint64_t Items = 0; - uint64_t ExpiredItems = 0; - uint64_t DeletedItems = 0; - uint64_t References = 0; - uint64_t PrunedReferences = 0; - uint64_t CompactedReferences = 0; - uint64_t RemovedDiskSpace = 0; - uint64_t RemovedMemory = 0; + GcReferencerStats ReferencerStat; + GcReferenceStoreStats ReferenceStoreStat; + + std::uint64_t RemovedDisk = 0; + std::uint64_t RemovedMemory = 0; + + std::vector<std::pair<std::string, GcReferencerStats>> ReferencerStats; + std::vector<std::pair<std::string, GcReferenceStoreStats>> ReferenceStoreStats; + + // Wall times, not sum of each + std::chrono::milliseconds RemoveExpiredDataMS = {}; + std::chrono::milliseconds CreateReferenceCheckersMS = {}; + std::chrono::milliseconds LockStateMS = {}; + + std::chrono::milliseconds CreateReferencePrunerMS = {}; + std::chrono::milliseconds RemoveUnreferencedDataMS = {}; + std::chrono::milliseconds CompactReferenceStoreMS = {}; + + std::chrono::milliseconds WriteBlockMS = {}; + + std::chrono::milliseconds ElapsedMS = {}; + + void Sum(); }; struct GcCtx { - const GcSettings Settings; - std::atomic_uint64_t Items = 0; - std::atomic_uint64_t ExpiredItems = 0; - std::atomic_uint64_t DeletedItems = 0; - std::atomic_uint64_t References = 0; - std::atomic_uint64_t PrunedReferences = 0; - std::atomic_uint64_t CompactedReferences = 0; - std::atomic_uint64_t RemovedDiskSpace = 0; - std::atomic_uint64_t RemovedMemory = 0; + const GcSettings Settings; }; typedef tsl::robin_set<IoHash> HashSet; @@ -106,7 +142,7 @@ public: virtual ~GcReferenceStoreCompactor() = default; // Remove data on disk based on results from GcReferencePruner::RemoveUnreferencedData - virtual void CompactReferenceStore(GcCtx& Ctx) = 0; + virtual void CompactReferenceStore(GcCtx& Ctx, GcReferenceStoreStats& Stats) = 0; }; /** @@ -149,11 +185,13 @@ protected: virtual ~GcReferencer() = default; public: + virtual std::string GetGcName(GcCtx& Ctx) = 0; + // Remove expired data based on either GcCtx::Settings CacheExpireTime/ProjectExpireTime // TODO: For disk layer we need to first update it with access times from the memory layer // The implementer of GcReferencer (in our case a disk bucket) does not know about any // potential memory cache layer :( - virtual void RemoveExpiredData(GcCtx& Ctx) = 0; + virtual void RemoveExpiredData(GcCtx& Ctx, GcReferencerStats& Stats) = 0; // Create 0-n GcReferenceChecker for this GcReferencer. Caller will manage lifetime of // returned instances @@ -178,7 +216,9 @@ public: // Caller will manage lifetime of returned instance // This function should execute as fast as possible, so try to prepare a list of references to check ahead of // call to this function and make sure the removal of unreferences items is as lightweight as possible. - virtual GcReferenceStoreCompactor* RemoveUnreferencedData(GcCtx& Ctx, const GetUnusedReferencesFunc& GetUnusedReferences) = 0; + virtual GcReferenceStoreCompactor* RemoveUnreferencedData(GcCtx& Ctx, + GcReferenceStoreStats& Stats, + const GetUnusedReferencesFunc& GetUnusedReferences) = 0; }; /** @@ -190,9 +230,11 @@ protected: virtual ~GcReferenceStore() = default; public: + virtual std::string GetGcName(GcCtx& Ctx) = 0; + // Create a GcReferencePruner which can check a set of references (decided by implementor) if they are no longer in use // Caller will manage lifetime of returned instance - virtual GcReferencePruner* CreateReferencePruner(GcCtx& Ctx) = 0; + virtual GcReferencePruner* CreateReferencePruner(GcCtx& Ctx, GcReferenceStoreStats& Stats) = 0; }; //////// End New GC WIP @@ -379,6 +421,9 @@ struct GcSchedulerState GcStorageSize LastFullGCDiff; std::chrono::milliseconds LastLightweightGcDuration{}; GcStorageSize LastLightweightGCDiff; + + std::optional<GcResult> LastLightweightGCV2Result; + std::optional<GcResult> LastFullGCV2Result; }; class DiskUsageWindow @@ -461,6 +506,9 @@ private: std::chrono::milliseconds m_LastLightweightGcDuration{}; GcStorageSize m_LastLightweightGCDiff; + std::optional<GcResult> m_LastLightweightGCV2Result; + std::optional<GcResult> m_LastFullGCV2Result; + std::atomic_uint32_t m_Status{}; std::thread m_GcThread; mutable std::mutex m_GcMutex; |