Propose a metrics interface
Some checks failed
Tests / Clang total: 1533, failed: 2, passed: 1531
Tests / SIMD fallback total: 1533, failed: 2, passed: 1531
Tests / Release [gcc] total: 1533, failed: 2, passed: 1531
Tests / Release [gcc,aarch64] total: 1144, failed: 2, passed: 1142
Tests / Coverage total: 1151, passed: 1151
weaselab/conflict-set/pipeline/head There was a failure building this commit
Some checks failed
Tests / Clang total: 1533, failed: 2, passed: 1531
Tests / SIMD fallback total: 1533, failed: 2, passed: 1531
Tests / Release [gcc] total: 1533, failed: 2, passed: 1531
Tests / Release [gcc,aarch64] total: 1144, failed: 2, passed: 1142
Tests / Coverage total: 1151, passed: 1151
weaselab/conflict-set/pipeline/head There was a failure building this commit
This commit is contained in:
19
Bench.cpp
19
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) {
|
int main(void) {
|
||||||
benchConflictSet();
|
benchConflictSet();
|
||||||
benchWorstCaseForRadixRangeRead();
|
benchWorstCaseForRadixRangeRead();
|
||||||
|
benchMetrics();
|
||||||
|
benchCreateAndDestroy();
|
||||||
}
|
}
|
||||||
|
|||||||
119
ConflictSet.cpp
119
ConflictSet.cpp
@@ -546,7 +546,38 @@ static_assert(kBytesPerKey - sizeof(Node0) >= kMinNodeSurplus);
|
|||||||
|
|
||||||
constexpr int64_t kFreeListMaxMemory = 1 << 20;
|
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 <class T> struct BoundedFreeListAllocator {
|
template <class T> struct BoundedFreeListAllocator {
|
||||||
|
Counter *allocated_total = nullptr;
|
||||||
|
Counter *released_total = nullptr;
|
||||||
|
|
||||||
static_assert(sizeof(T) >= sizeof(void *));
|
static_assert(sizeof(T) >= sizeof(void *));
|
||||||
static_assert(std::derived_from<T, Node>);
|
static_assert(std::derived_from<T, Node>);
|
||||||
static_assert(std::is_trivial_v<T>);
|
static_assert(std::is_trivial_v<T>);
|
||||||
@@ -580,6 +611,7 @@ template <class T> struct BoundedFreeListAllocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
T *allocate(int partialKeyCapacity) {
|
T *allocate(int partialKeyCapacity) {
|
||||||
|
allocated_total->add(1);
|
||||||
T *result = allocate_helper(partialKeyCapacity);
|
T *result = allocate_helper(partialKeyCapacity);
|
||||||
if constexpr (!std::is_same_v<T, Node0>) {
|
if constexpr (!std::is_same_v<T, Node0>) {
|
||||||
memset(result->children, 0, sizeof(result->children));
|
memset(result->children, 0, sizeof(result->children));
|
||||||
@@ -596,6 +628,7 @@ template <class T> struct BoundedFreeListAllocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void release(T *p) {
|
void release(T *p) {
|
||||||
|
released_total->add(1);
|
||||||
if (freeListBytes >= kFreeListMaxMemory) {
|
if (freeListBytes >= kFreeListMaxMemory) {
|
||||||
removeNode(p);
|
removeNode(p);
|
||||||
return safe_free(p, sizeof(T) + p->partialKeyCapacity);
|
return safe_free(p, sizeof(T) + p->partialKeyCapacity);
|
||||||
@@ -3136,6 +3169,16 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
|
|
||||||
allocators.~NodeAllocators();
|
allocators.~NodeAllocators();
|
||||||
new (&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{};
|
removalKeyArena = Arena{};
|
||||||
removalKey = {};
|
removalKey = {};
|
||||||
@@ -3160,8 +3203,14 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
// Intentionally not resetting totalBytes
|
// Intentionally not resetting totalBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit Impl(int64_t oldestVersion) { init(oldestVersion); }
|
explicit Impl(int64_t oldestVersion) {
|
||||||
~Impl() { destroyTree(root); }
|
init(oldestVersion);
|
||||||
|
initMetrics();
|
||||||
|
}
|
||||||
|
~Impl() {
|
||||||
|
destroyTree(root);
|
||||||
|
safe_free(metrics, metricsCount * sizeof(metrics[0]));
|
||||||
|
}
|
||||||
|
|
||||||
NodeAllocators allocators;
|
NodeAllocators allocators;
|
||||||
|
|
||||||
@@ -3177,8 +3226,63 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
int64_t oldestVersionAtGcBegin;
|
int64_t oldestVersionAtGcBegin;
|
||||||
int64_t newestVersionFullPrecision;
|
int64_t newestVersionFullPrecision;
|
||||||
int64_t totalBytes = 0;
|
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) {
|
InternalVersionT maxVersion(Node *n, ConflictSet::Impl *impl) {
|
||||||
int index = n->parentsIndex;
|
int index = n->parentsIndex;
|
||||||
n = n->parent;
|
n = n->parent;
|
||||||
@@ -3308,6 +3412,12 @@ void internal_destroy(ConflictSet::Impl *impl) {
|
|||||||
|
|
||||||
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->getBytes(); }
|
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 ====================
|
// ==================== END IMPLEMENTATION ====================
|
||||||
|
|
||||||
// GCOVR_EXCL_START
|
// GCOVR_EXCL_START
|
||||||
@@ -3392,6 +3502,10 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
|||||||
|
|
||||||
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
|
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)
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||||
: impl(internal_create(oldestVersion)) {}
|
: impl(internal_create(oldestVersion)) {}
|
||||||
|
|
||||||
@@ -3447,6 +3561,7 @@ static_assert(std::is_standard_layout_v<ConflictSet::Result>);
|
|||||||
static_assert(std::is_standard_layout_v<ConflictSet::Key>);
|
static_assert(std::is_standard_layout_v<ConflictSet::Key>);
|
||||||
static_assert(std::is_standard_layout_v<ConflictSet::ReadRange>);
|
static_assert(std::is_standard_layout_v<ConflictSet::ReadRange>);
|
||||||
static_assert(std::is_standard_layout_v<ConflictSet::WriteRange>);
|
static_assert(std::is_standard_layout_v<ConflictSet::WriteRange>);
|
||||||
|
static_assert(std::is_standard_layout_v<ConflictSet::MetricsV1>);
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|||||||
@@ -129,6 +129,16 @@ int main(int argc, const char **argv) {
|
|||||||
close(fd);
|
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: "
|
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
||||||
"%g%%, Peak idle memory: %g\n",
|
"%g%%, Peak idle memory: %g\n",
|
||||||
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
||||||
|
|||||||
10
SkipList.cpp
10
SkipList.cpp
@@ -861,6 +861,12 @@ void internal_destroy(ConflictSet::Impl *impl) {
|
|||||||
|
|
||||||
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
|
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,
|
void ConflictSet::check(const ReadRange *reads, Result *results,
|
||||||
int count) const {
|
int count) const {
|
||||||
internal_check(impl, reads, results, count);
|
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); }
|
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)
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||||
: impl(internal_create(oldestVersion)) {}
|
: impl(internal_create(oldestVersion)) {}
|
||||||
|
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ __ZN8weaselab11ConflictSetC2Ex
|
|||||||
__ZN8weaselab11ConflictSetD1Ev
|
__ZN8weaselab11ConflictSetD1Ev
|
||||||
__ZN8weaselab11ConflictSetD2Ev
|
__ZN8weaselab11ConflictSetD2Ev
|
||||||
__ZN8weaselab11ConflictSetaSEOS0_
|
__ZN8weaselab11ConflictSetaSEOS0_
|
||||||
|
__ZNK8weaselab11ConflictSet12getMetricsV1EPPNS0_9MetricsV1EPi
|
||||||
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
||||||
__ZNK8weaselab11ConflictSet8getBytesEv
|
__ZNK8weaselab11ConflictSet8getBytesEv
|
||||||
@@ -5,6 +5,8 @@ _abort
|
|||||||
_bzero
|
_bzero
|
||||||
_free
|
_free
|
||||||
_malloc
|
_malloc
|
||||||
|
_memcmp
|
||||||
_memcpy
|
_memcpy
|
||||||
_memmove
|
_memmove
|
||||||
|
_strlen
|
||||||
dyld_stub_binder
|
dyld_stub_binder
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
using namespace weaselab;
|
using namespace weaselab;
|
||||||
|
|
||||||
@@ -21,4 +22,14 @@ int main(void) {
|
|||||||
assert(result == ConflictSet::Conflict);
|
assert(result == ConflictSet::Conflict);
|
||||||
int64_t bytes = cs.getBytes();
|
int64_t bytes = cs.getBytes();
|
||||||
assert(bytes > 0);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,32 @@ struct __attribute__((__visibility__("default"))) ConflictSet {
|
|||||||
/** Returns the total bytes in use by this ConflictSet */
|
/** Returns the total bytes in use by this ConflictSet */
|
||||||
int64_t getBytes() const;
|
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
|
#if __cplusplus > 199711L
|
||||||
ConflictSet(ConflictSet &&) noexcept;
|
ConflictSet(ConflictSet &&) noexcept;
|
||||||
ConflictSet &operator=(ConflictSet &&) noexcept;
|
ConflictSet &operator=(ConflictSet &&) noexcept;
|
||||||
|
|||||||
Reference in New Issue
Block a user