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

This commit is contained in:
2024-07-12 12:00:46 -07:00
parent 1e82f7fe22
commit 3ac16bc966
8 changed files with 197 additions and 3 deletions

View File

@@ -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();
}

View File

@@ -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 <class T> struct BoundedFreeListAllocator {
Counter *allocated_total = nullptr;
Counter *released_total = nullptr;
static_assert(sizeof(T) >= sizeof(void *));
static_assert(std::derived_from<T, Node>);
static_assert(std::is_trivial_v<T>);
@@ -580,6 +611,7 @@ template <class T> struct BoundedFreeListAllocator {
}
T *allocate(int partialKeyCapacity) {
allocated_total->add(1);
T *result = allocate_helper(partialKeyCapacity);
if constexpr (!std::is_same_v<T, Node0>) {
memset(result->children, 0, sizeof(result->children));
@@ -596,6 +628,7 @@ template <class T> 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<ConflictSet::Result>);
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::WriteRange>);
static_assert(std::is_standard_layout_v<ConflictSet::MetricsV1>);
namespace {

View File

@@ -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,

View File

@@ -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)) {}

View File

@@ -13,5 +13,6 @@ __ZN8weaselab11ConflictSetC2Ex
__ZN8weaselab11ConflictSetD1Ev
__ZN8weaselab11ConflictSetD2Ev
__ZN8weaselab11ConflictSetaSEOS0_
__ZNK8weaselab11ConflictSet12getMetricsV1EPPNS0_9MetricsV1EPi
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
__ZNK8weaselab11ConflictSet8getBytesEv
__ZNK8weaselab11ConflictSet8getBytesEv

View File

@@ -5,6 +5,8 @@ _abort
_bzero
_free
_malloc
_memcmp
_memcpy
_memmove
_strlen
dyld_stub_binder

View File

@@ -1,6 +1,7 @@
#include "ConflictSet.h"
#include <cassert>
#include <cstdio>
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);
}
}

View File

@@ -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;