diff options
| author | Dan Engelbrecht <[email protected]> | 2023-11-13 16:19:39 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-11-13 16:19:39 +0100 |
| commit | d52bed8d5a37a39c88b78a19e22f2b7b3f6dd1d6 (patch) | |
| tree | 2709bce7020faa9c73e784dd2a5997b384395b56 /src | |
| parent | package dependency clean-ups (#531) (diff) | |
| download | zen-d52bed8d5a37a39c88b78a19e22f2b7b3f6dd1d6.tar.xz zen-d52bed8d5a37a39c88b78a19e22f2b7b3f6dd1d6.zip | |
gc history log (#519)
- Feature: Writes a `gc.log` with settings and detailed result after each GC execution (version 2 only)
- Break out file name rotate to allow access for gclog
- CompactBinaryToJson(MemoryView Data, StringBuilderBase& InBuilder)
Diffstat (limited to 'src')
| -rw-r--r-- | src/zen/cmds/print_cmd.cpp | 37 | ||||
| -rw-r--r-- | src/zencore/compactbinary.cpp | 160 | ||||
| -rw-r--r-- | src/zencore/filesystem.cpp | 64 | ||||
| -rw-r--r-- | src/zencore/include/zencore/compactbinary.h | 7 | ||||
| -rw-r--r-- | src/zencore/include/zencore/filesystem.h | 2 | ||||
| -rw-r--r-- | src/zenserver/admin/admin.cpp | 101 | ||||
| -rw-r--r-- | src/zenstore/gc.cpp | 287 | ||||
| -rw-r--r-- | src/zenstore/include/zenstore/gc.h | 7 | ||||
| -rw-r--r-- | src/zenutil/include/zenutil/logging/rotatingfilesink.h | 69 |
9 files changed, 535 insertions, 199 deletions
diff --git a/src/zen/cmds/print_cmd.cpp b/src/zen/cmds/print_cmd.cpp index acffb2002..ccd7f248e 100644 --- a/src/zen/cmds/print_cmd.cpp +++ b/src/zen/cmds/print_cmd.cpp @@ -17,17 +17,17 @@ namespace zen { static void PrintCbObject(CbObject Object) { - zen::ExtendableStringBuilder<1024> ObjStr; - zen::CompactBinaryToJson(Object, ObjStr); + ExtendableStringBuilder<1024> ObjStr; + CompactBinaryToJson(Object, ObjStr); ZEN_CONSOLE("{}", ObjStr); } static void -PrintCbObject(IoBuffer Data) +PrintCompactBinary(IoBuffer Data) { - zen::CbObject Object{SharedBuffer(Data)}; - - PrintCbObject(Object); + ExtendableStringBuilder<1024> StreamString; + CompactBinaryToJson(Data.GetView(), StreamString); + ZEN_CONSOLE("{}", StreamString); } PrintCommand::PrintCommand() @@ -54,15 +54,15 @@ PrintCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) if (m_Filename.empty()) throw std::runtime_error("No file specified"); - zen::FileContents Fc; + FileContents Fc; if (m_Filename == "-") { - Fc = zen::ReadStdIn(); + Fc = ReadStdIn(); } else { - Fc = zen::ReadFile(m_Filename); + Fc = ReadFile(m_Filename); } if (Fc.ErrorCode) @@ -128,9 +128,12 @@ PrintCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) PrintCbObject(Object); } - else if (CbValidateError Result = ValidateCompactBinary(Data, CbValidateMode::All); Result == CbValidateError::None) + else if (CbValidateError Result = ValidateCompactBinary(Data, + CbValidateMode::Default | CbValidateMode::Names | CbValidateMode::Format | + CbValidateMode::Package | CbValidateMode::PackageHash); + Result == CbValidateError::None) { - PrintCbObject(Data); + PrintCompactBinary(Data); } else { @@ -170,16 +173,16 @@ PrintPackageCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** ar if (m_Filename.empty()) throw std::runtime_error("No file specified"); - zen::FileContents Fc = zen::ReadFile(m_Filename); - IoBuffer Data = Fc.Flatten(); - zen::CbPackage Package; + FileContents Fc = ReadFile(m_Filename); + IoBuffer Data = Fc.Flatten(); + CbPackage Package; - bool Ok = Package.TryLoad(Data) || zen::legacy::TryLoadCbPackage(Package, Data, &UniqueBuffer::Alloc); + bool Ok = Package.TryLoad(Data) || legacy::TryLoadCbPackage(Package, Data, &UniqueBuffer::Alloc); if (Ok) { - zen::ExtendableStringBuilder<1024> ObjStr; - zen::CompactBinaryToJson(Package.GetObject(), ObjStr); + ExtendableStringBuilder<1024> ObjStr; + CompactBinaryToJson(Package.GetObject(), ObjStr); ZEN_CONSOLE("{}", ObjStr); } else diff --git a/src/zencore/compactbinary.cpp b/src/zencore/compactbinary.cpp index 416b49f62..5e8ce22ed 100644 --- a/src/zencore/compactbinary.cpp +++ b/src/zencore/compactbinary.cpp @@ -1472,6 +1472,40 @@ class CbJsonWriter public: explicit CbJsonWriter(StringBuilderBase& InBuilder) : Builder(InBuilder) { NewLineAndIndent << LINE_TERMINATOR_ANSI; } + void BeginObject() + { + Builder << '{'; + NewLineAndIndent << '\t'; + NeedsNewLine = true; + } + + void EndObject() + { + NewLineAndIndent.RemoveSuffix(1); + if (NeedsComma) + { + WriteOptionalNewLine(); + } + Builder << '}'; + } + + void BeginArray() + { + Builder << '['; + NewLineAndIndent << '\t'; + NeedsNewLine = true; + } + + void EndArray() + { + NewLineAndIndent.RemoveSuffix(1); + if (NeedsComma) + { + WriteOptionalNewLine(); + } + Builder << ']'; + } + void WriteField(CbFieldView Field) { using namespace std::literals; @@ -1493,37 +1527,23 @@ public: case CbFieldType::Object: case CbFieldType::UniformObject: { - Builder << '{'; - NewLineAndIndent << '\t'; - NeedsNewLine = true; + BeginObject(); for (CbFieldView It : Field) { WriteField(It); } - NewLineAndIndent.RemoveSuffix(1); - if (NeedsComma) - { - WriteOptionalNewLine(); - } - Builder << '}'; + EndObject(); } break; case CbFieldType::Array: case CbFieldType::UniformArray: { - Builder << '['; - NewLineAndIndent << '\t'; - NeedsNewLine = true; + BeginArray(); for (CbFieldView It : Field) { WriteField(It); } - NewLineAndIndent.RemoveSuffix(1); - if (NeedsComma) - { - WriteOptionalNewLine(); - } - Builder << ']'; + EndArray(); } break; case CbFieldType::Binary: @@ -1744,6 +1764,62 @@ CompactBinaryToJson(const CbArrayView& Array, StringBuilderBase& Builder) Writer.WriteField(Array.AsFieldView()); } +void +CompactBinaryToJson(MemoryView Data, StringBuilderBase& InBuilder) +{ + std::vector<CbFieldView> Fields = ReadCompactBinaryStream(Data); + CbJsonWriter Writer(InBuilder); + if (!Fields.empty()) + { + if (Fields.size() == 1) + { + Writer.WriteField(Fields[0]); + return; + } + bool UseTopLevelObject = Fields[0].HasName(); + if (UseTopLevelObject) + { + Writer.BeginObject(); + } + else + { + Writer.BeginArray(); + } + for (const CbFieldView& Field : Fields) + { + Writer.WriteField(Field); + } + if (UseTopLevelObject) + { + Writer.EndObject(); + } + else + { + Writer.EndArray(); + } + } +} + +std::vector<CbFieldView> +ReadCompactBinaryStream(MemoryView Data) +{ + std::vector<CbFieldView> Result; + const uint8_t* Buffer = reinterpret_cast<const uint8_t*>(Data.GetData()); + uint64_t Offset = 0; + const uint64_t Size = Data.GetSize(); + while (Offset < Size) + { + if (ValidateCompactBinary(MemoryView(Buffer + Offset, Size - Offset), CbValidateMode::Default) != CbValidateError::None) + { + break; + } + CbFieldView Field(Buffer + Offset); + Offset += Field.GetSize(); + Result.emplace_back(std::move(Field)); + } + return Result; +} + ////////////////////////////////////////////////////////////////////////// class CbJsonReader @@ -2136,6 +2212,8 @@ TEST_CASE("uson.null") TEST_CASE("uson.json") { + using namespace std::literals; + SUBCASE("string") { CbObjectWriter Writer; @@ -2208,6 +2286,52 @@ TEST_CASE("uson.json") CHECK(FloatValue == 0); CHECK(DoubleValue == 0); } + + SUBCASE("stream") + { + const auto MakeObject = [&](std::string_view Name, const std::vector<int>& Fields) -> CbObject { + CbWriter Writer; + Writer.SetName(Name); + Writer.BeginObject(); + for (const auto& Field : Fields) + { + Writer.AddInteger(fmt::format("{}", Field), Field); + } + Writer.EndObject(); + return Writer.Save().AsObject(); + }; + + std::vector<uint8_t> Buffer; + + auto AppendToBuffer = [&](const void* Data, size_t Count) { + const uint8_t* AppendBytes = reinterpret_cast<const uint8_t*>(Data); + Buffer.insert(Buffer.end(), AppendBytes, AppendBytes + Count); + }; + + auto Append = [&](const CbFieldView& Field) { + Field.WriteToStream([&](const void* Data, size_t Count) { + const uint8_t* AppendBytes = reinterpret_cast<const uint8_t*>(Data); + Buffer.insert(Buffer.end(), AppendBytes, AppendBytes + Count); + }); + }; + + CbObject DataObjects[] = {MakeObject("Empty object"sv, {}), + MakeObject("OneField object"sv, {5}), + MakeObject("TwoField object"sv, {-5, 999}), + MakeObject("ThreeField object"sv, {1, 2, -129})}; + for (const CbObject& Object : DataObjects) + { + Object.AsField().WriteToStream(AppendToBuffer); + } + + ExtendableStringBuilder<128> Sb; + CompactBinaryToJson(MemoryView(Buffer.data(), Buffer.size()), Sb); + std::string JsonText = Sb.ToString().c_str(); + std::string JsonError; + json11::Json Json = json11::Json::parse(JsonText, JsonError); + std::string ParsedJsonString = Json.dump(); + CHECK(!ParsedJsonString.empty()); + } } TEST_CASE("uson.datetime") diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp index b64c9973e..06cda7382 100644 --- a/src/zencore/filesystem.cpp +++ b/src/zencore/filesystem.cpp @@ -40,6 +40,7 @@ ZEN_THIRD_PARTY_INCLUDES_END # include <unistd.h> #endif +#include <fmt/format.h> #include <filesystem> #include <gsl/gsl-lite.hpp> @@ -1396,6 +1397,69 @@ GetEnvVariable(std::string_view VariableName) return ""; } +std::error_code +RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles) +{ + const std::filesystem::path BasePath(Filename.parent_path()); + const std::string Stem(Filename.stem().string()); + const std::string Extension(Filename.extension().string()); + + std::error_code Result; + + auto GetFileName = [&](size_t Index) -> std::filesystem::path { + if (Index == 0) + { + return BasePath / (Stem + Extension); + } + return BasePath / fmt::format("{}.{}{}", Stem, Index, Extension); + }; + + auto IsEmpty = [](const std::filesystem::path& Path, std::error_code& Ec) -> bool { + bool Exists = std::filesystem::exists(Path, Ec); + if (Ec) + { + return false; + } + if (!Exists) + { + return true; + } + uintmax_t Size = std::filesystem::file_size(Path, Ec); + if (Ec) + { + return false; + } + return Size == 0; + }; + + bool BaseIsEmpty = IsEmpty(GetFileName(0), Result); + if (Result) + { + return Result; + } + if (!BaseIsEmpty) + { + // We try our best to rotate the logs, if we fail we fail and will try to open the base log file anyway + for (auto i = MaxFiles; i > 0; i--) + { + std::filesystem::path src = GetFileName(i - 1); + if (!std::filesystem::exists(src)) + { + continue; + } + std::error_code DummyEc; + std::filesystem::path target = GetFileName(i); + if (std::filesystem::exists(target, DummyEc)) + { + std::filesystem::remove(target, DummyEc); + } + std::filesystem::rename(src, target, DummyEc); + } + } + + return Result; +} + ////////////////////////////////////////////////////////////////////////// // // Testing related code follows... diff --git a/src/zencore/include/zencore/compactbinary.h b/src/zencore/include/zencore/compactbinary.h index 66e621a8a..cb032e34a 100644 --- a/src/zencore/include/zencore/compactbinary.h +++ b/src/zencore/include/zencore/compactbinary.h @@ -1470,6 +1470,13 @@ end(CbFieldView&) return CbFieldViewIterator(); } +/** + * Serialize serialized compact binary blob to jaons. It must be 0 to n fields with including type for each field + */ +ZENCORE_API void CompactBinaryToJson(MemoryView Data, StringBuilderBase& InBuilder); + +ZENCORE_API std::vector<CbFieldView> ReadCompactBinaryStream(MemoryView Data); + void uson_forcelink(); // internal } // namespace zen diff --git a/src/zencore/include/zencore/filesystem.h b/src/zencore/include/zencore/filesystem.h index 075188993..22eb40e45 100644 --- a/src/zencore/include/zencore/filesystem.h +++ b/src/zencore/include/zencore/filesystem.h @@ -211,6 +211,8 @@ void GetDirectoryContent(const std::filesystem::path& RootDir, uint8_t Flags, Di std::string GetEnvVariable(std::string_view VariableName); +std::error_code RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles); + ////////////////////////////////////////////////////////////////////////// void filesystem_forcelink(); // internal diff --git a/src/zenserver/admin/admin.cpp b/src/zenserver/admin/admin.cpp index 00a5c79ed..d4c69f41b 100644 --- a/src/zenserver/admin/admin.cpp +++ b/src/zenserver/admin/admin.cpp @@ -231,101 +231,6 @@ 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); @@ -336,7 +241,8 @@ HttpAdminService::HttpAdminService(GcScheduler& Scheduler, } if (State.LastFullGCV2Result) { - WriteGCResult(Response, State.LastFullGCV2Result.value()); + const bool HumanReadable = true; + WriteGCResult(Response, State.LastFullGCV2Result.value(), HumanReadable, Details); } else { @@ -353,7 +259,8 @@ HttpAdminService::HttpAdminService(GcScheduler& Scheduler, if (State.LastLightweightGCV2Result) { - WriteGCResult(Response, State.LastLightweightGCV2Result.value()); + const bool HumanReadable = true; + WriteGCResult(Response, State.LastLightweightGCV2Result.value(), HumanReadable, Details); } else { diff --git a/src/zenstore/gc.cpp b/src/zenstore/gc.cpp index 365eb10ab..778a47626 100644 --- a/src/zenstore/gc.cpp +++ b/src/zenstore/gc.cpp @@ -173,6 +173,166 @@ SaveCompactBinaryObject(const fs::path& Path, const CbObject& Object) ////////////////////////////////////////////////////////////////////////// +void +WriteReferencerStats(CbObjectWriter& Writer, const GcReferencerStats& Stats, bool HumanReadable) +{ + if (Stats.Count == 0) + { + return; + } + Writer << "Count" << Stats.Count; + Writer << "Expired" << Stats.Expired; + Writer << "Deleted" << Stats.Deleted; + + if (HumanReadable) + { + Writer << "RemovedDisk" << NiceBytes(Stats.RemovedDisk); + Writer << "RemovedMemory" << NiceBytes(Stats.RemovedMemory); + } + else + { + Writer << "RemovedDiskBytes" << Stats.RemovedDisk; + Writer << "RemovedMemoryBytes" << Stats.RemovedMemory; + } + + if (HumanReadable) + { + Writer << "RemoveExpiredData" << NiceTimeSpanMs(Stats.RemoveExpiredDataMS.count()); + Writer << "CreateReferenceCheckers" << NiceTimeSpanMs(Stats.CreateReferenceCheckersMS.count()); + Writer << "LockState" << NiceTimeSpanMs(Stats.LockStateMS.count()); + Writer << "Elapsed" << NiceTimeSpanMs(Stats.ElapsedMS.count()); + } + else + { + Writer << "RemoveExpiredDataMS" << gsl::narrow<int64_t>(Stats.RemoveExpiredDataMS.count()); + Writer << "CreateReferenceCheckersMS" << gsl::narrow<int64_t>(Stats.CreateReferenceCheckersMS.count()); + Writer << "LockStateMS" << gsl::narrow<int64_t>(Stats.LockStateMS.count()); + Writer << "ElapsedMS" << gsl::narrow<int64_t>(Stats.ElapsedMS.count()); + } +}; + +void +WriteReferenceStoreStats(CbObjectWriter& Writer, const GcReferenceStoreStats& Stats, bool HumanReadable) +{ + if (Stats.Count == 0) + { + return; + } + Writer << "Count" << Stats.Count; + Writer << "Pruned" << Stats.Pruned; + Writer << "Compacted" << Stats.Compacted; + + if (HumanReadable) + { + Writer << "RemovedDisk" << NiceBytes(Stats.RemovedDisk); + Writer << "RemovedMemory" << NiceBytes(Stats.RemovedMemory); + } + else + { + Writer << "RemovedDiskBytes" << Stats.RemovedDisk; + Writer << "RemovedMemoryBytes" << Stats.RemovedMemory; + } + + if (HumanReadable) + { + Writer << "CreateReferencePruner" << NiceTimeSpanMs(Stats.CreateReferencePrunerMS.count()); + Writer << "RemoveUnreferencedData" << NiceTimeSpanMs(Stats.RemoveUnreferencedDataMS.count()); + Writer << "CompactReferenceStore" << NiceTimeSpanMs(Stats.CompactReferenceStoreMS.count()); + Writer << "Elapsed" << NiceTimeSpanMs(Stats.ElapsedMS.count()); + } + else + { + Writer << "CreateReferencePrunerMS" << gsl::narrow<int64_t>(Stats.CreateReferencePrunerMS.count()); + Writer << "RemoveUnreferencedDataMS" << gsl::narrow<int64_t>(Stats.RemoveUnreferencedDataMS.count()); + Writer << "CompactReferenceStoreMS" << gsl::narrow<int64_t>(Stats.CompactReferenceStoreMS.count()); + Writer << "ElapsedMS" << gsl::narrow<int64_t>(Stats.ElapsedMS.count()); + } +}; + +void +WriteGCResult(CbObjectWriter& Writer, const GcResult& Result, bool HumanReadable, bool IncludeDetails) +{ + if (HumanReadable) + { + Writer << "RemovedDisk" << NiceBytes(Result.RemovedDisk); + Writer << "RemovedMemory" << NiceBytes(Result.RemovedMemory); + Writer << "WriteBlock" << NiceTimeSpanMs(Result.WriteBlockMS.count()); + Writer << "Elapsed" << NiceTimeSpanMs(Result.ElapsedMS.count()); + } + else + { + Writer << "RemovedDiskBytes" << gsl::narrow<int64_t>(Result.RemovedDisk); + Writer << "RemovedMemoryBytes" << gsl::narrow<int64_t>(Result.RemovedMemory); + Writer << "WriteBlockMS" << gsl::narrow<int64_t>(Result.WriteBlockMS.count()); + Writer << "ElapsedMS" << gsl::narrow<int64_t>(Result.ElapsedMS.count()); + } + + if (!IncludeDetails) + { + return; + } + + if (HumanReadable) + { + Writer << "RemoveExpiredData" << NiceTimeSpanMs(Result.RemoveExpiredDataMS.count()); + Writer << "CreateReferenceCheckers" << NiceTimeSpanMs(Result.CreateReferenceCheckersMS.count()); + Writer << "LockState" << NiceTimeSpanMs(Result.LockStateMS.count()); + + Writer << "CreateReferencePruner" << NiceTimeSpanMs(Result.CreateReferencePrunerMS.count()); + Writer << "RemoveUnreferencedData" << NiceTimeSpanMs(Result.RemoveUnreferencedDataMS.count()); + Writer << "CompactReferenceStore" << NiceTimeSpanMs(Result.CompactReferenceStoreMS.count()); + } + else + { + Writer << "RemoveExpiredDataMS" << gsl::narrow<int64_t>(Result.RemoveExpiredDataMS.count()); + Writer << "CreateReferenceCheckersMS" << gsl::narrow<int64_t>(Result.CreateReferenceCheckersMS.count()); + Writer << "LockStateMS" << gsl::narrow<int64_t>(Result.LockStateMS.count()); + + Writer << "CreateReferencePrunerMS" << gsl::narrow<int64_t>(Result.CreateReferencePrunerMS.count()); + Writer << "RemoveUnreferencedDataMS" << gsl::narrow<int64_t>(Result.RemoveUnreferencedDataMS.count()); + Writer << "CompactReferenceStoreMS" << gsl::narrow<int64_t>(Result.CompactReferenceStoreMS.count()); + } + + Writer.BeginObject("ReferencerStats"); + { + WriteReferencerStats(Writer, Result.ReferencerStat, HumanReadable); + } + Writer.EndObject(); + + Writer.BeginObject("ReferenceStoreStats"); + { + WriteReferenceStoreStats(Writer, Result.ReferenceStoreStat, HumanReadable); + } + Writer.EndObject(); + + if (!Result.ReferencerStats.empty()) + { + Writer.BeginArray("Referencers"); + { + for (const std::pair<std::string, GcReferencerStats>& It : Result.ReferencerStats) + { + Writer.BeginObject(); + Writer << "Name" << It.first; + WriteReferencerStats(Writer, It.second, HumanReadable); + Writer.EndObject(); + } + } + Writer.EndArray(); + } + if (!Result.ReferenceStoreStats.empty()) + { + Writer.BeginArray("ReferenceStores"); + for (const std::pair<std::string, GcReferenceStoreStats>& It : Result.ReferenceStoreStats) + { + Writer.BeginObject(); + Writer << "Name" << It.first; + WriteReferenceStoreStats(Writer, It.second, HumanReadable); + Writer.EndObject(); + } + Writer.EndArray(); + } +}; + struct GcContext::GcState { using CacheKeyContexts = std::unordered_map<std::string, std::vector<IoHash>>; @@ -420,7 +580,6 @@ GcManager::CollectGarbage(const GcSettings& Settings) } Result.ReferencerStats.resize(m_GcReferencers.size()); - Result.ReferenceStoreStats.resize(m_GcReferenceStores.size()); WorkerThreadPool ThreadPool(WorkerThreadPoolCount); @@ -452,6 +611,8 @@ GcManager::CollectGarbage(const GcSettings& Settings) return Result; } + Result.ReferenceStoreStats.resize(m_GcReferenceStores.size()); + 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()) @@ -1055,6 +1216,106 @@ GcScheduler::CheckDiskSpace() return Space; } +void +GcScheduler::AppendGCLog(GcClock::TimePoint StartTime, const GcSettings& Settings, const GcResult& Result) +{ + try + { + std::vector<uint8_t> Blob; + { + CbObjectWriter Writer; + std::string Id = fmt::format("{}", gsl::narrow<int64_t>(StartTime.time_since_epoch().count())); + Writer.BeginObject(Id); + { + Writer << "StartTimeSec"sv + << gsl::narrow<int64_t>(std::chrono::duration_cast<std::chrono::seconds>(StartTime.time_since_epoch()).count()); + Writer.BeginObject("Settings"sv); + { + Writer << "CacheExpireTimeSec"sv + << gsl::narrow<int64_t>( + std::chrono::duration_cast<std::chrono::seconds>(Settings.CacheExpireTime.time_since_epoch()).count()); + Writer << "ProjectStoreExpireTimeSec"sv + << gsl::narrow<int64_t>( + std::chrono::duration_cast<std::chrono::seconds>(Settings.ProjectStoreExpireTime.time_since_epoch()) + .count()); + Writer << "CollectSmallObjects"sv << Settings.CollectSmallObjects; + Writer << "IsDeleteMode"sv << Settings.IsDeleteMode; + Writer << "SkipCidDelete"sv << Settings.SkipCidDelete; + Writer << "Verbose"sv << Settings.Verbose; + Writer << "SingleThread"sv << Settings.SingleThread; + } + Writer.EndObject(); + + const bool HumanReadable = false; + const bool IncludeDetails = true; + Writer.BeginObject("Result"sv); + { + WriteGCResult(Writer, Result, HumanReadable, IncludeDetails); + } + Writer.EndObject(); + } + Writer.EndObject(); + + CbObject Entry = Writer.Save(); + Entry.AsFieldView().WriteToStream([&](const void* Data, size_t Size) { + const uint8_t* BlobData(reinterpret_cast<const uint8_t*>(Data)); + Blob.insert(Blob.end(), BlobData, BlobData + Size); + }); + } + + BasicFile GcLogFile; + const fs::path Path = m_Config.RootDirectory / "gc.log"; + + MemoryView EntryBuffer(Blob.data(), Blob.size()); + { + RwLock::ExclusiveLockScope _(m_GcLogLock); + + GcLogFile.Open(Path, BasicFile::Mode::kWrite); + uint64_t AppendPos = GcLogFile.FileSize(); + + const uint64_t MaxGcLogFileSize = 1024 * 1024 * 8; + const uint64_t MaxGcLogFileCount = 16; + + if (AppendPos + EntryBuffer.GetSize() > MaxGcLogFileSize) + { + GcLogFile.Close(); + std::error_code Err = RotateFiles(Path, MaxGcLogFileCount); + if (Err) + { + ZEN_WARN("Failed to rotate gc log files at '{}'. Reason: '{}'", Path, Err.message()); + } + GcLogFile.Open(Path, BasicFile::Mode::kWrite); + AppendPos = 0; + } + + GcLogFile.Write(EntryBuffer, AppendPos); + } + } + catch (std::system_error& SystemError) + { + if (IsOOM(SystemError.code())) + { + ZEN_WARN("writing gc result ran out of memory: '{}'", SystemError.what()); + } + else if (IsOOD(SystemError.code())) + { + ZEN_WARN("writing gc result ran out of disk space: '{}'", SystemError.what()); + } + else + { + ZEN_ERROR("writing gc result failed with system error exception: '{}'", SystemError.what()); + } + } + catch (std::bad_alloc& BadAlloc) + { + ZEN_WARN("writing gc result ran out of memory: '{}'", BadAlloc.what()); + } + catch (std::exception& Ex) + { + ZEN_ERROR("writing gc result failed with: '{}'", Ex.what()); + } +} + GcSchedulerState GcScheduler::GetState() const { @@ -1552,17 +1813,20 @@ GcScheduler::CollectGarbage(const GcClock::TimePoint& CacheExpireTime, break; case GcVersion::kV2: { - GcResult Result = m_GcManager.CollectGarbage({.CacheExpireTime = CacheExpireTime, - .ProjectStoreExpireTime = ProjectStoreExpireTime, - .CollectSmallObjects = CollectSmallObjects, - .IsDeleteMode = Delete, - .SkipCidDelete = SkipCid}); + const GcSettings Settings = {.CacheExpireTime = CacheExpireTime, + .ProjectStoreExpireTime = ProjectStoreExpireTime, + .CollectSmallObjects = CollectSmallObjects, + .IsDeleteMode = Delete, + .SkipCidDelete = SkipCid}; + GcClock::TimePoint GcStartTime = GcClock::Now(); + GcResult Result = m_GcManager.CollectGarbage(Settings); 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 {}", + "{} on disk and {} of memory in {}. CacheExpireTime: {}, ProjectStoreExpireTime: {}, CollectSmallObjects: {}, " + "IsDeleteMode: {}, SkipCidDelete: {}", Result.ReferencerStat.Expired, Result.ReferencerStat.Count, Result.ReferencerStat.Deleted, @@ -1573,7 +1837,14 @@ GcScheduler::CollectGarbage(const GcClock::TimePoint& CacheExpireTime, Result.ReferenceStoreStat.Pruned, NiceBytes(Result.RemovedDisk), NiceBytes(Result.RemovedMemory), - NiceTimeSpanMs(Result.ElapsedMS.count())); + NiceTimeSpanMs(Result.ElapsedMS.count()), + Settings.CacheExpireTime, + Settings.ProjectStoreExpireTime, + Settings.CollectSmallObjects, + Settings.IsDeleteMode, + Settings.SkipCidDelete); + + AppendGCLog(GcStartTime, Settings, Result); if (SkipCid) { diff --git a/src/zenstore/include/zenstore/gc.h b/src/zenstore/include/zenstore/gc.h index 409c9b4d6..d4c7bba25 100644 --- a/src/zenstore/include/zenstore/gc.h +++ b/src/zenstore/include/zenstore/gc.h @@ -117,6 +117,10 @@ struct GcResult void Sum(); }; +class CbObjectWriter; + +void WriteGCResult(CbObjectWriter& Writer, const GcResult& Result, bool HumanReadable, bool IncludeDetails); + struct GcCtx { const GcSettings Settings; @@ -491,6 +495,7 @@ private: LoggerRef Log() { return m_Log; } virtual bool AreDiskWritesAllowed() const override { return !m_AreDiskWritesBlocked.load(); } DiskSpace CheckDiskSpace(); + void AppendGCLog(GcClock::TimePoint GcStartTime, const GcSettings& Settings, const GcResult& Result); LoggerRef m_Log; GcManager& m_GcManager; @@ -517,6 +522,8 @@ private: TCasLogFile<DiskUsageWindow::DiskUsageEntry> m_DiskUsageLog; DiskUsageWindow m_DiskUsageWindow; + + RwLock m_GcLogLock; }; void gc_forcelink(); diff --git a/src/zenutil/include/zenutil/logging/rotatingfilesink.h b/src/zenutil/include/zenutil/logging/rotatingfilesink.h index f28e908eb..e4a99fc30 100644 --- a/src/zenutil/include/zenutil/logging/rotatingfilesink.h +++ b/src/zenutil/include/zenutil/logging/rotatingfilesink.h @@ -20,14 +20,11 @@ class RotatingFileSink : public spdlog::sinks::sink { public: RotatingFileSink(const std::filesystem::path& BaseFilename, std::size_t MaxSize, std::size_t MaxFiles, bool RotateOnOpen = false) - : m_BasePath(BaseFilename.parent_path()) - , m_Stem(BaseFilename.stem().string()) - , m_Extension(BaseFilename.extension().string()) + : m_BaseFilename(BaseFilename) , m_MaxSize(MaxSize) , m_MaxFiles(MaxFiles) { - std::filesystem::path RootFileName = GetFileName(0); - std::error_code Ec; + std::error_code Ec; if (RotateOnOpen) { RwLock::ExclusiveLockScope RotateLock(m_Lock); @@ -35,7 +32,7 @@ public: } else { - m_CurrentFile.Open(RootFileName, BasicFile::Mode::kWrite, Ec); + m_CurrentFile.Open(m_BaseFilename, BasicFile::Mode::kWrite, Ec); if (!Ec) { m_CurrentSize = m_CurrentFile.FileSize(Ec); @@ -52,7 +49,7 @@ public: if (Ec) { - throw std::system_error(Ec, fmt::format("Failed to open log file '{}'", RootFileName.string())); + throw std::system_error(Ec, fmt::format("Failed to open log file '{}'", m_BaseFilename.string())); } } @@ -151,57 +148,22 @@ public: } private: - static bool IsEmpty(const std::filesystem::path& Path, std::error_code& Ec) - { - bool Exists = std::filesystem::exists(Path, Ec); - if (Ec) - { - return false; - } - if (!Exists) - { - return true; - } - uintmax_t Size = std::filesystem::file_size(Path, Ec); - if (Ec) - { - return false; - } - return Size == 0; - } - void Rotate(RwLock::ExclusiveLockScope&, std::error_code& OutEc) { m_CurrentFile.Close(); - bool BaseIsEmpty = IsEmpty(GetFileName(0), OutEc); + + OutEc = RotateFiles(m_BaseFilename, m_MaxFiles); if (OutEc) { return; } - if (!BaseIsEmpty) - { - // We try our best to rotate the logs, if we fail we fail and will try to open the base log file anyway - for (auto i = m_MaxFiles; i > 0; i--) - { - std::filesystem::path src = GetFileName(i - 1); - if (!std::filesystem::exists(src)) - { - continue; - } - std::error_code DummyEc; - std::filesystem::path target = GetFileName(i); - if (std::filesystem::exists(target, DummyEc)) - { - std::filesystem::remove(target, DummyEc); - } - std::filesystem::rename(src, target, DummyEc); - } - } - m_CurrentFile.Open(GetFileName(0), BasicFile::Mode::kWrite, OutEc); + + m_CurrentFile.Open(m_BaseFilename, BasicFile::Mode::kWrite, OutEc); if (OutEc) { return; } + // If we fail to rotate, try extending the current log file m_CurrentSize = m_CurrentFile.FileSize(OutEc); } @@ -252,19 +214,8 @@ private: return true; } - std::filesystem::path GetFileName(size_t Index) const - { - if (Index == 0) - { - return m_BasePath / (m_Stem + m_Extension); - } - return m_BasePath / fmt::format("{}.{}{}", m_Stem, Index, m_Extension); - } - RwLock m_Lock; - const std::filesystem::path m_BasePath; - const std::string m_Stem; - const std::string m_Extension; + const std::filesystem::path m_BaseFilename; std::unique_ptr<spdlog::formatter> m_Formatter; std::atomic_size_t m_CurrentSize; const std::size_t m_MaxSize; |