aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2023-11-13 16:19:39 +0100
committerGitHub <[email protected]>2023-11-13 16:19:39 +0100
commitd52bed8d5a37a39c88b78a19e22f2b7b3f6dd1d6 (patch)
tree2709bce7020faa9c73e784dd2a5997b384395b56 /src
parentpackage dependency clean-ups (#531) (diff)
downloadzen-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.cpp37
-rw-r--r--src/zencore/compactbinary.cpp160
-rw-r--r--src/zencore/filesystem.cpp64
-rw-r--r--src/zencore/include/zencore/compactbinary.h7
-rw-r--r--src/zencore/include/zencore/filesystem.h2
-rw-r--r--src/zenserver/admin/admin.cpp101
-rw-r--r--src/zenstore/gc.cpp287
-rw-r--r--src/zenstore/include/zenstore/gc.h7
-rw-r--r--src/zenutil/include/zenutil/logging/rotatingfilesink.h69
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;