Make metrics thread-safe
Some checks failed
Tests / Clang total: 1533, failed: 1, passed: 1532
Tests / Clang - debug total: 1531, passed: 1531
Tests / SIMD fallback total: 1533, failed: 1, passed: 1532
Tests / Release [gcc] total: 1533, failed: 1, passed: 1532
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: 1, passed: 1532
Tests / Clang - debug total: 1531, passed: 1531
Tests / SIMD fallback total: 1533, failed: 1, passed: 1532
Tests / Release [gcc] total: 1533, failed: 1, passed: 1532
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
Even concurrently with calling non-const methods on the associated ConflictSet
This commit is contained in:
12
Bench.cpp
12
Bench.cpp
@@ -362,10 +362,14 @@ void benchMetrics() {
|
|||||||
ankerl::nanobench::Bench bench;
|
ankerl::nanobench::Bench bench;
|
||||||
ConflictSet cs{0};
|
ConflictSet cs{0};
|
||||||
|
|
||||||
bench.run("metrics", [&]() {
|
int count;
|
||||||
ConflictSet::MetricsV1 *m;
|
ConflictSet::MetricsV1 *m;
|
||||||
int count;
|
cs.getMetricsV1(&m, &count);
|
||||||
cs.getMetricsV1(&m, &count);
|
bench.batch(count);
|
||||||
|
bench.run("fetch metric", [&]() {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
m[i].getValue();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -60,6 +60,8 @@ cmake_pop_check_state()
|
|||||||
option(USE_SIMD_FALLBACK
|
option(USE_SIMD_FALLBACK
|
||||||
"Use fallback implementations of functions that use SIMD" OFF)
|
"Use fallback implementations of functions that use SIMD" OFF)
|
||||||
|
|
||||||
|
option(DISABLE_TSAN "Disable TSAN" OFF)
|
||||||
|
|
||||||
# This is encouraged according to
|
# This is encouraged according to
|
||||||
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
||||||
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
||||||
@@ -245,7 +247,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# tsan tests
|
# tsan tests
|
||||||
if(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
if(NOT CMAKE_CROSSCOMPILING AND NOT DISABLE_TSAN)
|
||||||
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||||
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
|
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
|
||||||
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
|
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
|
||||||
|
@@ -551,7 +551,7 @@ struct Metric {
|
|||||||
const char *name;
|
const char *name;
|
||||||
const char *help;
|
const char *help;
|
||||||
ConflictSet::MetricsV1::Type type;
|
ConflictSet::MetricsV1::Type type;
|
||||||
double value;
|
std::atomic<double> value;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Metric(ConflictSet::Impl *impl, const char *name, const char *help,
|
Metric(ConflictSet::Impl *impl, const char *name, const char *help,
|
||||||
@@ -562,15 +562,27 @@ struct Gauge : private Metric {
|
|||||||
Gauge(ConflictSet::Impl *impl, const char *name, const char *help)
|
Gauge(ConflictSet::Impl *impl, const char *name, const char *help)
|
||||||
: Metric(impl, name, help, ConflictSet::MetricsV1::Gauge) {}
|
: Metric(impl, name, help, ConflictSet::MetricsV1::Gauge) {}
|
||||||
|
|
||||||
void set(double value) { this->value = value; }
|
void set(double value) {
|
||||||
|
this->value.store(value, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Counter : private Metric {
|
struct Counter : private Metric {
|
||||||
Counter(ConflictSet::Impl *impl, const char *name, const char *help)
|
Counter(ConflictSet::Impl *impl, const char *name, const char *help)
|
||||||
: Metric(impl, name, help, ConflictSet::MetricsV1::Counter) {}
|
: Metric(impl, name, help, ConflictSet::MetricsV1::Counter) {}
|
||||||
|
// Expensive. Accumulate locally and then call add instead of repeatedly
|
||||||
|
// calling add.
|
||||||
void add(double value) {
|
void add(double value) {
|
||||||
assert(value >= 0);
|
assert(value >= 0);
|
||||||
this->value += value;
|
static_assert(std::atomic<double>::is_always_lock_free);
|
||||||
|
double old = this->value.load(std::memory_order_relaxed);
|
||||||
|
for (;;) {
|
||||||
|
double newVal = old + value;
|
||||||
|
if (this->value.compare_exchange_weak(old, newVal,
|
||||||
|
std::memory_order_relaxed)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3235,7 +3247,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
++i, m = m->prev) {
|
++i, m = m->prev) {
|
||||||
metrics[i].name = m->name;
|
metrics[i].name = m->name;
|
||||||
metrics[i].help = m->help;
|
metrics[i].help = m->help;
|
||||||
metrics[i].value = &m->value;
|
metrics[i].p = m;
|
||||||
metrics[i].type = m->type;
|
metrics[i].type = m->type;
|
||||||
}
|
}
|
||||||
std::sort(metrics, metrics + metricsCount,
|
std::sort(metrics, metrics + metricsCount,
|
||||||
@@ -3274,6 +3286,11 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
// ==================== END METRICS DEFINITIONS ====================
|
// ==================== END METRICS DEFINITIONS ====================
|
||||||
#undef GAUGE
|
#undef GAUGE
|
||||||
#undef COUNTER
|
#undef COUNTER
|
||||||
|
|
||||||
|
void getMetricsV1(MetricsV1 **metrics, int *count) {
|
||||||
|
*metrics = this->metrics;
|
||||||
|
*count = metricsCount;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Metric::Metric(ConflictSet::Impl *impl, const char *name, const char *help,
|
Metric::Metric(ConflictSet::Impl *impl, const char *name, const char *help,
|
||||||
@@ -3414,8 +3431,11 @@ int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->getBytes(); }
|
|||||||
|
|
||||||
void internal_getMetricsV1(ConflictSet::Impl *impl,
|
void internal_getMetricsV1(ConflictSet::Impl *impl,
|
||||||
ConflictSet::MetricsV1 **metrics, int *count) {
|
ConflictSet::MetricsV1 **metrics, int *count) {
|
||||||
*metrics = impl->metrics;
|
impl->getMetricsV1(metrics, count);
|
||||||
*count = impl->metricsCount;
|
}
|
||||||
|
|
||||||
|
double internal_getMetricValue(const ConflictSet::MetricsV1 *metric) {
|
||||||
|
return ((Metric *)metric->p)->value.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== END IMPLEMENTATION ====================
|
// ==================== END IMPLEMENTATION ====================
|
||||||
@@ -3506,6 +3526,10 @@ void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const {
|
|||||||
return internal_getMetricsV1(impl, metrics, count);
|
return internal_getMetricsV1(impl, metrics, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double ConflictSet::MetricsV1::getValue() const {
|
||||||
|
return internal_getMetricValue(this);
|
||||||
|
}
|
||||||
|
|
||||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||||
: impl(internal_create(oldestVersion)) {}
|
: impl(internal_create(oldestVersion)) {}
|
||||||
|
|
||||||
|
42
Internal.h
42
Internal.h
@@ -702,17 +702,36 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldestVersion +=
|
||||||
|
arbitrary.bounded(10) ? arbitrary.bounded(10) : arbitrary.next();
|
||||||
|
oldestVersion = std::min(oldestVersion, writeVersion);
|
||||||
|
|
||||||
|
#ifdef THREAD_TEST
|
||||||
|
std::latch ready{1};
|
||||||
|
std::thread thread2{[&]() {
|
||||||
|
ready.count_down();
|
||||||
|
ConflictSet::MetricsV1 *m;
|
||||||
|
int count;
|
||||||
|
cs.getMetricsV1(&m, &count);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
m[i].getValue();
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
ready.wait();
|
||||||
|
#endif
|
||||||
|
|
||||||
CALLGRIND_START_INSTRUMENTATION;
|
CALLGRIND_START_INSTRUMENTATION;
|
||||||
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||||
CALLGRIND_STOP_INSTRUMENTATION;
|
CALLGRIND_STOP_INSTRUMENTATION;
|
||||||
|
|
||||||
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||||
|
|
||||||
oldestVersion +=
|
|
||||||
arbitrary.bounded(10) ? arbitrary.bounded(10) : arbitrary.next();
|
|
||||||
oldestVersion = std::min(oldestVersion, writeVersion);
|
|
||||||
cs.setOldestVersion(oldestVersion);
|
cs.setOldestVersion(oldestVersion);
|
||||||
refImpl.setOldestVersion(oldestVersion);
|
refImpl.setOldestVersion(oldestVersion);
|
||||||
|
|
||||||
|
#ifdef THREAD_TEST
|
||||||
|
thread2.join();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
int numPointReads = arbitrary.bounded(100);
|
int numPointReads = arbitrary.bounded(100);
|
||||||
@@ -785,7 +804,15 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
std::latch ready{1};
|
std::latch ready{1};
|
||||||
std::thread thread2{[&]() {
|
std::thread thread2{[&]() {
|
||||||
ready.count_down();
|
ready.count_down();
|
||||||
|
// Call all const methods
|
||||||
cs.check(reads, results3, numPointReads + numRangeReads);
|
cs.check(reads, results3, numPointReads + numRangeReads);
|
||||||
|
cs.getBytes();
|
||||||
|
ConflictSet::MetricsV1 *m;
|
||||||
|
int count;
|
||||||
|
cs.getMetricsV1(&m, &count);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
m[i].getValue();
|
||||||
|
}
|
||||||
}};
|
}};
|
||||||
ready.wait();
|
ready.wait();
|
||||||
#endif
|
#endif
|
||||||
@@ -794,6 +821,15 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
cs.check(reads, results1, numPointReads + numRangeReads);
|
cs.check(reads, results1, numPointReads + numRangeReads);
|
||||||
CALLGRIND_STOP_INSTRUMENTATION;
|
CALLGRIND_STOP_INSTRUMENTATION;
|
||||||
|
|
||||||
|
// Call remaining const methods
|
||||||
|
cs.getBytes();
|
||||||
|
ConflictSet::MetricsV1 *m;
|
||||||
|
int count;
|
||||||
|
cs.getMetricsV1(&m, &count);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
m[i].getValue();
|
||||||
|
}
|
||||||
|
|
||||||
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
||||||
|
|
||||||
auto compareResults = [reads](ConflictSet::Result *results1,
|
auto compareResults = [reads](ConflictSet::Result *results1,
|
||||||
|
14
Jenkinsfile
vendored
14
Jenkinsfile
vendored
@@ -48,6 +48,18 @@ pipeline {
|
|||||||
recordIssues(tools: [clang()])
|
recordIssues(tools: [clang()])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stage('Clang - debug') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DCMAKE_BUILD_TYPE=Debug")
|
||||||
|
recordIssues(tools: [clang()])
|
||||||
|
}
|
||||||
|
}
|
||||||
stage('SIMD fallback') {
|
stage('SIMD fallback') {
|
||||||
agent {
|
agent {
|
||||||
dockerfile {
|
dockerfile {
|
||||||
@@ -106,7 +118,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug")
|
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON")
|
||||||
sh '''
|
sh '''
|
||||||
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
|
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
|
||||||
'''
|
'''
|
||||||
|
@@ -136,7 +136,7 @@ int main(int argc, const char **argv) {
|
|||||||
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
printf("# TYPE %s %s\n", metrics[i].name,
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
printf("%s %g\n", metrics[i].name, *metrics[i].value);
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
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: "
|
||||||
|
@@ -867,6 +867,10 @@ void internal_getMetricsV1(ConflictSet::Impl *impl,
|
|||||||
*count = 0;
|
*count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double internal_getMetricValue(const ConflictSet::MetricsV1 *metric) {
|
||||||
|
return 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);
|
||||||
@@ -887,6 +891,10 @@ void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const {
|
|||||||
return internal_getMetricsV1(impl, metrics, count);
|
return internal_getMetricsV1(impl, metrics, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double ConflictSet::MetricsV1::getValue() const {
|
||||||
|
return internal_getMetricValue(this);
|
||||||
|
}
|
||||||
|
|
||||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||||
: impl(internal_create(oldestVersion)) {}
|
: impl(internal_create(oldestVersion)) {}
|
||||||
|
|
||||||
|
@@ -16,3 +16,4 @@ __ZN8weaselab11ConflictSetaSEOS0_
|
|||||||
__ZNK8weaselab11ConflictSet12getMetricsV1EPPNS0_9MetricsV1EPi
|
__ZNK8weaselab11ConflictSet12getMetricsV1EPPNS0_9MetricsV1EPi
|
||||||
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
||||||
__ZNK8weaselab11ConflictSet8getBytesEv
|
__ZNK8weaselab11ConflictSet8getBytesEv
|
||||||
|
__ZNK8weaselab11ConflictSet9MetricsV18getValueEv
|
@@ -30,6 +30,6 @@ int main(void) {
|
|||||||
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
printf("# TYPE %s %s\n", metrics[i].name,
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
printf("%s %g\n", metrics[i].name, *metrics[i].value);
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -101,9 +101,10 @@ struct __attribute__((__visibility__("default"))) ConflictSet {
|
|||||||
const char *help;
|
const char *help;
|
||||||
/** Counters are >= 0 and non-decreasing. Gauges are any value. */
|
/** Counters are >= 0 and non-decreasing. Gauges are any value. */
|
||||||
enum Type { Counter, Gauge } type;
|
enum Type { Counter, Gauge } type;
|
||||||
/** Thread-safety: do not read concurrently with a call to any non-const
|
/** Get the most up-to-date value available for this metric. Thread-safe. */
|
||||||
* method in the ConflictSet associated with this metric. */
|
double getValue() const;
|
||||||
const double *value;
|
/** @private */
|
||||||
|
void *p;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Experimental! Store a pointer to an array of MetricsV1 (owned by the
|
/** Experimental! Store a pointer to an array of MetricsV1 (owned by the
|
||||||
@@ -111,7 +112,8 @@ struct __attribute__((__visibility__("default"))) ConflictSet {
|
|||||||
* no guarantees about the contents of the metrics (e.g. names, help text, and
|
* 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
|
* the meaning of values). A correct implementation is free to return nonsense
|
||||||
* or nothing at all. Not intended to be inspected programmatically. Only
|
* or nothing at all. Not intended to be inspected programmatically. Only
|
||||||
* intended to be plumbed along to e.g. Prometheus. */
|
* intended to be plumbed along to e.g. Prometheus. Callers may repeatedly
|
||||||
|
* call `getValue` on the metrics they're interested in. */
|
||||||
void getMetricsV1(MetricsV1 **metrics, int *count) const;
|
void getMetricsV1(MetricsV1 **metrics, int *count) const;
|
||||||
|
|
||||||
#if __cplusplus > 199711L
|
#if __cplusplus > 199711L
|
||||||
|
Reference in New Issue
Block a user