foundationdb/fdbserver/workloads/ClientMetric.cpp

249 lines
8.6 KiB
C++

/*
* ClientMetric.cpp
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2026 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "fdbserver/tester/workloads.h"
#include "fdbserver/core/ServerDBInfo.h"
#include "fdbclient/GlobalConfig.h"
#include "fdbclient/ManagementAPI.h"
#include "fdbclient/RunTransaction.h"
#include "fdbclient/Tuple.h"
static const StringRef sampleTrInfoKey =
"\xff\x02/fdbClientInfo/client_latency/SSSSSSSSSS/RRRRRRRRRRRRRRRR/NNNNTTTT/XXXX/"_sr;
static const auto versionStampIndex = sampleTrInfoKey.toString().find('S');
static const int versionStampLength = 10;
static const Key CLIENT_LATENCY_INFO_PREFIX = "client_latency/"_sr;
struct ClientMetricWorkload : TestWorkload {
static constexpr auto NAME = "ClientMetric";
double samplingProbability;
double testDuration;
bool toSet;
bool observedAdvancingMetrics = false;
int64_t trInfoSizeLimit;
std::vector<Future<Void>> clients;
explicit ClientMetricWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) {
samplingProbability = getOption(options,
"samplingProbability"_sr,
deterministicRandom()->random01()); // rand range 0 - 1
toSet = getOption(options, "toSet"_sr, false);
trInfoSizeLimit = getOption(options,
"trInfoSizeLimit"_sr,
deterministicRandom()->randomInt(100 * 1024, 10 * 1024 * 1024)); // 100 KB - 10 MB
testDuration = getOption(options, "testDuration"_sr, 1000.0);
}
static uint64_t getVersionStamp(KeyRef key) {
return bigEndian64(
BinaryReader::fromStringRef<int64_t>(key.substr(versionStampIndex, versionStampLength), Unversioned()));
}
Future<Void> setup(Database const& cx) override {
if (toSet && this->clientId == 0) {
return changeProfilingParameters(cx, trInfoSizeLimit, samplingProbability);
}
return Void();
}
Future<Void> start(Database const& cx) override {
if (this->clientId != 0) {
return Void();
}
return _start(cx);
}
Future<Void> _start(Database cx) {
try {
clients.push_back(timeout(runner(cx, this), testDuration, Void()));
co_await waitForAll(clients);
} catch (Error& e) {
TraceEvent("ClientMetricError::_start").error(e);
}
}
Future<Void> changeProfilingParameters(Database cx, int64_t sizeLimit, double sampleProbability) {
co_await runRYWTransaction(cx, [=](Reference<ReadYourWritesTransaction> tr) -> Future<Void> {
Tuple rate = Tuple::makeTuple(sampleProbability);
Tuple size = Tuple::makeTuple(sizeLimit);
tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
tr->set(GlobalConfig::prefixedKey(fdbClientInfoTxnSampleRate), rate.pack());
tr->set(GlobalConfig::prefixedKey(fdbClientInfoTxnSizeLimit), size.pack());
std::cout << "Change globalconfig: sampleRate=" << sampleProbability << " sizeLimit=" << sizeLimit
<< std::endl;
return Void();
});
}
Future<RangeResult> latencyRangeQuery(Database cx, int keysLimit, bool reverse) {
KeySelector begin = firstGreaterOrEqual(CLIENT_LATENCY_INFO_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin));
KeySelector end = firstGreaterOrEqual(strinc(begin.getKey()));
auto tr = makeReference<ReadYourWritesTransaction>(cx);
RangeResult txInfoEntries;
// wait to make sure client metrics are updated
co_await delay(CLIENT_KNOBS->CSI_STATUS_DELAY);
while (true) {
Error err;
try {
tr->reset();
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
tr->setOption(FDBTransactionOptions::LOCK_AWARE);
std::string sampleRateStr = "default";
std::string sizeLimitStr = "default";
const double sampleRateDbl =
cx->globalConfig->get<double>(fdbClientInfoTxnSampleRate, std::numeric_limits<double>::infinity());
if (!std::isinf(sampleRateDbl)) {
sampleRateStr = std::to_string(sampleRateDbl);
}
const int64_t sizeLimit = cx->globalConfig->get<int64_t>(fdbClientInfoTxnSizeLimit, -1);
if (sizeLimit != -1) {
sizeLimitStr = std::to_string(sizeLimit);
}
std::cout << "Read from globalconfig: rate=" << sampleRateStr << " size=" << sizeLimitStr << std::endl;
RangeResult kvRange = co_await tr->getRange(
begin, end, keysLimit, Snapshot::False, reverse ? Reverse::True : Reverse::False);
if (kvRange.empty()) {
co_await delay(1.0);
std::cout << "WaitingForLatencyMetricToBePresent" << std::endl;
TraceEvent("WaitingForLatencyMetricToBePresent").log();
continue;
}
txInfoEntries.arena().dependsOn(kvRange.arena());
txInfoEntries.append(txInfoEntries.arena(), kvRange.begin(), kvRange.size());
break;
} catch (Error& e) {
err = e;
}
co_await tr->onError(err);
}
for (auto& kv : txInfoEntries) {
uint64_t vs = getVersionStamp(kv.key);
std::cout << "VersionStamp is " << vs << std::endl;
}
co_return txInfoEntries;
}
Future<Void> writeRandomKeys(Database cx, int total) {
int cnt = 0;
Transaction tr(cx);
try {
while (true) {
Error err;
try {
co_await delay(0.001);
tr.reset();
tr.set(Key(deterministicRandom()->randomAlphaNumeric(10)),
Value(Key(deterministicRandom()->randomAlphaNumeric(10))));
co_await tr.commit();
if (cnt >= total) {
break;
}
++cnt;
} catch (Error& e) {
err = e;
}
if (err.isValid()) {
co_await tr.onError(err);
}
}
} catch (Error& e) {
TraceEvent(SevError, "ClientMetricErrorWhenWriteKeys").error(e);
throw;
}
std::cout << "writeRandomKeys finish, written=" << cnt << std::endl;
}
Future<uint64_t> writeKeysAndGetLatencyVersion(Database cx,
ClientMetricWorkload* self,
int numKeys,
uint64_t previousVS) {
int retry = 0;
int max_retry = 50;
int keysLimit = 1;
while (true) {
if (retry > max_retry) {
TraceEvent(SevError, "WriteKeysAndGetLatencyVersionFailed")
.detail("Retry", retry)
.detail("MaxRetry", max_retry)
.detail("PreviousVS", previousVS);
ASSERT(false);
}
// write random keys to generate some latency metrics
co_await self->writeRandomKeys(cx, numKeys);
// get the latest latency metric and parse its version stamp
RangeResult r = co_await self->latencyRangeQuery(cx, keysLimit, true);
if (r.empty()) {
// latency metrics might not be present due to transaction batching, retry a few times
++retry;
continue;
}
ASSERT(!r.empty());
// [0] is the latest version, as we have reverse = true
KeyRef latest = r[0].key;
uint64_t vs = getVersionStamp(latest);
ASSERT(vs >= previousVS);
if (vs == previousVS) {
// it means there is no new latency metrics, retry until we see one
++retry;
continue;
}
co_return vs;
}
}
// goal:
// write some random keys, check the latency metric and the latest version stamp vs1
// write some other random keys, check the latency metric and latest version stamp again vs2
// vs2 should be strictly larger than vs1, to verify new latency metrics are added
Future<Void> runner(Database cx, ClientMetricWorkload* self) {
try {
int initialWrites = deterministicRandom()->randomInt(100, 200);
int secondWrites = deterministicRandom()->randomInt(100, 200);
uint64_t zeroVS = 0;
uint64_t vs1 = co_await self->writeKeysAndGetLatencyVersion(cx, self, initialWrites, zeroVS);
std::cout << "vs1=" << vs1 << std::endl;
ASSERT(vs1 > zeroVS);
uint64_t vs2 = co_await self->writeKeysAndGetLatencyVersion(cx, self, secondWrites, vs1);
std::cout << "vs2=" << vs2 << std::endl;
ASSERT(vs2 > vs1);
self->observedAdvancingMetrics = true;
} catch (Error& e) {
TraceEvent("ClientMetricError").error(e);
}
}
Future<bool> check(Database const& cx) override {
if (clientId != 0 || observedAdvancingMetrics) {
return true;
}
TraceEvent(SevError, "ClientMetricCheckFailed").detail("Reason", "WorkloadDidNotComplete");
return false;
}
void getMetrics(std::vector<PerfMetric>& m) override {}
};
WorkloadFactory<ClientMetricWorkload> ClientMetricWorkloadFactory;