From 95596f831f487fce5712a42bbd3043c690756c60 Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Wed, 17 Jul 2024 16:35:29 -0700 Subject: [PATCH] Add some metrics for addWrites and setOldestVersion --- ConflictSet.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/ConflictSet.cpp b/ConflictSet.cpp index 4131978..1f2cbe0 100644 --- a/ConflictSet.cpp +++ b/ConflictSet.cpp @@ -587,6 +587,9 @@ struct Counter : private Metric { } }; +thread_local double nodes_allocated_accum = 0; +thread_local double nodes_released_accum = 0; + template struct BoundedFreeListAllocator { static_assert(sizeof(T) >= sizeof(void *)); @@ -622,6 +625,7 @@ template struct BoundedFreeListAllocator { } T *allocate(int partialKeyCapacity) { + ++nodes_allocated_accum; T *result = allocate_helper(partialKeyCapacity); if constexpr (!std::is_same_v) { memset(result->children, 0, sizeof(result->children)); @@ -638,6 +642,7 @@ template struct BoundedFreeListAllocator { } void release(T *p) { + ++nodes_released_accum; if (freeListBytes >= kFreeListMaxMemory) { removeNode(p); return safe_free(p, sizeof(T) + p->partialKeyCapacity); @@ -1399,12 +1404,15 @@ void maybeDownsize(Node *self, NodeAllocators *allocators, } } +thread_local double entries_erased_accum; + // Precondition: self is not the root. May invalidate nodes along the search // path to self. May invalidate children of self->parent. Returns a pointer to // the node after self. If erase invalidates the pointee of `dontInvalidate`, it -// will update it to its new pointee as well. +// will update it to its new pointee as well. Precondition: `self->entryPresent` Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl, bool logical, Node *&dontInvalidate) { + ++entries_erased_accum; assert(self->parent != nullptr); #if DEBUG_VERBOSE && !defined(NDEBUG) @@ -1417,6 +1425,7 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl, auto *result = logical ? nextLogical(self) : nextPhysical(self); removeKey(self); + assert(self->entryPresent); self->entryPresent = false; if (self->numChildren != 0) { @@ -2779,6 +2788,8 @@ checkRangeRead(Node *n, std::span begin, } #endif +thread_local double insert_iterations_accum; + // Returns a pointer to the newly inserted node. Caller must set // `entryPresent`, `entry` fields and `maxVersion` on the result. The search // path of the result's parent will have `maxVersion` at least `writeVersion` as @@ -2788,7 +2799,7 @@ template insert(Node **self, std::span key, InternalVersionT writeVersion, NodeAllocators *allocators, ConflictSet::Impl *impl) { - for (;;) { + for (;; ++insert_iterations_accum) { if ((*self)->partialKeyLen > 0) { // Handle an existing partial key @@ -2897,11 +2908,14 @@ void destroyTree(Node *root) { } } +thread_local double entries_inserted_accum; + void addPointWrite(Node *&root, std::span key, InternalVersionT writeVersion, NodeAllocators *allocators, ConflictSet::Impl *impl) { auto *n = insert(&root, key, writeVersion, allocators, impl); if (!n->entryPresent) { + ++entries_inserted_accum; auto *p = nextLogical(n); addKey(n); @@ -2972,6 +2986,7 @@ void addWriteRange(Node *&root, std::span begin, beginNode->entryPresent = true; if (insertedBegin) { + ++entries_inserted_accum; auto *p = nextLogical(beginNode); beginNode->entry.rangeVersion = p == nullptr ? InternalVersionT::zero @@ -2992,6 +3007,7 @@ void addWriteRange(Node *&root, std::span begin, endNode->entryPresent = true; if (insertedEnd) { + ++entries_inserted_accum; auto *p = nextLogical(endNode); endNode->entry.pointVersion = p == nullptr ? InternalVersionT::zero @@ -3148,8 +3164,10 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { gcScanStep(1000); } + double write_byte_accum = 0; for (int i = 0; i < count; ++i) { const auto &w = writes[i]; + write_byte_accum += w.begin.len + w.end.len; auto begin = std::span(w.begin.p, w.begin.len); auto end = std::span(w.end.p, w.end.len); if (w.end.len > 0) { @@ -3164,6 +3182,12 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { } memory_bytes.set(totalBytes); + nodes_allocated_total.add(std::exchange(nodes_allocated_accum, 0)); + nodes_released_total.add(std::exchange(nodes_released_accum, 0)); + entries_inserted_total.add(std::exchange(entries_inserted_accum, 0)); + entries_erased_total.add(std::exchange(entries_erased_accum, 0)); + insert_iterations_total.add(std::exchange(insert_iterations_accum, 0)); + write_bytes_total.add(write_byte_accum); } // Spends up to `fuel` gc'ing, and returns its unused fuel. Reclaims memory @@ -3178,7 +3202,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { rootMaxVersion = std::max(rootMaxVersion, oldestVersion); n = nextPhysical(n); } - for (; fuel > 0 && n != nullptr;) { + double set_oldest_iterations_accum = 0; + for (; fuel > 0 && n != nullptr; ++set_oldest_iterations_accum) { rezero(n, oldestVersion); // The "make sure gc keeps up with writes" calculations assume that we're // scanning key by key, not node by node. Make sure we only spend fuel @@ -3199,6 +3224,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { n = nextPhysical(n); } } + gc_iterations_total.add(set_oldest_iterations_accum); if (n == nullptr) { removalKey = {}; oldestExtantVersion = oldestVersionAtGcBegin; @@ -3235,6 +3261,10 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { keyUpdates = gcScanStep(keyUpdates); memory_bytes.set(totalBytes); + nodes_allocated_total.add(std::exchange(nodes_allocated_accum, 0)); + nodes_released_total.add(std::exchange(nodes_released_accum, 0)); + entries_inserted_total.add(std::exchange(entries_inserted_accum, 0)); + entries_erased_total.add(std::exchange(entries_erased_accum, 0)); } int64_t getBytes() const { return totalBytes; } @@ -3314,7 +3344,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { #define COUNTER(name, help) \ Counter name { this, #name, help } // ==================== METRICS DEFINITIONS ==================== - GAUGE(memory_bytes, "Total number of bytes in use"); COUNTER(point_read_total, "Total number of point reads checked"); COUNTER(point_read_short_circuit_total, "Total number of point reads that did not require a full search to " @@ -3343,6 +3372,21 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { COUNTER(too_olds_total, "Total number of checks where the result is \"too old\""); COUNTER(check_bytes_total, "Total number of key bytes checked"); + GAUGE(memory_bytes, "Total number of bytes in use"); + COUNTER(nodes_allocated_total, + "The total number of physical tree nodes allocated"); + COUNTER(nodes_released_total, + "The total number of physical tree nodes released"); + COUNTER(insert_iterations_total, + "The total number of iterations of the main loop for insertion"); + COUNTER(entries_inserted_total, + "The total number of entries inserted in the tree"); + COUNTER(entries_erased_total, + "The total number of entries erased from the tree"); + COUNTER( + gc_iterations_total, + "The total number of iterations of the main loop for garbage collection"); + COUNTER(write_bytes_total, "Total number of key bytes in calls to addWrites"); #if MEASURE_CHECK_CPU_TIME COUNTER(check_cpu_seconds_total, "Total cpu seconds spent in a call to check");