From 3ac16bc966f8345e64251335df8533a72aece76d Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Fri, 12 Jul 2024 12:00:46 -0700 Subject: [PATCH] Propose a metrics interface --- Bench.cpp | 19 ++++++ ConflictSet.cpp | 119 +++++++++++++++++++++++++++++++++- RealDataBench.cpp | 10 +++ SkipList.cpp | 10 +++ apple-symbol-exports.txt | 3 +- apple-symbol-imports.txt | 2 + conflict_set_cxx_api_test.cpp | 11 ++++ include/ConflictSet.h | 26 ++++++++ 8 files changed, 197 insertions(+), 3 deletions(-) diff --git a/Bench.cpp b/Bench.cpp index d7b0e62..5137364 100644 --- a/Bench.cpp +++ b/Bench.cpp @@ -358,7 +358,26 @@ void benchWorstCaseForRadixRangeRead() { // } } +void benchMetrics() { + ankerl::nanobench::Bench bench; + ConflictSet cs{0}; + + bench.run("metrics", [&]() { + ConflictSet::MetricsV1 *m; + int count; + cs.getMetricsV1(&m, &count); + }); +} + +void benchCreateAndDestroy() { + ankerl::nanobench::Bench bench; + + bench.run("create and destroy", [&]() { ConflictSet cs{0}; }); +} + int main(void) { benchConflictSet(); benchWorstCaseForRadixRangeRead(); + benchMetrics(); + benchCreateAndDestroy(); } diff --git a/ConflictSet.cpp b/ConflictSet.cpp index 600fe4a..b39cb7e 100644 --- a/ConflictSet.cpp +++ b/ConflictSet.cpp @@ -546,7 +546,38 @@ static_assert(kBytesPerKey - sizeof(Node0) >= kMinNodeSurplus); constexpr int64_t kFreeListMaxMemory = 1 << 20; +struct Metric { + Metric *prev; + const char *name; + const char *help; + ConflictSet::MetricsV1::Type type; + double value; + +protected: + Metric(ConflictSet::Impl *impl, const char *name, const char *help, + ConflictSet::MetricsV1::Type type); +}; + +struct Gauge : private Metric { + Gauge(ConflictSet::Impl *impl, const char *name, const char *help) + : Metric(impl, name, help, ConflictSet::MetricsV1::Gauge) {} + + void set(double value) { this->value = value; } +}; + +struct Counter : private Metric { + Counter(ConflictSet::Impl *impl, const char *name, const char *help) + : Metric(impl, name, help, ConflictSet::MetricsV1::Counter) {} + void add(double value) { + assert(value >= 0); + this->value += value; + } +}; + template struct BoundedFreeListAllocator { + Counter *allocated_total = nullptr; + Counter *released_total = nullptr; + static_assert(sizeof(T) >= sizeof(void *)); static_assert(std::derived_from); static_assert(std::is_trivial_v); @@ -580,6 +611,7 @@ template struct BoundedFreeListAllocator { } T *allocate(int partialKeyCapacity) { + allocated_total->add(1); T *result = allocate_helper(partialKeyCapacity); if constexpr (!std::is_same_v) { memset(result->children, 0, sizeof(result->children)); @@ -596,6 +628,7 @@ template struct BoundedFreeListAllocator { } void release(T *p) { + released_total->add(1); if (freeListBytes >= kFreeListMaxMemory) { removeNode(p); return safe_free(p, sizeof(T) + p->partialKeyCapacity); @@ -3136,6 +3169,16 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { allocators.~NodeAllocators(); new (&allocators) NodeAllocators(); + allocators.node0.allocated_total = &node0_allocated_total; + allocators.node3.allocated_total = &node3_allocated_total; + allocators.node16.allocated_total = &node16_allocated_total; + allocators.node48.allocated_total = &node48_allocated_total; + allocators.node256.allocated_total = &node256_allocated_total; + allocators.node0.released_total = &node0_released_total; + allocators.node3.released_total = &node3_released_total; + allocators.node16.released_total = &node16_released_total; + allocators.node48.released_total = &node48_released_total; + allocators.node256.released_total = &node256_released_total; removalKeyArena = Arena{}; removalKey = {}; @@ -3160,8 +3203,14 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { // Intentionally not resetting totalBytes } - explicit Impl(int64_t oldestVersion) { init(oldestVersion); } - ~Impl() { destroyTree(root); } + explicit Impl(int64_t oldestVersion) { + init(oldestVersion); + initMetrics(); + } + ~Impl() { + destroyTree(root); + safe_free(metrics, metricsCount * sizeof(metrics[0])); + } NodeAllocators allocators; @@ -3177,8 +3226,63 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { int64_t oldestVersionAtGcBegin; int64_t newestVersionFullPrecision; int64_t totalBytes = 0; + + MetricsV1 *metrics; + int metricsCount = 0; + void initMetrics() { + metrics = (MetricsV1 *)safe_malloc(metricsCount * sizeof(metrics[0])); + for (auto [i, m] = std::make_tuple(0, metricList); i < metricsCount; + ++i, m = m->prev) { + metrics[i].name = m->name; + metrics[i].help = m->help; + metrics[i].value = &m->value; + metrics[i].type = m->type; + } + std::sort(metrics, metrics + metricsCount, + [](const MetricsV1 &lhs, const MetricsV1 &rhs) -> bool { + return std::string_view(lhs.name) < std::string_view(rhs.name); + }); + } + + Metric *metricList = nullptr; + +#define GAUGE(name, help) \ + Gauge name { this, #name, help } +#define COUNTER(name, help) \ + Counter name { this, #name, help } + // ==================== METRICS DEFINITIONS ==================== + COUNTER(node0_allocated_total, + "Total number of nodes of type \"Node0\" that have been allocated"); + COUNTER(node3_allocated_total, + "Total number of nodes of type \"Node3\" that have been allocated"); + COUNTER(node16_allocated_total, + "Total number of nodes of type \"Node16\" that have been allocated"); + COUNTER(node48_allocated_total, + "Total number of nodes of type \"Node48\" that have been allocated"); + COUNTER(node256_allocated_total, + "Total number of nodes of type \"Node256\" that have been allocated"); + COUNTER(node0_released_total, + "Total number of nodes of type \"Node0\" that have been released"); + COUNTER(node3_released_total, + "Total number of nodes of type \"Node3\" that have been released"); + COUNTER(node16_released_total, + "Total number of nodes of type \"Node16\" that have been released"); + COUNTER(node48_released_total, + "Total number of nodes of type \"Node48\" that have been released"); + COUNTER(node256_released_total, + "Total number of nodes of type \"Node256\" that have been released"); + // ==================== END METRICS DEFINITIONS ==================== +#undef GAUGE +#undef COUNTER }; +Metric::Metric(ConflictSet::Impl *impl, const char *name, const char *help, + ConflictSet::MetricsV1::Type type) + : prev(std::exchange(impl->metricList, this)), name(name), help(help), + type(type), value(0) { + ++impl->metricsCount; +} + InternalVersionT maxVersion(Node *n, ConflictSet::Impl *impl) { int index = n->parentsIndex; n = n->parent; @@ -3308,6 +3412,12 @@ void internal_destroy(ConflictSet::Impl *impl) { int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->getBytes(); } +void internal_getMetricsV1(ConflictSet::Impl *impl, + ConflictSet::MetricsV1 **metrics, int *count) { + *metrics = impl->metrics; + *count = impl->metricsCount; +} + // ==================== END IMPLEMENTATION ==================== // GCOVR_EXCL_START @@ -3392,6 +3502,10 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) { int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); } +void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const { + return internal_getMetricsV1(impl, metrics, count); +} + ConflictSet::ConflictSet(int64_t oldestVersion) : impl(internal_create(oldestVersion)) {} @@ -3447,6 +3561,7 @@ static_assert(std::is_standard_layout_v); static_assert(std::is_standard_layout_v); static_assert(std::is_standard_layout_v); static_assert(std::is_standard_layout_v); +static_assert(std::is_standard_layout_v); namespace { diff --git a/RealDataBench.cpp b/RealDataBench.cpp index 3a57868..d5a2261 100644 --- a/RealDataBench.cpp +++ b/RealDataBench.cpp @@ -129,6 +129,16 @@ int main(int argc, const char **argv) { close(fd); } + ConflictSet::MetricsV1 *metrics; + int metricsCount; + cs.getMetricsV1(&metrics, &metricsCount); + for (int i = 0; i < metricsCount; ++i) { + printf("# HELP %s %s\n", metrics[i].name, metrics[i].help); + printf("# TYPE %s %s\n", metrics[i].name, + metrics[i].type == metrics[i].Counter ? "counter" : "gauge"); + printf("%s %g\n", metrics[i].name, *metrics[i].value); + } + printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: " "%g%%, Peak idle memory: %g\n", checkTime, checkBytes / checkTime * 1e-6, addTime, diff --git a/SkipList.cpp b/SkipList.cpp index bca5f62..4420cf4 100644 --- a/SkipList.cpp +++ b/SkipList.cpp @@ -861,6 +861,12 @@ void internal_destroy(ConflictSet::Impl *impl) { int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; } +void internal_getMetricsV1(ConflictSet::Impl *impl, + ConflictSet::MetricsV1 **metrics, int *count) { + *metrics = nullptr; + *count = 0; +} + void ConflictSet::check(const ReadRange *reads, Result *results, int count) const { internal_check(impl, reads, results, count); @@ -877,6 +883,10 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) { int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); } +void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const { + return internal_getMetricsV1(impl, metrics, count); +} + ConflictSet::ConflictSet(int64_t oldestVersion) : impl(internal_create(oldestVersion)) {} diff --git a/apple-symbol-exports.txt b/apple-symbol-exports.txt index 3ed1c91..56b518f 100644 --- a/apple-symbol-exports.txt +++ b/apple-symbol-exports.txt @@ -13,5 +13,6 @@ __ZN8weaselab11ConflictSetC2Ex __ZN8weaselab11ConflictSetD1Ev __ZN8weaselab11ConflictSetD2Ev __ZN8weaselab11ConflictSetaSEOS0_ +__ZNK8weaselab11ConflictSet12getMetricsV1EPPNS0_9MetricsV1EPi __ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi -__ZNK8weaselab11ConflictSet8getBytesEv +__ZNK8weaselab11ConflictSet8getBytesEv \ No newline at end of file diff --git a/apple-symbol-imports.txt b/apple-symbol-imports.txt index d54df83..d799242 100644 --- a/apple-symbol-imports.txt +++ b/apple-symbol-imports.txt @@ -5,6 +5,8 @@ _abort _bzero _free _malloc +_memcmp _memcpy _memmove +_strlen dyld_stub_binder \ No newline at end of file diff --git a/conflict_set_cxx_api_test.cpp b/conflict_set_cxx_api_test.cpp index 85ad2e4..e17f2c5 100644 --- a/conflict_set_cxx_api_test.cpp +++ b/conflict_set_cxx_api_test.cpp @@ -1,6 +1,7 @@ #include "ConflictSet.h" #include +#include using namespace weaselab; @@ -21,4 +22,14 @@ int main(void) { assert(result == ConflictSet::Conflict); int64_t bytes = cs.getBytes(); assert(bytes > 0); + + ConflictSet::MetricsV1 *metrics; + int metricsCount; + cs.getMetricsV1(&metrics, &metricsCount); + for (int i = 0; i < metricsCount; ++i) { + printf("# HELP %s %s\n", metrics[i].name, metrics[i].help); + printf("# TYPE %s %s\n", metrics[i].name, + metrics[i].type == metrics[i].Counter ? "counter" : "gauge"); + printf("%s %g\n", metrics[i].name, *metrics[i].value); + } } diff --git a/include/ConflictSet.h b/include/ConflictSet.h index f493778..beacc56 100644 --- a/include/ConflictSet.h +++ b/include/ConflictSet.h @@ -88,6 +88,32 @@ struct __attribute__((__visibility__("default"))) ConflictSet { /** Returns the total bytes in use by this ConflictSet */ int64_t getBytes() const; + /** Experimental! */ + struct MetricsV1 { + /** A null-terminated string with static lifetime. Matches the regex + * [a-zA-Z_:][a-zA-Z0-9_:]* + */ + const char *name; + /** A null-terminated string with static lifetime. May contain any sequence + * of UTF-8 characters (after the metric name), but the backslash and the + * line feed characters have to be escaped as \\ and \n, respectively. + */ + const char *help; + /** Counters are >= 0 and non-decreasing. Gauges are any value. */ + enum Type { Counter, Gauge } type; + /** Thread-safety: do not read concurrently with a call to any non-const + * method in the ConflictSet associated with this metric. */ + const double *value; + }; + + /** Experimental! Store a pointer to an array of MetricsV1 (owned by the + * ConflictSet) to `*metrics`, and its length to `*count`. This function makes + * no guarantees about the contents of the metrics (e.g. names, help text, and + * the meaning of values). A correct implementation is free to return nonsense + * or nothing at all. Not intended to be inspected programmatically. Only + * intended to be plumbed along to e.g. Prometheus. */ + void getMetricsV1(MetricsV1 **metrics, int *count) const; + #if __cplusplus > 199711L ConflictSet(ConflictSet &&) noexcept; ConflictSet &operator=(ConflictSet &&) noexcept;