From 0af6e4d1c5de1008338d6767b3dcb58fc1fe68d3 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Wed, 13 May 2026 16:02:46 +0000 Subject: [PATCH 01/27] chore: take common slo library --- tests/slo_workloads/CMakeLists.txt | 9 +- tests/slo_workloads/Dockerfile | 2 +- tests/slo_workloads/core/CMakeLists.txt | 17 ++ tests/slo_workloads/core/constants.h | 7 + tests/slo_workloads/core/duration_meter.cpp | 11 + tests/slo_workloads/core/duration_meter.h | 11 + .../{utils => core}/generator.cpp | 11 +- tests/slo_workloads/core/generator.h | 99 +++++++ .../slo_workloads/{utils => core}/metrics.cpp | 91 ++++--- tests/slo_workloads/core/metrics.h | 29 +++ tests/slo_workloads/core/records.h | 22 ++ tests/slo_workloads/core/rps.cpp | 40 +++ tests/slo_workloads/core/rps.h | 22 ++ tests/slo_workloads/core/shard.cpp | 27 ++ tests/slo_workloads/core/shard.h | 7 + tests/slo_workloads/core/slo_text_utils.cpp | 35 +++ tests/slo_workloads/core/slo_text_utils.h | 8 + .../{utils => core}/statistics.cpp | 59 +++-- .../{utils => core}/statistics.h | 27 +- tests/slo_workloads/native/CMakeLists.txt | 2 + .../{ => native}/key_value/CMakeLists.txt | 0 .../{ => native}/key_value/create.cpp | 0 .../{ => native}/key_value/drop.cpp | 0 .../{ => native}/key_value/generate.cpp | 2 +- .../{ => native}/key_value/key_value.cpp | 0 .../{ => native}/key_value/key_value.h | 13 +- .../{ => native}/key_value/main.cpp | 0 .../{ => native}/key_value/run.cpp | 2 +- .../{ => native}/utils/CMakeLists.txt | 6 +- .../{ => native}/utils/executor.cpp | 10 +- .../{ => native}/utils/executor.h | 6 +- .../slo_workloads/{ => native}/utils/job.cpp | 7 +- tests/slo_workloads/{ => native}/utils/job.h | 5 +- .../{ => native}/utils/utils.cpp | 245 ++++++------------ .../slo_workloads/{ => native}/utils/utils.h | 141 +++++----- tests/slo_workloads/userver/CMakeLists.txt | 1 + .../userver/key_value/CMakeLists.txt | 11 + .../slo_workloads/userver/key_value/main.cpp | 10 + tests/slo_workloads/utils/generator.h | 90 ------- tests/slo_workloads/utils/metrics.h | 22 -- 40 files changed, 669 insertions(+), 438 deletions(-) create mode 100644 tests/slo_workloads/core/CMakeLists.txt create mode 100644 tests/slo_workloads/core/constants.h create mode 100644 tests/slo_workloads/core/duration_meter.cpp create mode 100644 tests/slo_workloads/core/duration_meter.h rename tests/slo_workloads/{utils => core}/generator.cpp (72%) create mode 100644 tests/slo_workloads/core/generator.h rename tests/slo_workloads/{utils => core}/metrics.cpp (84%) create mode 100644 tests/slo_workloads/core/metrics.h create mode 100644 tests/slo_workloads/core/records.h create mode 100644 tests/slo_workloads/core/rps.cpp create mode 100644 tests/slo_workloads/core/rps.h create mode 100644 tests/slo_workloads/core/shard.cpp create mode 100644 tests/slo_workloads/core/shard.h create mode 100644 tests/slo_workloads/core/slo_text_utils.cpp create mode 100644 tests/slo_workloads/core/slo_text_utils.h rename tests/slo_workloads/{utils => core}/statistics.cpp (68%) rename tests/slo_workloads/{utils => core}/statistics.h (77%) create mode 100644 tests/slo_workloads/native/CMakeLists.txt rename tests/slo_workloads/{ => native}/key_value/CMakeLists.txt (100%) rename tests/slo_workloads/{ => native}/key_value/create.cpp (100%) rename tests/slo_workloads/{ => native}/key_value/drop.cpp (100%) rename tests/slo_workloads/{ => native}/key_value/generate.cpp (98%) rename tests/slo_workloads/{ => native}/key_value/key_value.cpp (100%) rename tests/slo_workloads/{ => native}/key_value/key_value.h (82%) rename tests/slo_workloads/{ => native}/key_value/main.cpp (100%) rename tests/slo_workloads/{ => native}/key_value/run.cpp (99%) rename tests/slo_workloads/{ => native}/utils/CMakeLists.txt (84%) rename tests/slo_workloads/{ => native}/utils/executor.cpp (96%) rename tests/slo_workloads/{ => native}/utils/executor.h (95%) rename tests/slo_workloads/{ => native}/utils/job.cpp (91%) rename tests/slo_workloads/{ => native}/utils/job.h (92%) rename tests/slo_workloads/{ => native}/utils/utils.cpp (73%) rename tests/slo_workloads/{ => native}/utils/utils.h (54%) create mode 100644 tests/slo_workloads/userver/CMakeLists.txt create mode 100644 tests/slo_workloads/userver/key_value/CMakeLists.txt create mode 100644 tests/slo_workloads/userver/key_value/main.cpp delete mode 100644 tests/slo_workloads/utils/generator.h delete mode 100644 tests/slo_workloads/utils/metrics.h diff --git a/tests/slo_workloads/CMakeLists.txt b/tests/slo_workloads/CMakeLists.txt index d99d107ac4..ed3c7279d0 100644 --- a/tests/slo_workloads/CMakeLists.txt +++ b/tests/slo_workloads/CMakeLists.txt @@ -1,2 +1,7 @@ -add_subdirectory(key_value) -add_subdirectory(utils) +add_subdirectory(core) +add_subdirectory(native) + +option(YDB_CPP_SDK_BUILD_SLO_USERVER "Build userver SLO workload (requires userver)" OFF) +if (YDB_CPP_SDK_BUILD_SLO_USERVER) + add_subdirectory(userver) +endif() diff --git a/tests/slo_workloads/Dockerfile b/tests/slo_workloads/Dockerfile index f87be8f8df..e22754a095 100644 --- a/tests/slo_workloads/Dockerfile +++ b/tests/slo_workloads/Dockerfile @@ -165,4 +165,4 @@ RUN --mount=type=cache,target=/root/.ccache,sharing=locked \ && cmake --build --preset default --target slo-key-value \ && ccache --show-stats -ENTRYPOINT ["./build/tests/slo_workloads/key_value/slo-key-value"] +ENTRYPOINT ["./build/tests/slo_workloads/native/key_value/slo-key-value"] diff --git a/tests/slo_workloads/core/CMakeLists.txt b/tests/slo_workloads/core/CMakeLists.txt new file mode 100644 index 0000000000..1e7f093425 --- /dev/null +++ b/tests/slo_workloads/core/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library(slo-workload-core) + +target_link_libraries(slo-workload-core PUBLIC + yutil + opentelemetry-cpp::metrics + opentelemetry-cpp::otlp_http_metric_exporter +) + +target_sources(slo-workload-core PRIVATE + duration_meter.cpp + rps.cpp + slo_text_utils.cpp + shard.cpp + metrics.cpp + statistics.cpp + generator.cpp +) diff --git a/tests/slo_workloads/core/constants.h b/tests/slo_workloads/core/constants.h new file mode 100644 index 0000000000..b8421a1181 --- /dev/null +++ b/tests/slo_workloads/core/constants.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +inline constexpr TDuration DefaultReactionTime = TDuration::Minutes(2); +inline constexpr TDuration ReactionTimeDelay = TDuration::MilliSeconds(5); +inline constexpr std::uint64_t PartitionsCount = 64; diff --git a/tests/slo_workloads/core/duration_meter.cpp b/tests/slo_workloads/core/duration_meter.cpp new file mode 100644 index 0000000000..dd808a5be7 --- /dev/null +++ b/tests/slo_workloads/core/duration_meter.cpp @@ -0,0 +1,11 @@ +#include "duration_meter.h" + +TDurationMeter::TDurationMeter(TDuration& value) + : Value(value) + , StartTime(TInstant::Now()) +{ +} + +TDurationMeter::~TDurationMeter() { + Value += TInstant::Now() - StartTime; +} diff --git a/tests/slo_workloads/core/duration_meter.h b/tests/slo_workloads/core/duration_meter.h new file mode 100644 index 0000000000..16b54625d7 --- /dev/null +++ b/tests/slo_workloads/core/duration_meter.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +struct TDurationMeter { + TDurationMeter(TDuration& value); + ~TDurationMeter(); + + TDuration& Value; + TInstant StartTime; +}; diff --git a/tests/slo_workloads/utils/generator.cpp b/tests/slo_workloads/core/generator.cpp similarity index 72% rename from tests/slo_workloads/utils/generator.cpp rename to tests/slo_workloads/core/generator.cpp index 8543347838..b7081b5433 100644 --- a/tests/slo_workloads/utils/generator.cpp +++ b/tests/slo_workloads/core/generator.cpp @@ -1,9 +1,6 @@ #include "generator.h" -#include - - -TValueGenerator::TValueGenerator(const TCommonOptions& opts, ui32 startId) +TValueGenerator::TValueGenerator(const TSloGeneratorOptions& opts, ui32 startId) : Opts(opts) , CurrentObjectId(startId) { @@ -18,7 +15,7 @@ TRecordData TValueGenerator::Get() { CurrentObjectId, TInstant::Now().MicroSeconds(), CreateGuidAsString(), - GenerateRandomString(Opts.MinLength, Opts.MaxLength) + SloGenerateRandomString(Opts.MinLength, Opts.MaxLength) }; } @@ -26,7 +23,7 @@ TDuration TValueGenerator::GetComputeTime() const { return ComputeTime; } -TKeyValueGenerator::TKeyValueGenerator(const TCommonOptions& opts, ui32 startId) +TKeyValueGenerator::TKeyValueGenerator(const TSloGeneratorOptions& opts, ui32 startId) : Opts(opts) , CurrentObjectId(startId) { @@ -38,7 +35,7 @@ TKeyValueRecordData TKeyValueGenerator::Get() { return { CurrentObjectId, TInstant::Now().MicroSeconds(), - GenerateRandomString(Opts.MinLength, Opts.MaxLength) + SloGenerateRandomString(Opts.MinLength, Opts.MaxLength) }; } diff --git a/tests/slo_workloads/core/generator.h b/tests/slo_workloads/core/generator.h new file mode 100644 index 0000000000..6a57dcd481 --- /dev/null +++ b/tests/slo_workloads/core/generator.h @@ -0,0 +1,99 @@ +#pragma once + +#include "duration_meter.h" +#include "records.h" +#include "shard.h" +#include "slo_text_utils.h" + +#include +#include +#include + +#include +#include +#include + +class TValueGenerator { +public: + TValueGenerator(const TSloGeneratorOptions& opts, std::uint32_t startId = 0); + TRecordData Get(); + TDuration GetComputeTime() const; + +private: + TSloGeneratorOptions Opts; + std::uint32_t CurrentObjectId = 0; + TDuration ComputeTime; +}; + +class TKeyValueGenerator { +public: + TKeyValueGenerator(const TSloGeneratorOptions& opts, std::uint32_t startId = 0); + TKeyValueRecordData Get(); + TDuration GetComputeTime() const; + +private: + TSloGeneratorOptions Opts; + std::uint32_t CurrentObjectId = 0; + TDuration ComputeTime; +}; + +template +class TPackGenerator { +public: + using TBuildItem = std::function; + + TPackGenerator( + const TSloGeneratorOptions& opts, + std::uint32_t packSize, + TBuildItem buildItem, + std::uint64_t remain, + std::uint32_t startId = 0 + ) + : BuildItem_(std::move(buildItem)) + , Generator_(opts, startId) + , PackSize_(packSize) + , Remain_(remain) + { + } + + bool GetNextPack(std::vector& pack) { + pack.clear(); + while (Remain_) { + TRecordType record = Generator_.Get(); + --Remain_; + const std::uint32_t specialId = SloGetSpecialId(SloGetHash(record.ObjectId)); + auto& existingPack = Packs_[specialId]; + existingPack.emplace_back(BuildItem_(record)); + if (existingPack.size() >= PackSize_) { + existingPack.swap(pack); + return true; + } + } + for (auto& it : Packs_) { + if (it.second.size()) { + it.second.swap(pack); + return true; + } + } + return false; + } + + std::uint32_t GetPackSize() const { + return PackSize_; + } + + TDuration GetComputeTime() const { + return Generator_.GetComputeTime(); + } + + std::uint64_t GetRemain() const { + return Remain_; + } + +private: + TBuildItem BuildItem_; + TGeneratorType Generator_; + std::uint32_t PackSize_; + std::unordered_map> Packs_; + std::uint64_t Remain_; +}; diff --git a/tests/slo_workloads/utils/metrics.cpp b/tests/slo_workloads/core/metrics.cpp similarity index 84% rename from tests/slo_workloads/utils/metrics.cpp rename to tests/slo_workloads/core/metrics.cpp index 708c83a563..ec6b28f1f6 100644 --- a/tests/slo_workloads/utils/metrics.cpp +++ b/tests/slo_workloads/core/metrics.cpp @@ -1,22 +1,16 @@ #include "metrics.h" -#include "utils.h" - #include - #include -#include #include +#include #include -#include - -#include - #include #include #include +#include #include #include #include @@ -30,11 +24,6 @@ constexpr std::int64_t kHdrMinLatencyNs = 1'000; // 1 us constexpr std::int64_t kHdrMaxLatencyNs = 60'000'000'000; // 60 s constexpr int kHdrSignificantFigures = 3; -std::string ResolveWorkloadRef() { - std::string ref = GetEnv("WORKLOAD_REF"); - return ref.empty() ? "unknown" : ref; -} - // Thread-safe HDR histogram. Only successful latencies are recorded; errors // are excluded from the percentile stream (operation_status="success"). class TLatencyRecorder { @@ -98,13 +87,12 @@ class TLatencyRecorder { // resource label set, which Prometheus rejects as `out of order sample`. class TOtelSharedPusher { public: - explicit TOtelSharedPusher(const std::string& metricsPushUrl) - : Ref_(ResolveWorkloadRef()) - , CommonAttributes_{ - {"ref", Ref_}, - {"sdk", "cpp"}, - {"sdk_version", NYdb::GetSdkSemver()} - } + TOtelSharedPusher( + const std::string& metricsPushUrl, + std::map resourceAttributes, + const std::string& meterSchemaVersion + ) + : CommonAttributes_(std::move(resourceAttributes)) { auto exporterOptions = opentelemetry::exporter::otlp::OtlpHttpMetricExporterOptions(); exporterOptions.url = metricsPushUrl; @@ -113,7 +101,7 @@ class TOtelSharedPusher { opentelemetry::sdk::metrics::PeriodicExportingMetricReaderOptions readerOptions; readerOptions.export_interval_millis = 1000ms; - readerOptions.export_timeout_millis = 500ms; + readerOptions.export_timeout_millis = 500ms; auto metricReader = opentelemetry::sdk::metrics::PeriodicExportingMetricReaderFactory::Create( std::move(exporter), readerOptions); @@ -127,7 +115,7 @@ class TOtelSharedPusher { MeterProvider_ = opentelemetry::sdk::metrics::MeterProviderFactory::Create(std::move(context)); MeterProvider_->AddMetricReader(std::move(metricReader)); - Meter_ = MeterProvider_->GetMeter("slo_workloads", NYdb::GetSdkSemver()); + Meter_ = MeterProvider_->GetMeter("slo_workloads", meterSchemaVersion); InitMetrics(); } @@ -144,7 +132,7 @@ class TOtelSharedPusher { } void Record(const std::string& operationType, const TRequestData& data) { - const bool success = data.Status == NYdb::EStatus::SUCCESS; + const bool success = data.StatusLabel == "SUCCESS"; auto& series = GetOrCreateSeries(operationType); OperationsTotal_->Add(uint64_t{1}, @@ -284,7 +272,6 @@ class TOtelSharedPusher { } } - std::string Ref_; std::map CommonAttributes_; std::unique_ptr MeterProvider_; @@ -300,16 +287,47 @@ class TOtelSharedPusher { std::unordered_map> Series_; }; -std::mutex g_sharedMu; -std::weak_ptr g_shared; +struct TSharedPusherKey { + std::string Url; + std::map ResourceAttributes; + std::string MeterSchemaVersion; -std::shared_ptr GetOrCreateSharedPusher(const std::string& url) { + bool operator==(const TSharedPusherKey& other) const { + return Url == other.Url + && ResourceAttributes == other.ResourceAttributes + && MeterSchemaVersion == other.MeterSchemaVersion; + } +}; + +struct TSharedPusherKeyHash { + size_t operator()(const TSharedPusherKey& key) const { + size_t h = std::hash{}(key.Url); + for (const auto& [k, v] : key.ResourceAttributes) { + h ^= std::hash{}(k) ^ std::hash{}(v); + } + h ^= std::hash{}(key.MeterSchemaVersion); + return h; + } +}; + +std::mutex g_sharedMu; +std::unordered_map, TSharedPusherKeyHash> g_shared; + +std::shared_ptr GetOrCreateSharedPusher( + const std::string& url, + const std::map& resourceAttributes, + const std::string& meterSchemaVersion +) { + TSharedPusherKey key{url, resourceAttributes, meterSchemaVersion}; std::lock_guard lock(g_sharedMu); - auto sp = g_shared.lock(); - if (!sp) { - sp = std::make_shared(url); - g_shared = sp; + auto it = g_shared.find(key); + if (it != g_shared.end()) { + if (auto sp = it->second.lock()) { + return sp; + } } + auto sp = std::make_shared(url, resourceAttributes, meterSchemaVersion); + g_shared[key] = sp; return sp; } @@ -336,8 +354,15 @@ class TNoopMetricsPusher : public IMetricsPusher { } // namespace -std::unique_ptr CreateOtelMetricsPusher(const std::string& metricsPushUrl, const std::string& operationType) { - return std::make_unique(GetOrCreateSharedPusher(metricsPushUrl), operationType); +std::unique_ptr CreateOtelMetricsPusher( + const std::string& metricsPushUrl, + const std::string& operationType, + const std::map& resourceAttributes, + const std::string& meterSchemaVersion +) { + return std::make_unique( + GetOrCreateSharedPusher(metricsPushUrl, resourceAttributes, meterSchemaVersion), + operationType); } std::unique_ptr CreateNoopMetricsPusher() { diff --git a/tests/slo_workloads/core/metrics.h b/tests/slo_workloads/core/metrics.h new file mode 100644 index 0000000000..ccfd31a467 --- /dev/null +++ b/tests/slo_workloads/core/metrics.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +struct TRequestData { + TDuration Delay; + std::string StatusLabel; + std::uint64_t RetryAttempts; +}; + +class IMetricsPusher { +public: + virtual ~IMetricsPusher() = default; + virtual void PushRequestData(const TRequestData& requestData) = 0; +}; + +std::unique_ptr CreateOtelMetricsPusher( + const std::string& metricsPushUrl, + const std::string& operationType, + const std::map& resourceAttributes, + const std::string& meterSchemaVersion +); +std::unique_ptr CreateNoopMetricsPusher(); diff --git a/tests/slo_workloads/core/records.h b/tests/slo_workloads/core/records.h new file mode 100644 index 0000000000..5e9f9767cd --- /dev/null +++ b/tests/slo_workloads/core/records.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +struct TRecordData { + std::uint32_t ObjectId; + std::uint64_t Timestamp; + std::string Guid; + std::string Payload; +}; + +struct TKeyValueRecordData { + std::uint32_t ObjectId; + std::uint64_t Timestamp; + std::string Payload; +}; + +struct TSloGeneratorOptions { + std::uint32_t MinLength = 20; + std::uint32_t MaxLength = 200; +}; diff --git a/tests/slo_workloads/core/rps.cpp b/tests/slo_workloads/core/rps.cpp new file mode 100644 index 0000000000..5369280722 --- /dev/null +++ b/tests/slo_workloads/core/rps.cpp @@ -0,0 +1,40 @@ +#include "rps.h" + +#include + +TRpsProvider::TRpsProvider(std::uint64_t rps) + : Rps(rps) + , Period(Max(TDuration::MilliSeconds(10), TDuration::MicroSeconds(1000000 / Rps))) + , ProcessedTime(TInstant::Now()) +{ +} + +void TRpsProvider::Reset() { + ProcessedTime = TInstant::Now() - Period - Period; +} + +void TRpsProvider::Use() { + if (Allowed) { + --Allowed; + return; + } + + while (!TryUse()) { + SleepUntil(TInstant::Now() + Period); + } +} + +bool TRpsProvider::TryUse() { + TInstant now = TInstant::Now(); + Allowed = Rps * TDuration(now - ProcessedTime).MicroSeconds() / 1000000; + if (Allowed) { + ProcessedTime += TDuration::MicroSeconds(1000000 * Allowed / Rps); + --Allowed; + return true; + } + return false; +} + +std::uint64_t TRpsProvider::GetRps() const { + return Rps; +} diff --git a/tests/slo_workloads/core/rps.h b/tests/slo_workloads/core/rps.h new file mode 100644 index 0000000000..23e9e74f7f --- /dev/null +++ b/tests/slo_workloads/core/rps.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +class TRpsProvider { +public: + explicit TRpsProvider(std::uint64_t rps); + void Reset(); + void Use(); + bool TryUse(); + std::uint64_t GetRps() const; + +private: + std::uint64_t Rps; + TDuration Period; + TInstant ProcessedTime; + TInstant LastCheck; + std::uint32_t Allowed = 0; + TInstant StartTime; +}; diff --git a/tests/slo_workloads/core/shard.cpp b/tests/slo_workloads/core/shard.cpp new file mode 100644 index 0000000000..005abd9085 --- /dev/null +++ b/tests/slo_workloads/core/shard.cpp @@ -0,0 +1,27 @@ +#include "shard.h" + +#include "constants.h" + +#include +#include + +static double SloShardSizeDouble() { + return (static_cast(Max()) + 1) / static_cast(PartitionsCount); +} + +std::uint32_t SloGetSpecialId(std::uint32_t id) { + const double shardSize = SloShardSizeDouble(); + return static_cast(id / shardSize) * static_cast(shardSize) + 1; +} + +std::uint32_t SloGetShardSpecialId(std::uint64_t shardNo) { + return static_cast(shardNo * SloShardSizeDouble() + 1); +} + +std::uint32_t SloGetHash(std::uint32_t value) { + std::uint32_t result = NumericHash(value); + if (result == SloGetSpecialId(result)) { + ++result; + } + return result; +} diff --git a/tests/slo_workloads/core/shard.h b/tests/slo_workloads/core/shard.h new file mode 100644 index 0000000000..71f3693103 --- /dev/null +++ b/tests/slo_workloads/core/shard.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +std::uint32_t SloGetSpecialId(std::uint32_t id); +std::uint32_t SloGetShardSpecialId(std::uint64_t shardNo); +std::uint32_t SloGetHash(std::uint32_t value); diff --git a/tests/slo_workloads/core/slo_text_utils.cpp b/tests/slo_workloads/core/slo_text_utils.cpp new file mode 100644 index 0000000000..0d2eedbfaa --- /dev/null +++ b/tests/slo_workloads/core/slo_text_utils.cpp @@ -0,0 +1,35 @@ +#include "slo_text_utils.h" + +#include +#include + +#include + +std::string SloJoinPath(const std::string& prefix, const std::string& path) { + if (prefix.empty()) { + return path; + } + TPathSplitUnix prefixPathSplit(prefix); + prefixPathSplit.AppendComponent(path); + return prefixPathSplit.Reconstruct(); +} + +std::string SloGetDatabaseFromConnectionString(const std::string& connectionString) { + constexpr std::string_view databaseFlag = "/?database="; + size_t pathIndex = connectionString.find(databaseFlag); + if (pathIndex != std::string::npos) { + return connectionString.substr(pathIndex + databaseFlag.size()); + } + return {}; +} + +std::string SloGenerateRandomString(std::uint32_t minLength, std::uint32_t maxLength) { + std::uint32_t length = minLength + RandomNumber() % (maxLength - minLength); + static const char* symbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::string result; + result.reserve(length); + for (size_t i = 0; i < length; ++i) { + result.push_back(symbols[RandomNumber(61)]); + } + return result; +} diff --git a/tests/slo_workloads/core/slo_text_utils.h b/tests/slo_workloads/core/slo_text_utils.h new file mode 100644 index 0000000000..f32fa43a97 --- /dev/null +++ b/tests/slo_workloads/core/slo_text_utils.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +std::string SloJoinPath(const std::string& prefix, const std::string& path); +std::string SloGetDatabaseFromConnectionString(const std::string& connectionString); +std::string SloGenerateRandomString(std::uint32_t minLength, std::uint32_t maxLength); diff --git a/tests/slo_workloads/utils/statistics.cpp b/tests/slo_workloads/core/statistics.cpp similarity index 68% rename from tests/slo_workloads/utils/statistics.cpp rename to tests/slo_workloads/core/statistics.cpp index dddc35c56a..f65e0df49a 100644 --- a/tests/slo_workloads/utils/statistics.cpp +++ b/tests/slo_workloads/core/statistics.cpp @@ -1,11 +1,10 @@ #include "statistics.h" -#include "metrics.h" -#include "utils.h" +#include +#include namespace { - // Calculated percentiles for a period of time struct TPercentile { TDuration P50; TDuration P90; @@ -29,9 +28,18 @@ namespace { } } -TStat::TStat(const std::optional& metricsPushUrl, const std::string& operationType) +TStat::TStat( + const std::optional& metricsPushUrl, + const std::string& operationType, + const std::map& resourceAttributes, + const std::string& meterSchemaVersion +) : StartTime(TInstant::Now()) - , MetricsPusher(metricsPushUrl ? CreateOtelMetricsPusher(*metricsPushUrl, operationType) : CreateNoopMetricsPusher()) + , MetricsPusher( + metricsPushUrl + ? CreateOtelMetricsPusher(*metricsPushUrl, operationType, resourceAttributes, meterSchemaVersion) + : CreateNoopMetricsPusher() + ) { MetricsPushQueue.Start(20); } @@ -56,40 +64,39 @@ std::shared_ptr TStat::StartRequest() { return std::make_shared(TInstant::Now()); } -void TStat::FinishRequest(const std::shared_ptr& unit, const TFinalStatus& status) { +void TStat::FinishRequest(const std::shared_ptr& unit, const TSloRequestFinish& finish) { std::lock_guard lock(Mutex); unit->End = TInstant::Now(); - - auto delay = unit->End - unit->Start; - + const auto delay = unit->End - unit->Start; --Infly; - if (status) { - ++Statuses[status->GetStatus()]; - } else { + std::string metricStatusLabel; + if (finish.ApplicationTimeout) { ++ApplicationTimeout; + metricStatusLabel = "APPLICATION_TIMEOUT"; + } else if (finish.StatusLabel) { + ++Statuses[*finish.StatusLabel]; + metricStatusLabel = *finish.StatusLabel; + } else { + metricStatusLabel = "UNKNOWN"; } - if (status && status->GetStatus() == NYdb::EStatus::SUCCESS) { + if (!finish.ApplicationTimeout && finish.StatusLabel && *finish.StatusLabel == "SUCCESS") { OkDelays.push_back(delay); } - ScheduleMetricsPush([this, delay, status, unit]() { - NYdb::EStatus requestStatus = status - ? status->GetStatus() - : NYdb::EStatus::CLIENT_DEADLINE_EXCEEDED; + ScheduleMetricsPush([this, delay, metricStatusLabel, unit]() { MetricsPusher->PushRequestData({ .Delay = delay, - .Status = requestStatus, - .RetryAttempts = unit->RetryAttempts + .StatusLabel = metricStatusLabel, + .RetryAttempts = unit->RetryAttempts, }); }); } void TStat::ReportMaxInfly() { std::lock_guard lock(Mutex); - ++CountMaxInfly; } @@ -112,20 +119,20 @@ void TStat::PrintStatistics(TStringBuilder& out) { TDuration timePassed; if (FinishTime < StartTime) { - // If we ask for current progress timePassed = TInstant::Now() - StartTime; } else { timePassed = FinishTime - StartTime; } - std::uint64_t rps = total * 1000000 / timePassed.MicroSeconds(); + const std::uint64_t micros = timePassed.MicroSeconds(); + const std::uint64_t rps = micros ? total * 1000000 / micros : 0; out << total << " requests total" << Endl - << Statuses[NYdb::EStatus::SUCCESS] << " succeeded"; + << Statuses["SUCCESS"] << " succeeded"; if (total) { - out << " (" << Statuses[NYdb::EStatus::SUCCESS] * 100 / total << "%)"; + out << " (" << Statuses["SUCCESS"] * 100 / total << "%)"; } - for (const auto&[status, counter] : Statuses) { - out << Endl << counter << " replies with status " << YdbStatusToString(status) << Endl; + for (const auto& [status, counter] : Statuses) { + out << Endl << counter << " replies with status " << status << Endl; } out << Endl << CountMaxInfly << " failed due to max infly" << Endl << ApplicationTimeout << " application timeouts" << Endl diff --git a/tests/slo_workloads/utils/statistics.h b/tests/slo_workloads/core/statistics.h similarity index 77% rename from tests/slo_workloads/utils/statistics.h rename to tests/slo_workloads/core/statistics.h index c61a267db4..5f0948c53c 100644 --- a/tests/slo_workloads/utils/statistics.h +++ b/tests/slo_workloads/core/statistics.h @@ -2,14 +2,17 @@ #include "metrics.h" -#include - #include #include #include #include +#include +#include +#include #include +#include +#include inline std::string GetMillisecondsStr(const TDuration& d) { return TStringBuilder() << d.MilliSeconds() << '.' << Sprintf("%03" PRIu64, d.MicroSeconds() % 1000); @@ -19,10 +22,11 @@ inline double GetMillisecondsDouble(const TDuration& d) { return static_cast(d.MicroSeconds()) / 1000; } -using TFinalStatus = std::optional; -using TAsyncFinalStatus = NThreading::TFuture; +struct TSloRequestFinish { + bool ApplicationTimeout = false; + std::optional StatusLabel; +}; -// Request unit struct TStatUnit { TStatUnit(TInstant start) : Start(start) @@ -47,13 +51,18 @@ struct TStatUnit { class TStat { public: - explicit TStat(const std::optional& metricsPushUrl, const std::string& operationType); + TStat( + const std::optional& metricsPushUrl, + const std::string& operationType, + const std::map& resourceAttributes, + const std::string& meterSchemaVersion + ); void Start(); void Finish(); std::shared_ptr StartRequest(); - void FinishRequest(const std::shared_ptr& unit, const TFinalStatus& status); + void FinishRequest(const std::shared_ptr& unit, const TSloRequestFinish& finish); void ReportMaxInfly(); void ReportStats(std::uint64_t sessions, std::uint64_t readPromises, std::uint64_t executorPromises); @@ -73,16 +82,14 @@ class TStat { TInstant StartTime; TInstant FinishTime; - // program lifetime std::uint64_t Infly = 0; std::uint64_t ActiveSessions = 0; - std::map Statuses; + std::map Statuses; std::uint64_t CountMaxInfly = 0; std::uint64_t ApplicationTimeout = 0; std::vector OkDelays; - // Debug use only: std::uint64_t ReadPromises = 0; std::uint64_t ExecutorPromises = 0; diff --git a/tests/slo_workloads/native/CMakeLists.txt b/tests/slo_workloads/native/CMakeLists.txt new file mode 100644 index 0000000000..0692a7a141 --- /dev/null +++ b/tests/slo_workloads/native/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(utils) +add_subdirectory(key_value) diff --git a/tests/slo_workloads/key_value/CMakeLists.txt b/tests/slo_workloads/native/key_value/CMakeLists.txt similarity index 100% rename from tests/slo_workloads/key_value/CMakeLists.txt rename to tests/slo_workloads/native/key_value/CMakeLists.txt diff --git a/tests/slo_workloads/key_value/create.cpp b/tests/slo_workloads/native/key_value/create.cpp similarity index 100% rename from tests/slo_workloads/key_value/create.cpp rename to tests/slo_workloads/native/key_value/create.cpp diff --git a/tests/slo_workloads/key_value/drop.cpp b/tests/slo_workloads/native/key_value/drop.cpp similarity index 100% rename from tests/slo_workloads/key_value/drop.cpp rename to tests/slo_workloads/native/key_value/drop.cpp diff --git a/tests/slo_workloads/key_value/generate.cpp b/tests/slo_workloads/native/key_value/generate.cpp similarity index 98% rename from tests/slo_workloads/key_value/generate.cpp rename to tests/slo_workloads/native/key_value/generate.cpp index 4e6b8bb3d9..6b84b2a692 100644 --- a/tests/slo_workloads/key_value/generate.cpp +++ b/tests/slo_workloads/native/key_value/generate.cpp @@ -11,7 +11,7 @@ TGenerateInitialContentJob::TGenerateInitialContentJob(const TCreateOptions& cre : TThreadJob(createOpts.CommonOptions, "generate") , Executor(createOpts.CommonOptions, Stats, TExecutor::ModeBlocking) , PackGenerator( - createOpts.CommonOptions + MakeGeneratorOptions(createOpts.CommonOptions) , createOpts.PackSize , [](const TKeyValueRecordData& recordData) { return BuildValueFromRecord(recordData); } , createOpts.Count diff --git a/tests/slo_workloads/key_value/key_value.cpp b/tests/slo_workloads/native/key_value/key_value.cpp similarity index 100% rename from tests/slo_workloads/key_value/key_value.cpp rename to tests/slo_workloads/native/key_value/key_value.cpp diff --git a/tests/slo_workloads/key_value/key_value.h b/tests/slo_workloads/native/key_value/key_value.h similarity index 82% rename from tests/slo_workloads/key_value/key_value.h rename to tests/slo_workloads/native/key_value/key_value.h index 4238f01c73..86c432f2ef 100644 --- a/tests/slo_workloads/key_value/key_value.h +++ b/tests/slo_workloads/native/key_value/key_value.h @@ -1,9 +1,10 @@ #pragma once -#include -#include -#include -#include +#include + +#include +#include +#include extern const std::string TableName; @@ -19,7 +20,7 @@ class TGenerateInitialContentJob : public TThreadJob { private: TExecutor Executor; - TPackGenerator PackGenerator; + TPackGenerator PackGenerator; std::uint64_t Total; }; @@ -37,7 +38,7 @@ class TWriteJob : public TThreadJob { std::atomic ValuesGenerated = 0; }; -// Read workload job +// Read workload job class TReadJob : public TThreadJob { public: TReadJob(const TCommonOptions& opts, std::uint32_t maxId); diff --git a/tests/slo_workloads/key_value/main.cpp b/tests/slo_workloads/native/key_value/main.cpp similarity index 100% rename from tests/slo_workloads/key_value/main.cpp rename to tests/slo_workloads/native/key_value/main.cpp diff --git a/tests/slo_workloads/key_value/run.cpp b/tests/slo_workloads/native/key_value/run.cpp similarity index 99% rename from tests/slo_workloads/key_value/run.cpp rename to tests/slo_workloads/native/key_value/run.cpp index 671947ccf0..2a0a2a66c8 100644 --- a/tests/slo_workloads/key_value/run.cpp +++ b/tests/slo_workloads/native/key_value/run.cpp @@ -9,7 +9,7 @@ using namespace NYdb::NTable; TWriteJob::TWriteJob(const TCommonOptions& opts, std::uint32_t maxId) : TThreadJob(opts, "write") , Executor(opts, Stats, TExecutor::ModeNonBlocking) - , Generator(opts, maxId) + , Generator(MakeGeneratorOptions(opts), maxId) {} void TWriteJob::ShowProgress(TStringBuilder& report) { diff --git a/tests/slo_workloads/utils/CMakeLists.txt b/tests/slo_workloads/native/utils/CMakeLists.txt similarity index 84% rename from tests/slo_workloads/utils/CMakeLists.txt rename to tests/slo_workloads/native/utils/CMakeLists.txt index 5434af9bb4..d0479ac83c 100644 --- a/tests/slo_workloads/utils/CMakeLists.txt +++ b/tests/slo_workloads/native/utils/CMakeLists.txt @@ -14,12 +14,11 @@ FetchContent_MakeAvailable(hdr_histogram) add_library(slo-utils) target_link_libraries(slo-utils PUBLIC + slo-workload-core yutil getopt YDB-CPP-SDK::Table YDB-CPP-SDK::Iam - opentelemetry-cpp::metrics - opentelemetry-cpp::otlp_http_metric_exporter ) target_link_libraries(slo-utils PRIVATE @@ -28,9 +27,6 @@ target_link_libraries(slo-utils PRIVATE target_sources(slo-utils PRIVATE executor.cpp - generator.cpp job.cpp - metrics.cpp - statistics.cpp utils.cpp ) diff --git a/tests/slo_workloads/utils/executor.cpp b/tests/slo_workloads/native/utils/executor.cpp similarity index 96% rename from tests/slo_workloads/utils/executor.cpp rename to tests/slo_workloads/native/utils/executor.cpp index 270f89a8bc..9ea08ce341 100644 --- a/tests/slo_workloads/utils/executor.cpp +++ b/tests/slo_workloads/native/utils/executor.cpp @@ -1,5 +1,7 @@ #include "executor.h" +#include "utils.h" + const TDuration WaitTimeout = TDuration::Seconds(10); // Debug use only: @@ -157,7 +159,13 @@ bool TExecutor::Execute(const NYdb::NTable::TTableClient::TOperationFunc& func) future.Subscribe([this, stat, SemaphoreWrapper](const TAsyncFinalStatus& future) mutable { Y_ABORT_UNLESS(future.HasValue()); TFinalStatus resultStatus = future.GetValue(); - Stats.FinishRequest(stat, resultStatus); + TSloRequestFinish finish; + if (!resultStatus) { + finish.ApplicationTimeout = true; + } else { + finish.StatusLabel = YdbStatusToString(resultStatus->GetStatus()); + } + Stats.FinishRequest(stat, finish); if (resultStatus) { CheckForError(*resultStatus); } diff --git a/tests/slo_workloads/utils/executor.h b/tests/slo_workloads/native/utils/executor.h similarity index 95% rename from tests/slo_workloads/utils/executor.h rename to tests/slo_workloads/native/utils/executor.h index 637d791b9a..0b1f2e0a16 100644 --- a/tests/slo_workloads/utils/executor.h +++ b/tests/slo_workloads/native/utils/executor.h @@ -1,8 +1,9 @@ #pragma once -#include "statistics.h" #include "utils.h" +#include + #include #include @@ -19,6 +20,9 @@ extern const TDuration WaitTimeout; // Debug use only extern std::atomic ReadPromises; +using TFinalStatus = std::optional; +using TAsyncFinalStatus = NThreading::TFuture; + template class TTracedPromise : public NThreading::TPromise { public: diff --git a/tests/slo_workloads/utils/job.cpp b/tests/slo_workloads/native/utils/job.cpp similarity index 91% rename from tests/slo_workloads/utils/job.cpp rename to tests/slo_workloads/native/utils/job.cpp index cc627b2b70..18ec0690a7 100644 --- a/tests/slo_workloads/utils/job.cpp +++ b/tests/slo_workloads/native/utils/job.cpp @@ -8,7 +8,12 @@ const std::string LogFileName = "benchmark.log"; TThreadJob::TThreadJob(const TCommonOptions& opts, const std::string& operationType) : RpsProvider(opts.Rps) , Prefix(opts.DatabaseOptions.Prefix) - , Stats(opts.DontPushMetrics ? std::nullopt : std::make_optional(opts.MetricsPushUrl), operationType) + , Stats( + opts.DontPushMetrics ? std::nullopt : std::make_optional(opts.MetricsPushUrl), + operationType, + MakeNativeSloOtelResourceAttributes(), + NativeSloMeterSchemaVersion() + ) , StopOnError(opts.StopOnError) , MaxDelay(opts.ReactionTime) { diff --git a/tests/slo_workloads/utils/job.h b/tests/slo_workloads/native/utils/job.h similarity index 92% rename from tests/slo_workloads/utils/job.h rename to tests/slo_workloads/native/utils/job.h index a57a790b43..caf322f10e 100644 --- a/tests/slo_workloads/utils/job.h +++ b/tests/slo_workloads/native/utils/job.h @@ -1,8 +1,9 @@ #pragma once -#include "statistics.h" #include "utils.h" +#include + #include #include @@ -48,7 +49,7 @@ class TJobContainer { class TJobGC { public: - TJobGC(std::shared_ptr& jobs) + explicit TJobGC(std::shared_ptr& jobs) : Jobs(jobs) {} diff --git a/tests/slo_workloads/utils/utils.cpp b/tests/slo_workloads/native/utils/utils.cpp similarity index 73% rename from tests/slo_workloads/utils/utils.cpp rename to tests/slo_workloads/native/utils/utils.cpp index d686d4958c..15b41a810b 100644 --- a/tests/slo_workloads/utils/utils.cpp +++ b/tests/slo_workloads/native/utils/utils.cpp @@ -1,77 +1,43 @@ #include "utils.h" #include +#include #include #include -#include #include #include #include #include #include -#include + +#include using namespace NLastGetopt; using namespace NYdb; -const TDuration DefaultReactionTime = TDuration::Minutes(2); -const TDuration ReactionTimeDelay = TDuration::MilliSeconds(5); -const std::uint64_t PartitionsCount = 64; +#ifdef REF +static constexpr const char* RefLabel = Y_STRINGIZE(REF); +#else +static constexpr const char* RefLabel = "unknown"; +#endif Y_DECLARE_OUT_SPEC(, NYdb::TStatus, stream, value) { stream << "Status: " << value.GetStatus() << Endl; value.GetIssues().PrintTo(stream); } -TDurationMeter::TDurationMeter(TDuration& value) - : Value(value) - , StartTime(TInstant::Now()) -{ -} - -TDurationMeter::~TDurationMeter() { - Value += TInstant::Now() - StartTime; -} - -TRpsProvider::TRpsProvider(std::uint64_t rps) - : Rps(rps) - , Period(Max(TDuration::MilliSeconds(10), TDuration::MicroSeconds(1000000 / Rps))) - , ProcessedTime(TInstant::Now()) -{ -} - -void TRpsProvider::Reset() { - ProcessedTime = TInstant::Now() - Period - Period; +std::map MakeNativeSloOtelResourceAttributes() { + return { + {"ref", RefLabel}, + {"sdk", "cpp"}, + {"sdk_version", NYdb::GetSdkSemver()}, + }; } -void TRpsProvider::Use() { - if (Allowed) { - --Allowed; - return; - } - - while (!TryUse()) { - SleepUntil(TInstant::Now() + Period); - } -} - -bool TRpsProvider::TryUse() { - TInstant now = TInstant::Now(); - // Number of objects to process since ProcessedTime - Allowed = Rps * TDuration(now - ProcessedTime).MicroSeconds() / 1000000; - if (Allowed) { - ProcessedTime += TDuration::MicroSeconds(1000000 * Allowed / Rps); - --Allowed; - return true; - } else { - return false; - } -} - -std::uint64_t TRpsProvider::GetRps() const { - return Rps; +std::string NativeSloMeterSchemaVersion() { + return NYdb::GetSdkSemver(); } bool ParseToken(std::string& token, std::string& tokenFile) { @@ -99,17 +65,6 @@ void StartStatCollecting([[maybe_unused]] TDriver& driver, const std::string& st if (statConfigFile.empty()) { return; } - - // TODO: Implement -} - -std::string GetDatabase(const std::string& connectionString) { - constexpr std::string_view databaseFlag = "/?database="; - size_t pathIndex = connectionString.find(databaseFlag); - if (pathIndex != std::string::npos) { - return connectionString.substr(pathIndex + databaseFlag.size()); - } - return {}; } static std::string DefaultConnectionStringFromEnv() { @@ -203,7 +158,7 @@ int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TClean if (!iamSaKeyFile.empty()) { Cout << "Enabling IAM authentication..." << Endl; - TIamJwtFilename iamJwtFilename{ .JwtFilename = iamSaKeyFile }; + TIamJwtFilename iamJwtFilename{.JwtFilename = iamSaKeyFile}; config.SetCredentialsProviderFactory(CreateIamJwtFileCredentialsProviderFactory(iamJwtFilename)); } else if (!token.empty()) { Cout << "Enabling OAuth authentication..." << Endl; @@ -227,67 +182,66 @@ int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TClean StartStatCollecting(driver, statConfigFile); - TDatabaseOptions dbOptions{ driver, prefix }; + TDatabaseOptions dbOptions{driver, prefix}; int result; try { switch (command) { - case ECommandType::Create: - Cout << "Launching create command..." << Endl; - result = create(dbOptions, argc, argv); - break; - case ECommandType::Run: - Cout << "Launching run command..." << Endl; - result = run(dbOptions, argc, argv); - break; - case ECommandType::Cleanup: - Cout << "Launching cleanup command..." << Endl; - result = cleanup(dbOptions, argc); - break; - case ECommandType::All: { - Cout << "Launching full lifecycle: create -> run -> cleanup" << Endl; - // Forward leftover argv to the run phase so options like - // --read-rps / --write-rps take effect. argv[0] here is the first - // run-phase option (no subcommand keyword was supplied), so - // prepend a synthetic program name for ParseOptionsRun. - char programName[] = "slo"; - std::vector runArgv; - runArgv.reserve(argc + 2); - runArgv.push_back(programName); - for (int i = 0; i < argc; ++i) { - runArgv.push_back(argv[i]); - } - runArgv.push_back(nullptr); - int fakeArgc = 1; - char* fakeArgv[] = { programName, nullptr }; - - Cout << "[all] Launching create command..." << Endl; - result = create(dbOptions, fakeArgc, fakeArgv); - if (!result) { - Cout << "[all] Launching run command..." << Endl; - result = run(dbOptions, static_cast(runArgv.size() - 1), runArgv.data()); - } - Cout << "[all] Launching cleanup command..." << Endl; - int cleanupRc = cleanup(dbOptions, fakeArgc); - // Cleanup runs while chaos-monkey is still killing nodes, so a - // DropTable failure here is expected noise and must not mask a - // successful run. Surface the run's status; only fall back to - // the cleanup status when run itself failed and we have nothing - // else to report. - if (cleanupRc && !result) { - Cerr << "[all] Warning: cleanup failed (exit " << cleanupRc - << ") but run succeeded; ignoring cleanup exit code." << Endl; - } else if (cleanupRc) { - Cerr << "[all] Warning: cleanup failed (exit " << cleanupRc - << "); preserving earlier run failure." << Endl; + case ECommandType::Create: + Cout << "Launching create command..." << Endl; + result = create(dbOptions, argc, argv); + break; + case ECommandType::Run: + Cout << "Launching run command..." << Endl; + result = run(dbOptions, argc, argv); + break; + case ECommandType::Cleanup: + Cout << "Launching cleanup command..." << Endl; + result = cleanup(dbOptions, argc); + break; + case ECommandType::All: { + Cout << "Launching full lifecycle: create -> run -> cleanup" << Endl; + // Forward leftover argv to the run phase so options like + // --read-rps / --write-rps take effect. argv[0] here is the first + // run-phase option (no subcommand keyword was supplied), so + // prepend a synthetic program name for ParseOptionsRun. + char programName[] = "slo"; + std::vector runArgv; + runArgv.reserve(argc + 2); + runArgv.push_back(programName); + for (int i = 0; i < argc; ++i) { + runArgv.push_back(argv[i]); + } + runArgv.push_back(nullptr); + int fakeArgc = 1; + char* fakeArgv[] = { programName, nullptr }; + + Cout << "[all] Launching create command..." << Endl; + result = create(dbOptions, fakeArgc, fakeArgv); + if (!result) { + Cout << "[all] Launching run command..." << Endl; + result = run(dbOptions, static_cast(runArgv.size() - 1), runArgv.data()); + } + Cout << "[all] Launching cleanup command..." << Endl; + int cleanupRc = cleanup(dbOptions, fakeArgc); + // Cleanup runs while chaos-monkey is still killing nodes, so a + // DropTable failure here is expected noise and must not mask a + // successful run. Surface the run's status; only fall back to + // the cleanup status when run itself failed and we have nothing + // else to report. + if (cleanupRc && !result) { + Cerr << "[all] Warning: cleanup failed (exit " << cleanupRc + << ") but run succeeded; ignoring cleanup exit code." << Endl; + } else if (cleanupRc) { + Cerr << "[all] Warning: cleanup failed (exit " << cleanupRc + << "); preserving earlier run failure." << Endl; + } + break; } - break; + default: + Cerr << "Unknown command" << Endl; + return EXIT_FAILURE; } - default: - Cerr << "Unknown command" << Endl; - return EXIT_FAILURE; - } - } - catch (const NYdb::NStatusHelpers::TYdbErrorException& e) { + } catch (const NYdb::NStatusHelpers::TYdbErrorException& e) { Cerr << "Exception caught: " << e << Endl; return EXIT_FAILURE; } @@ -312,29 +266,6 @@ ECommandType ParseCommand(const char* cmd) { return ECommandType::Unknown; } -std::string JoinPath(const std::string& prefix, const std::string& path) { - if (prefix.empty()) { - return path; - } - - TPathSplitUnix prefixPathSplit(prefix); - prefixPathSplit.AppendComponent(path); - - return prefixPathSplit.Reconstruct(); -} - -std::string GenerateRandomString(std::uint32_t minLength, std::uint32_t maxLength) { - std::uint32_t length = minLength + RandomNumber() % (maxLength - minLength); - static const char* symbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - std::string result; - result.reserve(length); - for (size_t i = 0; i < length; ++i) { - result.push_back(symbols[RandomNumber(61)]); - } - return result; -} - -using namespace NYdb; using namespace NYdb::NTable; TParams PackValuesToParamsAsList(const std::vector& items, const std::string name) { @@ -350,24 +281,6 @@ TParams PackValuesToParamsAsList(const std::vector& items, const std::st return paramsBuilder.Build(); } -static double shardSize = (static_cast(Max()) + 1) / PartitionsCount; - -std::uint32_t GetSpecialId(std::uint32_t id) { - return static_cast(id / shardSize) * shardSize + 1; -} - -std::uint32_t GetShardSpecialId(std::uint64_t shardNo) { - return shardNo * shardSize + 1; -} - -std::uint32_t GetHash(std::uint32_t value) { - std::uint32_t result = NumericHash(value); - if (result == GetSpecialId(result)) { - ++result; - } - return result; -} - std::string YdbStatusToString(NYdb::EStatus status) { switch (status) { case NYdb::EStatus::SUCCESS: @@ -437,7 +350,7 @@ std::string YdbStatusToString(NYdb::EStatus status) { TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableName) { Cout << TInstant::Now().ToRfc822StringLocal() - << " Getting table stats (maxId and count of rows) with ReadTable... " << Endl; + << " Getting table stats (maxId and count of rows) with ReadTable... " << Endl; TInstant start_time = TInstant::Now(); NYdb::NTable::TTableClient client( dbOptions.Driver, @@ -449,9 +362,9 @@ TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableN std::optional tableIterator; NYdb::NStatusHelpers::ThrowOnError(client.RetryOperationSync([&tableIterator, &dbOptions, &tableName](TSession session) { auto result = session.ReadTable( - JoinPath(dbOptions.Prefix, tableName), - TReadTableSettings().AppendColumns("object_id") - ).GetValueSync(); + JoinPath(dbOptions.Prefix, tableName), + TReadTableSettings().AppendColumns("object_id")) + .GetValueSync(); if (result.IsSuccess()) { tableIterator = result; @@ -474,7 +387,7 @@ TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableN } futures.push_back( NThreading::Async( - [extractedPart = tablePart.ExtractPart()]{ + [extractedPart = tablePart.ExtractPart()] { auto rsParser = TResultSetParser(extractedPart); std::uint32_t partMax = 0; while (rsParser.TryNextRow()) { @@ -485,7 +398,7 @@ TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableN partMax = id; } } - return TTableStats{ rsParser.RowsCount(), partMax }; + return TTableStats{rsParser.RowsCount(), partMax}; }, pool ) @@ -500,7 +413,7 @@ TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableN result.RowCount += partStats.RowCount; } Cout << TInstant::Now().ToRfc822StringLocal() << " Done. maxId=" << result.MaxId << ", row count=" << result.RowCount - << ". Calculations took " << TInstant::Now() - start_time << Endl; + << ". Calculations took " << TInstant::Now() - start_time << Endl; return result; } diff --git a/tests/slo_workloads/utils/utils.h b/tests/slo_workloads/native/utils/utils.h similarity index 54% rename from tests/slo_workloads/utils/utils.h rename to tests/slo_workloads/native/utils/utils.h index 28576adaad..029a792e9b 100644 --- a/tests/slo_workloads/utils/utils.h +++ b/tests/slo_workloads/native/utils/utils.h @@ -1,5 +1,12 @@ #pragma once +#include +#include +#include +#include +#include +#include + #include #include #include @@ -8,30 +15,9 @@ #include -extern const TDuration DefaultReactionTime; -extern const TDuration ReactionTimeDelay; -extern const std::uint64_t PartitionsCount; - -struct TRecordData { - std::uint32_t ObjectId; - std::uint64_t Timestamp; - std::string Guid; - std::string Payload; -}; - -struct TKeyValueRecordData { - std::uint32_t ObjectId; - std::uint64_t Timestamp; - std::string Payload; -}; - -struct TDurationMeter { - TDurationMeter(TDuration& value); - ~TDurationMeter(); - - TDuration& Value; - TInstant StartTime; -}; +#include +#include +#include struct TDatabaseOptions { NYdb::TDriver& Driver; @@ -75,22 +61,13 @@ struct TRunOptions { TDuration WriteTimeout = DefaultReactionTime; }; -class TRpsProvider { -public: - TRpsProvider(std::uint64_t rps); - void Reset(); - void Use(); - bool TryUse(); - std::uint64_t GetRps() const; - -private: - std::uint64_t Rps; - TDuration Period; - TInstant ProcessedTime; - TInstant LastCheck; - std::uint32_t Allowed = 0; - TInstant StartTime; -}; +using TCreateCommand = std::function; +using TRunCommand = std::function; +using TCleanupCommand = std::function; + +int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TCleanupCommand cleanup); + +std::string GetCmdList(); enum class ECommandType { Unknown, @@ -100,36 +77,74 @@ enum class ECommandType { All, // No free-arg passed: execute Create -> Run -> Cleanup in one process }; -struct TTableStats { - std::uint64_t RowCount = 0; - std::uint32_t MaxId = 0; -}; - -using TCreateCommand = std::function; -using TRunCommand = std::function; -using TCleanupCommand = std::function; - -int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TCleanupCommand cleanup); - -std::string GetCmdList(); ECommandType ParseCommand(const char* cmd); -std::string JoinPath(const std::string& prefix, const std::string& path); - -std::string GenerateRandomString(std::uint32_t minLength, std::uint32_t maxLength); +inline std::string JoinPath(const std::string& prefix, const std::string& path) { + return SloJoinPath(prefix, path); +} + +inline std::string GetDatabase(const std::string& connectionString) { + return SloGetDatabaseFromConnectionString(connectionString); +} + +inline std::string GenerateRandomString(std::uint32_t minLength, std::uint32_t maxLength) { + return SloGenerateRandomString(minLength, maxLength); +} + +inline std::uint32_t GetSpecialId(std::uint32_t id) { + return SloGetSpecialId(id); +} + +inline std::uint32_t GetShardSpecialId(std::uint64_t shardNo) { + return SloGetShardSpecialId(shardNo); +} + +inline std::uint32_t GetHash(std::uint32_t value) { + return SloGetHash(value); +} + +inline TSloGeneratorOptions MakeGeneratorOptions(const TCommonOptions& opts) { + TSloGeneratorOptions o; + o.MinLength = opts.MinLength; + o.MaxLength = opts.MaxLength; + return o; +} + +std::map MakeNativeSloOtelResourceAttributes(); +std::string NativeSloMeterSchemaVersion(); + +inline void RetryBackoff( + NYdb::NTable::TTableClient& client, + std::uint32_t retries, + const NYdb::NTable::TTableClient::TOperationSyncFunc& func +) { + TDuration delay = TDuration::Seconds(5); + while (retries) { + NYdb::TStatus status = client.RetryOperationSync(func); + if (status.IsSuccess()) { + return; + } + --retries; + if (!retries) { + Cerr << "Create request failed after all retries." << Endl; + Cerr << status << Endl; + NYdb::NStatusHelpers::ThrowOnError(status); + } + Cerr << "Create request failed. Sleeping for " << delay << Endl; + Sleep(delay); + delay *= 2; + } +} NYdb::TParams PackValuesToParamsAsList(const std::vector& items, const std::string name = "$items"); -// Returns special object_id within the same shard as given id -std::uint32_t GetSpecialId(std::uint32_t id); - -// Returns special object_id for given shard -std::uint32_t GetShardSpecialId(std::uint64_t shardNo); - -std::uint32_t GetHash(std::uint32_t value); - std::string YdbStatusToString(NYdb::EStatus status); +struct TTableStats { + std::uint64_t RowCount = 0; + std::uint32_t MaxId = 0; +}; + TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableName); bool ParseOptionsCreate(int argc, char** argv, TCreateOptions& createOptions); diff --git a/tests/slo_workloads/userver/CMakeLists.txt b/tests/slo_workloads/userver/CMakeLists.txt new file mode 100644 index 0000000000..1975421a54 --- /dev/null +++ b/tests/slo_workloads/userver/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(key_value) diff --git a/tests/slo_workloads/userver/key_value/CMakeLists.txt b/tests/slo_workloads/userver/key_value/CMakeLists.txt new file mode 100644 index 0000000000..2b4909d8b2 --- /dev/null +++ b/tests/slo_workloads/userver/key_value/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(slo-userver-key-value) + +target_sources(slo-userver-key-value PRIVATE + main.cpp +) + +target_link_libraries(slo-userver-key-value PRIVATE + slo-workload-core +) + +vcs_info(slo-userver-key-value) diff --git a/tests/slo_workloads/userver/key_value/main.cpp b/tests/slo_workloads/userver/key_value/main.cpp new file mode 100644 index 0000000000..5bd41d6fcf --- /dev/null +++ b/tests/slo_workloads/userver/key_value/main.cpp @@ -0,0 +1,10 @@ +#include + +// Placeholder entry point: link userver-ydb and implement the same scenario as +// tests/slo_workloads/native/key_value when YDB_CPP_SDK_BUILD_SLO_USERVER is enabled. +int main() { + std::cout << "slo-userver-key-value: stub build. " + << "Wire userver-ydb client and workload loop; see tests/slo_workloads/core/slo_executor_contract.md" + << std::endl; + return 0; +} diff --git a/tests/slo_workloads/utils/generator.h b/tests/slo_workloads/utils/generator.h deleted file mode 100644 index 584ecd68e9..0000000000 --- a/tests/slo_workloads/utils/generator.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -#include "utils.h" - -#include - - -class TValueGenerator { -public: - TValueGenerator(const TCommonOptions& opts, std::uint32_t startId = 0); - TRecordData Get(); - TDuration GetComputeTime() const; - -private: - const TCommonOptions& Opts; - std::uint32_t CurrentObjectId = 0; - TDuration ComputeTime; -}; - -class TKeyValueGenerator { -public: - TKeyValueGenerator(const TCommonOptions& opts, std::uint32_t startId = 0); - TKeyValueRecordData Get(); - TDuration GetComputeTime() const; - -private: - const TCommonOptions& Opts; - std::uint32_t CurrentObjectId = 0; - TDuration ComputeTime; -}; - -template -class TPackGenerator { -public: - TPackGenerator( - const TCommonOptions& opts, - std::uint32_t packSize, - NYdb::TValue(*buildValueFromRecordFunc)(const TRecordType&), - std::uint64_t remain, - std::uint32_t startId = 0 - ) - : BuildValueFromRecordFunc(buildValueFromRecordFunc) - , Generator(opts, startId) - , PackSize(packSize) - , Remain(remain) - { - } - - // Returns One-shard pack - bool GetNextPack(std::vector& pack) { - pack.clear(); - while (Remain) { - TRecordType record = Generator.Get(); - --Remain; - std::uint32_t specialId = GetSpecialId(GetHash(record.ObjectId)); - auto& existingPack = Packs[specialId]; - existingPack.emplace_back(BuildValueFromRecordFunc(record)); - if (existingPack.size() >= PackSize) { - existingPack.swap(pack); - return true; - } - } - for (auto& it : Packs) { - if (it.second.size()) { - it.second.swap(pack); - return true; - } - } - return false; - } - - std::uint32_t GetPackSize() const { - return PackSize; - } - - TDuration GetComputeTime() const { - return Generator.GetComputeTime(); - } - - std::uint64_t GetRemain() const { - return Remain; - } - -private: - NYdb::TValue(*BuildValueFromRecordFunc)(const TRecordType&); - TGeneratorType Generator; - std::uint32_t PackSize; - std::unordered_map> Packs; - std::uint64_t Remain; -}; diff --git a/tests/slo_workloads/utils/metrics.h b/tests/slo_workloads/utils/metrics.h deleted file mode 100644 index cf6aac7be4..0000000000 --- a/tests/slo_workloads/utils/metrics.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -#include - - -struct TRequestData { - TDuration Delay; - NYdb::EStatus Status; - std::uint64_t RetryAttempts; -}; - -class IMetricsPusher { -public: - virtual ~IMetricsPusher() = default; - - virtual void PushRequestData(const TRequestData& requestData) = 0; -}; - -std::unique_ptr CreateOtelMetricsPusher(const std::string& metricsPushUrl, const std::string& operationType); -std::unique_ptr CreateNoopMetricsPusher(); From 894004585a4623f3fbea32aef0159ae21c1615c6 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Wed, 27 May 2026 18:32:18 +0300 Subject: [PATCH 02/27] full slo version --- .github/scripts/copy_sources.sh | 1 + .github/workflows/slo.yml | 60 +-- debian/libydb-cpp-dev.install | 10 + debian/libydb-cpp-iam-dev.install | 1 + debian/libydb-cpp-otel-common-dev.install | 4 + debian/libydb-cpp-otel-metrics-dev.install | 2 + debian/libydb-cpp-otel-tracing-dev.install | 2 + tests/slo_workloads/CMakeLists.txt | 1 + tests/slo_workloads/Dockerfile.userver | 153 ++++++ tests/slo_workloads/common/CMakeLists.txt | 1 + .../common/key_value/CMakeLists.txt | 13 + .../slo_workloads/common/key_value/create.cpp | 40 ++ .../{native => common}/key_value/drop.cpp | 3 +- .../common/key_value/kv_common.h | 18 + .../common/key_value/kv_data.cpp | 16 + tests/slo_workloads/core/CMakeLists.txt | 14 + .../native/key_value/CMakeLists.txt | 3 +- .../slo_workloads/native/key_value/create.cpp | 39 -- .../native/key_value/key_value.cpp | 15 - .../native/key_value/key_value.h | 12 +- .../slo_workloads/native/utils/CMakeLists.txt | 37 +- tests/slo_workloads/native/utils/utils.cpp | 210 -------- .../slo_workloads/native/utils/utils_main.cpp | 220 +++++++++ tests/slo_workloads/userver/CMakeLists.txt | 5 + .../userver/cmake/SetupYdbCppSDK.cmake | 8 + .../userver/cmake/UserverYdbFromTree.cmake | 55 +++ .../userver/key_value/CMakeLists.txt | 16 +- .../userver/key_value/create.cpp | 154 ++++++ .../userver/key_value/key_value.h | 18 + .../slo_workloads/userver/key_value/main.cpp | 12 +- tests/slo_workloads/userver/key_value/run.cpp | 447 ++++++++++++++++++ .../key_value/userver_table_client.cpp | 94 ++++ .../userver/key_value/userver_table_client.h | 32 ++ .../userver/key_value/userver_utils.cpp | 185 ++++++++ .../userver/key_value/userver_utils.h | 7 + tests/slo_workloads/verify_userver_docker.sh | 136 ++++++ third_party/aklomp-base64 | 1 + third_party/jwt-cpp | 1 + third_party/opentelemetry-cpp | 1 + 39 files changed, 1708 insertions(+), 339 deletions(-) create mode 100644 debian/libydb-cpp-dev.install create mode 100644 debian/libydb-cpp-iam-dev.install create mode 100644 debian/libydb-cpp-otel-common-dev.install create mode 100644 debian/libydb-cpp-otel-metrics-dev.install create mode 100644 debian/libydb-cpp-otel-tracing-dev.install create mode 100644 tests/slo_workloads/Dockerfile.userver create mode 100644 tests/slo_workloads/common/CMakeLists.txt create mode 100644 tests/slo_workloads/common/key_value/CMakeLists.txt create mode 100644 tests/slo_workloads/common/key_value/create.cpp rename tests/slo_workloads/{native => common}/key_value/drop.cpp (93%) create mode 100644 tests/slo_workloads/common/key_value/kv_common.h create mode 100644 tests/slo_workloads/common/key_value/kv_data.cpp delete mode 100644 tests/slo_workloads/native/key_value/create.cpp create mode 100644 tests/slo_workloads/native/utils/utils_main.cpp create mode 100644 tests/slo_workloads/userver/cmake/SetupYdbCppSDK.cmake create mode 100644 tests/slo_workloads/userver/cmake/UserverYdbFromTree.cmake create mode 100644 tests/slo_workloads/userver/key_value/create.cpp create mode 100644 tests/slo_workloads/userver/key_value/key_value.h create mode 100644 tests/slo_workloads/userver/key_value/run.cpp create mode 100644 tests/slo_workloads/userver/key_value/userver_table_client.cpp create mode 100644 tests/slo_workloads/userver/key_value/userver_table_client.h create mode 100644 tests/slo_workloads/userver/key_value/userver_utils.cpp create mode 100644 tests/slo_workloads/userver/key_value/userver_utils.h create mode 100755 tests/slo_workloads/verify_userver_docker.sh create mode 160000 third_party/aklomp-base64 create mode 160000 third_party/jwt-cpp create mode 160000 third_party/opentelemetry-cpp diff --git a/.github/scripts/copy_sources.sh b/.github/scripts/copy_sources.sh index 0df256bb9e..0430dab1a4 100755 --- a/.github/scripts/copy_sources.sh +++ b/.github/scripts/copy_sources.sh @@ -57,6 +57,7 @@ cp $2/LICENSE $tmp_dir cp $2/README.md $tmp_dir cp $2/tests/slo_workloads/.dockerignore $tmp_dir/tests/slo_workloads cp $2/tests/slo_workloads/Dockerfile $tmp_dir/tests/slo_workloads +cp $2/tests/slo_workloads/Dockerfile.userver $tmp_dir/tests/slo_workloads cp $2/include/ydb-cpp-sdk/type_switcher.h $tmp_dir/include/ydb-cpp-sdk/type_switcher.h cp $2/src/version.h $tmp_dir/src/version.h diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index fe31086f89..3b0f987c70 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -8,8 +8,9 @@ jobs: ydb-slo-action: if: contains(github.event.pull_request.labels.*.name, 'SLO') - name: Run YDB SLO Tests + name: Run YDB SLO Tests (${{ matrix.workload }}, ${{ matrix.compiler }}) runs-on: ubuntu-latest + timeout-minutes: 60 permissions: contents: read @@ -17,13 +18,16 @@ jobs: strategy: fail-fast: false matrix: - sdk: - - name: cpp-key-value - preset: release-test-clang - command: "" + compiler: [clang, gcc] + workload: [native, userver] + include: + - workload: native + dockerfile: tests/slo_workloads/Dockerfile + - workload: userver + dockerfile: tests/slo_workloads/Dockerfile.userver concurrency: - group: slo-${{ github.ref }}-${{ matrix.sdk.name }} + group: slo-${{ github.ref }}-${{ matrix.workload }}-${{ matrix.compiler }} cancel-in-progress: true steps: @@ -106,19 +110,14 @@ jobs: cp sdk-current/tests/slo_workloads/.dockerignore sdk-current/.dockerignore cp sdk-baseline/tests/slo_workloads/.dockerignore sdk-baseline/.dockerignore - # `cache-to: type=gha` does NOT export `--mount=type=cache` content, so - # ccache state is lost between runs. Persist /root/.ccache via host - # directory + cache-dance: actions/cache restores the host dir, the - # dance injects it into the BuildKit cache mount before the build and - # extracts the updated state afterwards for the next save. - name: Restore ccache id: ccache uses: actions/cache@v4 with: path: ccache - key: slo-ccache-${{ matrix.sdk.preset }}-${{ github.run_id }} + key: slo-ccache-${{ matrix.workload }}-${{ matrix.compiler }}-${{ github.run_id }} restore-keys: | - slo-ccache-${{ matrix.sdk.preset }}- + slo-ccache-${{ matrix.workload }}-${{ matrix.compiler }}- - name: Inject ccache into BuildKit uses: reproducible-containers/buildkit-cache-dance@v3.1.2 @@ -127,16 +126,8 @@ jobs: { "ccache": "/root/.ccache" } - # Always extract so newly-compiled TUs from this run are saved by - # actions/cache (key uses ${{ github.run_id }}, so each run gets - # its own snapshot). Without extraction the cache stays frozen at - # whatever was first persisted. skip-extraction: false - # A clean build of the SLO image takes ~30 min because the Dockerfile - # rebuilds the full C++ toolchain + abseil/protobuf/grpc from source. - # The GHA cache lets subsequent runs reuse every layer up to the SDK - # source COPY, so only the actual workload link step reruns (~3 min). - name: Build current workload image uses: docker/build-push-action@v6 env: @@ -144,13 +135,15 @@ jobs: DOCKER_BUILD_RECORD_UPLOAD: "false" with: context: sdk-current - file: sdk-current/tests/slo_workloads/Dockerfile + file: sdk-current/${{ matrix.dockerfile }} platforms: linux/amd64 tags: ydb-app-current load: true - build-args: PRESET=${{ matrix.sdk.preset }} - cache-from: type=gha,scope=slo-${{ matrix.sdk.preset }} - cache-to: type=gha,mode=max,scope=slo-${{ matrix.sdk.preset }} + build-args: | + PRESET=release-test-${{ matrix.compiler }} + REF=${{ github.head_ref || github.ref_name }} + cache-from: type=gha,scope=slo-${{ matrix.workload }}-${{ matrix.compiler }} + cache-to: type=gha,mode=max,scope=slo-${{ matrix.workload }}-${{ matrix.compiler }} - name: Build baseline workload image id: baseline-build @@ -161,16 +154,15 @@ jobs: DOCKER_BUILD_RECORD_UPLOAD: "false" with: context: sdk-baseline - file: sdk-baseline/tests/slo_workloads/Dockerfile + file: sdk-baseline/${{ matrix.dockerfile }} platforms: linux/amd64 tags: ydb-app-baseline load: true - build-args: PRESET=${{ matrix.sdk.preset }} - cache-from: type=gha,scope=slo-${{ matrix.sdk.preset }} + build-args: | + PRESET=release-test-${{ matrix.compiler }} + REF=${{ steps.baseline.outputs.ref }} + cache-from: type=gha,scope=slo-${{ matrix.workload }}-${{ matrix.compiler }} - # If the historical commit lacks the SLO Dockerfile or can't compile, - # reuse the current image so the SLO run is still comparable against - # itself rather than failing outright. - name: Fall back to current image for baseline if: steps.baseline-build.outcome == 'failure' run: | @@ -183,11 +175,11 @@ jobs: with: github_issue: ${{ github.event.pull_request.number }} github_token: ${{ secrets.GITHUB_TOKEN }} - workload_name: ${{ matrix.sdk.name }} + workload_name: cpp-key-value-${{ matrix.workload }}-${{ matrix.compiler }} workload_duration: "600" workload_current_ref: ${{ github.head_ref || github.ref_name }} workload_current_image: ydb-app-current - workload_current_command: ${{ matrix.sdk.command }} --read-rps 1000 --write-rps 100 + workload_current_command: --read-rps 1000 --write-rps 100 workload_baseline_ref: ${{ steps.baseline.outputs.ref }} workload_baseline_image: ydb-app-baseline - workload_baseline_command: ${{ matrix.sdk.command }} --read-rps 1000 --write-rps 100 + workload_baseline_command: --read-rps 1000 --write-rps 100 diff --git a/debian/libydb-cpp-dev.install b/debian/libydb-cpp-dev.install new file mode 100644 index 0000000000..9edd35538e --- /dev/null +++ b/debian/libydb-cpp-dev.install @@ -0,0 +1,10 @@ +debian/tmp/usr/share/yandex/lib/*/libydb-cpp.a usr/share/yandex/lib/ +debian/tmp/usr/share/yandex/include/ydb-cpp-sdk usr/share/yandex/include/ +debian/tmp/usr/share/yandex/include/__ydb_sdk_special_headers usr/share/yandex/include/ +debian/tmp/usr/share/yandex/lib/*/cmake/ydb-cpp-sdk usr/share/yandex/lib/*/cmake/ +debian/tmp/usr/share/yandex/include/libbase64.h usr/share/yandex/include/ +debian/tmp/usr/share/yandex/lib/*/libbase64.a usr/share/yandex/lib/ +debian/tmp/usr/share/yandex/lib/*/cmake/base64 usr/share/yandex/lib/*/cmake/ +debian/tmp/usr/share/yandex/include/picojson usr/share/yandex/include/ +debian/tmp/usr/share/yandex/include/jwt-cpp usr/share/yandex/include/ +debian/tmp/usr/share/yandex/cmake/jwt-cpp* usr/share/yandex/cmake/ diff --git a/debian/libydb-cpp-iam-dev.install b/debian/libydb-cpp-iam-dev.install new file mode 100644 index 0000000000..9667f34f8c --- /dev/null +++ b/debian/libydb-cpp-iam-dev.install @@ -0,0 +1 @@ +debian/tmp/usr/share/yandex/lib/*/libydb-cpp-iam.a usr/share/yandex/lib/ diff --git a/debian/libydb-cpp-otel-common-dev.install b/debian/libydb-cpp-otel-common-dev.install new file mode 100644 index 0000000000..7dd0ad85cb --- /dev/null +++ b/debian/libydb-cpp-otel-common-dev.install @@ -0,0 +1,4 @@ +debian/tmp/usr/share/yandex/include/opentelemetry usr/share/yandex/include/ +debian/tmp/usr/share/yandex/lib/*/libopentelemetry_* usr/share/yandex/lib/ +debian/tmp/usr/share/yandex/lib/*/cmake/opentelemetry-cpp usr/share/yandex/lib/*/cmake/ +debian/tmp/usr/share/yandex/lib/*/pkgconfig/opentelemetry_*.pc usr/share/yandex/lib/*/pkgconfig/ diff --git a/debian/libydb-cpp-otel-metrics-dev.install b/debian/libydb-cpp-otel-metrics-dev.install new file mode 100644 index 0000000000..9e60e657a2 --- /dev/null +++ b/debian/libydb-cpp-otel-metrics-dev.install @@ -0,0 +1,2 @@ +debian/tmp/usr/share/yandex/lib/*/libydb-cpp-otel-metrics.a usr/share/yandex/lib/ +debian/tmp/usr/share/yandex/include/ydb-cpp-sdk/open_telemetry/metrics.h usr/share/yandex/include/ydb-cpp-sdk/open_telemetry/ diff --git a/debian/libydb-cpp-otel-tracing-dev.install b/debian/libydb-cpp-otel-tracing-dev.install new file mode 100644 index 0000000000..b3af18b0e5 --- /dev/null +++ b/debian/libydb-cpp-otel-tracing-dev.install @@ -0,0 +1,2 @@ +debian/tmp/usr/share/yandex/lib/*/libydb-cpp-otel-tracing.a usr/share/yandex/lib/ +debian/tmp/usr/share/yandex/include/ydb-cpp-sdk/open_telemetry/trace.h usr/share/yandex/include/ydb-cpp-sdk/open_telemetry/ diff --git a/tests/slo_workloads/CMakeLists.txt b/tests/slo_workloads/CMakeLists.txt index ed3c7279d0..eccd0dc33d 100644 --- a/tests/slo_workloads/CMakeLists.txt +++ b/tests/slo_workloads/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(core) +add_subdirectory(common) add_subdirectory(native) option(YDB_CPP_SDK_BUILD_SLO_USERVER "Build userver SLO workload (requires userver)" OFF) diff --git a/tests/slo_workloads/Dockerfile.userver b/tests/slo_workloads/Dockerfile.userver new file mode 100644 index 0000000000..33acb06da4 --- /dev/null +++ b/tests/slo_workloads/Dockerfile.userver @@ -0,0 +1,153 @@ +# Build userver .deb package (core and other components; YDB is built from source in-repo). +FROM ghcr.io/userver-framework/ubuntu-22.04-userver-base:latest AS userver-deb +ARG USERVER_VERSION=v2.12 +RUN git clone --depth 1 --branch ${USERVER_VERSION} \ + https://github.com/userver-framework/userver.git /userver +WORKDIR /userver +ENV BUILD_OPTIONS="-DUSERVER_FEATURE_YDB=OFF" +RUN ./scripts/build_and_install.sh + +FROM ubuntu:22.04 AS base + +ENV DEBIAN_FRONTEND=noninteractive + +ARG PRESET=release-test-clang +ARG REF=unknown + +# Install software-properties-common for add-apt-repository +RUN apt-get -y update && apt-get -y install software-properties-common && add-apt-repository ppa:ubuntu-toolchain-r/test + +# Install C++ tools and libraries +RUN apt-get -y update && apt-get -y install \ + git gdb wget ninja-build libidn11-dev ragel yasm libc-ares-dev libre2-dev \ + rapidjson-dev zlib1g-dev libxxhash-dev libzstd-dev libsnappy-dev libgtest-dev libgmock-dev \ + libbz2-dev liblz4-dev libdouble-conversion-dev libssl-dev libstdc++-13-dev gcc-13 g++-13 \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install CMake +ENV CMAKE_VERSION=3.27.7 +RUN wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-x86_64.sh \ + -q -O cmake-install.sh \ + && chmod u+x cmake-install.sh \ + && ./cmake-install.sh --skip-license --prefix=/usr/local \ + && rm cmake-install.sh + +# Install LLVM +ENV LLVM_VERSION=16 +RUN wget https://apt.llvm.org/llvm.sh && \ + chmod u+x llvm.sh && \ + ./llvm.sh ${LLVM_VERSION} && \ + rm llvm.sh + +# Update alternatives to use clang-16 by default +RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-${LLVM_VERSION} 10000 && \ + update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-${LLVM_VERSION} 10000 && \ + update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-${LLVM_VERSION} 10000 + +# Update alternatives to use gcc-13 by default +RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 10000 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 10000 + +# Install abseil-cpp +ENV ABSEIL_CPP_VERSION=20230802.0 +ENV ABSEIL_CPP_INSTALL_DIR=/root/ydb_deps/absl +RUN wget -O abseil-cpp-${ABSEIL_CPP_VERSION}.tar.gz https://github.com/abseil/abseil-cpp/archive/refs/tags/${ABSEIL_CPP_VERSION}.tar.gz && \ + tar -xvzf abseil-cpp-${ABSEIL_CPP_VERSION}.tar.gz && cd abseil-cpp-${ABSEIL_CPP_VERSION} && \ + mkdir build && cd build && \ + cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DABSL_PROPAGATE_CXX_STD=ON .. && \ + cmake --build . --config Release && \ + cmake --install . --config Release --prefix ${ABSEIL_CPP_INSTALL_DIR} && \ + rm -rf abseil-cpp-${ABSEIL_CPP_VERSION}.tar.gz abseil-cpp-${ABSEIL_CPP_VERSION} + +# Install protobuf +ENV PROTOBUF_VERSION=3.21.12 +ENV PROTOBUF_INSTALL_DIR=/root/ydb_deps/protobuf +RUN wget -O protobuf-${PROTOBUF_VERSION}.tar.gz https://github.com/protocolbuffers/protobuf/archive/refs/tags/v${PROTOBUF_VERSION}.tar.gz && \ + tar -xvzf protobuf-${PROTOBUF_VERSION}.tar.gz && cd protobuf-${PROTOBUF_VERSION} && \ + mkdir build && cd build && \ + cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_INSTALL=ON -Dprotobuf_ABSL_PROVIDER=package .. && \ + cmake --build . --config Release && \ + cmake --install . --config Release --prefix ${PROTOBUF_INSTALL_DIR} && \ + rm -rf protobuf-${PROTOBUF_VERSION}.tar.gz protobuf-${PROTOBUF_VERSION} + +# Install grpc +ENV GRPC_VERSION=1.54.3 +ENV GRPC_INSTALL_DIR=/root/ydb_deps/grpc +RUN wget -O grpc-${GRPC_VERSION}.tar.gz https://github.com/grpc/grpc/archive/refs/tags/v${GRPC_VERSION}.tar.gz && \ + tar -xvzf grpc-${GRPC_VERSION}.tar.gz && cd grpc-${GRPC_VERSION} && \ + mkdir build && cd build && \ + cmake -G Ninja -DCMAKE_PREFIX_PATH="${ABSEIL_CPP_INSTALL_DIR};${PROTOBUF_INSTALL_DIR}" \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=17 \ + -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_BUILD_CSHARP_EXT=OFF \ + -DgRPC_ZLIB_PROVIDER=package -DgRPC_CARES_PROVIDER=package -DgRPC_RE2_PROVIDER=package \ + -DgRPC_SSL_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package -DgRPC_ABSL_PROVIDER=package \ + -DgRPC_BUILD_GRPC_NODE_PLUGIN=OFF -DgRPC_BUILD_GRPC_OBJECTIVE_C_PLUGIN=OFF -DgRPC_BUILD_GRPC_PHP_PLUGIN=OFF \ + -DgRPC_BUILD_GRPC_RUBY_PLUGIN=OFF -DgRPC_BUILD_GRPC_CSHARP_PLUGIN=OFF -DgRPC_BUILD_GRPC_PYTHON_PLUGIN=OFF .. && \ + cmake --build . --config Release && \ + cmake --install . --config Release --prefix ${GRPC_INSTALL_DIR} && \ + rm -rf grpc-${GRPC_VERSION}.tar.gz grpc-${GRPC_VERSION} + +# Install base64 +ENV BASE64_VERSION=0.5.2 +ENV BASE64_INSTALL_DIR=/root/ydb_deps/base64 +RUN wget -O base64-${BASE64_VERSION}.tar.gz https://github.com/aklomp/base64/archive/refs/tags/v${BASE64_VERSION}.tar.gz && \ + tar -xvzf base64-${BASE64_VERSION}.tar.gz && cd base64-${BASE64_VERSION} && \ + mkdir build && cd build && \ + cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. && \ + cmake --build . --config Release && \ + cmake --install . --config Release --prefix ${BASE64_INSTALL_DIR} && \ + rm -rf base64-${BASE64_VERSION}.tar.gz base64-${BASE64_VERSION} + +# Install brotli +ENV BROTLI_VERSION=1.1.0 +ENV BROTLI_INSTALL_DIR=/root/ydb_deps/brotli +RUN wget -O brotli-${BROTLI_VERSION}.tar.gz https://github.com/google/brotli/archive/refs/tags/v${BROTLI_VERSION}.tar.gz && \ + tar -xvzf brotli-${BROTLI_VERSION}.tar.gz && cd brotli-${BROTLI_VERSION} && \ + mkdir build && cd build && \ + cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. && \ + cmake --build . --config Release && \ + cmake --install . --config Release --prefix ${BROTLI_INSTALL_DIR} && \ + rm -rf brotli-${BROTLI_VERSION}.tar.gz brotli-${BROTLI_VERSION} + +# Install jwt-cpp +ENV JWT_CPP_VERSION=0.7.0 +ENV JWT_CPP_INSTALL_DIR=/root/ydb_deps/jwt-cpp +RUN wget -O jwt-cpp-${JWT_CPP_VERSION}.tar.gz https://github.com/Thalhammer/jwt-cpp/archive/refs/tags/v${JWT_CPP_VERSION}.tar.gz && \ + tar -xvzf jwt-cpp-${JWT_CPP_VERSION}.tar.gz && cd jwt-cpp-${JWT_CPP_VERSION} && \ + mkdir build && cd build && \ + cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. && \ + cmake --build . --config Release && \ + cmake --install . --config Release --prefix ${JWT_CPP_INSTALL_DIR} && \ + rm -rf jwt-cpp-${JWT_CPP_VERSION}.tar.gz jwt-cpp-${JWT_CPP_VERSION} + +# Install ccache 4.8.1 or above +ENV CCACHE_VERSION=4.8.1 +RUN wget https://github.com/ccache/ccache/releases/download/v${CCACHE_VERSION}/ccache-${CCACHE_VERSION}-linux-x86_64.tar.xz \ + && tar -xf ccache-${CCACHE_VERSION}-linux-x86_64.tar.xz \ + && cp ccache-${CCACHE_VERSION}-linux-x86_64/ccache /usr/local/bin/ \ + && rm -rf ccache-${CCACHE_VERSION}-linux-x86_64 ccache-${CCACHE_VERSION}-linux-x86_64.tar.xz + +FROM base + +ARG PRESET=release-test-clang +ARG REF=unknown +ARG USERVER_VERSION=v2.12 + +COPY --from=userver-deb /userver/libuserver-all-dev*.deb /tmp/ +RUN apt-get -y update && apt-get -y install --no-install-recommends /tmp/libuserver-all-dev*.deb \ + && rm -rf /tmp/libuserver-all-dev*.deb /var/lib/apt/lists/* + +RUN git clone --depth 1 --branch ${USERVER_VERSION} \ + https://github.com/userver-framework/userver.git /userver-src \ + && chmod +x /userver-src/chaotic/bin-dynamic-configs/chaotic-gen-dynamic-configs \ + /userver-src/chaotic/bin/chaotic-gen 2>/dev/null || true +ENV USERVER_SOURCE_DIR=/userver-src + +COPY . /ydb-cpp-sdk +WORKDIR /ydb-cpp-sdk +RUN rm -rf build + +RUN cmake -DSLO_BRANCH_REF=${REF} -DYDB_CPP_SDK_BUILD_SLO_USERVER=ON --preset ${PRESET} +RUN cmake --build --preset default --target slo-userver-key-value + +ENTRYPOINT ["./build/tests/slo_workloads/userver/key_value/slo-userver-key-value"] diff --git a/tests/slo_workloads/common/CMakeLists.txt b/tests/slo_workloads/common/CMakeLists.txt new file mode 100644 index 0000000000..1975421a54 --- /dev/null +++ b/tests/slo_workloads/common/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(key_value) diff --git a/tests/slo_workloads/common/key_value/CMakeLists.txt b/tests/slo_workloads/common/key_value/CMakeLists.txt new file mode 100644 index 0000000000..86160e3efc --- /dev/null +++ b/tests/slo_workloads/common/key_value/CMakeLists.txt @@ -0,0 +1,13 @@ +# Shared key-value workload library: TableName, BuildValueFromRecord, CreateTable, DropTable +# Used by both native and userver SLO workloads. +add_library(slo-kv-common) + +target_link_libraries(slo-kv-common PUBLIC + slo-utils-base +) + +target_sources(slo-kv-common PRIVATE + kv_data.cpp + create.cpp + drop.cpp +) diff --git a/tests/slo_workloads/common/key_value/create.cpp b/tests/slo_workloads/common/key_value/create.cpp new file mode 100644 index 0000000000..49e583bf83 --- /dev/null +++ b/tests/slo_workloads/common/key_value/create.cpp @@ -0,0 +1,40 @@ +#include "kv_common.h" + +#include + +using namespace NYdb; +using namespace NYdb::NTable; + +namespace { + +void CreateTableDDL(TTableClient& client, const std::string& prefix) { + RetryBackoff(client, 5, [prefix](TSession session) { + auto desc = TTableBuilder() + .AddNullableColumn("object_id_key", EPrimitiveType::Uint32) + .AddNullableColumn("object_id", EPrimitiveType::Uint32) + .AddNullableColumn("timestamp", EPrimitiveType::Uint64) + .AddNullableColumn("payload", EPrimitiveType::Utf8) + .SetPrimaryKeyColumns({ "object_id_key", "object_id" }) + .Build(); + + auto tableSettings = TCreateTableSettings() + .PartitioningPolicy(TPartitioningPolicy().UniformPartitions(PartitionsCount)) + .CancelAfter(DefaultReactionTime) + .ClientTimeout(DefaultReactionTime + TDuration::Seconds(5)); + + return session.CreateTable( + JoinPath(prefix, TableName) + , std::move(desc) + , std::move(tableSettings) + ).ExtractValueSync(); + }); +} + +} // namespace + +int CreateTable(TDatabaseOptions& dbOptions) { + TTableClient client(dbOptions.Driver); + CreateTableDDL(client, dbOptions.Prefix); + Cout << "Table created." << Endl; + return EXIT_SUCCESS; +} diff --git a/tests/slo_workloads/native/key_value/drop.cpp b/tests/slo_workloads/common/key_value/drop.cpp similarity index 93% rename from tests/slo_workloads/native/key_value/drop.cpp rename to tests/slo_workloads/common/key_value/drop.cpp index e22c92c894..a658fb2b79 100644 --- a/tests/slo_workloads/native/key_value/drop.cpp +++ b/tests/slo_workloads/common/key_value/drop.cpp @@ -1,6 +1,5 @@ -#include "key_value.h" +#include "kv_common.h" -using namespace NLastGetopt; using namespace NYdb; using namespace NYdb::NTable; diff --git a/tests/slo_workloads/common/key_value/kv_common.h b/tests/slo_workloads/common/key_value/kv_common.h new file mode 100644 index 0000000000..bedda0db0b --- /dev/null +++ b/tests/slo_workloads/common/key_value/kv_common.h @@ -0,0 +1,18 @@ +#pragma once + +// Shared key-value workload declarations used by both native and userver SLO tests. +// This header provides table name, value builder, and DDL operations (create/drop) +// that are identical across workload implementations. + +#include + +#include + +#include + +extern const std::string TableName; + +NYdb::TValue BuildValueFromRecord(const TKeyValueRecordData& recordData); + +int CreateTable(TDatabaseOptions& dbOptions); +int DropTable(TDatabaseOptions& dbOptions); diff --git a/tests/slo_workloads/common/key_value/kv_data.cpp b/tests/slo_workloads/common/key_value/kv_data.cpp new file mode 100644 index 0000000000..801ec358f0 --- /dev/null +++ b/tests/slo_workloads/common/key_value/kv_data.cpp @@ -0,0 +1,16 @@ +#include "kv_common.h" + +using namespace NYdb; + +const std::string TableName = "key_value"; + +NYdb::TValue BuildValueFromRecord(const TKeyValueRecordData& recordData) { + NYdb::TValueBuilder value; + value.BeginStruct(); + value.AddMember("object_id_key").Uint32(GetHash(recordData.ObjectId)); + value.AddMember("object_id").Uint32(recordData.ObjectId); + value.AddMember("timestamp").Uint64(recordData.Timestamp); + value.AddMember("payload").Utf8(recordData.Payload); + value.EndStruct(); + return value.Build(); +} diff --git a/tests/slo_workloads/core/CMakeLists.txt b/tests/slo_workloads/core/CMakeLists.txt index 1e7f093425..9808bf0fdd 100644 --- a/tests/slo_workloads/core/CMakeLists.txt +++ b/tests/slo_workloads/core/CMakeLists.txt @@ -1,9 +1,23 @@ +include(FetchContent) + +FetchContent_Declare( + hdr_histogram + GIT_REPOSITORY https://github.com/HdrHistogram/HdrHistogram_c.git + GIT_TAG 0.11.8 + EXCLUDE_FROM_ALL +) +set(HDR_HISTOGRAM_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE) +set(HDR_HISTOGRAM_BUILD_SHARED OFF CACHE BOOL "" FORCE) +set(HDR_LOG_REQUIRED OFF CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(hdr_histogram) + add_library(slo-workload-core) target_link_libraries(slo-workload-core PUBLIC yutil opentelemetry-cpp::metrics opentelemetry-cpp::otlp_http_metric_exporter + hdr_histogram_static ) target_sources(slo-workload-core PRIVATE diff --git a/tests/slo_workloads/native/key_value/CMakeLists.txt b/tests/slo_workloads/native/key_value/CMakeLists.txt index d3c87cb920..42128e5cdb 100644 --- a/tests/slo_workloads/native/key_value/CMakeLists.txt +++ b/tests/slo_workloads/native/key_value/CMakeLists.txt @@ -4,12 +4,11 @@ target_link_libraries(slo-key-value PUBLIC yutil getopt slo-utils + slo-kv-common YDB-CPP-SDK::Table ) target_sources(slo-key-value PRIVATE - create.cpp - drop.cpp generate.cpp key_value.cpp main.cpp diff --git a/tests/slo_workloads/native/key_value/create.cpp b/tests/slo_workloads/native/key_value/create.cpp deleted file mode 100644 index a105caba7f..0000000000 --- a/tests/slo_workloads/native/key_value/create.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "key_value.h" - -#include - -using namespace NYdb; -using namespace NYdb::NTable; - - -namespace { - void CreateTable(TTableClient& client, const std::string& prefix) { - NYdb::NStatusHelpers::ThrowOnError(client.RetryOperationSync([prefix](TSession session) { - auto desc = TTableBuilder() - .AddNullableColumn("object_id_key", EPrimitiveType::Uint32) - .AddNullableColumn("object_id", EPrimitiveType::Uint32) - .AddNullableColumn("timestamp", EPrimitiveType::Uint64) - .AddNullableColumn("payload", EPrimitiveType::Utf8) - .SetPrimaryKeyColumns({ "object_id_key", "object_id" }) - .Build(); - - auto tableSettings = TCreateTableSettings() - .PartitioningPolicy(TPartitioningPolicy().UniformPartitions(PartitionsCount)) - .CancelAfter(DefaultReactionTime) - .ClientTimeout(DefaultReactionTime + TDuration::Seconds(5)); - - return session.CreateTable( - JoinPath(prefix, TableName) - , std::move(desc) - , std::move(tableSettings) - ).ExtractValueSync(); - })); - } -} //namespace - -int CreateTable(TDatabaseOptions& dbOptions) { - TTableClient client(dbOptions.Driver); - CreateTable(client, dbOptions.Prefix); - Cout << "Table created." << Endl; - return EXIT_SUCCESS; -} diff --git a/tests/slo_workloads/native/key_value/key_value.cpp b/tests/slo_workloads/native/key_value/key_value.cpp index 23bd3ff184..72718277c3 100644 --- a/tests/slo_workloads/native/key_value/key_value.cpp +++ b/tests/slo_workloads/native/key_value/key_value.cpp @@ -4,21 +4,6 @@ #include #include -using namespace NYdb; - -const std::string TableName = "key_value"; - -NYdb::TValue BuildValueFromRecord(const TKeyValueRecordData& recordData) { - NYdb::TValueBuilder value; - value.BeginStruct(); - value.AddMember("object_id_key").Uint32(GetHash(recordData.ObjectId)); - value.AddMember("object_id").Uint32(recordData.ObjectId); - value.AddMember("timestamp").Uint64(recordData.Timestamp); - value.AddMember("payload").Utf8(recordData.Payload); - value.EndStruct(); - return value.Build(); -} - int DoCreate(TDatabaseOptions& dbOptions, int argc, char** argv) { TCreateOptions createOptions{ {dbOptions} }; if (!ParseOptionsCreate(argc, argv, createOptions)) { diff --git a/tests/slo_workloads/native/key_value/key_value.h b/tests/slo_workloads/native/key_value/key_value.h index 86c432f2ef..31be3e7d07 100644 --- a/tests/slo_workloads/native/key_value/key_value.h +++ b/tests/slo_workloads/native/key_value/key_value.h @@ -1,15 +1,10 @@ #pragma once -#include +#include -#include #include #include -extern const std::string TableName; - -NYdb::TValue BuildValueFromRecord(const TKeyValueRecordData& recordData); - // Initial content generation class TGenerateInitialContentJob : public TThreadJob { public: @@ -51,12 +46,9 @@ class TReadJob : public TThreadJob { std::uint32_t ObjectIdRange; }; -int CreateTable(TDatabaseOptions& dbOptions); -int DropTable(TDatabaseOptions& dbOptions); - // Creates a table and fills it with initial content int DoCreate(TDatabaseOptions& dbOptions, int argc, char** argv); -// Not implemented +// Runs read/write workload int DoRun(TDatabaseOptions& dbOptions, int argc, char** argv); // Drops the table int DoCleanup(TDatabaseOptions& dbOptions, int argc); diff --git a/tests/slo_workloads/native/utils/CMakeLists.txt b/tests/slo_workloads/native/utils/CMakeLists.txt index d0479ac83c..bd1ae20bf3 100644 --- a/tests/slo_workloads/native/utils/CMakeLists.txt +++ b/tests/slo_workloads/native/utils/CMakeLists.txt @@ -1,19 +1,9 @@ -include(FetchContent) +# Base utils library: shared between native and userver workloads +# Contains: ParseOptions*, GetTableStats, YdbStatusToString, PackValuesToParamsAsList, etc. +# Does NOT contain DoMain (each workload provides its own). +add_library(slo-utils-base) -FetchContent_Declare( - hdr_histogram - GIT_REPOSITORY https://github.com/HdrHistogram/HdrHistogram_c.git - GIT_TAG 0.11.8 - EXCLUDE_FROM_ALL -) -set(HDR_HISTOGRAM_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE) -set(HDR_HISTOGRAM_BUILD_SHARED OFF CACHE BOOL "" FORCE) -set(HDR_LOG_REQUIRED OFF CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(hdr_histogram) - -add_library(slo-utils) - -target_link_libraries(slo-utils PUBLIC +target_link_libraries(slo-utils-base PUBLIC slo-workload-core yutil getopt @@ -21,12 +11,23 @@ target_link_libraries(slo-utils PUBLIC YDB-CPP-SDK::Iam ) -target_link_libraries(slo-utils PRIVATE - hdr_histogram_static +if (SLO_BRANCH_REF) + target_compile_definitions(slo-utils-base PRIVATE REF=${SLO_BRANCH_REF}) +endif() + +target_sources(slo-utils-base PRIVATE + utils.cpp +) + +# Full native utils library: adds DoMain, executor and job infrastructure (thread-based) +add_library(slo-utils) + +target_link_libraries(slo-utils PUBLIC + slo-utils-base ) target_sources(slo-utils PRIVATE + utils_main.cpp executor.cpp job.cpp - utils.cpp ) diff --git a/tests/slo_workloads/native/utils/utils.cpp b/tests/slo_workloads/native/utils/utils.cpp index 15b41a810b..c0f060a43c 100644 --- a/tests/slo_workloads/native/utils/utils.cpp +++ b/tests/slo_workloads/native/utils/utils.cpp @@ -1,6 +1,5 @@ #include "utils.h" -#include #include #include @@ -40,215 +39,6 @@ std::string NativeSloMeterSchemaVersion() { return NYdb::GetSdkSemver(); } -bool ParseToken(std::string& token, std::string& tokenFile) { - if (!tokenFile.empty()) { - if (!token.empty()) { - Cerr << "Both token and token_file provided. Choose one." << Endl; - } else { - TFsPath path(tokenFile); - if (path.Exists()) { - token = Strip(TUnbufferedFileInput(path).ReadAll()); - return true; - } - Cerr << "Wrong path provided for token_file." << Endl; - } - } else if (!token.empty()) { - return true; - } else { - token = GetEnv("YDB_TOKEN"); - return true; - } - return false; -} - -void StartStatCollecting([[maybe_unused]] TDriver& driver, const std::string& statConfigFile) { - if (statConfigFile.empty()) { - return; - } -} - -static std::string DefaultConnectionStringFromEnv() { - std::string cs = GetEnv("YDB_CONNECTION_STRING"); - if (!cs.empty()) { - return cs; - } - std::string endpoint = GetEnv("YDB_ENDPOINT"); - std::string database = GetEnv("YDB_DATABASE"); - if (!endpoint.empty() && !database.empty()) { - return TStringBuilder() << endpoint << "/?database=" << database; - } - return {}; -} - -int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TCleanupCommand cleanup) { - TOpts opts = TOpts::Default(); - - std::string connectionString; - std::string prefix; - std::string token; - std::string tokenFile; - std::string iamSaKeyFile; - std::string statConfigFile; - std::string balancingPolicy; - - std::string defaultConnectionString = DefaultConnectionStringFromEnv(); - - auto& connOpt = opts.AddLongOption('c', "connection-string", "YDB connection string").RequiredArgument("SCHEMA://HOST:PORT/?DATABASE=DATABASE") - .StoreResult(&connectionString); - if (!defaultConnectionString.empty()) { - connOpt.DefaultValue(defaultConnectionString); - } else { - connOpt.Required(); - } - opts.AddLongOption('p', "prefix", "Base prefix for tables").RequiredArgument("PATH") - .StoreResult(&prefix); - opts.AddLongOption('k', "token", "security token").RequiredArgument("TOKEN") - .StoreResult(&token); - opts.AddLongOption('f', "token-file", "security token file").RequiredArgument("PATH") - .StoreResult(&tokenFile); - opts.AddLongOption("iam-sa-key-file", "IAM service account key file").RequiredArgument("SECRET") - .StoreResult(&iamSaKeyFile); - opts.AddLongOption('s', "stat-config", "statistics config file").Optional().RequiredArgument("PATH") - .StoreResult(&statConfigFile); - opts.AddLongOption('b', "balancing-policy", "Balancing policy").Optional().DefaultValue("use-all-nodes").RequiredArgument("(use-all-nodes|prefer-local-dc|prefer-primary-pile)") - .StoreResult(&balancingPolicy); - opts.AddHelpOption('h'); - opts.SetFreeArgsMin(0); - opts.SetFreeArgTitle(0, "", GetCmdList()); - opts.ArgPermutation_ = NLastGetopt::REQUIRE_ORDER; - // Run-phase options (--read-rps, --write-rps, …) reach DoMain when the - // caller invokes the workload without an explicit subcommand (the v2 SLO - // action contract). Tolerate them here so the global parser stops at the - // first unknown option instead of erroring; they are forwarded to the - // run phase below. - opts.AllowUnknownLongOptions_ = true; - - TOptsParseResult res(&opts, argc, argv); - size_t freeArgsPos = res.GetFreeArgsPos(); - argc -= freeArgsPos; - argv += freeArgsPos; - - ECommandType command = (argc > 0) ? ParseCommand(*argv) : ECommandType::All; - if (command == ECommandType::Unknown) { - if (argv[0][0] == '-') { - // First leftover token is an option, not a subcommand keyword: - // treat as implicit All mode and let the run phase parse it. - command = ECommandType::All; - } else { - Cerr << "Unknown command '" << *argv << "'" << Endl; - return EXIT_FAILURE; - } - } - - if (prefix.empty()) { - prefix = GetDatabase(connectionString); - } - if (prefix.empty()) { - // YDB SLO action sets YDB_CONNECTION_STRING in path form - // (grpc://host:port/Root/testdb), which GetDatabase can't parse. - // Fall back to YDB_DATABASE which the action sets alongside it. - prefix = GetEnv("YDB_DATABASE"); - } - - if (!ParseToken(token, tokenFile)) { - return EXIT_FAILURE; - } - - auto config = TDriverConfig(connectionString); - - if (!iamSaKeyFile.empty()) { - Cout << "Enabling IAM authentication..." << Endl; - TIamJwtFilename iamJwtFilename{.JwtFilename = iamSaKeyFile}; - config.SetCredentialsProviderFactory(CreateIamJwtFileCredentialsProviderFactory(iamJwtFilename)); - } else if (!token.empty()) { - Cout << "Enabling OAuth authentication..." << Endl; - config.SetCredentialsProviderFactory(CreateOAuthCredentialsProviderFactory(token)); - } else { - Cerr << "Warning: No authentication methods provided." << Endl; - } - - if (balancingPolicy == "use-all-nodes") { - config.SetBalancingPolicy(TBalancingPolicy::UseAllNodes()); - } else if (balancingPolicy == "prefer-local-dc") { - config.SetBalancingPolicy(TBalancingPolicy::UsePreferableLocation()); - } else if (balancingPolicy == "prefer-primary-pile") { - config.SetBalancingPolicy(TBalancingPolicy::UsePreferablePileState()); - } else { - Cerr << "Unknown balancing policy: " << balancingPolicy << Endl; - return EXIT_FAILURE; - } - - TDriver driver(config); - - StartStatCollecting(driver, statConfigFile); - - TDatabaseOptions dbOptions{driver, prefix}; - int result; - try { - switch (command) { - case ECommandType::Create: - Cout << "Launching create command..." << Endl; - result = create(dbOptions, argc, argv); - break; - case ECommandType::Run: - Cout << "Launching run command..." << Endl; - result = run(dbOptions, argc, argv); - break; - case ECommandType::Cleanup: - Cout << "Launching cleanup command..." << Endl; - result = cleanup(dbOptions, argc); - break; - case ECommandType::All: { - Cout << "Launching full lifecycle: create -> run -> cleanup" << Endl; - // Forward leftover argv to the run phase so options like - // --read-rps / --write-rps take effect. argv[0] here is the first - // run-phase option (no subcommand keyword was supplied), so - // prepend a synthetic program name for ParseOptionsRun. - char programName[] = "slo"; - std::vector runArgv; - runArgv.reserve(argc + 2); - runArgv.push_back(programName); - for (int i = 0; i < argc; ++i) { - runArgv.push_back(argv[i]); - } - runArgv.push_back(nullptr); - int fakeArgc = 1; - char* fakeArgv[] = { programName, nullptr }; - - Cout << "[all] Launching create command..." << Endl; - result = create(dbOptions, fakeArgc, fakeArgv); - if (!result) { - Cout << "[all] Launching run command..." << Endl; - result = run(dbOptions, static_cast(runArgv.size() - 1), runArgv.data()); - } - Cout << "[all] Launching cleanup command..." << Endl; - int cleanupRc = cleanup(dbOptions, fakeArgc); - // Cleanup runs while chaos-monkey is still killing nodes, so a - // DropTable failure here is expected noise and must not mask a - // successful run. Surface the run's status; only fall back to - // the cleanup status when run itself failed and we have nothing - // else to report. - if (cleanupRc && !result) { - Cerr << "[all] Warning: cleanup failed (exit " << cleanupRc - << ") but run succeeded; ignoring cleanup exit code." << Endl; - } else if (cleanupRc) { - Cerr << "[all] Warning: cleanup failed (exit " << cleanupRc - << "); preserving earlier run failure." << Endl; - } - break; - } - default: - Cerr << "Unknown command" << Endl; - return EXIT_FAILURE; - } - } catch (const NYdb::NStatusHelpers::TYdbErrorException& e) { - Cerr << "Exception caught: " << e << Endl; - return EXIT_FAILURE; - } - driver.Stop(true); - return result; -} - std::string GetCmdList() { return "create, run, cleanup (omit to run create -> run -> cleanup in one process)"; } diff --git a/tests/slo_workloads/native/utils/utils_main.cpp b/tests/slo_workloads/native/utils/utils_main.cpp new file mode 100644 index 0000000000..c07792e003 --- /dev/null +++ b/tests/slo_workloads/native/utils/utils_main.cpp @@ -0,0 +1,220 @@ +#include "utils.h" + +#include + +#include +#include +#include +#include +#include + +#include + +using namespace NLastGetopt; +using namespace NYdb; + +namespace { + +bool ParseToken(std::string& token, std::string& tokenFile) { + if (!tokenFile.empty()) { + if (!token.empty()) { + Cerr << "Both token and token_file provided. Choose one." << Endl; + } else { + TFsPath path(tokenFile); + if (path.Exists()) { + token = Strip(TUnbufferedFileInput(path).ReadAll()); + return true; + } + Cerr << "Wrong path provided for token_file." << Endl; + } + } else if (!token.empty()) { + return true; + } else { + token = GetEnv("YDB_TOKEN"); + return true; + } + return false; +} + +void StartStatCollecting([[maybe_unused]] TDriver& driver, const std::string& statConfigFile) { + if (statConfigFile.empty()) { + return; + } +} + +std::string DefaultConnectionStringFromEnv() { + std::string cs = GetEnv("YDB_CONNECTION_STRING"); + if (!cs.empty()) { + return cs; + } + std::string endpoint = GetEnv("YDB_ENDPOINT"); + std::string database = GetEnv("YDB_DATABASE"); + if (!endpoint.empty() && !database.empty()) { + return TStringBuilder() << endpoint << "/?database=" << database; + } + return {}; +} + +} // namespace + +int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TCleanupCommand cleanup) { + TOpts opts = TOpts::Default(); + + std::string connectionString; + std::string prefix; + std::string token; + std::string tokenFile; + std::string iamSaKeyFile; + std::string statConfigFile; + std::string balancingPolicy; + + std::string defaultConnectionString = DefaultConnectionStringFromEnv(); + + auto& connOpt = opts.AddLongOption('c', "connection-string", "YDB connection string").RequiredArgument("SCHEMA://HOST:PORT/?DATABASE=DATABASE") + .StoreResult(&connectionString); + if (!defaultConnectionString.empty()) { + connOpt.DefaultValue(defaultConnectionString); + } else { + connOpt.Required(); + } + opts.AddLongOption('p', "prefix", "Base prefix for tables").RequiredArgument("PATH") + .StoreResult(&prefix); + opts.AddLongOption('k', "token", "security token").RequiredArgument("TOKEN") + .StoreResult(&token); + opts.AddLongOption('f', "token-file", "security token file").RequiredArgument("PATH") + .StoreResult(&tokenFile); + opts.AddLongOption("iam-sa-key-file", "IAM service account key file").RequiredArgument("SECRET") + .StoreResult(&iamSaKeyFile); + opts.AddLongOption('s', "stat-config", "statistics config file").Optional().RequiredArgument("PATH") + .StoreResult(&statConfigFile); + opts.AddLongOption('b', "balancing-policy", "Balancing policy").Optional().DefaultValue("use-all-nodes").RequiredArgument("(use-all-nodes|prefer-local-dc|prefer-primary-pile)") + .StoreResult(&balancingPolicy); + opts.AddHelpOption('h'); + opts.SetFreeArgsMin(0); + opts.SetFreeArgTitle(0, "", GetCmdList()); + opts.ArgPermutation_ = NLastGetopt::REQUIRE_ORDER; + // Run-phase options (--read-rps, --write-rps, …) reach DoMain when the + // caller invokes the workload without an explicit subcommand (the v2 SLO + // action contract). Tolerate them here so the global parser stops at the + // first unknown option instead of erroring; they are forwarded to the + // run phase below. + opts.AllowUnknownLongOptions_ = true; + + TOptsParseResult res(&opts, argc, argv); + size_t freeArgsPos = res.GetFreeArgsPos(); + argc -= freeArgsPos; + argv += freeArgsPos; + + ECommandType command = (argc > 0) ? ParseCommand(*argv) : ECommandType::All; + if (command == ECommandType::Unknown) { + if (argc > 0 && argv[0][0] == '-') { + // First leftover token is an option, not a subcommand keyword: + // treat as implicit All mode and let the run phase parse it. + command = ECommandType::All; + } else if (argc > 0) { + Cerr << "Unknown command '" << *argv << "'" << Endl; + return EXIT_FAILURE; + } else { + command = ECommandType::All; + } + } + + if (prefix.empty()) { + prefix = GetDatabase(connectionString); + } + if (prefix.empty()) { + // YDB SLO action sets YDB_CONNECTION_STRING in path form + // (grpc://host:port/Root/testdb), which GetDatabase can't parse. + // Fall back to YDB_DATABASE which the action sets alongside it. + prefix = GetEnv("YDB_DATABASE"); + } + + if (!ParseToken(token, tokenFile)) { + return EXIT_FAILURE; + } + + auto config = TDriverConfig(connectionString); + + if (!iamSaKeyFile.empty()) { + Cout << "Enabling IAM authentication..." << Endl; + TIamJwtFilename iamJwtFilename{.JwtFilename = iamSaKeyFile}; + config.SetCredentialsProviderFactory(CreateIamJwtFileCredentialsProviderFactory(iamJwtFilename)); + } else if (!token.empty()) { + Cout << "Enabling OAuth authentication..." << Endl; + config.SetCredentialsProviderFactory(CreateOAuthCredentialsProviderFactory(token)); + } else { + Cerr << "Warning: No authentication methods provided." << Endl; + } + + if (balancingPolicy == "use-all-nodes") { + config.SetBalancingPolicy(TBalancingPolicy::UseAllNodes()); + } else if (balancingPolicy == "prefer-local-dc") { + config.SetBalancingPolicy(TBalancingPolicy::UsePreferableLocation()); + } else if (balancingPolicy == "prefer-primary-pile") { + config.SetBalancingPolicy(TBalancingPolicy::UsePreferablePileState()); + } else { + Cerr << "Unknown balancing policy: " << balancingPolicy << Endl; + return EXIT_FAILURE; + } + + TDriver driver(config); + + StartStatCollecting(driver, statConfigFile); + + TDatabaseOptions dbOptions{driver, prefix}; + int result; + try { + switch (command) { + case ECommandType::Create: + Cout << "Launching create command..." << Endl; + result = create(dbOptions, argc, argv); + break; + case ECommandType::Run: + Cout << "Launching run command..." << Endl; + result = run(dbOptions, argc, argv); + break; + case ECommandType::Cleanup: + Cout << "Launching cleanup command..." << Endl; + result = cleanup(dbOptions, argc); + break; + case ECommandType::All: { + Cout << "Launching full lifecycle: create -> run -> cleanup" << Endl; + char programName[] = "slo"; + std::vector runArgv; + runArgv.reserve(argc + 2); + runArgv.push_back(programName); + for (int i = 0; i < argc; ++i) { + runArgv.push_back(argv[i]); + } + runArgv.push_back(nullptr); + int fakeArgc = 1; + char* fakeArgv[] = { programName, nullptr }; + + Cout << "[all] Launching create command..." << Endl; + result = create(dbOptions, fakeArgc, fakeArgv); + if (!result) { + Cout << "[all] Launching run command..." << Endl; + result = run(dbOptions, static_cast(runArgv.size() - 1), runArgv.data()); + } + Cout << "[all] Launching cleanup command..." << Endl; + int cleanupRc = cleanup(dbOptions, fakeArgc); + if (cleanupRc && !result) { + Cerr << "[all] Warning: cleanup failed (exit " << cleanupRc + << ") but run succeeded; ignoring cleanup exit code." << Endl; + } else if (cleanupRc) { + Cerr << "[all] Warning: cleanup failed (exit " << cleanupRc + << "); preserving earlier run failure." << Endl; + } + break; + } + default: + Cerr << "Unknown command" << Endl; + return EXIT_FAILURE; + } + } catch (const NYdb::NStatusHelpers::TYdbErrorException& e) { + Cerr << "Exception caught: " << e << Endl; + return EXIT_FAILURE; + } + driver.Stop(true); + return result; +} diff --git a/tests/slo_workloads/userver/CMakeLists.txt b/tests/slo_workloads/userver/CMakeLists.txt index 1975421a54..ec86159d8f 100644 --- a/tests/slo_workloads/userver/CMakeLists.txt +++ b/tests/slo_workloads/userver/CMakeLists.txt @@ -1 +1,6 @@ +find_package(userver REQUIRED COMPONENTS core) + +include(${CMAKE_CURRENT_LIST_DIR}/cmake/UserverYdbFromTree.cmake) +userver_ydb_from_tree() + add_subdirectory(key_value) diff --git a/tests/slo_workloads/userver/cmake/SetupYdbCppSDK.cmake b/tests/slo_workloads/userver/cmake/SetupYdbCppSDK.cmake new file mode 100644 index 0000000000..0f647dbfce --- /dev/null +++ b/tests/slo_workloads/userver/cmake/SetupYdbCppSDK.cmake @@ -0,0 +1,8 @@ +# Override userver's SetupYdbCppSDK.cmake: use in-tree YDB-CPP-SDK targets +# from this repo instead of downloading ydb-cpp-sdk via CPM. + +if(NOT TARGET YDB-CPP-SDK::Table) + message(FATAL_ERROR "In-tree YDB-CPP-SDK must be configured before building userver-ydb") +endif() + +set(ydb-cpp-sdk_INCLUDE_DIRS "") diff --git a/tests/slo_workloads/userver/cmake/UserverYdbFromTree.cmake b/tests/slo_workloads/userver/cmake/UserverYdbFromTree.cmake new file mode 100644 index 0000000000..974292234e --- /dev/null +++ b/tests/slo_workloads/userver/cmake/UserverYdbFromTree.cmake @@ -0,0 +1,55 @@ +function(userver_ydb_from_tree) + if(TARGET userver::ydb) + return() + endif() + + if(NOT TARGET userver-core AND TARGET userver::core) + add_library(userver-core ALIAS userver::core) + endif() + + if(NOT USERVER_ROOT_DIR) + if(DEFINED ENV{USERVER_SOURCE_DIR}) + set(USERVER_ROOT_DIR "$ENV{USERVER_SOURCE_DIR}" CACHE PATH "" FORCE) + else() + message(FATAL_ERROR "USERVER_SOURCE_DIR must point to a userver source tree (matching the installed .deb version)") + endif() + endif() + + if(NOT EXISTS "${USERVER_ROOT_DIR}/ydb/CMakeLists.txt") + message(FATAL_ERROR "userver ydb sources not found at ${USERVER_ROOT_DIR}/ydb") + endif() + + set(USERVER_INSTALL OFF CACHE BOOL "" FORCE) + set(USERVER_BUILD_TESTS OFF CACHE BOOL "" FORCE) + + # The .deb is built with USERVER_FEATURE_YDB=OFF and omits scripts/chaotic and + # chaotic-gen-dynamic-configs; use the matching source tree for codegen instead. + set(USERVER_CMAKE_DIR "${USERVER_ROOT_DIR}/cmake" CACHE PATH "" FORCE) + set(USERVER_CHAOTIC_SCRIPTS_PATH "${USERVER_ROOT_DIR}/scripts/chaotic" CACHE PATH "" FORCE) + + list(PREPEND CMAKE_MODULE_PATH + "${CMAKE_CURRENT_LIST_DIR}/cmake" + "${USERVER_ROOT_DIR}/cmake" + ) + list(PREPEND CMAKE_PROGRAM_PATH + "${USERVER_ROOT_DIR}/chaotic/bin-dynamic-configs" + "${USERVER_ROOT_DIR}/chaotic/bin" + ) + + include(${USERVER_ROOT_DIR}/cmake/PrepareInstall.cmake) + include(${USERVER_ROOT_DIR}/cmake/UserverCodegenTarget.cmake) + include(${USERVER_ROOT_DIR}/cmake/UserverModule.cmake) + include(${USERVER_ROOT_DIR}/cmake/ChaoticGen.cmake) + + set(_userver_chaotic_dynamic_configs_bin + "${USERVER_ROOT_DIR}/chaotic/bin-dynamic-configs/chaotic-gen-dynamic-configs") + if(EXISTS "${_userver_chaotic_dynamic_configs_bin}") + set_property(GLOBAL PROPERTY userver_chaotic_dynamic_configs_bin "${_userver_chaotic_dynamic_configs_bin}") + endif() + + add_subdirectory(${USERVER_ROOT_DIR}/ydb ${CMAKE_CURRENT_BINARY_DIR}/userver-ydb-build) + + if(TARGET userver-ydb AND NOT TARGET userver::ydb) + add_library(userver::ydb ALIAS userver-ydb) + endif() +endfunction() diff --git a/tests/slo_workloads/userver/key_value/CMakeLists.txt b/tests/slo_workloads/userver/key_value/CMakeLists.txt index 2b4909d8b2..480c5fd9bb 100644 --- a/tests/slo_workloads/userver/key_value/CMakeLists.txt +++ b/tests/slo_workloads/userver/key_value/CMakeLists.txt @@ -2,10 +2,24 @@ add_executable(slo-userver-key-value) target_sources(slo-userver-key-value PRIVATE main.cpp + userver_utils.cpp + userver_table_client.cpp + create.cpp + run.cpp ) target_link_libraries(slo-userver-key-value PRIVATE - slo-workload-core + slo-utils-base # Shared utils: ParseOptions*, GetTableStats, YdbStatusToString, etc. + slo-kv-common # Shared KV: TableName, BuildValueFromRecord, CreateTable, DropTable + userver::ydb # ydb::TableClient (workload queries) + userver::core # engine::RunStandalone, Semaphore, AsyncNoSpan, SleepFor ) +# userver::ydb impl headers (ydb/impl/driver.hpp) live under the source tree, not in the .deb. +if(DEFINED USERVER_ROOT_DIR) + target_include_directories(slo-userver-key-value PRIVATE "${USERVER_ROOT_DIR}/ydb/src") +elseif(DEFINED ENV{USERVER_SOURCE_DIR}) + target_include_directories(slo-userver-key-value PRIVATE "$ENV{USERVER_SOURCE_DIR}/ydb/src") +endif() + vcs_info(slo-userver-key-value) diff --git a/tests/slo_workloads/userver/key_value/create.cpp b/tests/slo_workloads/userver/key_value/create.cpp new file mode 100644 index 0000000000..4991c374ac --- /dev/null +++ b/tests/slo_workloads/userver/key_value/create.cpp @@ -0,0 +1,154 @@ +#include "key_value.h" +#include "userver_table_client.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace NYdb; +using namespace NYdb::NTable; + +namespace uydb = userver::ydb; + +namespace { + +// Shared stop flag for signal handling +std::atomic ShouldStop{false}; + +} // namespace + +int DoCreate(TDatabaseOptions& dbOptions, int argc, char** argv) { + TCreateOptions createOptions{ {dbOptions} }; + if (!ParseOptionsCreate(argc, argv, createOptions)) { + return EXIT_FAILURE; + } + + createOptions.CommonOptions.MaxInfly = createOptions.CommonOptions.MaxInputThreads; + + int result = CreateTable(dbOptions); + if (result) { + return result; + } + + std::uint32_t maxId = GetTableStats(dbOptions, TableName).MaxId; + + createOptions.CommonOptions.ReactionTime = TDuration::Seconds(20); + + Cout << TInstant::Now().ToRfc822StringLocal() << " Uploading initial content... do 'kill -USR1 " << GetPID() + << "' for progress details or Ctrl/Cmd+C to interrupt" << Endl; + + // Set up signal handlers + ShouldStop.store(false); + signal(SIGINT, [](int) { + Cerr << TInstant::Now().ToRfc822StringLocal() << " SIGINT received. Stopping..." << Endl; + ShouldStop.store(true); + }); + + const auto& opts = createOptions.CommonOptions; + const std::string& prefix = opts.DatabaseOptions.Prefix; + + auto& ydbClient = userver_slo::GetTableClient(); + + TStat stats( + opts.DontPushMetrics ? std::nullopt : std::make_optional(opts.MetricsPushUrl), + "generate", + MakeNativeSloOtelResourceAttributes(), + NativeSloMeterSchemaVersion() + ); + stats.Start(); + + // Generate initial content using userver coroutines + TPackGenerator packGenerator( + MakeGeneratorOptions(opts), + createOptions.PackSize, + [](const TKeyValueRecordData& recordData) { return BuildValueFromRecord(recordData); }, + createOptions.Count, + maxId + ); + + userver::engine::Semaphore semaphore{ + static_cast(opts.MaxInfly)}; + + std::atomic succeeded{0}; + std::atomic failed{0}; + std::atomic total{0}; + + const TString query = Sprintf(R"( +--!syntax_v1 +PRAGMA TablePathPrefix("%s"); + +DECLARE $items AS + List>; + +UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); + +)", prefix.c_str(), TableName.c_str()); + + std::vector> tasks; + + std::vector pack; + while (!ShouldStop.load() && packGenerator.GetNextPack(pack)) { + semaphore.lock_shared(); + total.fetch_add(1); + + auto params = userver_slo::PackValuesToPreparedArgs(pack); + + tasks.push_back(userver::engine::AsyncNoSpan( + [&ydbClient, &semaphore, &stats, &succeeded, &failed, + &opts, query, params = std::move(params)]() mutable { + auto stat = stats.StartRequest(); + try { + uydb::OperationSettings settings; + settings.client_timeout_ms = std::chrono::milliseconds(opts.ReactionTime.MilliSeconds()); + + ydbClient.ExecuteDataQuery(settings, uydb::Query{query}, std::move(params)); + + TSloRequestFinish finish; + finish.StatusLabel = "SUCCESS"; + stats.FinishRequest(stat, finish); + succeeded.fetch_add(1); + } catch (const uydb::YdbResponseError& e) { + TSloRequestFinish finish; + finish.StatusLabel = YdbStatusToString(e.GetStatus().GetStatus()); + stats.FinishRequest(stat, finish); + failed.fetch_add(1); + } catch (const std::exception& e) { + TSloRequestFinish finish; + finish.StatusLabel = "UNKNOWN_ERROR"; + stats.FinishRequest(stat, finish); + failed.fetch_add(1); + } + semaphore.unlock_shared(); + } + )); + } + + // Wait for all tasks to complete + for (auto& task : tasks) { + task.Wait(); + } + + stats.Finish(); + + TStringBuilder report; + report << Endl << "======- GenerateInitialContentJob report -======" << Endl; + report << "Total: " << total.load() << ", Succeeded: " << succeeded.load() + << ", Failed: " << failed.load() << Endl; + stats.PrintStatistics(report); + report << "========================================" << Endl; + Cout << report; + + return EXIT_SUCCESS; +} diff --git a/tests/slo_workloads/userver/key_value/key_value.h b/tests/slo_workloads/userver/key_value/key_value.h new file mode 100644 index 0000000000..51de91fc91 --- /dev/null +++ b/tests/slo_workloads/userver/key_value/key_value.h @@ -0,0 +1,18 @@ +#pragma once + +#include "userver_utils.h" + +#include + +#include + +#include +#include +#include + +// Creates a table and fills it with initial content (userver coroutine-based) +int DoCreate(TDatabaseOptions& dbOptions, int argc, char** argv); +// Runs read/write workload (userver coroutine-based) +int DoRun(TDatabaseOptions& dbOptions, int argc, char** argv); +// Drops the table +int DoCleanup(TDatabaseOptions& dbOptions, int argc); diff --git a/tests/slo_workloads/userver/key_value/main.cpp b/tests/slo_workloads/userver/key_value/main.cpp index 5bd41d6fcf..1b9ba8a2de 100644 --- a/tests/slo_workloads/userver/key_value/main.cpp +++ b/tests/slo_workloads/userver/key_value/main.cpp @@ -1,10 +1,6 @@ -#include +#include "userver_utils.h" +#include "key_value.h" -// Placeholder entry point: link userver-ydb and implement the same scenario as -// tests/slo_workloads/native/key_value when YDB_CPP_SDK_BUILD_SLO_USERVER is enabled. -int main() { - std::cout << "slo-userver-key-value: stub build. " - << "Wire userver-ydb client and workload loop; see tests/slo_workloads/core/slo_executor_contract.md" - << std::endl; - return 0; +int main(int argc, char** argv) { + return DoMain(argc, argv, DoCreate, DoRun, DoCleanup); } diff --git a/tests/slo_workloads/userver/key_value/run.cpp b/tests/slo_workloads/userver/key_value/run.cpp new file mode 100644 index 0000000000..707edae484 --- /dev/null +++ b/tests/slo_workloads/userver/key_value/run.cpp @@ -0,0 +1,447 @@ +#include "key_value.h" +#include "userver_table_client.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace NYdb; +using namespace NYdb::NTable; + +namespace uydb = userver::ydb; + +namespace { + +// Shared stop flag for signal handling +std::atomic ShouldStop{false}; + +// Show progress callback +struct TProgressReporter { + TStat* ReadStats = nullptr; + TStat* WriteStats = nullptr; + std::atomic* ReadSucceeded = nullptr; + std::atomic* ReadFailed = nullptr; + std::atomic* WriteSucceeded = nullptr; + std::atomic* WriteFailed = nullptr; + std::atomic* WriteGenerated = nullptr; + + void ShowProgress() const { + TStringBuilder report; + if (ReadStats) { + report << Endl << "======- ReadJob report (Thread A) -======" << Endl; + report << "Succeeded: " << ReadSucceeded->load() + << ", Failed: " << ReadFailed->load() << Endl; + ReadStats->PrintStatistics(report); + report << "========================================" << Endl; + } + if (WriteStats) { + report << Endl << "=====- WriteJob report (Thread B) -=====" << Endl; + report << "Generated: " << WriteGenerated->load() + << ", Succeeded: " << WriteSucceeded->load() + << ", Failed: " << WriteFailed->load() << Endl; + WriteStats->PrintStatistics(report); + report << "==========================================" << Endl; + } + Cout << report; + } +}; + +TProgressReporter* GlobalReporter = nullptr; + +// Execute a query with optional application timeout and preventive request +void ExecuteWithTimeout( + uydb::TableClient& client, + const uydb::OperationSettings& settings, + const uydb::Query& query, + const std::function& makeParams, + TStat& stats, + std::atomic& succeeded, + std::atomic& failed, + bool useApplicationTimeout, + bool sendPreventiveRequest, + TDuration reactionTime) +{ + auto stat = stats.StartRequest(); + + auto doQuery = [&client, &settings, &query](uydb::PreparedArgsBuilder queryParams) { + client.ExecuteDataQuery(settings, query, std::move(queryParams)); + }; + + try { + if (!useApplicationTimeout && !sendPreventiveRequest) { + doQuery(makeParams()); + + TSloRequestFinish finish; + finish.StatusLabel = "SUCCESS"; + stats.FinishRequest(stat, finish); + succeeded.fetch_add(1); + return; + } + + if (useApplicationTimeout && !sendPreventiveRequest) { + auto task = userver::engine::AsyncNoSpan( + [&doQuery, &makeParams]() { + doQuery(makeParams()); + } + ); + + if (task.WaitNothrowUntil(userver::engine::Deadline::FromDuration( + std::chrono::milliseconds(reactionTime.MilliSeconds()))) + == userver::engine::FutureStatus::kReady) { + task.Get(); // may throw + TSloRequestFinish finish; + finish.StatusLabel = "SUCCESS"; + stats.FinishRequest(stat, finish); + succeeded.fetch_add(1); + } else { + task.RequestCancel(); + TSloRequestFinish finish; + finish.ApplicationTimeout = true; + stats.FinishRequest(stat, finish); + failed.fetch_add(1); + } + return; + } + + if (sendPreventiveRequest) { + auto task1 = userver::engine::AsyncNoSpan( + [&doQuery, &makeParams]() { + doQuery(makeParams()); + } + ); + + userver::engine::SleepFor( + std::chrono::milliseconds(reactionTime.MilliSeconds() / 2)); + + if (task1.IsFinished()) { + task1.Get(); // may throw + TSloRequestFinish finish; + finish.StatusLabel = "SUCCESS"; + stats.FinishRequest(stat, finish); + succeeded.fetch_add(1); + return; + } + + auto task2 = userver::engine::AsyncNoSpan( + [&doQuery, &makeParams]() { + doQuery(makeParams()); + } + ); + + auto idx = userver::engine::WaitAny(task1, task2); + + if (idx.has_value()) { + try { + if (*idx == 0) { + task1.Get(); + } else { + task2.Get(); + } + TSloRequestFinish finish; + finish.StatusLabel = "SUCCESS"; + stats.FinishRequest(stat, finish); + succeeded.fetch_add(1); + } catch (const uydb::YdbResponseError& e) { + TSloRequestFinish finish; + finish.StatusLabel = YdbStatusToString(e.GetStatus().GetStatus()); + stats.FinishRequest(stat, finish); + failed.fetch_add(1); + } + } else if (useApplicationTimeout) { + TSloRequestFinish finish; + finish.ApplicationTimeout = true; + stats.FinishRequest(stat, finish); + failed.fetch_add(1); + } + // Cancel remaining tasks + task1.RequestCancel(); + task2.RequestCancel(); + return; + } + } catch (const uydb::YdbResponseError& e) { + TSloRequestFinish finish; + finish.StatusLabel = YdbStatusToString(e.GetStatus().GetStatus()); + stats.FinishRequest(stat, finish); + failed.fetch_add(1); + } catch (const std::exception& e) { + TSloRequestFinish finish; + finish.StatusLabel = "UNKNOWN_ERROR"; + stats.FinishRequest(stat, finish); + failed.fetch_add(1); + } +} + +} // namespace + +int DoRun(TDatabaseOptions& dbOptions, int argc, char** argv) { + TRunOptions runOptions{ {dbOptions} }; + if (!ParseOptionsRun(argc, argv, runOptions)) { + return EXIT_FAILURE; + } + + Cout << TInstant::Now().ToRfc822StringLocal() << " Creating and initializing jobs..." << Endl; + + std::uint32_t maxId = GetTableStats(dbOptions, TableName).MaxId; + + const std::string& prefix = dbOptions.Prefix; + + auto& ydbClient = userver_slo::GetTableClient(); + + // Stats objects + std::unique_ptr readStats; + std::unique_ptr writeStats; + + std::atomic readSucceeded{0}; + std::atomic readFailed{0}; + std::atomic writeSucceeded{0}; + std::atomic writeFailed{0}; + std::atomic writeGenerated{0}; + + if (!runOptions.DontRunA) { + readStats = std::make_unique( + runOptions.CommonOptions.DontPushMetrics ? std::nullopt : std::make_optional(runOptions.CommonOptions.MetricsPushUrl), + "read", + MakeNativeSloOtelResourceAttributes(), + NativeSloMeterSchemaVersion() + ); + } + if (!runOptions.DontRunB) { + writeStats = std::make_unique( + runOptions.CommonOptions.DontPushMetrics ? std::nullopt : std::make_optional(runOptions.CommonOptions.MetricsPushUrl), + "write", + MakeNativeSloOtelResourceAttributes(), + NativeSloMeterSchemaVersion() + ); + } + + // Set up progress reporter + TProgressReporter reporter; + reporter.ReadStats = readStats.get(); + reporter.WriteStats = writeStats.get(); + reporter.ReadSucceeded = &readSucceeded; + reporter.ReadFailed = &readFailed; + reporter.WriteSucceeded = &writeSucceeded; + reporter.WriteFailed = &writeFailed; + reporter.WriteGenerated = &writeGenerated; + GlobalReporter = &reporter; + + // Set up signal handlers + ShouldStop.store(false); + signal(SIGUSR1, [](int) { + Cout << TInstant::Now().ToRfc822StringLocal() << " SIGUSR1 handle" << Endl; + if (GlobalReporter) { + GlobalReporter->ShowProgress(); + } + }); + signal(SIGINT, [](int) { + Cerr << TInstant::Now().ToRfc822StringLocal() << " SIGINT received. Stopping..." << Endl; + ShouldStop.store(true); + }); + + TInstant start = TInstant::Now(); + TInstant deadline = start + TDuration::Seconds(runOptions.CommonOptions.SecondsToRun); + + Cout << "Jobs launched. Do 'kill -USR1 " << GetPID() + << "' for progress details or 'kill -INT " << GetPID() << "' (Ctrl/Cmd + C) to interrupt" << Endl + << " Start time: " << start.ToRfc822StringLocal() << Endl + << "Estimated finish time: " << deadline.ToRfc822StringLocal() << Endl; + + std::vector> jobTasks; + + // Thread A: Read workload + if (!runOptions.DontRunA) { + auto readRps = runOptions.Read_rps; + auto readTimeout = runOptions.ReadTimeout; + auto useAppTimeout = runOptions.CommonOptions.UseApplicationTimeout; + auto sendPreventive = runOptions.CommonOptions.SendPreventiveRequest; + auto maxInfly = runOptions.CommonOptions.MaxInfly; + auto objectIdRange = static_cast(maxId * 1.25); // 20% no-result reads + + const TString readQuery = Sprintf(R"( +--!syntax_v1 +PRAGMA TablePathPrefix("%s"); + +DECLARE $object_id_key AS Uint32; +DECLARE $object_id AS Uint32; + +SELECT * FROM `%s` WHERE `object_id_key` = $object_id_key AND `object_id` = $object_id; + +)", prefix.c_str(), TableName.c_str()); + + jobTasks.push_back(userver::engine::AsyncNoSpan( + [&ydbClient, &readStats, &readSucceeded, &readFailed, + readRps, readTimeout, useAppTimeout, sendPreventive, maxInfly, + objectIdRange, readQuery, deadline]() { + + readStats->Start(); + TRpsProvider rpsProvider(readRps); + rpsProvider.Reset(); + + userver::engine::Semaphore semaphore{ + static_cast(maxInfly)}; + + std::vector> tasks; + + while (!ShouldStop.load() && TInstant::Now() < deadline) { + std::uint32_t idToSelect = RandomNumber() % objectIdRange; + const std::uint32_t objectIdKey = GetHash(idToSelect); + + rpsProvider.Use(); + + semaphore.lock_shared(); + + tasks.push_back(userver::engine::AsyncNoSpan( + [&ydbClient, &semaphore, &readStats, &readSucceeded, &readFailed, + readTimeout, useAppTimeout, sendPreventive, + readQuery, objectIdKey, idToSelect]() { + + uydb::OperationSettings settings; + settings.client_timeout_ms = std::chrono::milliseconds(readTimeout.MilliSeconds()); + + const auto makeParams = [&ydbClient, objectIdKey, idToSelect]() { + auto builder = ydbClient.GetBuilder(); + builder.Add("$object_id_key", objectIdKey); + builder.Add("$object_id", idToSelect); + return builder; + }; + + ExecuteWithTimeout( + ydbClient, settings, uydb::Query{readQuery}, + makeParams, *readStats, + readSucceeded, readFailed, + useAppTimeout, sendPreventive, readTimeout); + + semaphore.unlock_shared(); + } + )); + } + + // Wait for all read tasks + for (auto& task : tasks) { + task.Wait(); + } + + readStats->Finish(); + } + )); + } + + // Thread B: Write workload + if (!runOptions.DontRunB) { + auto writeRps = runOptions.Write_rps; + auto writeTimeout = runOptions.WriteTimeout; + auto useAppTimeout = runOptions.CommonOptions.UseApplicationTimeout; + auto sendPreventive = runOptions.CommonOptions.SendPreventiveRequest; + auto maxInfly = runOptions.CommonOptions.MaxInfly; + auto minLength = runOptions.CommonOptions.MinLength; + auto maxLength = runOptions.CommonOptions.MaxLength; + + const TString writeQuery = Sprintf(R"( +--!syntax_v1 +PRAGMA TablePathPrefix("%s"); + +DECLARE $items AS + List>; + +UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); + +)", prefix.c_str(), TableName.c_str()); + + jobTasks.push_back(userver::engine::AsyncNoSpan( + [&ydbClient, &writeStats, &writeSucceeded, &writeFailed, &writeGenerated, + writeRps, writeTimeout, useAppTimeout, sendPreventive, maxInfly, + minLength, maxLength, maxId, writeQuery, deadline]() { + + writeStats->Start(); + TRpsProvider rpsProvider(writeRps); + rpsProvider.Reset(); + + TSloGeneratorOptions genOpts; + genOpts.MinLength = minLength; + genOpts.MaxLength = maxLength; + TKeyValueGenerator generator(genOpts, maxId); + + userver::engine::Semaphore semaphore{ + static_cast(maxInfly)}; + + std::vector> tasks; + + while (!ShouldStop.load() && TInstant::Now() < deadline) { + const auto value = BuildValueFromRecord(generator.Get()); + writeGenerated.fetch_add(1); + + rpsProvider.Use(); + + semaphore.lock_shared(); + + tasks.push_back(userver::engine::AsyncNoSpan( + [&ydbClient, &semaphore, &writeStats, &writeSucceeded, &writeFailed, + writeTimeout, useAppTimeout, sendPreventive, + writeQuery, value]() { + + uydb::OperationSettings settings; + settings.client_timeout_ms = std::chrono::milliseconds(writeTimeout.MilliSeconds()); + + const auto makeParams = [value]() { + return userver_slo::PackValuesToPreparedArgs({value}); + }; + + ExecuteWithTimeout( + ydbClient, settings, uydb::Query{writeQuery}, + makeParams, *writeStats, + writeSucceeded, writeFailed, + useAppTimeout, sendPreventive, writeTimeout); + + semaphore.unlock_shared(); + } + )); + } + + // Wait for all write tasks + for (auto& task : tasks) { + task.Wait(); + } + + writeStats->Finish(); + } + )); + } + + // Wait for all job tasks (read + write loops) + for (auto& task : jobTasks) { + task.Wait(); + } + + Cout << "All jobs finished: " << TInstant::Now().ToRfc822StringLocal() << Endl; + + reporter.ShowProgress(); + + GlobalReporter = nullptr; + + return EXIT_SUCCESS; +} + +int DoCleanup(TDatabaseOptions& dbOptions, int argc) { + if (argc > 1) { + Cerr << "Unexpected arguments after cleanup" << Endl; + return EXIT_FAILURE; + } + return DropTable(dbOptions); +} diff --git a/tests/slo_workloads/userver/key_value/userver_table_client.cpp b/tests/slo_workloads/userver/key_value/userver_table_client.cpp new file mode 100644 index 0000000000..728c6f074f --- /dev/null +++ b/tests/slo_workloads/userver/key_value/userver_table_client.cpp @@ -0,0 +1,94 @@ +#include "userver_table_client.h" + +#include +#include + +#include +#include + +namespace userver_slo { +namespace { + +struct Clients final { + std::shared_ptr driver; + std::shared_ptr table_client; +}; + +Clients& GetClients() { + static Clients clients; + return clients; +} + +} // namespace + +void InitTableClient( + const std::string& endpoint, + const std::string& database, + const std::shared_ptr& credentials_provider_factory, + const std::string& oauth_token, + const bool prefer_local_dc +) { + auto& clients = GetClients(); + if (clients.table_client) { + return; + } + + userver::ydb::impl::DriverSettings driver_settings; + driver_settings.endpoint = endpoint; + driver_settings.database = database; + driver_settings.prefer_local_dc = prefer_local_dc; + driver_settings.credentials_provider_factory = credentials_provider_factory; + if (!oauth_token.empty()) { + driver_settings.oauth_token = oauth_token; + } + + const userver::ydb::impl::TableSettings table_settings; + const userver::ydb::OperationSettings operation_settings{ + 3, + std::chrono::minutes{3}, + std::chrono::minutes{3}, + std::chrono::minutes{3}, + userver::ydb::TransactionMode::kSerializableRW, + std::chrono::minutes{3}, + }; + + clients.driver = std::make_shared("slo", std::move(driver_settings)); + clients.table_client = std::make_shared( + table_settings, + operation_settings, + userver::dynamic_config::GetDefaultSource(), + clients.driver + ); +} + +userver::ydb::TableClient& GetTableClient() { + auto& clients = GetClients(); + if (!clients.table_client) { + throw std::runtime_error("userver TableClient is not initialized"); + } + return *clients.table_client; +} + +void ShutdownTableClient() { + auto& clients = GetClients(); + clients.table_client.reset(); + clients.driver.reset(); +} + +userver::ydb::PreparedArgsBuilder PackValuesToPreparedArgs( + const std::vector& items, + const std::string& name +) { + NYdb::TValueBuilder items_as_list; + items_as_list.BeginList(); + for (const auto& item : items) { + items_as_list.AddListItem(item); + } + items_as_list.EndList(); + + NYdb::TParamsBuilder params_builder; + params_builder.AddParam(name, items_as_list.Build()); + return userver::ydb::PreparedArgsBuilder(std::move(params_builder)); +} + +} // namespace userver_slo diff --git a/tests/slo_workloads/userver/key_value/userver_table_client.h b/tests/slo_workloads/userver/key_value/userver_table_client.h new file mode 100644 index 0000000000..40dc888126 --- /dev/null +++ b/tests/slo_workloads/userver/key_value/userver_table_client.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include +#include + +#include +#include +#include + +namespace userver_slo { + +void InitTableClient( + const std::string& endpoint, + const std::string& database, + const std::shared_ptr& credentials_provider_factory, + const std::string& oauth_token, + bool prefer_local_dc +); + +userver::ydb::TableClient& GetTableClient(); + +void ShutdownTableClient(); + +userver::ydb::PreparedArgsBuilder PackValuesToPreparedArgs( + const std::vector& items, + const std::string& name = "$items" +); + +} // namespace userver_slo diff --git a/tests/slo_workloads/userver/key_value/userver_utils.cpp b/tests/slo_workloads/userver/key_value/userver_utils.cpp new file mode 100644 index 0000000000..db313e9fb9 --- /dev/null +++ b/tests/slo_workloads/userver/key_value/userver_utils.cpp @@ -0,0 +1,185 @@ +// Userver-specific DoMain() override. +// Wraps command execution inside userver::engine::RunStandalone() so that +// coroutine-based ydb::TableClient and engine primitives work correctly. +// All other utils (ParseOptions*, GetTableStats, YdbStatusToString, etc.) +// come from slo-utils-base via the shared header. + +#include "userver_utils.h" +#include "userver_table_client.h" + +#include + +#include +#include +#include +#include + +#include + +using namespace NLastGetopt; +using namespace NYdb; + +namespace { + +bool ParseToken(std::string& token, std::string& tokenFile) { + if (!tokenFile.empty()) { + if (!token.empty()) { + Cerr << "Both token and token_file provided. Choose one." << Endl; + } else { + TFsPath path(tokenFile); + if (path.Exists()) { + token = Strip(TUnbufferedFileInput(path).ReadAll()); + return true; + } + Cerr << "Wrong path provided for token_file." << Endl; + } + } else if (!token.empty()) { + return true; + } else { + token = GetEnv("YDB_TOKEN"); + return true; + } + return false; +} + +void StartStatCollecting([[maybe_unused]] TDriver& driver, const std::string& statConfigFile) { + if (statConfigFile.empty()) { + return; + } +} + +} // namespace + +// Override DoMain to wrap command dispatch in RunStandalone. +// The native DoMain (from slo-utils-base) runs commands directly in the calling +// thread. The userver version needs the coroutine engine running for +// ydb::TableClient, engine::Semaphore, AsyncNoSpan, SleepFor, WaitAny, etc. +int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TCleanupCommand cleanup) { + TOpts opts = TOpts::Default(); + + std::string connectionString; + std::string prefix; + std::string token; + std::string tokenFile; + std::string iamSaKeyFile; + std::string statConfigFile; + std::string balancingPolicy; + + opts.AddLongOption('c', "connection-string", "YDB connection string").Required().RequiredArgument("SCHEMA://HOST:PORT/?DATABASE=DATABASE") + .StoreResult(&connectionString); + opts.AddLongOption('p', "prefix", "Base prefix for tables").RequiredArgument("PATH") + .StoreResult(&prefix); + opts.AddLongOption('k', "token", "security token").RequiredArgument("TOKEN") + .StoreResult(&token); + opts.AddLongOption('f', "token-file", "security token file").RequiredArgument("PATH") + .StoreResult(&tokenFile); + opts.AddLongOption("iam-sa-key-file", "IAM service account key file").RequiredArgument("SECRET") + .StoreResult(&iamSaKeyFile); + opts.AddLongOption('s', "stat-config", "statistics config file").Optional().RequiredArgument("PATH") + .StoreResult(&statConfigFile); + opts.AddLongOption('b', "balancing-policy", "Balancing policy").Optional().DefaultValue("use-all-nodes").RequiredArgument("(use-all-nodes|prefer-local-dc|prefer-primary-pile)") + .StoreResult(&balancingPolicy); + opts.AddHelpOption('h'); + opts.SetFreeArgsMin(1); + opts.SetFreeArgTitle(0, "", GetCmdList()); + opts.ArgPermutation_ = NLastGetopt::REQUIRE_ORDER; + + TOptsParseResult res(&opts, argc, argv); + size_t freeArgsPos = res.GetFreeArgsPos(); + argc -= freeArgsPos; + argv += freeArgsPos; + ECommandType command = ParseCommand(*argv); + if (command == ECommandType::Unknown) { + Cerr << "Unknown command '" << *argv << "'" << Endl; + return EXIT_FAILURE; + } + + if (prefix.empty()) { + prefix = GetDatabase(connectionString); + } + + if (!ParseToken(token, tokenFile)) { + return EXIT_FAILURE; + } + + auto config = TDriverConfig(connectionString); + + std::shared_ptr credentials_provider_factory; + if (!iamSaKeyFile.empty()) { + Cout << "Enabling IAM authentication..." << Endl; + credentials_provider_factory = + CreateIamJwtFileCredentialsProviderFactory(TIamJwtFilename{.JwtFilename = iamSaKeyFile}); + } else if (!token.empty()) { + Cout << "Enabling OAuth authentication..." << Endl; + credentials_provider_factory = CreateOAuthCredentialsProviderFactory(token); + } else { + Cerr << "Warning: No authentication methods provided." << Endl; + } + + if (credentials_provider_factory) { + config.SetCredentialsProviderFactory(credentials_provider_factory); + } + + if (balancingPolicy == "use-all-nodes") { + config.SetBalancingPolicy(TBalancingPolicy::UseAllNodes()); + } else if (balancingPolicy == "prefer-local-dc") { + config.SetBalancingPolicy(TBalancingPolicy::UsePreferableLocation()); + } else if (balancingPolicy == "prefer-primary-pile") { + config.SetBalancingPolicy(TBalancingPolicy::UsePreferablePileState()); + } else { + Cerr << "Unknown balancing policy: " << balancingPolicy << Endl; + return EXIT_FAILURE; + } + + const bool prefer_local_dc = balancingPolicy == "prefer-local-dc"; + + TDriver driver(config); + + StartStatCollecting(driver, statConfigFile); + + TDatabaseOptions dbOptions{driver, prefix}; + int result = EXIT_FAILURE; + + try { + // Wrap command execution in userver engine so coroutine-based + // userver::ydb::TableClient and engine primitives work. + // DDL operations (create table, drop table) use native SDK directly and + // don't need the engine, but workload queries use userver::ydb::TableClient + // which requires the engine to be running. + userver::engine::RunStandalone(4, [&] { + userver_slo::InitTableClient( + config.GetEndpoint(), + prefix, + credentials_provider_factory, + token, + prefer_local_dc + ); + switch (command) { + case ECommandType::Create: + Cout << "Launching create command..." << Endl; + result = create(dbOptions, argc, argv); + break; + case ECommandType::Run: + Cout << "Launching run command..." << Endl; + result = run(dbOptions, argc, argv); + break; + case ECommandType::Cleanup: + Cout << "Launching cleanup command..." << Endl; + result = cleanup(dbOptions, argc); + break; + default: + Cerr << "Unknown command" << Endl; + result = EXIT_FAILURE; + break; + } + // Destroy userver clients while the engine is still running. + userver_slo::ShutdownTableClient(); + }); + } catch (const NYdb::NStatusHelpers::TYdbErrorException& e) { + Cerr << "Exception caught: " << e << Endl; + driver.Stop(true); + return EXIT_FAILURE; + } + driver.Stop(true); + return result; +} diff --git a/tests/slo_workloads/userver/key_value/userver_utils.h b/tests/slo_workloads/userver/key_value/userver_utils.h new file mode 100644 index 0000000000..f583895c9f --- /dev/null +++ b/tests/slo_workloads/userver/key_value/userver_utils.h @@ -0,0 +1,7 @@ +#pragma once + +// Re-export the shared utils header. The userver workload uses the same +// CLI parsing, option structs, and helper functions as the native workload. +// Only DoMain() is overridden in userver_utils.cpp to wrap command execution +// inside userver::engine::RunStandalone(). +#include diff --git a/tests/slo_workloads/verify_userver_docker.sh b/tests/slo_workloads/verify_userver_docker.sh new file mode 100755 index 0000000000..aa9034c34d --- /dev/null +++ b/tests/slo_workloads/verify_userver_docker.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# Smoke-test the userver SLO workload Docker image against a local YDB instance. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +COMPOSE_FILE="${REPO_ROOT}/examples/otel_tracing/docker-compose.yml" +IMAGE_NAME="${IMAGE_NAME:-ydb-slo-userver-verify}" +PRESET="${PRESET:-release-test-clang}" +CONNECTION_STRING="${CONNECTION_STRING:-grpc://127.0.0.1:2136/?database=/local}" +YDB_IMAGE="${YDB_IMAGE:-cr.yandex/yc/yandex-docker-local-ydb:25.2.1}" +YDB_CONTAINER="${YDB_CONTAINER:-ydb-slo-userver-verify-ydb}" + +failures=0 +USE_COMPOSE=0 + +log() { + echo "[verify_userver_docker] $*" +} + +run_step() { + local name="$1" + shift + log "Running: ${name}" + if "$@"; then + log "PASS: ${name}" + else + log "FAIL: ${name} (exit $?)" + failures=$((failures + 1)) + fi +} + +have_docker_compose() { + docker compose version >/dev/null 2>&1 +} + +start_ydb() { + if have_docker_compose; then + USE_COMPOSE=1 + log "Starting local YDB via docker compose..." + docker compose -f "${COMPOSE_FILE}" up -d ydb + return + fi + + USE_COMPOSE=0 + log "Starting local YDB via docker run (docker compose not available)..." + docker rm -f "${YDB_CONTAINER}" >/dev/null 2>&1 || true + docker run -d --name "${YDB_CONTAINER}" --network host \ + -e GRPC_TLS_PORT=2135 \ + -e GRPC_PORT=2136 \ + -e MON_PORT=8765 \ + -e YDB_DEFAULT_LOG_LEVEL=NOTICE \ + -e YDB_USE_IN_MEMORY_PDISKS=true \ + "${YDB_IMAGE}" >/dev/null +} + +wait_for_ydb() { + log "Waiting for YDB to become healthy..." + for _ in $(seq 1 60); do + if [ "${USE_COMPOSE}" -eq 1 ]; then + if docker compose -f "${COMPOSE_FILE}" ps ydb 2>/dev/null | grep -q "(healthy)"; then + return 0 + fi + elif docker exec "${YDB_CONTAINER}" /bin/sh -c \ + "/ydb -e grpc://localhost:2136 -d /local scheme ls" >/dev/null 2>&1; then + return 0 + fi + sleep 2 + done + return 1 +} + +cleanup() { + log "Stopping local YDB..." + if [ "${USE_COMPOSE}" -eq 1 ]; then + docker compose -f "${COMPOSE_FILE}" stop ydb 2>/dev/null || true + else + docker rm -f "${YDB_CONTAINER}" >/dev/null 2>&1 || true + fi +} + +trap cleanup EXIT + +cd "${REPO_ROOT}" + +if [ "${SKIP_BUILD:-0}" != "1" ]; then + log "Building userver SLO Docker image (preset=${PRESET})..." + cp tests/slo_workloads/.dockerignore .dockerignore + docker build -t "${IMAGE_NAME}" \ + --network=host \ + --build-arg REF=local \ + --build-arg PRESET="${PRESET}" \ + -f tests/slo_workloads/Dockerfile.userver . + rm -f .dockerignore +else + log "Skipping Docker image build (SKIP_BUILD=1)..." +fi + +start_ydb + +if ! wait_for_ydb; then + log "FAIL: YDB did not become healthy in time" + if [ "${USE_COMPOSE}" -eq 1 ]; then + docker compose -f "${COMPOSE_FILE}" logs ydb || true + else + docker logs "${YDB_CONTAINER}" || true + fi + exit 1 +fi + +run_workload() { + docker run --rm --network host "${IMAGE_NAME}" \ + --connection-string "${CONNECTION_STRING}" "$@" +} + +run_step "create" \ + run_workload create --dont-push + +run_step "run" \ + run_workload run \ + --time 30 \ + --read-rps 50 \ + --write-rps 10 \ + --read-timeout 100 \ + --write-timeout 100 \ + --dont-push + +run_step "cleanup" \ + run_workload cleanup + +if [ "${failures}" -ne 0 ]; then + log "${failures} step(s) failed" + exit 1 +fi + +log "All steps passed" diff --git a/third_party/aklomp-base64 b/third_party/aklomp-base64 new file mode 160000 index 0000000000..8bdda2d47c --- /dev/null +++ b/third_party/aklomp-base64 @@ -0,0 +1 @@ +Subproject commit 8bdda2d47caf8b066999c5bd01069e55bcd0d396 diff --git a/third_party/jwt-cpp b/third_party/jwt-cpp new file mode 160000 index 0000000000..4a537e9698 --- /dev/null +++ b/third_party/jwt-cpp @@ -0,0 +1 @@ +Subproject commit 4a537e969891dde542ad8b1a4a214955a83be29f diff --git a/third_party/opentelemetry-cpp b/third_party/opentelemetry-cpp new file mode 160000 index 0000000000..2d80af1b1d --- /dev/null +++ b/third_party/opentelemetry-cpp @@ -0,0 +1 @@ +Subproject commit 2d80af1b1d26e300d9c0f7f51fa360f22c773523 From 92077dc09badd15c5319309c83eacc610ca91287 Mon Sep 17 00:00:00 2001 From: Nikita Vasilev Date: Thu, 14 May 2026 13:56:20 +0000 Subject: [PATCH 03/27] Read Committed Prototype: Writes (#39261) --- .github/last_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last_commit.txt b/.github/last_commit.txt index 50efb6d06a..302b1c3c0c 100644 --- a/.github/last_commit.txt +++ b/.github/last_commit.txt @@ -1 +1 @@ -03a9e93a82181a6a9dc51ad88263b3ecedbb2413 +e8f8503f0807f6b8b1b2a4bedd0ccfa04559435e From 717647340850ceeed15c5c8f342e808d50413fd9 Mon Sep 17 00:00:00 2001 From: Bulat Date: Thu, 14 May 2026 13:56:28 +0000 Subject: [PATCH 04/27] fix sdk: fixed self thread join in iam cred provider (#39506) --- .github/last_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last_commit.txt b/.github/last_commit.txt index 302b1c3c0c..a5dd013ba6 100644 --- a/.github/last_commit.txt +++ b/.github/last_commit.txt @@ -1 +1 @@ -e8f8503f0807f6b8b1b2a4bedd0ccfa04559435e +0140ad83476bf596b22c297ae6b397015eedb415 From fbe66c3e4100bb22e109d6a2921f3f156eba0449 Mon Sep 17 00:00:00 2001 From: Kuzin Roman Date: Thu, 14 May 2026 13:56:34 +0000 Subject: [PATCH 05/27] LOGBROKER-10375 Add new fields to api (#39843) --- .github/last_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last_commit.txt b/.github/last_commit.txt index a5dd013ba6..79dd748456 100644 --- a/.github/last_commit.txt +++ b/.github/last_commit.txt @@ -1 +1 @@ -0140ad83476bf596b22c297ae6b397015eedb415 +5c8005dc72cdde8e947e3c136e93fa5b2fae92d0 From 13af03a0aa6c6d6508daa8f47f6d4d26dd26efdf Mon Sep 17 00:00:00 2001 From: Maria Okorochkova Date: Thu, 14 May 2026 13:56:41 +0000 Subject: [PATCH 06/27] Upgrade metrics and spans (#38844) --- .github/last_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last_commit.txt b/.github/last_commit.txt index 79dd748456..aad050b59d 100644 --- a/.github/last_commit.txt +++ b/.github/last_commit.txt @@ -1 +1 @@ -5c8005dc72cdde8e947e3c136e93fa5b2fae92d0 +2a61872a4fca91a57c22fb14a50c4ba7bc080595 From 0e129383e88c231363c1e3447576191bd367799c Mon Sep 17 00:00:00 2001 From: Bulat Date: Thu, 14 May 2026 13:56:48 +0000 Subject: [PATCH 07/27] fix sdk: build in internal repo (#40078) --- .github/last_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last_commit.txt b/.github/last_commit.txt index aad050b59d..9757f06d5f 100644 --- a/.github/last_commit.txt +++ b/.github/last_commit.txt @@ -1 +1 @@ -2a61872a4fca91a57c22fb14a50c4ba7bc080595 +fafb82af7e0c98cf09f07942d358ed1d61d0356a From 5fde914e29a8ba910cb39d7d5449d0a66e1b1ac3 Mon Sep 17 00:00:00 2001 From: Ermoshkin Artem <94714022+Shfdis@users.noreply.github.com> Date: Thu, 14 May 2026 13:56:54 +0000 Subject: [PATCH 08/27] assert callback contract (#40049) --- .github/last_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last_commit.txt b/.github/last_commit.txt index 9757f06d5f..405827c17a 100644 --- a/.github/last_commit.txt +++ b/.github/last_commit.txt @@ -1 +1 @@ -fafb82af7e0c98cf09f07942d358ed1d61d0356a +2f11d1cfdb9b255e35e629adf66d9a5da07a8b60 From 3b93fc7d2bce2ec0574423963ef96e75592625de Mon Sep 17 00:00:00 2001 From: nfrmtk Date: Thu, 14 May 2026 13:57:01 +0000 Subject: [PATCH 09/27] cs min_max index kqp integration (#38585) --- .github/last_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last_commit.txt b/.github/last_commit.txt index 405827c17a..3fc90834c3 100644 --- a/.github/last_commit.txt +++ b/.github/last_commit.txt @@ -1 +1 @@ -2f11d1cfdb9b255e35e629adf66d9a5da07a8b60 +f2cc2cd62f485fed7fa25581c8831fa51c8ce5de From e0ef02e5968c07b1bb04605084e95c6871cfced6 Mon Sep 17 00:00:00 2001 From: Ermoshkin Artem <94714022+Shfdis@users.noreply.github.com> Date: Thu, 14 May 2026 13:57:07 +0000 Subject: [PATCH 10/27] changelog version (#40180) --- .github/last_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last_commit.txt b/.github/last_commit.txt index 3fc90834c3..b528becfa8 100644 --- a/.github/last_commit.txt +++ b/.github/last_commit.txt @@ -1 +1 @@ -f2cc2cd62f485fed7fa25581c8831fa51c8ce5de +e8e44c8c3d8f374f68b2d93837e7d23644c23f32 From 723bbc6ccfdfbacae74a9a52aea1e0e625dca22d Mon Sep 17 00:00:00 2001 From: Ermoshkin Artem <94714022+Shfdis@users.noreply.github.com> Date: Thu, 14 May 2026 13:57:14 +0000 Subject: [PATCH 11/27] Fix tests that are killed by timeout (#39847) --- .github/last_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last_commit.txt b/.github/last_commit.txt index b528becfa8..67b48918bc 100644 --- a/.github/last_commit.txt +++ b/.github/last_commit.txt @@ -1 +1 @@ -e8e44c8c3d8f374f68b2d93837e7d23644c23f32 +75c80b9e924f15409677944dca9c1db28d5f6b73 From d8e6d5857cebdb65f5b402a6e787aef548c6922d Mon Sep 17 00:00:00 2001 From: Yuriy Kaminskiy Date: Thu, 14 May 2026 13:57:20 +0000 Subject: [PATCH 12/27] sdk & pq provider: set and use TReadSessionSettings TraceId (#40214) --- .github/last_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last_commit.txt b/.github/last_commit.txt index 67b48918bc..c7183b8e46 100644 --- a/.github/last_commit.txt +++ b/.github/last_commit.txt @@ -1 +1 @@ -75c80b9e924f15409677944dca9c1db28d5f6b73 +216c2fd7a6dbe62c1f5c0aa9e7f810c3f61524e6 From e172089e60e3e7396963fd4b61a3a51a6ab5a927 Mon Sep 17 00:00:00 2001 From: Ermoshkin Artem <94714022+Shfdis@users.noreply.github.com> Date: Thu, 14 May 2026 13:57:27 +0000 Subject: [PATCH 13/27] fix sdk: added missing includes (#40272) --- .github/last_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last_commit.txt b/.github/last_commit.txt index c7183b8e46..50efb6d06a 100644 --- a/.github/last_commit.txt +++ b/.github/last_commit.txt @@ -1 +1 @@ -216c2fd7a6dbe62c1f5c0aa9e7f810c3f61524e6 +03a9e93a82181a6a9dc51ad88263b3ecedbb2413 From 534ff2af366a4ce6b1eea7946190dd1680bf9281 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Thu, 28 May 2026 10:58:43 +0300 Subject: [PATCH 14/27] Remove accidental submodule gitlinks without .gitmodules entries --- third_party/aklomp-base64 | 1 - third_party/jwt-cpp | 1 - third_party/opentelemetry-cpp | 1 - 3 files changed, 3 deletions(-) delete mode 160000 third_party/aklomp-base64 delete mode 160000 third_party/jwt-cpp delete mode 160000 third_party/opentelemetry-cpp diff --git a/third_party/aklomp-base64 b/third_party/aklomp-base64 deleted file mode 160000 index 8bdda2d47c..0000000000 --- a/third_party/aklomp-base64 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8bdda2d47caf8b066999c5bd01069e55bcd0d396 diff --git a/third_party/jwt-cpp b/third_party/jwt-cpp deleted file mode 160000 index 4a537e9698..0000000000 --- a/third_party/jwt-cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4a537e969891dde542ad8b1a4a214955a83be29f diff --git a/third_party/opentelemetry-cpp b/third_party/opentelemetry-cpp deleted file mode 160000 index 2d80af1b1d..0000000000 --- a/third_party/opentelemetry-cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2d80af1b1d26e300d9c0f7f51fa360f22c773523 From a58dd89ff3c23e0350ed7eb0d5ed978deffb6a54 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Thu, 28 May 2026 13:03:17 +0300 Subject: [PATCH 15/27] fixe --- .dockerignore | 48 ++++++++++++++++++++++++++ tests/slo_workloads/Dockerfile.userver | 8 ++--- 2 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..9e958bd1bf --- /dev/null +++ b/.dockerignore @@ -0,0 +1,48 @@ +# Git and version control +.git +.gitignore +.github +.gitattributes + +# IDE and editor files +.idea/ +.vscode/ +.cache/ +.cursor/ +.cursorrules +*.swp +*.swo +.DS_Store +*.dSYM +.clangd +compile_commands.json + +# Build artifacts and cache +*.o +*.a +*.so +*.dylib +*.exe +*.out +*.log +*.pyc +__pycache__/ +*.egg-info/ +.pytest_cache/ +node_modules/ + +# Documentation +*.md +docs/ +!requirements.txt + +# CI/CD +.circleci +.travis.yml +azure-pipelines.yml + +# Large directories not needed for SDK builds +ydb/core/ +ydb/apps/ +ydb/services/ +ydb/tools/ diff --git a/tests/slo_workloads/Dockerfile.userver b/tests/slo_workloads/Dockerfile.userver index 33acb06da4..19dcd7872f 100644 --- a/tests/slo_workloads/Dockerfile.userver +++ b/tests/slo_workloads/Dockerfile.userver @@ -21,7 +21,7 @@ RUN apt-get -y update && apt-get -y install software-properties-common && add-ap RUN apt-get -y update && apt-get -y install \ git gdb wget ninja-build libidn11-dev ragel yasm libc-ares-dev libre2-dev \ rapidjson-dev zlib1g-dev libxxhash-dev libzstd-dev libsnappy-dev libgtest-dev libgmock-dev \ - libbz2-dev liblz4-dev libdouble-conversion-dev libssl-dev libstdc++-13-dev gcc-13 g++-13 \ + libbz2-dev liblz4-dev libdouble-conversion-dev libssl-dev libstdc++-14-dev gcc-14 g++-14 \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Install CMake @@ -44,9 +44,9 @@ RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-${LLVM_VER update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-${LLVM_VERSION} 10000 && \ update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-${LLVM_VERSION} 10000 -# Update alternatives to use gcc-13 by default -RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 10000 && \ - update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 10000 +# Update alternatives to use gcc-14 by default +RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 10000 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 10000 # Install abseil-cpp ENV ABSEIL_CPP_VERSION=20230802.0 From 071191e149f4463d5090b8b0e46ea330c4eadc10 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Thu, 28 May 2026 13:17:47 +0300 Subject: [PATCH 16/27] Remove unrelated debian packaging files from SLO branch --- debian/libydb-cpp-dev.install | 10 ---------- debian/libydb-cpp-iam-dev.install | 1 - debian/libydb-cpp-otel-common-dev.install | 4 ---- debian/libydb-cpp-otel-metrics-dev.install | 2 -- debian/libydb-cpp-otel-tracing-dev.install | 2 -- 5 files changed, 19 deletions(-) delete mode 100644 debian/libydb-cpp-dev.install delete mode 100644 debian/libydb-cpp-iam-dev.install delete mode 100644 debian/libydb-cpp-otel-common-dev.install delete mode 100644 debian/libydb-cpp-otel-metrics-dev.install delete mode 100644 debian/libydb-cpp-otel-tracing-dev.install diff --git a/debian/libydb-cpp-dev.install b/debian/libydb-cpp-dev.install deleted file mode 100644 index 9edd35538e..0000000000 --- a/debian/libydb-cpp-dev.install +++ /dev/null @@ -1,10 +0,0 @@ -debian/tmp/usr/share/yandex/lib/*/libydb-cpp.a usr/share/yandex/lib/ -debian/tmp/usr/share/yandex/include/ydb-cpp-sdk usr/share/yandex/include/ -debian/tmp/usr/share/yandex/include/__ydb_sdk_special_headers usr/share/yandex/include/ -debian/tmp/usr/share/yandex/lib/*/cmake/ydb-cpp-sdk usr/share/yandex/lib/*/cmake/ -debian/tmp/usr/share/yandex/include/libbase64.h usr/share/yandex/include/ -debian/tmp/usr/share/yandex/lib/*/libbase64.a usr/share/yandex/lib/ -debian/tmp/usr/share/yandex/lib/*/cmake/base64 usr/share/yandex/lib/*/cmake/ -debian/tmp/usr/share/yandex/include/picojson usr/share/yandex/include/ -debian/tmp/usr/share/yandex/include/jwt-cpp usr/share/yandex/include/ -debian/tmp/usr/share/yandex/cmake/jwt-cpp* usr/share/yandex/cmake/ diff --git a/debian/libydb-cpp-iam-dev.install b/debian/libydb-cpp-iam-dev.install deleted file mode 100644 index 9667f34f8c..0000000000 --- a/debian/libydb-cpp-iam-dev.install +++ /dev/null @@ -1 +0,0 @@ -debian/tmp/usr/share/yandex/lib/*/libydb-cpp-iam.a usr/share/yandex/lib/ diff --git a/debian/libydb-cpp-otel-common-dev.install b/debian/libydb-cpp-otel-common-dev.install deleted file mode 100644 index 7dd0ad85cb..0000000000 --- a/debian/libydb-cpp-otel-common-dev.install +++ /dev/null @@ -1,4 +0,0 @@ -debian/tmp/usr/share/yandex/include/opentelemetry usr/share/yandex/include/ -debian/tmp/usr/share/yandex/lib/*/libopentelemetry_* usr/share/yandex/lib/ -debian/tmp/usr/share/yandex/lib/*/cmake/opentelemetry-cpp usr/share/yandex/lib/*/cmake/ -debian/tmp/usr/share/yandex/lib/*/pkgconfig/opentelemetry_*.pc usr/share/yandex/lib/*/pkgconfig/ diff --git a/debian/libydb-cpp-otel-metrics-dev.install b/debian/libydb-cpp-otel-metrics-dev.install deleted file mode 100644 index 9e60e657a2..0000000000 --- a/debian/libydb-cpp-otel-metrics-dev.install +++ /dev/null @@ -1,2 +0,0 @@ -debian/tmp/usr/share/yandex/lib/*/libydb-cpp-otel-metrics.a usr/share/yandex/lib/ -debian/tmp/usr/share/yandex/include/ydb-cpp-sdk/open_telemetry/metrics.h usr/share/yandex/include/ydb-cpp-sdk/open_telemetry/ diff --git a/debian/libydb-cpp-otel-tracing-dev.install b/debian/libydb-cpp-otel-tracing-dev.install deleted file mode 100644 index b3af18b0e5..0000000000 --- a/debian/libydb-cpp-otel-tracing-dev.install +++ /dev/null @@ -1,2 +0,0 @@ -debian/tmp/usr/share/yandex/lib/*/libydb-cpp-otel-tracing.a usr/share/yandex/lib/ -debian/tmp/usr/share/yandex/include/ydb-cpp-sdk/open_telemetry/trace.h usr/share/yandex/include/ydb-cpp-sdk/open_telemetry/ From 456f295d6949e1a9d0ecdd7f4f476e9df1f1e7d2 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Thu, 28 May 2026 15:18:06 +0300 Subject: [PATCH 17/27] fix gcc version for the sdk --- tests/slo_workloads/Dockerfile.userver | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/slo_workloads/Dockerfile.userver b/tests/slo_workloads/Dockerfile.userver index 19dcd7872f..ea403a8c1b 100644 --- a/tests/slo_workloads/Dockerfile.userver +++ b/tests/slo_workloads/Dockerfile.userver @@ -134,8 +134,13 @@ ARG REF=unknown ARG USERVER_VERSION=v2.12 COPY --from=userver-deb /userver/libuserver-all-dev*.deb /tmp/ +# libuserver-all-dev depends on build-essential (gcc-11), which overrides gcc-14 alternatives. RUN apt-get -y update && apt-get -y install --no-install-recommends /tmp/libuserver-all-dev*.deb \ - && rm -rf /tmp/libuserver-all-dev*.deb /var/lib/apt/lists/* + && rm -rf /tmp/libuserver-all-dev*.deb /var/lib/apt/lists/* \ + && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 10000 \ + && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 10000 \ + && update-alternatives --set gcc /usr/bin/gcc-14 \ + && update-alternatives --set g++ /usr/bin/g++-14 RUN git clone --depth 1 --branch ${USERVER_VERSION} \ https://github.com/userver-framework/userver.git /userver-src \ From b828c034c2de314aa5622a72f3e7aeabe490be4f Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Thu, 28 May 2026 20:29:33 +0300 Subject: [PATCH 18/27] fix slo workload --- .dockerignore | 48 ------ tests/slo_workloads/.dockerignore | 5 + .../userver/key_value/create.cpp | 156 +++++++++--------- 3 files changed, 84 insertions(+), 125 deletions(-) delete mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 9e958bd1bf..0000000000 --- a/.dockerignore +++ /dev/null @@ -1,48 +0,0 @@ -# Git and version control -.git -.gitignore -.github -.gitattributes - -# IDE and editor files -.idea/ -.vscode/ -.cache/ -.cursor/ -.cursorrules -*.swp -*.swo -.DS_Store -*.dSYM -.clangd -compile_commands.json - -# Build artifacts and cache -*.o -*.a -*.so -*.dylib -*.exe -*.out -*.log -*.pyc -__pycache__/ -*.egg-info/ -.pytest_cache/ -node_modules/ - -# Documentation -*.md -docs/ -!requirements.txt - -# CI/CD -.circleci -.travis.yml -azure-pipelines.yml - -# Large directories not needed for SDK builds -ydb/core/ -ydb/apps/ -ydb/services/ -ydb/tools/ diff --git a/tests/slo_workloads/.dockerignore b/tests/slo_workloads/.dockerignore index 9e958bd1bf..78983a952d 100644 --- a/tests/slo_workloads/.dockerignore +++ b/tests/slo_workloads/.dockerignore @@ -46,3 +46,8 @@ ydb/core/ ydb/apps/ ydb/services/ ydb/tools/ + +# Local orphan submodule checkouts (untracked; not used by SLO Docker builds) +third_party/aklomp-base64/ +third_party/jwt-cpp/ +third_party/opentelemetry-cpp/ diff --git a/tests/slo_workloads/userver/key_value/create.cpp b/tests/slo_workloads/userver/key_value/create.cpp index 4991c374ac..37d298af7b 100644 --- a/tests/slo_workloads/userver/key_value/create.cpp +++ b/tests/slo_workloads/userver/key_value/create.cpp @@ -57,31 +57,33 @@ int DoCreate(TDatabaseOptions& dbOptions, int argc, char** argv) { auto& ydbClient = userver_slo::GetTableClient(); - TStat stats( - opts.DontPushMetrics ? std::nullopt : std::make_optional(opts.MetricsPushUrl), - "generate", - MakeNativeSloOtelResourceAttributes(), - NativeSloMeterSchemaVersion() - ); - stats.Start(); - - // Generate initial content using userver coroutines - TPackGenerator packGenerator( - MakeGeneratorOptions(opts), - createOptions.PackSize, - [](const TKeyValueRecordData& recordData) { return BuildValueFromRecord(recordData); }, - createOptions.Count, - maxId - ); - - userver::engine::Semaphore semaphore{ - static_cast(opts.MaxInfly)}; - - std::atomic succeeded{0}; - std::atomic failed{0}; - std::atomic total{0}; - - const TString query = Sprintf(R"( + // Engine primitives (Semaphore, AsyncNoSpan) require a task context; run.cpp + // wraps its workload loop the same way. + userver::engine::AsyncNoSpan([&]() { + TStat stats( + opts.DontPushMetrics ? std::nullopt : std::make_optional(opts.MetricsPushUrl), + "generate", + MakeNativeSloOtelResourceAttributes(), + NativeSloMeterSchemaVersion() + ); + stats.Start(); + + TPackGenerator packGenerator( + MakeGeneratorOptions(opts), + createOptions.PackSize, + [](const TKeyValueRecordData& recordData) { return BuildValueFromRecord(recordData); }, + createOptions.Count, + maxId + ); + + userver::engine::Semaphore semaphore{ + static_cast(opts.MaxInfly)}; + + std::atomic succeeded{0}; + std::atomic failed{0}; + std::atomic total{0}; + + const TString query = Sprintf(R"( --!syntax_v1 PRAGMA TablePathPrefix("%s"); @@ -96,59 +98,59 @@ UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); )", prefix.c_str(), TableName.c_str()); - std::vector> tasks; - - std::vector pack; - while (!ShouldStop.load() && packGenerator.GetNextPack(pack)) { - semaphore.lock_shared(); - total.fetch_add(1); - - auto params = userver_slo::PackValuesToPreparedArgs(pack); - - tasks.push_back(userver::engine::AsyncNoSpan( - [&ydbClient, &semaphore, &stats, &succeeded, &failed, - &opts, query, params = std::move(params)]() mutable { - auto stat = stats.StartRequest(); - try { - uydb::OperationSettings settings; - settings.client_timeout_ms = std::chrono::milliseconds(opts.ReactionTime.MilliSeconds()); - - ydbClient.ExecuteDataQuery(settings, uydb::Query{query}, std::move(params)); - - TSloRequestFinish finish; - finish.StatusLabel = "SUCCESS"; - stats.FinishRequest(stat, finish); - succeeded.fetch_add(1); - } catch (const uydb::YdbResponseError& e) { - TSloRequestFinish finish; - finish.StatusLabel = YdbStatusToString(e.GetStatus().GetStatus()); - stats.FinishRequest(stat, finish); - failed.fetch_add(1); - } catch (const std::exception& e) { - TSloRequestFinish finish; - finish.StatusLabel = "UNKNOWN_ERROR"; - stats.FinishRequest(stat, finish); - failed.fetch_add(1); + std::vector> tasks; + + std::vector pack; + while (!ShouldStop.load() && packGenerator.GetNextPack(pack)) { + semaphore.lock_shared(); + total.fetch_add(1); + + auto params = userver_slo::PackValuesToPreparedArgs(pack); + + tasks.push_back(userver::engine::AsyncNoSpan( + [&ydbClient, &semaphore, &stats, &succeeded, &failed, + &opts, query, params = std::move(params)]() mutable { + auto stat = stats.StartRequest(); + try { + uydb::OperationSettings settings; + settings.client_timeout_ms = std::chrono::milliseconds(opts.ReactionTime.MilliSeconds()); + + ydbClient.ExecuteDataQuery(settings, uydb::Query{query}, std::move(params)); + + TSloRequestFinish finish; + finish.StatusLabel = "SUCCESS"; + stats.FinishRequest(stat, finish); + succeeded.fetch_add(1); + } catch (const uydb::YdbResponseError& e) { + TSloRequestFinish finish; + finish.StatusLabel = YdbStatusToString(e.GetStatus().GetStatus()); + stats.FinishRequest(stat, finish); + failed.fetch_add(1); + } catch (const std::exception& e) { + TSloRequestFinish finish; + finish.StatusLabel = "UNKNOWN_ERROR"; + stats.FinishRequest(stat, finish); + failed.fetch_add(1); + } + semaphore.unlock_shared(); } - semaphore.unlock_shared(); - } - )); - } - - // Wait for all tasks to complete - for (auto& task : tasks) { - task.Wait(); - } - - stats.Finish(); - - TStringBuilder report; - report << Endl << "======- GenerateInitialContentJob report -======" << Endl; - report << "Total: " << total.load() << ", Succeeded: " << succeeded.load() - << ", Failed: " << failed.load() << Endl; - stats.PrintStatistics(report); - report << "========================================" << Endl; - Cout << report; + )); + } + + for (auto& task : tasks) { + task.Wait(); + } + + stats.Finish(); + + TStringBuilder report; + report << Endl << "======- GenerateInitialContentJob report -======" << Endl; + report << "Total: " << total.load() << ", Succeeded: " << succeeded.load() + << ", Failed: " << failed.load() << Endl; + stats.PrintStatistics(report); + report << "========================================" << Endl; + Cout << report; + }).Wait(); return EXIT_SUCCESS; } From 822fe339c106714147efa1a093dba217b66d7f14 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Thu, 28 May 2026 23:14:10 +0300 Subject: [PATCH 19/27] fix workload build time --- .github/workflows/slo.yml | 2 +- third_party/jwt-cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 third_party/jwt-cpp diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index 3b0f987c70..6d01f11430 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -10,7 +10,7 @@ jobs: name: Run YDB SLO Tests (${{ matrix.workload }}, ${{ matrix.compiler }}) runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 120 permissions: contents: read diff --git a/third_party/jwt-cpp b/third_party/jwt-cpp new file mode 160000 index 0000000000..4a537e9698 --- /dev/null +++ b/third_party/jwt-cpp @@ -0,0 +1 @@ +Subproject commit 4a537e969891dde542ad8b1a4a214955a83be29f From 3d756aeb28f94c9f64cd4b0fd03301854d26b6d2 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Thu, 28 May 2026 23:16:22 +0300 Subject: [PATCH 20/27] fix --- third_party/jwt-cpp | 1 - 1 file changed, 1 deletion(-) delete mode 160000 third_party/jwt-cpp diff --git a/third_party/jwt-cpp b/third_party/jwt-cpp deleted file mode 160000 index 4a537e9698..0000000000 --- a/third_party/jwt-cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4a537e969891dde542ad8b1a4a214955a83be29f From bf67c64bade8a75761fe88cf706a7d7052d0348d Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Fri, 29 May 2026 10:56:07 +0300 Subject: [PATCH 21/27] restore native behaviour --- tests/slo_workloads/core/CMakeLists.txt | 2 + tests/slo_workloads/core/metrics.h | 5 +- tests/slo_workloads/core/slo_text_utils.cpp | 164 ++++++++++++++++++ tests/slo_workloads/core/slo_text_utils.h | 4 + tests/slo_workloads/core/statistics.cpp | 50 ++++-- tests/slo_workloads/core/statistics.h | 8 +- tests/slo_workloads/native/utils/executor.cpp | 8 +- tests/slo_workloads/native/utils/utils.cpp | 65 +------ 8 files changed, 221 insertions(+), 85 deletions(-) diff --git a/tests/slo_workloads/core/CMakeLists.txt b/tests/slo_workloads/core/CMakeLists.txt index 9808bf0fdd..873edad0fa 100644 --- a/tests/slo_workloads/core/CMakeLists.txt +++ b/tests/slo_workloads/core/CMakeLists.txt @@ -15,6 +15,8 @@ add_library(slo-workload-core) target_link_libraries(slo-workload-core PUBLIC yutil + client-types-status + client-ydb_driver opentelemetry-cpp::metrics opentelemetry-cpp::otlp_http_metric_exporter hdr_histogram_static diff --git a/tests/slo_workloads/core/metrics.h b/tests/slo_workloads/core/metrics.h index ccfd31a467..ff39d19a7b 100644 --- a/tests/slo_workloads/core/metrics.h +++ b/tests/slo_workloads/core/metrics.h @@ -1,16 +1,17 @@ #pragma once +#include + #include #include #include #include -#include #include struct TRequestData { TDuration Delay; - std::string StatusLabel; + NYdb::EStatus Status; std::uint64_t RetryAttempts; }; diff --git a/tests/slo_workloads/core/slo_text_utils.cpp b/tests/slo_workloads/core/slo_text_utils.cpp index 0d2eedbfaa..c7bacab22d 100644 --- a/tests/slo_workloads/core/slo_text_utils.cpp +++ b/tests/slo_workloads/core/slo_text_utils.cpp @@ -33,3 +33,167 @@ std::string SloGenerateRandomString(std::uint32_t minLength, std::uint32_t maxLe } return result; } + +std::string SloYdbStatusToString(NYdb::EStatus status) { + switch (status) { + case NYdb::EStatus::SUCCESS: + return "SUCCESS"; + case NYdb::EStatus::BAD_REQUEST: + return "BAD_REQUEST"; + case NYdb::EStatus::UNAUTHORIZED: + return "UNAUTHORIZED"; + case NYdb::EStatus::INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case NYdb::EStatus::ABORTED: + return "ABORTED"; + case NYdb::EStatus::UNAVAILABLE: + return "UNAVAILABLE"; + case NYdb::EStatus::OVERLOADED: + return "OVERLOADED"; + case NYdb::EStatus::SCHEME_ERROR: + return "SCHEME_ERROR"; + case NYdb::EStatus::GENERIC_ERROR: + return "GENERIC_ERROR"; + case NYdb::EStatus::TIMEOUT: + return "TIMEOUT"; + case NYdb::EStatus::BAD_SESSION: + return "BAD_SESSION"; + case NYdb::EStatus::PRECONDITION_FAILED: + return "PRECONDITION_FAILED"; + case NYdb::EStatus::ALREADY_EXISTS: + return "ALREADY_EXISTS"; + case NYdb::EStatus::NOT_FOUND: + return "NOT_FOUND"; + case NYdb::EStatus::SESSION_EXPIRED: + return "SESSION_EXPIRED"; + case NYdb::EStatus::CANCELLED: + return "CANCELLED"; + case NYdb::EStatus::UNDETERMINED: + return "UNDETERMINED"; + case NYdb::EStatus::UNSUPPORTED: + return "UNSUPPORTED"; + case NYdb::EStatus::SESSION_BUSY: + return "SESSION_BUSY"; + case NYdb::EStatus::EXTERNAL_ERROR: + return "EXTERNAL_ERROR"; + case NYdb::EStatus::STATUS_UNDEFINED: + return "STATUS_UNDEFINED"; + case NYdb::EStatus::TRANSPORT_UNAVAILABLE: + return "TRANSPORT_UNAVAILABLE"; + case NYdb::EStatus::CLIENT_RESOURCE_EXHAUSTED: + return "CLIENT_RESOURCE_EXHAUSTED"; + case NYdb::EStatus::CLIENT_DEADLINE_EXCEEDED: + return "CLIENT_DEADLINE_EXCEEDED"; + case NYdb::EStatus::CLIENT_INTERNAL_ERROR: + return "CLIENT_INTERNAL_ERROR"; + case NYdb::EStatus::CLIENT_CANCELLED: + return "CLIENT_CANCELLED"; + case NYdb::EStatus::CLIENT_UNAUTHENTICATED: + return "CLIENT_UNAUTHENTICATED"; + case NYdb::EStatus::CLIENT_CALL_UNIMPLEMENTED: + return "CLIENT_CALL_UNIMPLEMENTED"; + case NYdb::EStatus::CLIENT_OUT_OF_RANGE: + return "CLIENT_OUT_OF_RANGE"; + case NYdb::EStatus::CLIENT_DISCOVERY_FAILED: + return "CLIENT_DISCOVERY_FAILED"; + case NYdb::EStatus::CLIENT_LIMITS_REACHED: + return "CLIENT_LIMITS_REACHED"; + } +} + +NYdb::EStatus SloYdbStatusFromString(const std::string& label) { + if (label == "SUCCESS") { + return NYdb::EStatus::SUCCESS; + } + if (label == "BAD_REQUEST") { + return NYdb::EStatus::BAD_REQUEST; + } + if (label == "UNAUTHORIZED") { + return NYdb::EStatus::UNAUTHORIZED; + } + if (label == "INTERNAL_ERROR") { + return NYdb::EStatus::INTERNAL_ERROR; + } + if (label == "ABORTED") { + return NYdb::EStatus::ABORTED; + } + if (label == "UNAVAILABLE") { + return NYdb::EStatus::UNAVAILABLE; + } + if (label == "OVERLOADED") { + return NYdb::EStatus::OVERLOADED; + } + if (label == "SCHEME_ERROR") { + return NYdb::EStatus::SCHEME_ERROR; + } + if (label == "GENERIC_ERROR" || label == "UNKNOWN_ERROR") { + return NYdb::EStatus::GENERIC_ERROR; + } + if (label == "TIMEOUT" || label == "APPLICATION_TIMEOUT") { + return NYdb::EStatus::TIMEOUT; + } + if (label == "BAD_SESSION") { + return NYdb::EStatus::BAD_SESSION; + } + if (label == "PRECONDITION_FAILED") { + return NYdb::EStatus::PRECONDITION_FAILED; + } + if (label == "ALREADY_EXISTS") { + return NYdb::EStatus::ALREADY_EXISTS; + } + if (label == "NOT_FOUND") { + return NYdb::EStatus::NOT_FOUND; + } + if (label == "SESSION_EXPIRED") { + return NYdb::EStatus::SESSION_EXPIRED; + } + if (label == "CANCELLED") { + return NYdb::EStatus::CANCELLED; + } + if (label == "UNDETERMINED") { + return NYdb::EStatus::UNDETERMINED; + } + if (label == "UNSUPPORTED") { + return NYdb::EStatus::UNSUPPORTED; + } + if (label == "SESSION_BUSY") { + return NYdb::EStatus::SESSION_BUSY; + } + if (label == "EXTERNAL_ERROR") { + return NYdb::EStatus::EXTERNAL_ERROR; + } + if (label == "STATUS_UNDEFINED") { + return NYdb::EStatus::STATUS_UNDEFINED; + } + if (label == "TRANSPORT_UNAVAILABLE") { + return NYdb::EStatus::TRANSPORT_UNAVAILABLE; + } + if (label == "CLIENT_RESOURCE_EXHAUSTED") { + return NYdb::EStatus::CLIENT_RESOURCE_EXHAUSTED; + } + if (label == "CLIENT_DEADLINE_EXCEEDED") { + return NYdb::EStatus::CLIENT_DEADLINE_EXCEEDED; + } + if (label == "CLIENT_INTERNAL_ERROR") { + return NYdb::EStatus::CLIENT_INTERNAL_ERROR; + } + if (label == "CLIENT_CANCELLED") { + return NYdb::EStatus::CLIENT_CANCELLED; + } + if (label == "CLIENT_UNAUTHENTICATED") { + return NYdb::EStatus::CLIENT_UNAUTHENTICATED; + } + if (label == "CLIENT_CALL_UNIMPLEMENTED") { + return NYdb::EStatus::CLIENT_CALL_UNIMPLEMENTED; + } + if (label == "CLIENT_OUT_OF_RANGE") { + return NYdb::EStatus::CLIENT_OUT_OF_RANGE; + } + if (label == "CLIENT_DISCOVERY_FAILED") { + return NYdb::EStatus::CLIENT_DISCOVERY_FAILED; + } + if (label == "CLIENT_LIMITS_REACHED") { + return NYdb::EStatus::CLIENT_LIMITS_REACHED; + } + return NYdb::EStatus::GENERIC_ERROR; +} diff --git a/tests/slo_workloads/core/slo_text_utils.h b/tests/slo_workloads/core/slo_text_utils.h index f32fa43a97..e9850cacd4 100644 --- a/tests/slo_workloads/core/slo_text_utils.h +++ b/tests/slo_workloads/core/slo_text_utils.h @@ -1,8 +1,12 @@ #pragma once +#include + #include #include std::string SloJoinPath(const std::string& prefix, const std::string& path); std::string SloGetDatabaseFromConnectionString(const std::string& connectionString); std::string SloGenerateRandomString(std::uint32_t minLength, std::uint32_t maxLength); +std::string SloYdbStatusToString(NYdb::EStatus status); +NYdb::EStatus SloYdbStatusFromString(const std::string& label); diff --git a/tests/slo_workloads/core/statistics.cpp b/tests/slo_workloads/core/statistics.cpp index f65e0df49a..e8caec271f 100644 --- a/tests/slo_workloads/core/statistics.cpp +++ b/tests/slo_workloads/core/statistics.cpp @@ -1,5 +1,7 @@ #include "statistics.h" +#include "slo_text_utils.h" + #include #include @@ -64,6 +66,34 @@ std::shared_ptr TStat::StartRequest() { return std::make_shared(TInstant::Now()); } +void TStat::FinishRequest(const std::shared_ptr& unit, const TFinalStatus& status) { + std::lock_guard lock(Mutex); + + unit->End = TInstant::Now(); + + auto delay = unit->End - unit->Start; + + --Infly; + + if (status) { + ++Statuses[status->GetStatus()]; + } else { + ++ApplicationTimeout; + } + + if (status && status->GetStatus() == NYdb::EStatus::SUCCESS) { + OkDelays.push_back(delay); + } + + ScheduleMetricsPush([this, delay, status, unit]() { + MetricsPusher->PushRequestData({ + .Delay = delay, + .Status = status ? status->GetStatus() : NYdb::EStatus::TIMEOUT, + .RetryAttempts = unit->RetryAttempts, + }); + }); +} + void TStat::FinishRequest(const std::shared_ptr& unit, const TSloRequestFinish& finish) { std::lock_guard lock(Mutex); @@ -71,25 +101,23 @@ void TStat::FinishRequest(const std::shared_ptr& unit, const TSloRequ const auto delay = unit->End - unit->Start; --Infly; - std::string metricStatusLabel; + NYdb::EStatus metricStatus = NYdb::EStatus::GENERIC_ERROR; if (finish.ApplicationTimeout) { ++ApplicationTimeout; - metricStatusLabel = "APPLICATION_TIMEOUT"; + metricStatus = NYdb::EStatus::TIMEOUT; } else if (finish.StatusLabel) { - ++Statuses[*finish.StatusLabel]; - metricStatusLabel = *finish.StatusLabel; - } else { - metricStatusLabel = "UNKNOWN"; + metricStatus = SloYdbStatusFromString(*finish.StatusLabel); + ++Statuses[metricStatus]; } if (!finish.ApplicationTimeout && finish.StatusLabel && *finish.StatusLabel == "SUCCESS") { OkDelays.push_back(delay); } - ScheduleMetricsPush([this, delay, metricStatusLabel, unit]() { + ScheduleMetricsPush([this, delay, metricStatus, unit]() { MetricsPusher->PushRequestData({ .Delay = delay, - .StatusLabel = metricStatusLabel, + .Status = metricStatus, .RetryAttempts = unit->RetryAttempts, }); }); @@ -127,12 +155,12 @@ void TStat::PrintStatistics(TStringBuilder& out) { const std::uint64_t micros = timePassed.MicroSeconds(); const std::uint64_t rps = micros ? total * 1000000 / micros : 0; out << total << " requests total" << Endl - << Statuses["SUCCESS"] << " succeeded"; + << Statuses[NYdb::EStatus::SUCCESS] << " succeeded"; if (total) { - out << " (" << Statuses["SUCCESS"] * 100 / total << "%)"; + out << " (" << Statuses[NYdb::EStatus::SUCCESS] * 100 / total << "%)"; } for (const auto& [status, counter] : Statuses) { - out << Endl << counter << " replies with status " << status << Endl; + out << Endl << counter << " replies with status " << SloYdbStatusToString(status) << Endl; } out << Endl << CountMaxInfly << " failed due to max infly" << Endl << ApplicationTimeout << " application timeouts" << Endl diff --git a/tests/slo_workloads/core/statistics.h b/tests/slo_workloads/core/statistics.h index 5f0948c53c..ead4d8406d 100644 --- a/tests/slo_workloads/core/statistics.h +++ b/tests/slo_workloads/core/statistics.h @@ -2,6 +2,8 @@ #include "metrics.h" +#include + #include #include #include @@ -22,6 +24,9 @@ inline double GetMillisecondsDouble(const TDuration& d) { return static_cast(d.MicroSeconds()) / 1000; } +using TFinalStatus = std::optional; +using TAsyncFinalStatus = NThreading::TFuture; + struct TSloRequestFinish { bool ApplicationTimeout = false; std::optional StatusLabel; @@ -62,6 +67,7 @@ class TStat { void Finish(); std::shared_ptr StartRequest(); + void FinishRequest(const std::shared_ptr& unit, const TFinalStatus& status); void FinishRequest(const std::shared_ptr& unit, const TSloRequestFinish& finish); void ReportMaxInfly(); @@ -85,7 +91,7 @@ class TStat { std::uint64_t Infly = 0; std::uint64_t ActiveSessions = 0; - std::map Statuses; + std::map Statuses; std::uint64_t CountMaxInfly = 0; std::uint64_t ApplicationTimeout = 0; std::vector OkDelays; diff --git a/tests/slo_workloads/native/utils/executor.cpp b/tests/slo_workloads/native/utils/executor.cpp index 9ea08ce341..2d7d77a53b 100644 --- a/tests/slo_workloads/native/utils/executor.cpp +++ b/tests/slo_workloads/native/utils/executor.cpp @@ -159,13 +159,7 @@ bool TExecutor::Execute(const NYdb::NTable::TTableClient::TOperationFunc& func) future.Subscribe([this, stat, SemaphoreWrapper](const TAsyncFinalStatus& future) mutable { Y_ABORT_UNLESS(future.HasValue()); TFinalStatus resultStatus = future.GetValue(); - TSloRequestFinish finish; - if (!resultStatus) { - finish.ApplicationTimeout = true; - } else { - finish.StatusLabel = YdbStatusToString(resultStatus->GetStatus()); - } - Stats.FinishRequest(stat, finish); + Stats.FinishRequest(stat, resultStatus); if (resultStatus) { CheckForError(*resultStatus); } diff --git a/tests/slo_workloads/native/utils/utils.cpp b/tests/slo_workloads/native/utils/utils.cpp index c0f060a43c..8645ea48c1 100644 --- a/tests/slo_workloads/native/utils/utils.cpp +++ b/tests/slo_workloads/native/utils/utils.cpp @@ -72,70 +72,7 @@ TParams PackValuesToParamsAsList(const std::vector& items, const std::st } std::string YdbStatusToString(NYdb::EStatus status) { - switch (status) { - case NYdb::EStatus::SUCCESS: - return "SUCCESS"; - case NYdb::EStatus::BAD_REQUEST: - return "BAD_REQUEST"; - case NYdb::EStatus::UNAUTHORIZED: - return "UNAUTHORIZED"; - case NYdb::EStatus::INTERNAL_ERROR: - return "INTERNAL_ERROR"; - case NYdb::EStatus::ABORTED: - return "ABORTED"; - case NYdb::EStatus::UNAVAILABLE: - return "UNAVAILABLE"; - case NYdb::EStatus::OVERLOADED: - return "OVERLOADED"; - case NYdb::EStatus::SCHEME_ERROR: - return "SCHEME_ERROR"; - case NYdb::EStatus::GENERIC_ERROR: - return "GENERIC_ERROR"; - case NYdb::EStatus::TIMEOUT: - return "TIMEOUT"; - case NYdb::EStatus::BAD_SESSION: - return "BAD_SESSION"; - case NYdb::EStatus::PRECONDITION_FAILED: - return "PRECONDITION_FAILED"; - case NYdb::EStatus::ALREADY_EXISTS: - return "ALREADY_EXISTS"; - case NYdb::EStatus::NOT_FOUND: - return "NOT_FOUND"; - case NYdb::EStatus::SESSION_EXPIRED: - return "SESSION_EXPIRED"; - case NYdb::EStatus::CANCELLED: - return "CANCELLED"; - case NYdb::EStatus::UNDETERMINED: - return "UNDETERMINED"; - case NYdb::EStatus::UNSUPPORTED: - return "UNSUPPORTED"; - case NYdb::EStatus::SESSION_BUSY: - return "SESSION_BUSY"; - case NYdb::EStatus::EXTERNAL_ERROR: - return "EXTERNAL_ERROR"; - case NYdb::EStatus::STATUS_UNDEFINED: - return "STATUS_UNDEFINED"; - case NYdb::EStatus::TRANSPORT_UNAVAILABLE: - return "TRANSPORT_UNAVAILABLE"; - case NYdb::EStatus::CLIENT_RESOURCE_EXHAUSTED: - return "CLIENT_RESOURCE_EXHAUSTED"; - case NYdb::EStatus::CLIENT_DEADLINE_EXCEEDED: - return "CLIENT_DEADLINE_EXCEEDED"; - case NYdb::EStatus::CLIENT_INTERNAL_ERROR: - return "CLIENT_INTERNAL_ERROR"; - case NYdb::EStatus::CLIENT_CANCELLED: - return "CLIENT_CANCELLED"; - case NYdb::EStatus::CLIENT_UNAUTHENTICATED: - return "CLIENT_UNAUTHENTICATED"; - case NYdb::EStatus::CLIENT_CALL_UNIMPLEMENTED: - return "CLIENT_CALL_UNIMPLEMENTED"; - case NYdb::EStatus::CLIENT_OUT_OF_RANGE: - return "CLIENT_OUT_OF_RANGE"; - case NYdb::EStatus::CLIENT_DISCOVERY_FAILED: - return "CLIENT_DISCOVERY_FAILED"; - case NYdb::EStatus::CLIENT_LIMITS_REACHED: - return "CLIENT_LIMITS_REACHED"; - } + return SloYdbStatusToString(status); } TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableName) { From a3955f5696228d290d69f4d86427698621b3f6ca Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Sun, 31 May 2026 15:46:20 +0300 Subject: [PATCH 22/27] align userver slo with v2 --- tests/slo_workloads/Dockerfile.userver | 118 ++++++++++++------ .../slo_workloads/common/key_value/create.cpp | 49 ++++---- tests/slo_workloads/core/CMakeLists.txt | 4 + tests/slo_workloads/native/utils/executor.h | 3 - .../userver/key_value/userver_utils.cpp | 80 ++++++++++-- 5 files changed, 180 insertions(+), 74 deletions(-) diff --git a/tests/slo_workloads/Dockerfile.userver b/tests/slo_workloads/Dockerfile.userver index ea403a8c1b..737c718140 100644 --- a/tests/slo_workloads/Dockerfile.userver +++ b/tests/slo_workloads/Dockerfile.userver @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1.7 + # Build userver .deb package (core and other components; YDB is built from source in-repo). FROM ghcr.io/userver-framework/ubuntu-22.04-userver-base:latest AS userver-deb ARG USERVER_VERSION=v2.12 @@ -10,23 +12,51 @@ RUN ./scripts/build_and_install.sh FROM ubuntu:22.04 AS base ENV DEBIAN_FRONTEND=noninteractive - ARG PRESET=release-test-clang -ARG REF=unknown -# Install software-properties-common for add-apt-repository -RUN apt-get -y update && apt-get -y install software-properties-common && add-apt-repository ppa:ubuntu-toolchain-r/test +# ccache settings consumed by the configure/build steps below. +ENV CCACHE_DIR=/root/.ccache +ENV CCACHE_MAXSIZE=2G +ENV CCACHE_COMPRESS=true +ENV CCACHE_COMPILERCHECK=content + +# Every RUN that hits the network retries on transient failures so one flake +# doesn't throw away 30 min of previous build work. +RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries && \ + echo 'Acquire::http::Timeout "60";' >> /etc/apt/apt.conf.d/80-retries && \ + echo 'Acquire::https::Timeout "60";' >> /etc/apt/apt.conf.d/80-retries + +ENV WGET_OPTS="--tries=5 --waitretry=15 --timeout=60 --retry-connrefused --retry-on-http-error=500,502,503,504" + +# Install software-properties-common and add the gcc-13 PPA with shell-level +# retry loop — Acquire::Retries doesn't cover TCP connect timeouts. +RUN for i in 1 2 3 4 5; do \ + apt-get -y update && \ + apt-get -y install software-properties-common && \ + add-apt-repository -y ppa:ubuntu-toolchain-r/test && \ + apt-get -y update && \ + break; \ + echo "add-apt-repository attempt $i failed; sleeping $((i * 15))s"; \ + sleep $((i * 15)); \ + done && \ + apt-cache show gcc-13 > /dev/null # Install C++ tools and libraries -RUN apt-get -y update && apt-get -y install \ - git gdb wget ninja-build libidn11-dev ragel yasm libc-ares-dev libre2-dev \ - rapidjson-dev zlib1g-dev libxxhash-dev libzstd-dev libsnappy-dev libgtest-dev libgmock-dev \ - libbz2-dev liblz4-dev libdouble-conversion-dev libssl-dev libstdc++-14-dev gcc-14 g++-14 \ - && apt-get clean && rm -rf /var/lib/apt/lists/* +RUN for i in 1 2 3 4 5; do \ + apt-get -y install \ + git gdb wget ninja-build libidn11-dev ragel yasm libc-ares-dev libre2-dev \ + rapidjson-dev zlib1g-dev libxxhash-dev libzstd-dev libsnappy-dev libgtest-dev libgmock-dev \ + libbz2-dev liblz4-dev libdouble-conversion-dev libssl-dev libstdc++-13-dev gcc-13 g++-13 && \ + break; \ + echo "apt-get install attempt $i failed; sleeping $((i * 15))s"; \ + sleep $((i * 15)); \ + apt-get -y update || true; \ + done && \ + apt-get clean && rm -rf /var/lib/apt/lists/* # Install CMake ENV CMAKE_VERSION=3.27.7 -RUN wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-x86_64.sh \ +RUN wget $WGET_OPTS https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-x86_64.sh \ -q -O cmake-install.sh \ && chmod u+x cmake-install.sh \ && ./cmake-install.sh --skip-license --prefix=/usr/local \ @@ -34,24 +64,22 @@ RUN wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cm # Install LLVM ENV LLVM_VERSION=16 -RUN wget https://apt.llvm.org/llvm.sh && \ +RUN wget $WGET_OPTS https://apt.llvm.org/llvm.sh && \ chmod u+x llvm.sh && \ ./llvm.sh ${LLVM_VERSION} && \ rm llvm.sh -# Update alternatives to use clang-16 by default RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-${LLVM_VERSION} 10000 && \ update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-${LLVM_VERSION} 10000 && \ update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-${LLVM_VERSION} 10000 -# Update alternatives to use gcc-14 by default -RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 10000 && \ - update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 10000 +RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 10000 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 10000 # Install abseil-cpp ENV ABSEIL_CPP_VERSION=20230802.0 ENV ABSEIL_CPP_INSTALL_DIR=/root/ydb_deps/absl -RUN wget -O abseil-cpp-${ABSEIL_CPP_VERSION}.tar.gz https://github.com/abseil/abseil-cpp/archive/refs/tags/${ABSEIL_CPP_VERSION}.tar.gz && \ +RUN wget $WGET_OPTS -O abseil-cpp-${ABSEIL_CPP_VERSION}.tar.gz https://github.com/abseil/abseil-cpp/archive/refs/tags/${ABSEIL_CPP_VERSION}.tar.gz && \ tar -xvzf abseil-cpp-${ABSEIL_CPP_VERSION}.tar.gz && cd abseil-cpp-${ABSEIL_CPP_VERSION} && \ mkdir build && cd build && \ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DABSL_PROPAGATE_CXX_STD=ON .. && \ @@ -62,7 +90,7 @@ RUN wget -O abseil-cpp-${ABSEIL_CPP_VERSION}.tar.gz https://github.com/abseil/ab # Install protobuf ENV PROTOBUF_VERSION=3.21.12 ENV PROTOBUF_INSTALL_DIR=/root/ydb_deps/protobuf -RUN wget -O protobuf-${PROTOBUF_VERSION}.tar.gz https://github.com/protocolbuffers/protobuf/archive/refs/tags/v${PROTOBUF_VERSION}.tar.gz && \ +RUN wget $WGET_OPTS -O protobuf-${PROTOBUF_VERSION}.tar.gz https://github.com/protocolbuffers/protobuf/archive/refs/tags/v${PROTOBUF_VERSION}.tar.gz && \ tar -xvzf protobuf-${PROTOBUF_VERSION}.tar.gz && cd protobuf-${PROTOBUF_VERSION} && \ mkdir build && cd build && \ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_INSTALL=ON -Dprotobuf_ABSL_PROVIDER=package .. && \ @@ -73,7 +101,7 @@ RUN wget -O protobuf-${PROTOBUF_VERSION}.tar.gz https://github.com/protocolbuffe # Install grpc ENV GRPC_VERSION=1.54.3 ENV GRPC_INSTALL_DIR=/root/ydb_deps/grpc -RUN wget -O grpc-${GRPC_VERSION}.tar.gz https://github.com/grpc/grpc/archive/refs/tags/v${GRPC_VERSION}.tar.gz && \ +RUN wget $WGET_OPTS -O grpc-${GRPC_VERSION}.tar.gz https://github.com/grpc/grpc/archive/refs/tags/v${GRPC_VERSION}.tar.gz && \ tar -xvzf grpc-${GRPC_VERSION}.tar.gz && cd grpc-${GRPC_VERSION} && \ mkdir build && cd build && \ cmake -G Ninja -DCMAKE_PREFIX_PATH="${ABSEIL_CPP_INSTALL_DIR};${PROTOBUF_INSTALL_DIR}" \ @@ -90,7 +118,7 @@ RUN wget -O grpc-${GRPC_VERSION}.tar.gz https://github.com/grpc/grpc/archive/ref # Install base64 ENV BASE64_VERSION=0.5.2 ENV BASE64_INSTALL_DIR=/root/ydb_deps/base64 -RUN wget -O base64-${BASE64_VERSION}.tar.gz https://github.com/aklomp/base64/archive/refs/tags/v${BASE64_VERSION}.tar.gz && \ +RUN wget $WGET_OPTS -O base64-${BASE64_VERSION}.tar.gz https://github.com/aklomp/base64/archive/refs/tags/v${BASE64_VERSION}.tar.gz && \ tar -xvzf base64-${BASE64_VERSION}.tar.gz && cd base64-${BASE64_VERSION} && \ mkdir build && cd build && \ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. && \ @@ -101,7 +129,7 @@ RUN wget -O base64-${BASE64_VERSION}.tar.gz https://github.com/aklomp/base64/arc # Install brotli ENV BROTLI_VERSION=1.1.0 ENV BROTLI_INSTALL_DIR=/root/ydb_deps/brotli -RUN wget -O brotli-${BROTLI_VERSION}.tar.gz https://github.com/google/brotli/archive/refs/tags/v${BROTLI_VERSION}.tar.gz && \ +RUN wget $WGET_OPTS -O brotli-${BROTLI_VERSION}.tar.gz https://github.com/google/brotli/archive/refs/tags/v${BROTLI_VERSION}.tar.gz && \ tar -xvzf brotli-${BROTLI_VERSION}.tar.gz && cd brotli-${BROTLI_VERSION} && \ mkdir build && cd build && \ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. && \ @@ -112,7 +140,7 @@ RUN wget -O brotli-${BROTLI_VERSION}.tar.gz https://github.com/google/brotli/arc # Install jwt-cpp ENV JWT_CPP_VERSION=0.7.0 ENV JWT_CPP_INSTALL_DIR=/root/ydb_deps/jwt-cpp -RUN wget -O jwt-cpp-${JWT_CPP_VERSION}.tar.gz https://github.com/Thalhammer/jwt-cpp/archive/refs/tags/v${JWT_CPP_VERSION}.tar.gz && \ +RUN wget $WGET_OPTS -O jwt-cpp-${JWT_CPP_VERSION}.tar.gz https://github.com/Thalhammer/jwt-cpp/archive/refs/tags/v${JWT_CPP_VERSION}.tar.gz && \ tar -xvzf jwt-cpp-${JWT_CPP_VERSION}.tar.gz && cd jwt-cpp-${JWT_CPP_VERSION} && \ mkdir build && cd build && \ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. && \ @@ -120,9 +148,9 @@ RUN wget -O jwt-cpp-${JWT_CPP_VERSION}.tar.gz https://github.com/Thalhammer/jwt- cmake --install . --config Release --prefix ${JWT_CPP_INSTALL_DIR} && \ rm -rf jwt-cpp-${JWT_CPP_VERSION}.tar.gz jwt-cpp-${JWT_CPP_VERSION} -# Install ccache 4.8.1 or above +# Install ccache ENV CCACHE_VERSION=4.8.1 -RUN wget https://github.com/ccache/ccache/releases/download/v${CCACHE_VERSION}/ccache-${CCACHE_VERSION}-linux-x86_64.tar.xz \ +RUN wget $WGET_OPTS https://github.com/ccache/ccache/releases/download/v${CCACHE_VERSION}/ccache-${CCACHE_VERSION}-linux-x86_64.tar.xz \ && tar -xf ccache-${CCACHE_VERSION}-linux-x86_64.tar.xz \ && cp ccache-${CCACHE_VERSION}-linux-x86_64/ccache /usr/local/bin/ \ && rm -rf ccache-${CCACHE_VERSION}-linux-x86_64 ccache-${CCACHE_VERSION}-linux-x86_64.tar.xz @@ -130,20 +158,33 @@ RUN wget https://github.com/ccache/ccache/releases/download/v${CCACHE_VERSION}/c FROM base ARG PRESET=release-test-clang -ARG REF=unknown ARG USERVER_VERSION=v2.12 COPY --from=userver-deb /userver/libuserver-all-dev*.deb /tmp/ -# libuserver-all-dev depends on build-essential (gcc-11), which overrides gcc-14 alternatives. -RUN apt-get -y update && apt-get -y install --no-install-recommends /tmp/libuserver-all-dev*.deb \ +# libuserver-all-dev pulls build-essential (gcc-11), which would override +# gcc-13 alternatives. Reassert gcc-13 after install. +RUN for i in 1 2 3 4 5; do \ + apt-get -y update && \ + apt-get -y install --no-install-recommends /tmp/libuserver-all-dev*.deb && \ + break; \ + echo "userver .deb install attempt $i failed; sleeping $((i * 15))s"; \ + sleep $((i * 15)); \ + done \ && rm -rf /tmp/libuserver-all-dev*.deb /var/lib/apt/lists/* \ - && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 10000 \ - && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 10000 \ - && update-alternatives --set gcc /usr/bin/gcc-14 \ - && update-alternatives --set g++ /usr/bin/g++-14 - -RUN git clone --depth 1 --branch ${USERVER_VERSION} \ - https://github.com/userver-framework/userver.git /userver-src \ + && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 10000 \ + && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 10000 \ + && update-alternatives --set gcc /usr/bin/gcc-13 \ + && update-alternatives --set g++ /usr/bin/g++-13 + +# Userver source checkout for chaotic codegen tools used during configure. +RUN for i in 1 2 3 4 5; do \ + git clone --depth 1 --branch ${USERVER_VERSION} \ + https://github.com/userver-framework/userver.git /userver-src && \ + break; \ + echo "userver clone attempt $i failed; sleeping $((i * 15))s"; \ + sleep $((i * 15)); \ + rm -rf /userver-src; \ + done \ && chmod +x /userver-src/chaotic/bin-dynamic-configs/chaotic-gen-dynamic-configs \ /userver-src/chaotic/bin/chaotic-gen 2>/dev/null || true ENV USERVER_SOURCE_DIR=/userver-src @@ -152,7 +193,14 @@ COPY . /ydb-cpp-sdk WORKDIR /ydb-cpp-sdk RUN rm -rf build -RUN cmake -DSLO_BRANCH_REF=${REF} -DYDB_CPP_SDK_BUILD_SLO_USERVER=ON --preset ${PRESET} -RUN cmake --build --preset default --target slo-userver-key-value +RUN --mount=type=cache,target=/root/.ccache,sharing=locked \ + cmake --preset ${PRESET} \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DYDB_CPP_SDK_BUILD_SLO_USERVER=ON +RUN --mount=type=cache,target=/root/.ccache,sharing=locked \ + ccache --zero-stats >/dev/null \ + && cmake --build --preset default --target slo-userver-key-value \ + && ccache --show-stats ENTRYPOINT ["./build/tests/slo_workloads/userver/key_value/slo-userver-key-value"] diff --git a/tests/slo_workloads/common/key_value/create.cpp b/tests/slo_workloads/common/key_value/create.cpp index 49e583bf83..9c6eb80495 100644 --- a/tests/slo_workloads/common/key_value/create.cpp +++ b/tests/slo_workloads/common/key_value/create.cpp @@ -5,36 +5,29 @@ using namespace NYdb; using namespace NYdb::NTable; -namespace { - -void CreateTableDDL(TTableClient& client, const std::string& prefix) { - RetryBackoff(client, 5, [prefix](TSession session) { - auto desc = TTableBuilder() - .AddNullableColumn("object_id_key", EPrimitiveType::Uint32) - .AddNullableColumn("object_id", EPrimitiveType::Uint32) - .AddNullableColumn("timestamp", EPrimitiveType::Uint64) - .AddNullableColumn("payload", EPrimitiveType::Utf8) - .SetPrimaryKeyColumns({ "object_id_key", "object_id" }) - .Build(); - - auto tableSettings = TCreateTableSettings() - .PartitioningPolicy(TPartitioningPolicy().UniformPartitions(PartitionsCount)) - .CancelAfter(DefaultReactionTime) - .ClientTimeout(DefaultReactionTime + TDuration::Seconds(5)); - - return session.CreateTable( - JoinPath(prefix, TableName) - , std::move(desc) - , std::move(tableSettings) - ).ExtractValueSync(); - }); -} - -} // namespace - int CreateTable(TDatabaseOptions& dbOptions) { TTableClient client(dbOptions.Driver); - CreateTableDDL(client, dbOptions.Prefix); + NYdb::NStatusHelpers::ThrowOnError(client.RetryOperationSync( + [prefix = dbOptions.Prefix](TSession session) { + auto desc = TTableBuilder() + .AddNullableColumn("object_id_key", EPrimitiveType::Uint32) + .AddNullableColumn("object_id", EPrimitiveType::Uint32) + .AddNullableColumn("timestamp", EPrimitiveType::Uint64) + .AddNullableColumn("payload", EPrimitiveType::Utf8) + .SetPrimaryKeyColumns({"object_id_key", "object_id"}) + .Build(); + + auto tableSettings = TCreateTableSettings() + .PartitioningPolicy(TPartitioningPolicy().UniformPartitions(PartitionsCount)) + .CancelAfter(DefaultReactionTime) + .ClientTimeout(DefaultReactionTime + TDuration::Seconds(5)); + + return session.CreateTable( + JoinPath(prefix, TableName), + std::move(desc), + std::move(tableSettings) + ).ExtractValueSync(); + })); Cout << "Table created." << Endl; return EXIT_SUCCESS; } diff --git a/tests/slo_workloads/core/CMakeLists.txt b/tests/slo_workloads/core/CMakeLists.txt index 873edad0fa..bff42a25a4 100644 --- a/tests/slo_workloads/core/CMakeLists.txt +++ b/tests/slo_workloads/core/CMakeLists.txt @@ -22,6 +22,10 @@ target_link_libraries(slo-workload-core PUBLIC hdr_histogram_static ) +target_link_libraries(slo-workload-core PRIVATE + hdr_histogram_static +) + target_sources(slo-workload-core PRIVATE duration_meter.cpp rps.cpp diff --git a/tests/slo_workloads/native/utils/executor.h b/tests/slo_workloads/native/utils/executor.h index 0b1f2e0a16..049c1bd436 100644 --- a/tests/slo_workloads/native/utils/executor.h +++ b/tests/slo_workloads/native/utils/executor.h @@ -20,9 +20,6 @@ extern const TDuration WaitTimeout; // Debug use only extern std::atomic ReadPromises; -using TFinalStatus = std::optional; -using TAsyncFinalStatus = NThreading::TFuture; - template class TTracedPromise : public NThreading::TPromise { public: diff --git a/tests/slo_workloads/userver/key_value/userver_utils.cpp b/tests/slo_workloads/userver/key_value/userver_utils.cpp index db313e9fb9..39c9d15c3a 100644 --- a/tests/slo_workloads/userver/key_value/userver_utils.cpp +++ b/tests/slo_workloads/userver/key_value/userver_utils.cpp @@ -16,6 +16,8 @@ #include +#include + using namespace NLastGetopt; using namespace NYdb; @@ -65,8 +67,15 @@ int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TClean std::string statConfigFile; std::string balancingPolicy; - opts.AddLongOption('c', "connection-string", "YDB connection string").Required().RequiredArgument("SCHEMA://HOST:PORT/?DATABASE=DATABASE") + std::string defaultConnectionString = DefaultConnectionStringFromEnv(); + + auto& connOpt = opts.AddLongOption('c', "connection-string", "YDB connection string").RequiredArgument("SCHEMA://HOST:PORT/?DATABASE=DATABASE") .StoreResult(&connectionString); + if (!defaultConnectionString.empty()) { + connOpt.DefaultValue(defaultConnectionString); + } else { + connOpt.Required(); + } opts.AddLongOption('p', "prefix", "Base prefix for tables").RequiredArgument("PATH") .StoreResult(&prefix); opts.AddLongOption('k', "token", "security token").RequiredArgument("TOKEN") @@ -80,23 +89,37 @@ int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TClean opts.AddLongOption('b', "balancing-policy", "Balancing policy").Optional().DefaultValue("use-all-nodes").RequiredArgument("(use-all-nodes|prefer-local-dc|prefer-primary-pile)") .StoreResult(&balancingPolicy); opts.AddHelpOption('h'); - opts.SetFreeArgsMin(1); + opts.SetFreeArgsMin(0); opts.SetFreeArgTitle(0, "", GetCmdList()); opts.ArgPermutation_ = NLastGetopt::REQUIRE_ORDER; + // Run-phase options (--read-rps, --write-rps, …) reach DoMain when the + // caller invokes the workload without an explicit subcommand (the v2 SLO + // action contract). Tolerate them here so the global parser stops at the + // first unknown option instead of erroring; they are forwarded to the + // run phase below. + opts.AllowUnknownLongOptions_ = true; TOptsParseResult res(&opts, argc, argv); size_t freeArgsPos = res.GetFreeArgsPos(); argc -= freeArgsPos; argv += freeArgsPos; - ECommandType command = ParseCommand(*argv); + + ECommandType command = (argc > 0) ? ParseCommand(*argv) : ECommandType::All; if (command == ECommandType::Unknown) { - Cerr << "Unknown command '" << *argv << "'" << Endl; - return EXIT_FAILURE; + if (argv[0][0] == '-') { + command = ECommandType::All; + } else { + Cerr << "Unknown command '" << *argv << "'" << Endl; + return EXIT_FAILURE; + } } if (prefix.empty()) { prefix = GetDatabase(connectionString); } + if (prefix.empty()) { + prefix = GetEnv("YDB_DATABASE"); + } if (!ParseToken(token, tokenFile)) { return EXIT_FAILURE; @@ -143,9 +166,9 @@ int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TClean try { // Wrap command execution in userver engine so coroutine-based // userver::ydb::TableClient and engine primitives work. - // DDL operations (create table, drop table) use native SDK directly and - // don't need the engine, but workload queries use userver::ydb::TableClient - // which requires the engine to be running. + // DDL operations (create table, drop table) use native SDK directly + // and don't need the engine, but workload queries use + // userver::ydb::TableClient which requires the engine to be running. userver::engine::RunStandalone(4, [&] { userver_slo::InitTableClient( config.GetEndpoint(), @@ -154,6 +177,7 @@ int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TClean token, prefer_local_dc ); + switch (command) { case ECommandType::Create: Cout << "Launching create command..." << Endl; @@ -167,6 +191,46 @@ int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TClean Cout << "Launching cleanup command..." << Endl; result = cleanup(dbOptions, argc); break; + case ECommandType::All: { + Cout << "Launching full lifecycle: create -> run -> cleanup" << Endl; + // Forward leftover argv to the run phase so options + // like --read-rps / --write-rps take effect. argv[0] + // here is the first run-phase option (no subcommand + // keyword was supplied), so prepend a synthetic + // program name for ParseOptionsRun. + char programName[] = "slo"; + std::vector runArgv; + runArgv.reserve(argc + 2); + runArgv.push_back(programName); + for (int i = 0; i < argc; ++i) { + runArgv.push_back(argv[i]); + } + runArgv.push_back(nullptr); + int fakeArgc = 1; + char* fakeArgv[] = { programName, nullptr }; + + Cout << "[all] Launching create command..." << Endl; + result = create(dbOptions, fakeArgc, fakeArgv); + if (!result) { + Cout << "[all] Launching run command..." << Endl; + result = run(dbOptions, static_cast(runArgv.size() - 1), runArgv.data()); + } + Cout << "[all] Launching cleanup command..." << Endl; + int cleanupRc = cleanup(dbOptions, fakeArgc); + // Cleanup runs while chaos-monkey is still killing + // nodes, so a DropTable failure here is expected noise + // and must not mask a successful run. Surface the + // run's status; only fall back to cleanup status when + // run itself failed. + if (cleanupRc && !result) { + Cerr << "[all] Warning: cleanup failed (exit " << cleanupRc + << ") but run succeeded; ignoring cleanup exit code." << Endl; + } else if (cleanupRc) { + Cerr << "[all] Warning: cleanup failed (exit " << cleanupRc + << "); preserving earlier run failure." << Endl; + } + break; + } default: Cerr << "Unknown command" << Endl; result = EXIT_FAILURE; From 30dde358a0bd113a6c080c8a21feef4c154d5e67 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Sun, 31 May 2026 19:52:58 +0300 Subject: [PATCH 23/27] fix the slo action behaviour: must be uncanged --- tests/slo_workloads/.dockerignore | 5 - tests/slo_workloads/CMakeLists.txt | 5 +- tests/slo_workloads/Dockerfile | 2 +- tests/slo_workloads/common/CMakeLists.txt | 1 - .../common/key_value/CMakeLists.txt | 13 -- .../common/key_value/kv_common.h | 18 -- tests/slo_workloads/core/constants.h | 7 - tests/slo_workloads/core/duration_meter.cpp | 11 - tests/slo_workloads/core/duration_meter.h | 11 - tests/slo_workloads/core/generator.h | 99 --------- tests/slo_workloads/core/records.h | 22 -- tests/slo_workloads/core/rps.cpp | 40 ---- tests/slo_workloads/core/rps.h | 22 -- tests/slo_workloads/core/shard.cpp | 27 --- tests/slo_workloads/core/shard.h | 7 - tests/slo_workloads/core/slo_text_utils.cpp | 199 ------------------ tests/slo_workloads/core/slo_text_utils.h | 12 -- .../{native => }/key_value/CMakeLists.txt | 3 +- .../{common => }/key_value/create.cpp | 24 ++- tests/slo_workloads/key_value/drop.cpp | 23 ++ .../{native => }/key_value/generate.cpp | 2 +- .../{native => }/key_value/key_value.cpp | 15 ++ .../{native => }/key_value/key_value.h | 19 +- .../{native => }/key_value/main.cpp | 0 .../{native => }/key_value/run.cpp | 2 +- tests/slo_workloads/native/CMakeLists.txt | 2 - .../slo_workloads/native/utils/CMakeLists.txt | 33 --- .../userver/key_value/CMakeLists.txt | 7 +- .../userver/key_value/create.cpp | 29 +-- .../key_value/key_value.cpp} | 2 +- .../userver/key_value/key_value.h | 14 +- tests/slo_workloads/userver/key_value/run.cpp | 196 +++-------------- .../userver/key_value/userver_utils.cpp | 31 --- .../userver/key_value/userver_utils.h | 2 +- tests/slo_workloads/utils/CMakeLists.txt | 51 +++++ .../{native => }/utils/executor.cpp | 0 .../{native => }/utils/executor.h | 3 +- .../{core => utils}/generator.cpp | 11 +- tests/slo_workloads/utils/generator.h | 90 ++++++++ .../slo_workloads/{native => }/utils/job.cpp | 7 +- tests/slo_workloads/{native => }/utils/job.h | 5 +- .../slo_workloads/{core => utils}/metrics.cpp | 0 tests/slo_workloads/{core => utils}/metrics.h | 12 +- .../{core => utils}/statistics.cpp | 65 ++---- .../{core => utils}/statistics.h | 21 +- .../{native => }/utils/utils.cpp | 0 .../slo_workloads/{native => }/utils/utils.h | 0 .../{native => }/utils/utils_main.cpp | 0 48 files changed, 303 insertions(+), 867 deletions(-) delete mode 100644 tests/slo_workloads/common/CMakeLists.txt delete mode 100644 tests/slo_workloads/common/key_value/CMakeLists.txt delete mode 100644 tests/slo_workloads/common/key_value/kv_common.h delete mode 100644 tests/slo_workloads/core/constants.h delete mode 100644 tests/slo_workloads/core/duration_meter.cpp delete mode 100644 tests/slo_workloads/core/duration_meter.h delete mode 100644 tests/slo_workloads/core/generator.h delete mode 100644 tests/slo_workloads/core/records.h delete mode 100644 tests/slo_workloads/core/rps.cpp delete mode 100644 tests/slo_workloads/core/rps.h delete mode 100644 tests/slo_workloads/core/shard.cpp delete mode 100644 tests/slo_workloads/core/shard.h delete mode 100644 tests/slo_workloads/core/slo_text_utils.cpp delete mode 100644 tests/slo_workloads/core/slo_text_utils.h rename tests/slo_workloads/{native => }/key_value/CMakeLists.txt (96%) rename tests/slo_workloads/{common => }/key_value/create.cpp (67%) create mode 100644 tests/slo_workloads/key_value/drop.cpp rename tests/slo_workloads/{native => }/key_value/generate.cpp (98%) rename tests/slo_workloads/{native => }/key_value/key_value.cpp (85%) rename tests/slo_workloads/{native => }/key_value/key_value.h (72%) rename tests/slo_workloads/{native => }/key_value/main.cpp (100%) rename tests/slo_workloads/{native => }/key_value/run.cpp (99%) delete mode 100644 tests/slo_workloads/native/CMakeLists.txt delete mode 100644 tests/slo_workloads/native/utils/CMakeLists.txt rename tests/slo_workloads/{common/key_value/kv_data.cpp => userver/key_value/key_value.cpp} (95%) create mode 100644 tests/slo_workloads/utils/CMakeLists.txt rename tests/slo_workloads/{native => }/utils/executor.cpp (100%) rename tests/slo_workloads/{native => }/utils/executor.h (98%) rename tests/slo_workloads/{core => utils}/generator.cpp (72%) create mode 100644 tests/slo_workloads/utils/generator.h rename tests/slo_workloads/{native => }/utils/job.cpp (91%) rename tests/slo_workloads/{native => }/utils/job.h (92%) rename tests/slo_workloads/{core => utils}/metrics.cpp (100%) rename tests/slo_workloads/{core => utils}/metrics.h (56%) rename tests/slo_workloads/{core => utils}/statistics.cpp (66%) rename tests/slo_workloads/{core => utils}/statistics.h (78%) rename tests/slo_workloads/{native => }/utils/utils.cpp (100%) rename tests/slo_workloads/{native => }/utils/utils.h (100%) rename tests/slo_workloads/{native => }/utils/utils_main.cpp (100%) diff --git a/tests/slo_workloads/.dockerignore b/tests/slo_workloads/.dockerignore index 78983a952d..9e958bd1bf 100644 --- a/tests/slo_workloads/.dockerignore +++ b/tests/slo_workloads/.dockerignore @@ -46,8 +46,3 @@ ydb/core/ ydb/apps/ ydb/services/ ydb/tools/ - -# Local orphan submodule checkouts (untracked; not used by SLO Docker builds) -third_party/aklomp-base64/ -third_party/jwt-cpp/ -third_party/opentelemetry-cpp/ diff --git a/tests/slo_workloads/CMakeLists.txt b/tests/slo_workloads/CMakeLists.txt index eccd0dc33d..69b177fcba 100644 --- a/tests/slo_workloads/CMakeLists.txt +++ b/tests/slo_workloads/CMakeLists.txt @@ -1,6 +1,5 @@ -add_subdirectory(core) -add_subdirectory(common) -add_subdirectory(native) +add_subdirectory(key_value) +add_subdirectory(utils) option(YDB_CPP_SDK_BUILD_SLO_USERVER "Build userver SLO workload (requires userver)" OFF) if (YDB_CPP_SDK_BUILD_SLO_USERVER) diff --git a/tests/slo_workloads/Dockerfile b/tests/slo_workloads/Dockerfile index e22754a095..f87be8f8df 100644 --- a/tests/slo_workloads/Dockerfile +++ b/tests/slo_workloads/Dockerfile @@ -165,4 +165,4 @@ RUN --mount=type=cache,target=/root/.ccache,sharing=locked \ && cmake --build --preset default --target slo-key-value \ && ccache --show-stats -ENTRYPOINT ["./build/tests/slo_workloads/native/key_value/slo-key-value"] +ENTRYPOINT ["./build/tests/slo_workloads/key_value/slo-key-value"] diff --git a/tests/slo_workloads/common/CMakeLists.txt b/tests/slo_workloads/common/CMakeLists.txt deleted file mode 100644 index 1975421a54..0000000000 --- a/tests/slo_workloads/common/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(key_value) diff --git a/tests/slo_workloads/common/key_value/CMakeLists.txt b/tests/slo_workloads/common/key_value/CMakeLists.txt deleted file mode 100644 index 86160e3efc..0000000000 --- a/tests/slo_workloads/common/key_value/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Shared key-value workload library: TableName, BuildValueFromRecord, CreateTable, DropTable -# Used by both native and userver SLO workloads. -add_library(slo-kv-common) - -target_link_libraries(slo-kv-common PUBLIC - slo-utils-base -) - -target_sources(slo-kv-common PRIVATE - kv_data.cpp - create.cpp - drop.cpp -) diff --git a/tests/slo_workloads/common/key_value/kv_common.h b/tests/slo_workloads/common/key_value/kv_common.h deleted file mode 100644 index bedda0db0b..0000000000 --- a/tests/slo_workloads/common/key_value/kv_common.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -// Shared key-value workload declarations used by both native and userver SLO tests. -// This header provides table name, value builder, and DDL operations (create/drop) -// that are identical across workload implementations. - -#include - -#include - -#include - -extern const std::string TableName; - -NYdb::TValue BuildValueFromRecord(const TKeyValueRecordData& recordData); - -int CreateTable(TDatabaseOptions& dbOptions); -int DropTable(TDatabaseOptions& dbOptions); diff --git a/tests/slo_workloads/core/constants.h b/tests/slo_workloads/core/constants.h deleted file mode 100644 index b8421a1181..0000000000 --- a/tests/slo_workloads/core/constants.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -inline constexpr TDuration DefaultReactionTime = TDuration::Minutes(2); -inline constexpr TDuration ReactionTimeDelay = TDuration::MilliSeconds(5); -inline constexpr std::uint64_t PartitionsCount = 64; diff --git a/tests/slo_workloads/core/duration_meter.cpp b/tests/slo_workloads/core/duration_meter.cpp deleted file mode 100644 index dd808a5be7..0000000000 --- a/tests/slo_workloads/core/duration_meter.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "duration_meter.h" - -TDurationMeter::TDurationMeter(TDuration& value) - : Value(value) - , StartTime(TInstant::Now()) -{ -} - -TDurationMeter::~TDurationMeter() { - Value += TInstant::Now() - StartTime; -} diff --git a/tests/slo_workloads/core/duration_meter.h b/tests/slo_workloads/core/duration_meter.h deleted file mode 100644 index 16b54625d7..0000000000 --- a/tests/slo_workloads/core/duration_meter.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -struct TDurationMeter { - TDurationMeter(TDuration& value); - ~TDurationMeter(); - - TDuration& Value; - TInstant StartTime; -}; diff --git a/tests/slo_workloads/core/generator.h b/tests/slo_workloads/core/generator.h deleted file mode 100644 index 6a57dcd481..0000000000 --- a/tests/slo_workloads/core/generator.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include "duration_meter.h" -#include "records.h" -#include "shard.h" -#include "slo_text_utils.h" - -#include -#include -#include - -#include -#include -#include - -class TValueGenerator { -public: - TValueGenerator(const TSloGeneratorOptions& opts, std::uint32_t startId = 0); - TRecordData Get(); - TDuration GetComputeTime() const; - -private: - TSloGeneratorOptions Opts; - std::uint32_t CurrentObjectId = 0; - TDuration ComputeTime; -}; - -class TKeyValueGenerator { -public: - TKeyValueGenerator(const TSloGeneratorOptions& opts, std::uint32_t startId = 0); - TKeyValueRecordData Get(); - TDuration GetComputeTime() const; - -private: - TSloGeneratorOptions Opts; - std::uint32_t CurrentObjectId = 0; - TDuration ComputeTime; -}; - -template -class TPackGenerator { -public: - using TBuildItem = std::function; - - TPackGenerator( - const TSloGeneratorOptions& opts, - std::uint32_t packSize, - TBuildItem buildItem, - std::uint64_t remain, - std::uint32_t startId = 0 - ) - : BuildItem_(std::move(buildItem)) - , Generator_(opts, startId) - , PackSize_(packSize) - , Remain_(remain) - { - } - - bool GetNextPack(std::vector& pack) { - pack.clear(); - while (Remain_) { - TRecordType record = Generator_.Get(); - --Remain_; - const std::uint32_t specialId = SloGetSpecialId(SloGetHash(record.ObjectId)); - auto& existingPack = Packs_[specialId]; - existingPack.emplace_back(BuildItem_(record)); - if (existingPack.size() >= PackSize_) { - existingPack.swap(pack); - return true; - } - } - for (auto& it : Packs_) { - if (it.second.size()) { - it.second.swap(pack); - return true; - } - } - return false; - } - - std::uint32_t GetPackSize() const { - return PackSize_; - } - - TDuration GetComputeTime() const { - return Generator_.GetComputeTime(); - } - - std::uint64_t GetRemain() const { - return Remain_; - } - -private: - TBuildItem BuildItem_; - TGeneratorType Generator_; - std::uint32_t PackSize_; - std::unordered_map> Packs_; - std::uint64_t Remain_; -}; diff --git a/tests/slo_workloads/core/records.h b/tests/slo_workloads/core/records.h deleted file mode 100644 index 5e9f9767cd..0000000000 --- a/tests/slo_workloads/core/records.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -struct TRecordData { - std::uint32_t ObjectId; - std::uint64_t Timestamp; - std::string Guid; - std::string Payload; -}; - -struct TKeyValueRecordData { - std::uint32_t ObjectId; - std::uint64_t Timestamp; - std::string Payload; -}; - -struct TSloGeneratorOptions { - std::uint32_t MinLength = 20; - std::uint32_t MaxLength = 200; -}; diff --git a/tests/slo_workloads/core/rps.cpp b/tests/slo_workloads/core/rps.cpp deleted file mode 100644 index 5369280722..0000000000 --- a/tests/slo_workloads/core/rps.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "rps.h" - -#include - -TRpsProvider::TRpsProvider(std::uint64_t rps) - : Rps(rps) - , Period(Max(TDuration::MilliSeconds(10), TDuration::MicroSeconds(1000000 / Rps))) - , ProcessedTime(TInstant::Now()) -{ -} - -void TRpsProvider::Reset() { - ProcessedTime = TInstant::Now() - Period - Period; -} - -void TRpsProvider::Use() { - if (Allowed) { - --Allowed; - return; - } - - while (!TryUse()) { - SleepUntil(TInstant::Now() + Period); - } -} - -bool TRpsProvider::TryUse() { - TInstant now = TInstant::Now(); - Allowed = Rps * TDuration(now - ProcessedTime).MicroSeconds() / 1000000; - if (Allowed) { - ProcessedTime += TDuration::MicroSeconds(1000000 * Allowed / Rps); - --Allowed; - return true; - } - return false; -} - -std::uint64_t TRpsProvider::GetRps() const { - return Rps; -} diff --git a/tests/slo_workloads/core/rps.h b/tests/slo_workloads/core/rps.h deleted file mode 100644 index 23e9e74f7f..0000000000 --- a/tests/slo_workloads/core/rps.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -#include - -class TRpsProvider { -public: - explicit TRpsProvider(std::uint64_t rps); - void Reset(); - void Use(); - bool TryUse(); - std::uint64_t GetRps() const; - -private: - std::uint64_t Rps; - TDuration Period; - TInstant ProcessedTime; - TInstant LastCheck; - std::uint32_t Allowed = 0; - TInstant StartTime; -}; diff --git a/tests/slo_workloads/core/shard.cpp b/tests/slo_workloads/core/shard.cpp deleted file mode 100644 index 005abd9085..0000000000 --- a/tests/slo_workloads/core/shard.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "shard.h" - -#include "constants.h" - -#include -#include - -static double SloShardSizeDouble() { - return (static_cast(Max()) + 1) / static_cast(PartitionsCount); -} - -std::uint32_t SloGetSpecialId(std::uint32_t id) { - const double shardSize = SloShardSizeDouble(); - return static_cast(id / shardSize) * static_cast(shardSize) + 1; -} - -std::uint32_t SloGetShardSpecialId(std::uint64_t shardNo) { - return static_cast(shardNo * SloShardSizeDouble() + 1); -} - -std::uint32_t SloGetHash(std::uint32_t value) { - std::uint32_t result = NumericHash(value); - if (result == SloGetSpecialId(result)) { - ++result; - } - return result; -} diff --git a/tests/slo_workloads/core/shard.h b/tests/slo_workloads/core/shard.h deleted file mode 100644 index 71f3693103..0000000000 --- a/tests/slo_workloads/core/shard.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -std::uint32_t SloGetSpecialId(std::uint32_t id); -std::uint32_t SloGetShardSpecialId(std::uint64_t shardNo); -std::uint32_t SloGetHash(std::uint32_t value); diff --git a/tests/slo_workloads/core/slo_text_utils.cpp b/tests/slo_workloads/core/slo_text_utils.cpp deleted file mode 100644 index c7bacab22d..0000000000 --- a/tests/slo_workloads/core/slo_text_utils.cpp +++ /dev/null @@ -1,199 +0,0 @@ -#include "slo_text_utils.h" - -#include -#include - -#include - -std::string SloJoinPath(const std::string& prefix, const std::string& path) { - if (prefix.empty()) { - return path; - } - TPathSplitUnix prefixPathSplit(prefix); - prefixPathSplit.AppendComponent(path); - return prefixPathSplit.Reconstruct(); -} - -std::string SloGetDatabaseFromConnectionString(const std::string& connectionString) { - constexpr std::string_view databaseFlag = "/?database="; - size_t pathIndex = connectionString.find(databaseFlag); - if (pathIndex != std::string::npos) { - return connectionString.substr(pathIndex + databaseFlag.size()); - } - return {}; -} - -std::string SloGenerateRandomString(std::uint32_t minLength, std::uint32_t maxLength) { - std::uint32_t length = minLength + RandomNumber() % (maxLength - minLength); - static const char* symbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - std::string result; - result.reserve(length); - for (size_t i = 0; i < length; ++i) { - result.push_back(symbols[RandomNumber(61)]); - } - return result; -} - -std::string SloYdbStatusToString(NYdb::EStatus status) { - switch (status) { - case NYdb::EStatus::SUCCESS: - return "SUCCESS"; - case NYdb::EStatus::BAD_REQUEST: - return "BAD_REQUEST"; - case NYdb::EStatus::UNAUTHORIZED: - return "UNAUTHORIZED"; - case NYdb::EStatus::INTERNAL_ERROR: - return "INTERNAL_ERROR"; - case NYdb::EStatus::ABORTED: - return "ABORTED"; - case NYdb::EStatus::UNAVAILABLE: - return "UNAVAILABLE"; - case NYdb::EStatus::OVERLOADED: - return "OVERLOADED"; - case NYdb::EStatus::SCHEME_ERROR: - return "SCHEME_ERROR"; - case NYdb::EStatus::GENERIC_ERROR: - return "GENERIC_ERROR"; - case NYdb::EStatus::TIMEOUT: - return "TIMEOUT"; - case NYdb::EStatus::BAD_SESSION: - return "BAD_SESSION"; - case NYdb::EStatus::PRECONDITION_FAILED: - return "PRECONDITION_FAILED"; - case NYdb::EStatus::ALREADY_EXISTS: - return "ALREADY_EXISTS"; - case NYdb::EStatus::NOT_FOUND: - return "NOT_FOUND"; - case NYdb::EStatus::SESSION_EXPIRED: - return "SESSION_EXPIRED"; - case NYdb::EStatus::CANCELLED: - return "CANCELLED"; - case NYdb::EStatus::UNDETERMINED: - return "UNDETERMINED"; - case NYdb::EStatus::UNSUPPORTED: - return "UNSUPPORTED"; - case NYdb::EStatus::SESSION_BUSY: - return "SESSION_BUSY"; - case NYdb::EStatus::EXTERNAL_ERROR: - return "EXTERNAL_ERROR"; - case NYdb::EStatus::STATUS_UNDEFINED: - return "STATUS_UNDEFINED"; - case NYdb::EStatus::TRANSPORT_UNAVAILABLE: - return "TRANSPORT_UNAVAILABLE"; - case NYdb::EStatus::CLIENT_RESOURCE_EXHAUSTED: - return "CLIENT_RESOURCE_EXHAUSTED"; - case NYdb::EStatus::CLIENT_DEADLINE_EXCEEDED: - return "CLIENT_DEADLINE_EXCEEDED"; - case NYdb::EStatus::CLIENT_INTERNAL_ERROR: - return "CLIENT_INTERNAL_ERROR"; - case NYdb::EStatus::CLIENT_CANCELLED: - return "CLIENT_CANCELLED"; - case NYdb::EStatus::CLIENT_UNAUTHENTICATED: - return "CLIENT_UNAUTHENTICATED"; - case NYdb::EStatus::CLIENT_CALL_UNIMPLEMENTED: - return "CLIENT_CALL_UNIMPLEMENTED"; - case NYdb::EStatus::CLIENT_OUT_OF_RANGE: - return "CLIENT_OUT_OF_RANGE"; - case NYdb::EStatus::CLIENT_DISCOVERY_FAILED: - return "CLIENT_DISCOVERY_FAILED"; - case NYdb::EStatus::CLIENT_LIMITS_REACHED: - return "CLIENT_LIMITS_REACHED"; - } -} - -NYdb::EStatus SloYdbStatusFromString(const std::string& label) { - if (label == "SUCCESS") { - return NYdb::EStatus::SUCCESS; - } - if (label == "BAD_REQUEST") { - return NYdb::EStatus::BAD_REQUEST; - } - if (label == "UNAUTHORIZED") { - return NYdb::EStatus::UNAUTHORIZED; - } - if (label == "INTERNAL_ERROR") { - return NYdb::EStatus::INTERNAL_ERROR; - } - if (label == "ABORTED") { - return NYdb::EStatus::ABORTED; - } - if (label == "UNAVAILABLE") { - return NYdb::EStatus::UNAVAILABLE; - } - if (label == "OVERLOADED") { - return NYdb::EStatus::OVERLOADED; - } - if (label == "SCHEME_ERROR") { - return NYdb::EStatus::SCHEME_ERROR; - } - if (label == "GENERIC_ERROR" || label == "UNKNOWN_ERROR") { - return NYdb::EStatus::GENERIC_ERROR; - } - if (label == "TIMEOUT" || label == "APPLICATION_TIMEOUT") { - return NYdb::EStatus::TIMEOUT; - } - if (label == "BAD_SESSION") { - return NYdb::EStatus::BAD_SESSION; - } - if (label == "PRECONDITION_FAILED") { - return NYdb::EStatus::PRECONDITION_FAILED; - } - if (label == "ALREADY_EXISTS") { - return NYdb::EStatus::ALREADY_EXISTS; - } - if (label == "NOT_FOUND") { - return NYdb::EStatus::NOT_FOUND; - } - if (label == "SESSION_EXPIRED") { - return NYdb::EStatus::SESSION_EXPIRED; - } - if (label == "CANCELLED") { - return NYdb::EStatus::CANCELLED; - } - if (label == "UNDETERMINED") { - return NYdb::EStatus::UNDETERMINED; - } - if (label == "UNSUPPORTED") { - return NYdb::EStatus::UNSUPPORTED; - } - if (label == "SESSION_BUSY") { - return NYdb::EStatus::SESSION_BUSY; - } - if (label == "EXTERNAL_ERROR") { - return NYdb::EStatus::EXTERNAL_ERROR; - } - if (label == "STATUS_UNDEFINED") { - return NYdb::EStatus::STATUS_UNDEFINED; - } - if (label == "TRANSPORT_UNAVAILABLE") { - return NYdb::EStatus::TRANSPORT_UNAVAILABLE; - } - if (label == "CLIENT_RESOURCE_EXHAUSTED") { - return NYdb::EStatus::CLIENT_RESOURCE_EXHAUSTED; - } - if (label == "CLIENT_DEADLINE_EXCEEDED") { - return NYdb::EStatus::CLIENT_DEADLINE_EXCEEDED; - } - if (label == "CLIENT_INTERNAL_ERROR") { - return NYdb::EStatus::CLIENT_INTERNAL_ERROR; - } - if (label == "CLIENT_CANCELLED") { - return NYdb::EStatus::CLIENT_CANCELLED; - } - if (label == "CLIENT_UNAUTHENTICATED") { - return NYdb::EStatus::CLIENT_UNAUTHENTICATED; - } - if (label == "CLIENT_CALL_UNIMPLEMENTED") { - return NYdb::EStatus::CLIENT_CALL_UNIMPLEMENTED; - } - if (label == "CLIENT_OUT_OF_RANGE") { - return NYdb::EStatus::CLIENT_OUT_OF_RANGE; - } - if (label == "CLIENT_DISCOVERY_FAILED") { - return NYdb::EStatus::CLIENT_DISCOVERY_FAILED; - } - if (label == "CLIENT_LIMITS_REACHED") { - return NYdb::EStatus::CLIENT_LIMITS_REACHED; - } - return NYdb::EStatus::GENERIC_ERROR; -} diff --git a/tests/slo_workloads/core/slo_text_utils.h b/tests/slo_workloads/core/slo_text_utils.h deleted file mode 100644 index e9850cacd4..0000000000 --- a/tests/slo_workloads/core/slo_text_utils.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -#include -#include - -std::string SloJoinPath(const std::string& prefix, const std::string& path); -std::string SloGetDatabaseFromConnectionString(const std::string& connectionString); -std::string SloGenerateRandomString(std::uint32_t minLength, std::uint32_t maxLength); -std::string SloYdbStatusToString(NYdb::EStatus status); -NYdb::EStatus SloYdbStatusFromString(const std::string& label); diff --git a/tests/slo_workloads/native/key_value/CMakeLists.txt b/tests/slo_workloads/key_value/CMakeLists.txt similarity index 96% rename from tests/slo_workloads/native/key_value/CMakeLists.txt rename to tests/slo_workloads/key_value/CMakeLists.txt index 42128e5cdb..d3c87cb920 100644 --- a/tests/slo_workloads/native/key_value/CMakeLists.txt +++ b/tests/slo_workloads/key_value/CMakeLists.txt @@ -4,11 +4,12 @@ target_link_libraries(slo-key-value PUBLIC yutil getopt slo-utils - slo-kv-common YDB-CPP-SDK::Table ) target_sources(slo-key-value PRIVATE + create.cpp + drop.cpp generate.cpp key_value.cpp main.cpp diff --git a/tests/slo_workloads/common/key_value/create.cpp b/tests/slo_workloads/key_value/create.cpp similarity index 67% rename from tests/slo_workloads/common/key_value/create.cpp rename to tests/slo_workloads/key_value/create.cpp index 9c6eb80495..a105caba7f 100644 --- a/tests/slo_workloads/common/key_value/create.cpp +++ b/tests/slo_workloads/key_value/create.cpp @@ -1,20 +1,20 @@ -#include "kv_common.h" +#include "key_value.h" #include using namespace NYdb; using namespace NYdb::NTable; -int CreateTable(TDatabaseOptions& dbOptions) { - TTableClient client(dbOptions.Driver); - NYdb::NStatusHelpers::ThrowOnError(client.RetryOperationSync( - [prefix = dbOptions.Prefix](TSession session) { + +namespace { + void CreateTable(TTableClient& client, const std::string& prefix) { + NYdb::NStatusHelpers::ThrowOnError(client.RetryOperationSync([prefix](TSession session) { auto desc = TTableBuilder() .AddNullableColumn("object_id_key", EPrimitiveType::Uint32) .AddNullableColumn("object_id", EPrimitiveType::Uint32) .AddNullableColumn("timestamp", EPrimitiveType::Uint64) .AddNullableColumn("payload", EPrimitiveType::Utf8) - .SetPrimaryKeyColumns({"object_id_key", "object_id"}) + .SetPrimaryKeyColumns({ "object_id_key", "object_id" }) .Build(); auto tableSettings = TCreateTableSettings() @@ -23,11 +23,17 @@ int CreateTable(TDatabaseOptions& dbOptions) { .ClientTimeout(DefaultReactionTime + TDuration::Seconds(5)); return session.CreateTable( - JoinPath(prefix, TableName), - std::move(desc), - std::move(tableSettings) + JoinPath(prefix, TableName) + , std::move(desc) + , std::move(tableSettings) ).ExtractValueSync(); })); + } +} //namespace + +int CreateTable(TDatabaseOptions& dbOptions) { + TTableClient client(dbOptions.Driver); + CreateTable(client, dbOptions.Prefix); Cout << "Table created." << Endl; return EXIT_SUCCESS; } diff --git a/tests/slo_workloads/key_value/drop.cpp b/tests/slo_workloads/key_value/drop.cpp new file mode 100644 index 0000000000..e22c92c894 --- /dev/null +++ b/tests/slo_workloads/key_value/drop.cpp @@ -0,0 +1,23 @@ +#include "key_value.h" + +using namespace NLastGetopt; +using namespace NYdb; +using namespace NYdb::NTable; + +int DropTable(TDatabaseOptions& dbOptions) { + TTableClient client(dbOptions.Driver); + const std::string path = JoinPath(dbOptions.Prefix, TableName); + TStatus status = client.RetryOperationSync([path](TSession session) { + TStatus dropStatus = session.DropTable(path).ExtractValueSync(); + if (dropStatus.GetStatus() == EStatus::NOT_FOUND) { + return TStatus(EStatus::SUCCESS, NYdb::NIssue::TIssues()); + } + return dropStatus; + }); + if (!status.IsSuccess()) { + Cerr << "DropTable failed: " << status << Endl; + return EXIT_FAILURE; + } + Cout << "Table dropped." << Endl; + return EXIT_SUCCESS; +} diff --git a/tests/slo_workloads/native/key_value/generate.cpp b/tests/slo_workloads/key_value/generate.cpp similarity index 98% rename from tests/slo_workloads/native/key_value/generate.cpp rename to tests/slo_workloads/key_value/generate.cpp index 6b84b2a692..4e6b8bb3d9 100644 --- a/tests/slo_workloads/native/key_value/generate.cpp +++ b/tests/slo_workloads/key_value/generate.cpp @@ -11,7 +11,7 @@ TGenerateInitialContentJob::TGenerateInitialContentJob(const TCreateOptions& cre : TThreadJob(createOpts.CommonOptions, "generate") , Executor(createOpts.CommonOptions, Stats, TExecutor::ModeBlocking) , PackGenerator( - MakeGeneratorOptions(createOpts.CommonOptions) + createOpts.CommonOptions , createOpts.PackSize , [](const TKeyValueRecordData& recordData) { return BuildValueFromRecord(recordData); } , createOpts.Count diff --git a/tests/slo_workloads/native/key_value/key_value.cpp b/tests/slo_workloads/key_value/key_value.cpp similarity index 85% rename from tests/slo_workloads/native/key_value/key_value.cpp rename to tests/slo_workloads/key_value/key_value.cpp index 72718277c3..23bd3ff184 100644 --- a/tests/slo_workloads/native/key_value/key_value.cpp +++ b/tests/slo_workloads/key_value/key_value.cpp @@ -4,6 +4,21 @@ #include #include +using namespace NYdb; + +const std::string TableName = "key_value"; + +NYdb::TValue BuildValueFromRecord(const TKeyValueRecordData& recordData) { + NYdb::TValueBuilder value; + value.BeginStruct(); + value.AddMember("object_id_key").Uint32(GetHash(recordData.ObjectId)); + value.AddMember("object_id").Uint32(recordData.ObjectId); + value.AddMember("timestamp").Uint64(recordData.Timestamp); + value.AddMember("payload").Utf8(recordData.Payload); + value.EndStruct(); + return value.Build(); +} + int DoCreate(TDatabaseOptions& dbOptions, int argc, char** argv) { TCreateOptions createOptions{ {dbOptions} }; if (!ParseOptionsCreate(argc, argv, createOptions)) { diff --git a/tests/slo_workloads/native/key_value/key_value.h b/tests/slo_workloads/key_value/key_value.h similarity index 72% rename from tests/slo_workloads/native/key_value/key_value.h rename to tests/slo_workloads/key_value/key_value.h index 31be3e7d07..4238f01c73 100644 --- a/tests/slo_workloads/native/key_value/key_value.h +++ b/tests/slo_workloads/key_value/key_value.h @@ -1,9 +1,13 @@ #pragma once -#include +#include +#include +#include +#include -#include -#include +extern const std::string TableName; + +NYdb::TValue BuildValueFromRecord(const TKeyValueRecordData& recordData); // Initial content generation class TGenerateInitialContentJob : public TThreadJob { @@ -15,7 +19,7 @@ class TGenerateInitialContentJob : public TThreadJob { private: TExecutor Executor; - TPackGenerator PackGenerator; + TPackGenerator PackGenerator; std::uint64_t Total; }; @@ -33,7 +37,7 @@ class TWriteJob : public TThreadJob { std::atomic ValuesGenerated = 0; }; -// Read workload job +// Read workload job class TReadJob : public TThreadJob { public: TReadJob(const TCommonOptions& opts, std::uint32_t maxId); @@ -46,9 +50,12 @@ class TReadJob : public TThreadJob { std::uint32_t ObjectIdRange; }; +int CreateTable(TDatabaseOptions& dbOptions); +int DropTable(TDatabaseOptions& dbOptions); + // Creates a table and fills it with initial content int DoCreate(TDatabaseOptions& dbOptions, int argc, char** argv); -// Runs read/write workload +// Not implemented int DoRun(TDatabaseOptions& dbOptions, int argc, char** argv); // Drops the table int DoCleanup(TDatabaseOptions& dbOptions, int argc); diff --git a/tests/slo_workloads/native/key_value/main.cpp b/tests/slo_workloads/key_value/main.cpp similarity index 100% rename from tests/slo_workloads/native/key_value/main.cpp rename to tests/slo_workloads/key_value/main.cpp diff --git a/tests/slo_workloads/native/key_value/run.cpp b/tests/slo_workloads/key_value/run.cpp similarity index 99% rename from tests/slo_workloads/native/key_value/run.cpp rename to tests/slo_workloads/key_value/run.cpp index 2a0a2a66c8..671947ccf0 100644 --- a/tests/slo_workloads/native/key_value/run.cpp +++ b/tests/slo_workloads/key_value/run.cpp @@ -9,7 +9,7 @@ using namespace NYdb::NTable; TWriteJob::TWriteJob(const TCommonOptions& opts, std::uint32_t maxId) : TThreadJob(opts, "write") , Executor(opts, Stats, TExecutor::ModeNonBlocking) - , Generator(MakeGeneratorOptions(opts), maxId) + , Generator(opts, maxId) {} void TWriteJob::ShowProgress(TStringBuilder& report) { diff --git a/tests/slo_workloads/native/CMakeLists.txt b/tests/slo_workloads/native/CMakeLists.txt deleted file mode 100644 index 0692a7a141..0000000000 --- a/tests/slo_workloads/native/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_subdirectory(utils) -add_subdirectory(key_value) diff --git a/tests/slo_workloads/native/utils/CMakeLists.txt b/tests/slo_workloads/native/utils/CMakeLists.txt deleted file mode 100644 index bd1ae20bf3..0000000000 --- a/tests/slo_workloads/native/utils/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Base utils library: shared between native and userver workloads -# Contains: ParseOptions*, GetTableStats, YdbStatusToString, PackValuesToParamsAsList, etc. -# Does NOT contain DoMain (each workload provides its own). -add_library(slo-utils-base) - -target_link_libraries(slo-utils-base PUBLIC - slo-workload-core - yutil - getopt - YDB-CPP-SDK::Table - YDB-CPP-SDK::Iam -) - -if (SLO_BRANCH_REF) - target_compile_definitions(slo-utils-base PRIVATE REF=${SLO_BRANCH_REF}) -endif() - -target_sources(slo-utils-base PRIVATE - utils.cpp -) - -# Full native utils library: adds DoMain, executor and job infrastructure (thread-based) -add_library(slo-utils) - -target_link_libraries(slo-utils PUBLIC - slo-utils-base -) - -target_sources(slo-utils PRIVATE - utils_main.cpp - executor.cpp - job.cpp -) diff --git a/tests/slo_workloads/userver/key_value/CMakeLists.txt b/tests/slo_workloads/userver/key_value/CMakeLists.txt index 480c5fd9bb..afda760ba2 100644 --- a/tests/slo_workloads/userver/key_value/CMakeLists.txt +++ b/tests/slo_workloads/userver/key_value/CMakeLists.txt @@ -4,15 +4,18 @@ target_sources(slo-userver-key-value PRIVATE main.cpp userver_utils.cpp userver_table_client.cpp + key_value.cpp create.cpp run.cpp + # CreateTable / DropTable: reuse native sources verbatim. + ${CMAKE_SOURCE_DIR}/tests/slo_workloads/key_value/create.cpp + ${CMAKE_SOURCE_DIR}/tests/slo_workloads/key_value/drop.cpp ) target_link_libraries(slo-userver-key-value PRIVATE slo-utils-base # Shared utils: ParseOptions*, GetTableStats, YdbStatusToString, etc. - slo-kv-common # Shared KV: TableName, BuildValueFromRecord, CreateTable, DropTable userver::ydb # ydb::TableClient (workload queries) - userver::core # engine::RunStandalone, Semaphore, AsyncNoSpan, SleepFor + userver::core # engine::RunStandalone, Semaphore, AsyncNoSpan ) # userver::ydb impl headers (ydb/impl/driver.hpp) live under the source tree, not in the .deb. diff --git a/tests/slo_workloads/userver/key_value/create.cpp b/tests/slo_workloads/userver/key_value/create.cpp index 37d298af7b..87d5a118f5 100644 --- a/tests/slo_workloads/userver/key_value/create.cpp +++ b/tests/slo_workloads/userver/key_value/create.cpp @@ -11,6 +11,7 @@ #include #include +#include #include using namespace NYdb; @@ -20,7 +21,6 @@ namespace uydb = userver::ydb; namespace { -// Shared stop flag for signal handling std::atomic ShouldStop{false}; } // namespace @@ -45,7 +45,6 @@ int DoCreate(TDatabaseOptions& dbOptions, int argc, char** argv) { Cout << TInstant::Now().ToRfc822StringLocal() << " Uploading initial content... do 'kill -USR1 " << GetPID() << "' for progress details or Ctrl/Cmd+C to interrupt" << Endl; - // Set up signal handlers ShouldStop.store(false); signal(SIGINT, [](int) { Cerr << TInstant::Now().ToRfc822StringLocal() << " SIGINT received. Stopping..." << Endl; @@ -57,21 +56,17 @@ int DoCreate(TDatabaseOptions& dbOptions, int argc, char** argv) { auto& ydbClient = userver_slo::GetTableClient(); - // Engine primitives (Semaphore, AsyncNoSpan) require a task context; run.cpp - // wraps its workload loop the same way. userver::engine::AsyncNoSpan([&]() { TStat stats( opts.DontPushMetrics ? std::nullopt : std::make_optional(opts.MetricsPushUrl), - "generate", - MakeNativeSloOtelResourceAttributes(), - NativeSloMeterSchemaVersion() + "generate" ); stats.Start(); - TPackGenerator packGenerator( - MakeGeneratorOptions(opts), + TPackGenerator packGenerator( + opts, createOptions.PackSize, - [](const TKeyValueRecordData& recordData) { return BuildValueFromRecord(recordData); }, + &BuildValueFromRecord, createOptions.Count, maxId ); @@ -117,19 +112,13 @@ UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); ydbClient.ExecuteDataQuery(settings, uydb::Query{query}, std::move(params)); - TSloRequestFinish finish; - finish.StatusLabel = "SUCCESS"; - stats.FinishRequest(stat, finish); + stats.FinishRequest(stat, TStatus(EStatus::SUCCESS, NYdb::NIssue::TIssues())); succeeded.fetch_add(1); } catch (const uydb::YdbResponseError& e) { - TSloRequestFinish finish; - finish.StatusLabel = YdbStatusToString(e.GetStatus().GetStatus()); - stats.FinishRequest(stat, finish); + stats.FinishRequest(stat, TStatus(e.GetStatus().GetStatus(), NYdb::NIssue::TIssues())); failed.fetch_add(1); - } catch (const std::exception& e) { - TSloRequestFinish finish; - finish.StatusLabel = "UNKNOWN_ERROR"; - stats.FinishRequest(stat, finish); + } catch (const std::exception&) { + stats.FinishRequest(stat, TStatus(EStatus::CLIENT_INTERNAL_ERROR, NYdb::NIssue::TIssues())); failed.fetch_add(1); } semaphore.unlock_shared(); diff --git a/tests/slo_workloads/common/key_value/kv_data.cpp b/tests/slo_workloads/userver/key_value/key_value.cpp similarity index 95% rename from tests/slo_workloads/common/key_value/kv_data.cpp rename to tests/slo_workloads/userver/key_value/key_value.cpp index 801ec358f0..08fee6bdde 100644 --- a/tests/slo_workloads/common/key_value/kv_data.cpp +++ b/tests/slo_workloads/userver/key_value/key_value.cpp @@ -1,4 +1,4 @@ -#include "kv_common.h" +#include "key_value.h" using namespace NYdb; diff --git a/tests/slo_workloads/userver/key_value/key_value.h b/tests/slo_workloads/userver/key_value/key_value.h index 51de91fc91..462e27dea3 100644 --- a/tests/slo_workloads/userver/key_value/key_value.h +++ b/tests/slo_workloads/userver/key_value/key_value.h @@ -2,13 +2,19 @@ #include "userver_utils.h" -#include +#include +#include -#include +#include #include -#include -#include + +extern const std::string TableName; + +NYdb::TValue BuildValueFromRecord(const TKeyValueRecordData& recordData); + +int CreateTable(TDatabaseOptions& dbOptions); +int DropTable(TDatabaseOptions& dbOptions); // Creates a table and fills it with initial content (userver coroutine-based) int DoCreate(TDatabaseOptions& dbOptions, int argc, char** argv); diff --git a/tests/slo_workloads/userver/key_value/run.cpp b/tests/slo_workloads/userver/key_value/run.cpp index 707edae484..2de02015ca 100644 --- a/tests/slo_workloads/userver/key_value/run.cpp +++ b/tests/slo_workloads/userver/key_value/run.cpp @@ -2,11 +2,7 @@ #include "userver_table_client.h" #include -#include -#include #include -#include -#include #include #include #include @@ -15,8 +11,8 @@ #include #include +#include #include -#include using namespace NYdb; using namespace NYdb::NTable; @@ -25,10 +21,8 @@ namespace uydb = userver::ydb; namespace { -// Shared stop flag for signal handling std::atomic ShouldStop{false}; -// Show progress callback struct TProgressReporter { TStat* ReadStats = nullptr; TStat* WriteStats = nullptr; @@ -61,125 +55,25 @@ struct TProgressReporter { TProgressReporter* GlobalReporter = nullptr; -// Execute a query with optional application timeout and preventive request -void ExecuteWithTimeout( +void ExecuteQuery( uydb::TableClient& client, const uydb::OperationSettings& settings, const uydb::Query& query, - const std::function& makeParams, + uydb::PreparedArgsBuilder params, TStat& stats, std::atomic& succeeded, - std::atomic& failed, - bool useApplicationTimeout, - bool sendPreventiveRequest, - TDuration reactionTime) + std::atomic& failed) { auto stat = stats.StartRequest(); - - auto doQuery = [&client, &settings, &query](uydb::PreparedArgsBuilder queryParams) { - client.ExecuteDataQuery(settings, query, std::move(queryParams)); - }; - try { - if (!useApplicationTimeout && !sendPreventiveRequest) { - doQuery(makeParams()); - - TSloRequestFinish finish; - finish.StatusLabel = "SUCCESS"; - stats.FinishRequest(stat, finish); - succeeded.fetch_add(1); - return; - } - - if (useApplicationTimeout && !sendPreventiveRequest) { - auto task = userver::engine::AsyncNoSpan( - [&doQuery, &makeParams]() { - doQuery(makeParams()); - } - ); - - if (task.WaitNothrowUntil(userver::engine::Deadline::FromDuration( - std::chrono::milliseconds(reactionTime.MilliSeconds()))) - == userver::engine::FutureStatus::kReady) { - task.Get(); // may throw - TSloRequestFinish finish; - finish.StatusLabel = "SUCCESS"; - stats.FinishRequest(stat, finish); - succeeded.fetch_add(1); - } else { - task.RequestCancel(); - TSloRequestFinish finish; - finish.ApplicationTimeout = true; - stats.FinishRequest(stat, finish); - failed.fetch_add(1); - } - return; - } - - if (sendPreventiveRequest) { - auto task1 = userver::engine::AsyncNoSpan( - [&doQuery, &makeParams]() { - doQuery(makeParams()); - } - ); - - userver::engine::SleepFor( - std::chrono::milliseconds(reactionTime.MilliSeconds() / 2)); - - if (task1.IsFinished()) { - task1.Get(); // may throw - TSloRequestFinish finish; - finish.StatusLabel = "SUCCESS"; - stats.FinishRequest(stat, finish); - succeeded.fetch_add(1); - return; - } - - auto task2 = userver::engine::AsyncNoSpan( - [&doQuery, &makeParams]() { - doQuery(makeParams()); - } - ); - - auto idx = userver::engine::WaitAny(task1, task2); - - if (idx.has_value()) { - try { - if (*idx == 0) { - task1.Get(); - } else { - task2.Get(); - } - TSloRequestFinish finish; - finish.StatusLabel = "SUCCESS"; - stats.FinishRequest(stat, finish); - succeeded.fetch_add(1); - } catch (const uydb::YdbResponseError& e) { - TSloRequestFinish finish; - finish.StatusLabel = YdbStatusToString(e.GetStatus().GetStatus()); - stats.FinishRequest(stat, finish); - failed.fetch_add(1); - } - } else if (useApplicationTimeout) { - TSloRequestFinish finish; - finish.ApplicationTimeout = true; - stats.FinishRequest(stat, finish); - failed.fetch_add(1); - } - // Cancel remaining tasks - task1.RequestCancel(); - task2.RequestCancel(); - return; - } + client.ExecuteDataQuery(settings, query, std::move(params)); + stats.FinishRequest(stat, TStatus(EStatus::SUCCESS, NYdb::NIssue::TIssues())); + succeeded.fetch_add(1); } catch (const uydb::YdbResponseError& e) { - TSloRequestFinish finish; - finish.StatusLabel = YdbStatusToString(e.GetStatus().GetStatus()); - stats.FinishRequest(stat, finish); + stats.FinishRequest(stat, TStatus(e.GetStatus().GetStatus(), NYdb::NIssue::TIssues())); failed.fetch_add(1); - } catch (const std::exception& e) { - TSloRequestFinish finish; - finish.StatusLabel = "UNKNOWN_ERROR"; - stats.FinishRequest(stat, finish); + } catch (const std::exception&) { + stats.FinishRequest(stat, TStatus(EStatus::CLIENT_INTERNAL_ERROR, NYdb::NIssue::TIssues())); failed.fetch_add(1); } } @@ -200,7 +94,6 @@ int DoRun(TDatabaseOptions& dbOptions, int argc, char** argv) { auto& ydbClient = userver_slo::GetTableClient(); - // Stats objects std::unique_ptr readStats; std::unique_ptr writeStats; @@ -213,21 +106,16 @@ int DoRun(TDatabaseOptions& dbOptions, int argc, char** argv) { if (!runOptions.DontRunA) { readStats = std::make_unique( runOptions.CommonOptions.DontPushMetrics ? std::nullopt : std::make_optional(runOptions.CommonOptions.MetricsPushUrl), - "read", - MakeNativeSloOtelResourceAttributes(), - NativeSloMeterSchemaVersion() + "read" ); } if (!runOptions.DontRunB) { writeStats = std::make_unique( runOptions.CommonOptions.DontPushMetrics ? std::nullopt : std::make_optional(runOptions.CommonOptions.MetricsPushUrl), - "write", - MakeNativeSloOtelResourceAttributes(), - NativeSloMeterSchemaVersion() + "write" ); } - // Set up progress reporter TProgressReporter reporter; reporter.ReadStats = readStats.get(); reporter.WriteStats = writeStats.get(); @@ -238,7 +126,6 @@ int DoRun(TDatabaseOptions& dbOptions, int argc, char** argv) { reporter.WriteGenerated = &writeGenerated; GlobalReporter = &reporter; - // Set up signal handlers ShouldStop.store(false); signal(SIGUSR1, [](int) { Cout << TInstant::Now().ToRfc822StringLocal() << " SIGUSR1 handle" << Endl; @@ -261,14 +148,11 @@ int DoRun(TDatabaseOptions& dbOptions, int argc, char** argv) { std::vector> jobTasks; - // Thread A: Read workload if (!runOptions.DontRunA) { auto readRps = runOptions.Read_rps; auto readTimeout = runOptions.ReadTimeout; - auto useAppTimeout = runOptions.CommonOptions.UseApplicationTimeout; - auto sendPreventive = runOptions.CommonOptions.SendPreventiveRequest; auto maxInfly = runOptions.CommonOptions.MaxInfly; - auto objectIdRange = static_cast(maxId * 1.25); // 20% no-result reads + auto objectIdRange = static_cast(maxId * 1.25); const TString readQuery = Sprintf(R"( --!syntax_v1 @@ -283,7 +167,7 @@ SELECT * FROM `%s` WHERE `object_id_key` = $object_id_key AND `object_id` = $obj jobTasks.push_back(userver::engine::AsyncNoSpan( [&ydbClient, &readStats, &readSucceeded, &readFailed, - readRps, readTimeout, useAppTimeout, sendPreventive, maxInfly, + readRps, readTimeout, maxInfly, objectIdRange, readQuery, deadline]() { readStats->Start(); @@ -305,31 +189,25 @@ SELECT * FROM `%s` WHERE `object_id_key` = $object_id_key AND `object_id` = $obj tasks.push_back(userver::engine::AsyncNoSpan( [&ydbClient, &semaphore, &readStats, &readSucceeded, &readFailed, - readTimeout, useAppTimeout, sendPreventive, - readQuery, objectIdKey, idToSelect]() { + readTimeout, readQuery, objectIdKey, idToSelect]() { uydb::OperationSettings settings; settings.client_timeout_ms = std::chrono::milliseconds(readTimeout.MilliSeconds()); - const auto makeParams = [&ydbClient, objectIdKey, idToSelect]() { - auto builder = ydbClient.GetBuilder(); - builder.Add("$object_id_key", objectIdKey); - builder.Add("$object_id", idToSelect); - return builder; - }; + auto builder = ydbClient.GetBuilder(); + builder.Add("$object_id_key", objectIdKey); + builder.Add("$object_id", idToSelect); - ExecuteWithTimeout( + ExecuteQuery( ydbClient, settings, uydb::Query{readQuery}, - makeParams, *readStats, - readSucceeded, readFailed, - useAppTimeout, sendPreventive, readTimeout); + std::move(builder), *readStats, + readSucceeded, readFailed); semaphore.unlock_shared(); } )); } - // Wait for all read tasks for (auto& task : tasks) { task.Wait(); } @@ -339,15 +217,12 @@ SELECT * FROM `%s` WHERE `object_id_key` = $object_id_key AND `object_id` = $obj )); } - // Thread B: Write workload if (!runOptions.DontRunB) { auto writeRps = runOptions.Write_rps; auto writeTimeout = runOptions.WriteTimeout; - auto useAppTimeout = runOptions.CommonOptions.UseApplicationTimeout; - auto sendPreventive = runOptions.CommonOptions.SendPreventiveRequest; auto maxInfly = runOptions.CommonOptions.MaxInfly; - auto minLength = runOptions.CommonOptions.MinLength; - auto maxLength = runOptions.CommonOptions.MaxLength; + + const TCommonOptions writeCommon = runOptions.CommonOptions; const TString writeQuery = Sprintf(R"( --!syntax_v1 @@ -366,17 +241,14 @@ UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); jobTasks.push_back(userver::engine::AsyncNoSpan( [&ydbClient, &writeStats, &writeSucceeded, &writeFailed, &writeGenerated, - writeRps, writeTimeout, useAppTimeout, sendPreventive, maxInfly, - minLength, maxLength, maxId, writeQuery, deadline]() { + writeRps, writeTimeout, maxInfly, + writeCommon, maxId, writeQuery, deadline]() { writeStats->Start(); TRpsProvider rpsProvider(writeRps); rpsProvider.Reset(); - TSloGeneratorOptions genOpts; - genOpts.MinLength = minLength; - genOpts.MaxLength = maxLength; - TKeyValueGenerator generator(genOpts, maxId); + TKeyValueGenerator generator(writeCommon, maxId); userver::engine::Semaphore semaphore{ static_cast(maxInfly)}; @@ -393,28 +265,23 @@ UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); tasks.push_back(userver::engine::AsyncNoSpan( [&ydbClient, &semaphore, &writeStats, &writeSucceeded, &writeFailed, - writeTimeout, useAppTimeout, sendPreventive, - writeQuery, value]() { + writeTimeout, writeQuery, value]() { uydb::OperationSettings settings; settings.client_timeout_ms = std::chrono::milliseconds(writeTimeout.MilliSeconds()); - const auto makeParams = [value]() { - return userver_slo::PackValuesToPreparedArgs({value}); - }; + auto params = userver_slo::PackValuesToPreparedArgs({value}); - ExecuteWithTimeout( + ExecuteQuery( ydbClient, settings, uydb::Query{writeQuery}, - makeParams, *writeStats, - writeSucceeded, writeFailed, - useAppTimeout, sendPreventive, writeTimeout); + std::move(params), *writeStats, + writeSucceeded, writeFailed); semaphore.unlock_shared(); } )); } - // Wait for all write tasks for (auto& task : tasks) { task.Wait(); } @@ -424,7 +291,6 @@ UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); )); } - // Wait for all job tasks (read + write loops) for (auto& task : jobTasks) { task.Wait(); } diff --git a/tests/slo_workloads/userver/key_value/userver_utils.cpp b/tests/slo_workloads/userver/key_value/userver_utils.cpp index 39c9d15c3a..5661675fbe 100644 --- a/tests/slo_workloads/userver/key_value/userver_utils.cpp +++ b/tests/slo_workloads/userver/key_value/userver_utils.cpp @@ -21,37 +21,6 @@ using namespace NLastGetopt; using namespace NYdb; -namespace { - -bool ParseToken(std::string& token, std::string& tokenFile) { - if (!tokenFile.empty()) { - if (!token.empty()) { - Cerr << "Both token and token_file provided. Choose one." << Endl; - } else { - TFsPath path(tokenFile); - if (path.Exists()) { - token = Strip(TUnbufferedFileInput(path).ReadAll()); - return true; - } - Cerr << "Wrong path provided for token_file." << Endl; - } - } else if (!token.empty()) { - return true; - } else { - token = GetEnv("YDB_TOKEN"); - return true; - } - return false; -} - -void StartStatCollecting([[maybe_unused]] TDriver& driver, const std::string& statConfigFile) { - if (statConfigFile.empty()) { - return; - } -} - -} // namespace - // Override DoMain to wrap command dispatch in RunStandalone. // The native DoMain (from slo-utils-base) runs commands directly in the calling // thread. The userver version needs the coroutine engine running for diff --git a/tests/slo_workloads/userver/key_value/userver_utils.h b/tests/slo_workloads/userver/key_value/userver_utils.h index f583895c9f..dde60901d6 100644 --- a/tests/slo_workloads/userver/key_value/userver_utils.h +++ b/tests/slo_workloads/userver/key_value/userver_utils.h @@ -4,4 +4,4 @@ // CLI parsing, option structs, and helper functions as the native workload. // Only DoMain() is overridden in userver_utils.cpp to wrap command execution // inside userver::engine::RunStandalone(). -#include +#include diff --git a/tests/slo_workloads/utils/CMakeLists.txt b/tests/slo_workloads/utils/CMakeLists.txt new file mode 100644 index 0000000000..4dd24019a2 --- /dev/null +++ b/tests/slo_workloads/utils/CMakeLists.txt @@ -0,0 +1,51 @@ +include(FetchContent) + +FetchContent_Declare( + hdr_histogram + GIT_REPOSITORY https://github.com/HdrHistogram/HdrHistogram_c.git + GIT_TAG 0.11.8 + EXCLUDE_FROM_ALL +) +set(HDR_HISTOGRAM_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE) +set(HDR_HISTOGRAM_BUILD_SHARED OFF CACHE BOOL "" FORCE) +set(HDR_LOG_REQUIRED OFF CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(hdr_histogram) + +# slo-utils-base: shared helpers, no DoMain. The userver workload links this +# and supplies its own DoMain that wraps command dispatch in RunStandalone. +add_library(slo-utils-base) + +target_link_libraries(slo-utils-base PUBLIC + yutil + getopt + YDB-CPP-SDK::Table + YDB-CPP-SDK::Iam + opentelemetry-cpp::metrics + opentelemetry-cpp::otlp_http_metric_exporter +) + +target_link_libraries(slo-utils-base PRIVATE + hdr_histogram_static +) + +target_sources(slo-utils-base PRIVATE + executor.cpp + generator.cpp + job.cpp + metrics.cpp + statistics.cpp + utils.cpp +) + +# slo-utils: slo-utils-base + DoMain. Native workload links this. +# The set of .o files linked into slo-key-value is byte-equivalent to the +# pre-split single-library layout used in slo/align-with-action-v2. +add_library(slo-utils) + +target_link_libraries(slo-utils PUBLIC + slo-utils-base +) + +target_sources(slo-utils PRIVATE + utils_main.cpp +) diff --git a/tests/slo_workloads/native/utils/executor.cpp b/tests/slo_workloads/utils/executor.cpp similarity index 100% rename from tests/slo_workloads/native/utils/executor.cpp rename to tests/slo_workloads/utils/executor.cpp diff --git a/tests/slo_workloads/native/utils/executor.h b/tests/slo_workloads/utils/executor.h similarity index 98% rename from tests/slo_workloads/native/utils/executor.h rename to tests/slo_workloads/utils/executor.h index 049c1bd436..637d791b9a 100644 --- a/tests/slo_workloads/native/utils/executor.h +++ b/tests/slo_workloads/utils/executor.h @@ -1,9 +1,8 @@ #pragma once +#include "statistics.h" #include "utils.h" -#include - #include #include diff --git a/tests/slo_workloads/core/generator.cpp b/tests/slo_workloads/utils/generator.cpp similarity index 72% rename from tests/slo_workloads/core/generator.cpp rename to tests/slo_workloads/utils/generator.cpp index b7081b5433..8543347838 100644 --- a/tests/slo_workloads/core/generator.cpp +++ b/tests/slo_workloads/utils/generator.cpp @@ -1,6 +1,9 @@ #include "generator.h" -TValueGenerator::TValueGenerator(const TSloGeneratorOptions& opts, ui32 startId) +#include + + +TValueGenerator::TValueGenerator(const TCommonOptions& opts, ui32 startId) : Opts(opts) , CurrentObjectId(startId) { @@ -15,7 +18,7 @@ TRecordData TValueGenerator::Get() { CurrentObjectId, TInstant::Now().MicroSeconds(), CreateGuidAsString(), - SloGenerateRandomString(Opts.MinLength, Opts.MaxLength) + GenerateRandomString(Opts.MinLength, Opts.MaxLength) }; } @@ -23,7 +26,7 @@ TDuration TValueGenerator::GetComputeTime() const { return ComputeTime; } -TKeyValueGenerator::TKeyValueGenerator(const TSloGeneratorOptions& opts, ui32 startId) +TKeyValueGenerator::TKeyValueGenerator(const TCommonOptions& opts, ui32 startId) : Opts(opts) , CurrentObjectId(startId) { @@ -35,7 +38,7 @@ TKeyValueRecordData TKeyValueGenerator::Get() { return { CurrentObjectId, TInstant::Now().MicroSeconds(), - SloGenerateRandomString(Opts.MinLength, Opts.MaxLength) + GenerateRandomString(Opts.MinLength, Opts.MaxLength) }; } diff --git a/tests/slo_workloads/utils/generator.h b/tests/slo_workloads/utils/generator.h new file mode 100644 index 0000000000..584ecd68e9 --- /dev/null +++ b/tests/slo_workloads/utils/generator.h @@ -0,0 +1,90 @@ +#pragma once + +#include "utils.h" + +#include + + +class TValueGenerator { +public: + TValueGenerator(const TCommonOptions& opts, std::uint32_t startId = 0); + TRecordData Get(); + TDuration GetComputeTime() const; + +private: + const TCommonOptions& Opts; + std::uint32_t CurrentObjectId = 0; + TDuration ComputeTime; +}; + +class TKeyValueGenerator { +public: + TKeyValueGenerator(const TCommonOptions& opts, std::uint32_t startId = 0); + TKeyValueRecordData Get(); + TDuration GetComputeTime() const; + +private: + const TCommonOptions& Opts; + std::uint32_t CurrentObjectId = 0; + TDuration ComputeTime; +}; + +template +class TPackGenerator { +public: + TPackGenerator( + const TCommonOptions& opts, + std::uint32_t packSize, + NYdb::TValue(*buildValueFromRecordFunc)(const TRecordType&), + std::uint64_t remain, + std::uint32_t startId = 0 + ) + : BuildValueFromRecordFunc(buildValueFromRecordFunc) + , Generator(opts, startId) + , PackSize(packSize) + , Remain(remain) + { + } + + // Returns One-shard pack + bool GetNextPack(std::vector& pack) { + pack.clear(); + while (Remain) { + TRecordType record = Generator.Get(); + --Remain; + std::uint32_t specialId = GetSpecialId(GetHash(record.ObjectId)); + auto& existingPack = Packs[specialId]; + existingPack.emplace_back(BuildValueFromRecordFunc(record)); + if (existingPack.size() >= PackSize) { + existingPack.swap(pack); + return true; + } + } + for (auto& it : Packs) { + if (it.second.size()) { + it.second.swap(pack); + return true; + } + } + return false; + } + + std::uint32_t GetPackSize() const { + return PackSize; + } + + TDuration GetComputeTime() const { + return Generator.GetComputeTime(); + } + + std::uint64_t GetRemain() const { + return Remain; + } + +private: + NYdb::TValue(*BuildValueFromRecordFunc)(const TRecordType&); + TGeneratorType Generator; + std::uint32_t PackSize; + std::unordered_map> Packs; + std::uint64_t Remain; +}; diff --git a/tests/slo_workloads/native/utils/job.cpp b/tests/slo_workloads/utils/job.cpp similarity index 91% rename from tests/slo_workloads/native/utils/job.cpp rename to tests/slo_workloads/utils/job.cpp index 18ec0690a7..cc627b2b70 100644 --- a/tests/slo_workloads/native/utils/job.cpp +++ b/tests/slo_workloads/utils/job.cpp @@ -8,12 +8,7 @@ const std::string LogFileName = "benchmark.log"; TThreadJob::TThreadJob(const TCommonOptions& opts, const std::string& operationType) : RpsProvider(opts.Rps) , Prefix(opts.DatabaseOptions.Prefix) - , Stats( - opts.DontPushMetrics ? std::nullopt : std::make_optional(opts.MetricsPushUrl), - operationType, - MakeNativeSloOtelResourceAttributes(), - NativeSloMeterSchemaVersion() - ) + , Stats(opts.DontPushMetrics ? std::nullopt : std::make_optional(opts.MetricsPushUrl), operationType) , StopOnError(opts.StopOnError) , MaxDelay(opts.ReactionTime) { diff --git a/tests/slo_workloads/native/utils/job.h b/tests/slo_workloads/utils/job.h similarity index 92% rename from tests/slo_workloads/native/utils/job.h rename to tests/slo_workloads/utils/job.h index caf322f10e..a57a790b43 100644 --- a/tests/slo_workloads/native/utils/job.h +++ b/tests/slo_workloads/utils/job.h @@ -1,9 +1,8 @@ #pragma once +#include "statistics.h" #include "utils.h" -#include - #include #include @@ -49,7 +48,7 @@ class TJobContainer { class TJobGC { public: - explicit TJobGC(std::shared_ptr& jobs) + TJobGC(std::shared_ptr& jobs) : Jobs(jobs) {} diff --git a/tests/slo_workloads/core/metrics.cpp b/tests/slo_workloads/utils/metrics.cpp similarity index 100% rename from tests/slo_workloads/core/metrics.cpp rename to tests/slo_workloads/utils/metrics.cpp diff --git a/tests/slo_workloads/core/metrics.h b/tests/slo_workloads/utils/metrics.h similarity index 56% rename from tests/slo_workloads/core/metrics.h rename to tests/slo_workloads/utils/metrics.h index ff39d19a7b..cf6aac7be4 100644 --- a/tests/slo_workloads/core/metrics.h +++ b/tests/slo_workloads/utils/metrics.h @@ -4,10 +4,6 @@ #include -#include -#include -#include -#include struct TRequestData { TDuration Delay; @@ -18,13 +14,9 @@ struct TRequestData { class IMetricsPusher { public: virtual ~IMetricsPusher() = default; + virtual void PushRequestData(const TRequestData& requestData) = 0; }; -std::unique_ptr CreateOtelMetricsPusher( - const std::string& metricsPushUrl, - const std::string& operationType, - const std::map& resourceAttributes, - const std::string& meterSchemaVersion -); +std::unique_ptr CreateOtelMetricsPusher(const std::string& metricsPushUrl, const std::string& operationType); std::unique_ptr CreateNoopMetricsPusher(); diff --git a/tests/slo_workloads/core/statistics.cpp b/tests/slo_workloads/utils/statistics.cpp similarity index 66% rename from tests/slo_workloads/core/statistics.cpp rename to tests/slo_workloads/utils/statistics.cpp index e8caec271f..dddc35c56a 100644 --- a/tests/slo_workloads/core/statistics.cpp +++ b/tests/slo_workloads/utils/statistics.cpp @@ -1,12 +1,11 @@ #include "statistics.h" -#include "slo_text_utils.h" +#include "metrics.h" +#include "utils.h" -#include - -#include namespace { + // Calculated percentiles for a period of time struct TPercentile { TDuration P50; TDuration P90; @@ -30,18 +29,9 @@ namespace { } } -TStat::TStat( - const std::optional& metricsPushUrl, - const std::string& operationType, - const std::map& resourceAttributes, - const std::string& meterSchemaVersion -) +TStat::TStat(const std::optional& metricsPushUrl, const std::string& operationType) : StartTime(TInstant::Now()) - , MetricsPusher( - metricsPushUrl - ? CreateOtelMetricsPusher(*metricsPushUrl, operationType, resourceAttributes, meterSchemaVersion) - : CreateNoopMetricsPusher() - ) + , MetricsPusher(metricsPushUrl ? CreateOtelMetricsPusher(*metricsPushUrl, operationType) : CreateNoopMetricsPusher()) { MetricsPushQueue.Start(20); } @@ -86,45 +76,20 @@ void TStat::FinishRequest(const std::shared_ptr& unit, const TFinalSt } ScheduleMetricsPush([this, delay, status, unit]() { + NYdb::EStatus requestStatus = status + ? status->GetStatus() + : NYdb::EStatus::CLIENT_DEADLINE_EXCEEDED; MetricsPusher->PushRequestData({ .Delay = delay, - .Status = status ? status->GetStatus() : NYdb::EStatus::TIMEOUT, - .RetryAttempts = unit->RetryAttempts, - }); - }); -} - -void TStat::FinishRequest(const std::shared_ptr& unit, const TSloRequestFinish& finish) { - std::lock_guard lock(Mutex); - - unit->End = TInstant::Now(); - const auto delay = unit->End - unit->Start; - --Infly; - - NYdb::EStatus metricStatus = NYdb::EStatus::GENERIC_ERROR; - if (finish.ApplicationTimeout) { - ++ApplicationTimeout; - metricStatus = NYdb::EStatus::TIMEOUT; - } else if (finish.StatusLabel) { - metricStatus = SloYdbStatusFromString(*finish.StatusLabel); - ++Statuses[metricStatus]; - } - - if (!finish.ApplicationTimeout && finish.StatusLabel && *finish.StatusLabel == "SUCCESS") { - OkDelays.push_back(delay); - } - - ScheduleMetricsPush([this, delay, metricStatus, unit]() { - MetricsPusher->PushRequestData({ - .Delay = delay, - .Status = metricStatus, - .RetryAttempts = unit->RetryAttempts, + .Status = requestStatus, + .RetryAttempts = unit->RetryAttempts }); }); } void TStat::ReportMaxInfly() { std::lock_guard lock(Mutex); + ++CountMaxInfly; } @@ -147,20 +112,20 @@ void TStat::PrintStatistics(TStringBuilder& out) { TDuration timePassed; if (FinishTime < StartTime) { + // If we ask for current progress timePassed = TInstant::Now() - StartTime; } else { timePassed = FinishTime - StartTime; } - const std::uint64_t micros = timePassed.MicroSeconds(); - const std::uint64_t rps = micros ? total * 1000000 / micros : 0; + std::uint64_t rps = total * 1000000 / timePassed.MicroSeconds(); out << total << " requests total" << Endl << Statuses[NYdb::EStatus::SUCCESS] << " succeeded"; if (total) { out << " (" << Statuses[NYdb::EStatus::SUCCESS] * 100 / total << "%)"; } - for (const auto& [status, counter] : Statuses) { - out << Endl << counter << " replies with status " << SloYdbStatusToString(status) << Endl; + for (const auto&[status, counter] : Statuses) { + out << Endl << counter << " replies with status " << YdbStatusToString(status) << Endl; } out << Endl << CountMaxInfly << " failed due to max infly" << Endl << ApplicationTimeout << " application timeouts" << Endl diff --git a/tests/slo_workloads/core/statistics.h b/tests/slo_workloads/utils/statistics.h similarity index 78% rename from tests/slo_workloads/core/statistics.h rename to tests/slo_workloads/utils/statistics.h index ead4d8406d..c61a267db4 100644 --- a/tests/slo_workloads/core/statistics.h +++ b/tests/slo_workloads/utils/statistics.h @@ -9,12 +9,7 @@ #include #include -#include -#include -#include #include -#include -#include inline std::string GetMillisecondsStr(const TDuration& d) { return TStringBuilder() << d.MilliSeconds() << '.' << Sprintf("%03" PRIu64, d.MicroSeconds() % 1000); @@ -27,11 +22,7 @@ inline double GetMillisecondsDouble(const TDuration& d) { using TFinalStatus = std::optional; using TAsyncFinalStatus = NThreading::TFuture; -struct TSloRequestFinish { - bool ApplicationTimeout = false; - std::optional StatusLabel; -}; - +// Request unit struct TStatUnit { TStatUnit(TInstant start) : Start(start) @@ -56,19 +47,13 @@ struct TStatUnit { class TStat { public: - TStat( - const std::optional& metricsPushUrl, - const std::string& operationType, - const std::map& resourceAttributes, - const std::string& meterSchemaVersion - ); + explicit TStat(const std::optional& metricsPushUrl, const std::string& operationType); void Start(); void Finish(); std::shared_ptr StartRequest(); void FinishRequest(const std::shared_ptr& unit, const TFinalStatus& status); - void FinishRequest(const std::shared_ptr& unit, const TSloRequestFinish& finish); void ReportMaxInfly(); void ReportStats(std::uint64_t sessions, std::uint64_t readPromises, std::uint64_t executorPromises); @@ -88,6 +73,7 @@ class TStat { TInstant StartTime; TInstant FinishTime; + // program lifetime std::uint64_t Infly = 0; std::uint64_t ActiveSessions = 0; @@ -96,6 +82,7 @@ class TStat { std::uint64_t ApplicationTimeout = 0; std::vector OkDelays; + // Debug use only: std::uint64_t ReadPromises = 0; std::uint64_t ExecutorPromises = 0; diff --git a/tests/slo_workloads/native/utils/utils.cpp b/tests/slo_workloads/utils/utils.cpp similarity index 100% rename from tests/slo_workloads/native/utils/utils.cpp rename to tests/slo_workloads/utils/utils.cpp diff --git a/tests/slo_workloads/native/utils/utils.h b/tests/slo_workloads/utils/utils.h similarity index 100% rename from tests/slo_workloads/native/utils/utils.h rename to tests/slo_workloads/utils/utils.h diff --git a/tests/slo_workloads/native/utils/utils_main.cpp b/tests/slo_workloads/utils/utils_main.cpp similarity index 100% rename from tests/slo_workloads/native/utils/utils_main.cpp rename to tests/slo_workloads/utils/utils_main.cpp From 7964301353d95d95d8174fbcd2fb99bc0ac73ac3 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Tue, 2 Jun 2026 12:32:10 +0300 Subject: [PATCH 24/27] Fix slo_workloads tree after rebase onto main --- tests/slo_workloads/common/key_value/drop.cpp | 22 -- tests/slo_workloads/core/CMakeLists.txt | 37 ---- tests/slo_workloads/utils/utils.cpp | 196 ++++++++++++++++-- tests/slo_workloads/utils/utils.h | 136 ++++++------ 4 files changed, 245 insertions(+), 146 deletions(-) delete mode 100644 tests/slo_workloads/common/key_value/drop.cpp delete mode 100644 tests/slo_workloads/core/CMakeLists.txt diff --git a/tests/slo_workloads/common/key_value/drop.cpp b/tests/slo_workloads/common/key_value/drop.cpp deleted file mode 100644 index a658fb2b79..0000000000 --- a/tests/slo_workloads/common/key_value/drop.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "kv_common.h" - -using namespace NYdb; -using namespace NYdb::NTable; - -int DropTable(TDatabaseOptions& dbOptions) { - TTableClient client(dbOptions.Driver); - const std::string path = JoinPath(dbOptions.Prefix, TableName); - TStatus status = client.RetryOperationSync([path](TSession session) { - TStatus dropStatus = session.DropTable(path).ExtractValueSync(); - if (dropStatus.GetStatus() == EStatus::NOT_FOUND) { - return TStatus(EStatus::SUCCESS, NYdb::NIssue::TIssues()); - } - return dropStatus; - }); - if (!status.IsSuccess()) { - Cerr << "DropTable failed: " << status << Endl; - return EXIT_FAILURE; - } - Cout << "Table dropped." << Endl; - return EXIT_SUCCESS; -} diff --git a/tests/slo_workloads/core/CMakeLists.txt b/tests/slo_workloads/core/CMakeLists.txt deleted file mode 100644 index bff42a25a4..0000000000 --- a/tests/slo_workloads/core/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -include(FetchContent) - -FetchContent_Declare( - hdr_histogram - GIT_REPOSITORY https://github.com/HdrHistogram/HdrHistogram_c.git - GIT_TAG 0.11.8 - EXCLUDE_FROM_ALL -) -set(HDR_HISTOGRAM_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE) -set(HDR_HISTOGRAM_BUILD_SHARED OFF CACHE BOOL "" FORCE) -set(HDR_LOG_REQUIRED OFF CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(hdr_histogram) - -add_library(slo-workload-core) - -target_link_libraries(slo-workload-core PUBLIC - yutil - client-types-status - client-ydb_driver - opentelemetry-cpp::metrics - opentelemetry-cpp::otlp_http_metric_exporter - hdr_histogram_static -) - -target_link_libraries(slo-workload-core PRIVATE - hdr_histogram_static -) - -target_sources(slo-workload-core PRIVATE - duration_meter.cpp - rps.cpp - slo_text_utils.cpp - shard.cpp - metrics.cpp - statistics.cpp - generator.cpp -) diff --git a/tests/slo_workloads/utils/utils.cpp b/tests/slo_workloads/utils/utils.cpp index 8645ea48c1..edb8e1de6f 100644 --- a/tests/slo_workloads/utils/utils.cpp +++ b/tests/slo_workloads/utils/utils.cpp @@ -5,28 +5,32 @@ #include #include +#include #include #include #include #include #include - -#include +#include using namespace NLastGetopt; using namespace NYdb; -#ifdef REF -static constexpr const char* RefLabel = Y_STRINGIZE(REF); -#else -static constexpr const char* RefLabel = "unknown"; -#endif +const TDuration DefaultReactionTime = TDuration::Minutes(2); +const TDuration ReactionTimeDelay = TDuration::MilliSeconds(5); +const std::uint64_t PartitionsCount = 64; Y_DECLARE_OUT_SPEC(, NYdb::TStatus, stream, value) { stream << "Status: " << value.GetStatus() << Endl; value.GetIssues().PrintTo(stream); } +#ifdef REF +static constexpr const char* RefLabel = Y_STRINGIZE(REF); +#else +static constexpr const char* RefLabel = "unknown"; +#endif + std::map MakeNativeSloOtelResourceAttributes() { return { {"ref", RefLabel}, @@ -39,6 +43,64 @@ std::string NativeSloMeterSchemaVersion() { return NYdb::GetSdkSemver(); } +TDurationMeter::TDurationMeter(TDuration& value) + : Value(value) + , StartTime(TInstant::Now()) +{ +} + +TDurationMeter::~TDurationMeter() { + Value += TInstant::Now() - StartTime; +} + +TRpsProvider::TRpsProvider(std::uint64_t rps) + : Rps(rps) + , Period(Max(TDuration::MilliSeconds(10), TDuration::MicroSeconds(1000000 / Rps))) + , ProcessedTime(TInstant::Now()) +{ +} + +void TRpsProvider::Reset() { + ProcessedTime = TInstant::Now() - Period - Period; +} + +void TRpsProvider::Use() { + if (Allowed) { + --Allowed; + return; + } + + while (!TryUse()) { + SleepUntil(TInstant::Now() + Period); + } +} + +bool TRpsProvider::TryUse() { + TInstant now = TInstant::Now(); + // Number of objects to process since ProcessedTime + Allowed = Rps * TDuration(now - ProcessedTime).MicroSeconds() / 1000000; + if (Allowed) { + ProcessedTime += TDuration::MicroSeconds(1000000 * Allowed / Rps); + --Allowed; + return true; + } else { + return false; + } +} + +std::uint64_t TRpsProvider::GetRps() const { + return Rps; +} + +std::string GetDatabase(const std::string& connectionString) { + constexpr std::string_view databaseFlag = "/?database="; + size_t pathIndex = connectionString.find(databaseFlag); + if (pathIndex != std::string::npos) { + return connectionString.substr(pathIndex + databaseFlag.size()); + } + return {}; +} + std::string GetCmdList() { return "create, run, cleanup (omit to run create -> run -> cleanup in one process)"; } @@ -56,6 +118,29 @@ ECommandType ParseCommand(const char* cmd) { return ECommandType::Unknown; } +std::string JoinPath(const std::string& prefix, const std::string& path) { + if (prefix.empty()) { + return path; + } + + TPathSplitUnix prefixPathSplit(prefix); + prefixPathSplit.AppendComponent(path); + + return prefixPathSplit.Reconstruct(); +} + +std::string GenerateRandomString(std::uint32_t minLength, std::uint32_t maxLength) { + std::uint32_t length = minLength + RandomNumber() % (maxLength - minLength); + static const char* symbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::string result; + result.reserve(length); + for (size_t i = 0; i < length; ++i) { + result.push_back(symbols[RandomNumber(61)]); + } + return result; +} + +using namespace NYdb; using namespace NYdb::NTable; TParams PackValuesToParamsAsList(const std::vector& items, const std::string name) { @@ -71,13 +156,94 @@ TParams PackValuesToParamsAsList(const std::vector& items, const std::st return paramsBuilder.Build(); } +static double shardSize = (static_cast(Max()) + 1) / PartitionsCount; + +std::uint32_t GetSpecialId(std::uint32_t id) { + return static_cast(id / shardSize) * shardSize + 1; +} + +std::uint32_t GetShardSpecialId(std::uint64_t shardNo) { + return shardNo * shardSize + 1; +} + +std::uint32_t GetHash(std::uint32_t value) { + std::uint32_t result = NumericHash(value); + if (result == GetSpecialId(result)) { + ++result; + } + return result; +} + std::string YdbStatusToString(NYdb::EStatus status) { - return SloYdbStatusToString(status); + switch (status) { + case NYdb::EStatus::SUCCESS: + return "SUCCESS"; + case NYdb::EStatus::BAD_REQUEST: + return "BAD_REQUEST"; + case NYdb::EStatus::UNAUTHORIZED: + return "UNAUTHORIZED"; + case NYdb::EStatus::INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case NYdb::EStatus::ABORTED: + return "ABORTED"; + case NYdb::EStatus::UNAVAILABLE: + return "UNAVAILABLE"; + case NYdb::EStatus::OVERLOADED: + return "OVERLOADED"; + case NYdb::EStatus::SCHEME_ERROR: + return "SCHEME_ERROR"; + case NYdb::EStatus::GENERIC_ERROR: + return "GENERIC_ERROR"; + case NYdb::EStatus::TIMEOUT: + return "TIMEOUT"; + case NYdb::EStatus::BAD_SESSION: + return "BAD_SESSION"; + case NYdb::EStatus::PRECONDITION_FAILED: + return "PRECONDITION_FAILED"; + case NYdb::EStatus::ALREADY_EXISTS: + return "ALREADY_EXISTS"; + case NYdb::EStatus::NOT_FOUND: + return "NOT_FOUND"; + case NYdb::EStatus::SESSION_EXPIRED: + return "SESSION_EXPIRED"; + case NYdb::EStatus::CANCELLED: + return "CANCELLED"; + case NYdb::EStatus::UNDETERMINED: + return "UNDETERMINED"; + case NYdb::EStatus::UNSUPPORTED: + return "UNSUPPORTED"; + case NYdb::EStatus::SESSION_BUSY: + return "SESSION_BUSY"; + case NYdb::EStatus::EXTERNAL_ERROR: + return "EXTERNAL_ERROR"; + case NYdb::EStatus::STATUS_UNDEFINED: + return "STATUS_UNDEFINED"; + case NYdb::EStatus::TRANSPORT_UNAVAILABLE: + return "TRANSPORT_UNAVAILABLE"; + case NYdb::EStatus::CLIENT_RESOURCE_EXHAUSTED: + return "CLIENT_RESOURCE_EXHAUSTED"; + case NYdb::EStatus::CLIENT_DEADLINE_EXCEEDED: + return "CLIENT_DEADLINE_EXCEEDED"; + case NYdb::EStatus::CLIENT_INTERNAL_ERROR: + return "CLIENT_INTERNAL_ERROR"; + case NYdb::EStatus::CLIENT_CANCELLED: + return "CLIENT_CANCELLED"; + case NYdb::EStatus::CLIENT_UNAUTHENTICATED: + return "CLIENT_UNAUTHENTICATED"; + case NYdb::EStatus::CLIENT_CALL_UNIMPLEMENTED: + return "CLIENT_CALL_UNIMPLEMENTED"; + case NYdb::EStatus::CLIENT_OUT_OF_RANGE: + return "CLIENT_OUT_OF_RANGE"; + case NYdb::EStatus::CLIENT_DISCOVERY_FAILED: + return "CLIENT_DISCOVERY_FAILED"; + case NYdb::EStatus::CLIENT_LIMITS_REACHED: + return "CLIENT_LIMITS_REACHED"; + } } TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableName) { Cout << TInstant::Now().ToRfc822StringLocal() - << " Getting table stats (maxId and count of rows) with ReadTable... " << Endl; + << " Getting table stats (maxId and count of rows) with ReadTable... " << Endl; TInstant start_time = TInstant::Now(); NYdb::NTable::TTableClient client( dbOptions.Driver, @@ -89,9 +255,9 @@ TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableN std::optional tableIterator; NYdb::NStatusHelpers::ThrowOnError(client.RetryOperationSync([&tableIterator, &dbOptions, &tableName](TSession session) { auto result = session.ReadTable( - JoinPath(dbOptions.Prefix, tableName), - TReadTableSettings().AppendColumns("object_id")) - .GetValueSync(); + JoinPath(dbOptions.Prefix, tableName), + TReadTableSettings().AppendColumns("object_id") + ).GetValueSync(); if (result.IsSuccess()) { tableIterator = result; @@ -114,7 +280,7 @@ TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableN } futures.push_back( NThreading::Async( - [extractedPart = tablePart.ExtractPart()] { + [extractedPart = tablePart.ExtractPart()]{ auto rsParser = TResultSetParser(extractedPart); std::uint32_t partMax = 0; while (rsParser.TryNextRow()) { @@ -125,7 +291,7 @@ TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableN partMax = id; } } - return TTableStats{rsParser.RowsCount(), partMax}; + return TTableStats{ rsParser.RowsCount(), partMax }; }, pool ) @@ -140,7 +306,7 @@ TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableN result.RowCount += partStats.RowCount; } Cout << TInstant::Now().ToRfc822StringLocal() << " Done. maxId=" << result.MaxId << ", row count=" << result.RowCount - << ". Calculations took " << TInstant::Now() - start_time << Endl; + << ". Calculations took " << TInstant::Now() - start_time << Endl; return result; } diff --git a/tests/slo_workloads/utils/utils.h b/tests/slo_workloads/utils/utils.h index 029a792e9b..3c436d7039 100644 --- a/tests/slo_workloads/utils/utils.h +++ b/tests/slo_workloads/utils/utils.h @@ -1,12 +1,5 @@ #pragma once -#include -#include -#include -#include -#include -#include - #include #include #include @@ -15,9 +8,32 @@ #include -#include #include -#include + +extern const TDuration DefaultReactionTime; +extern const TDuration ReactionTimeDelay; +extern const std::uint64_t PartitionsCount; + +struct TRecordData { + std::uint32_t ObjectId; + std::uint64_t Timestamp; + std::string Guid; + std::string Payload; +}; + +struct TKeyValueRecordData { + std::uint32_t ObjectId; + std::uint64_t Timestamp; + std::string Payload; +}; + +struct TDurationMeter { + TDurationMeter(TDuration& value); + ~TDurationMeter(); + + TDuration& Value; + TInstant StartTime; +}; struct TDatabaseOptions { NYdb::TDriver& Driver; @@ -61,13 +77,22 @@ struct TRunOptions { TDuration WriteTimeout = DefaultReactionTime; }; -using TCreateCommand = std::function; -using TRunCommand = std::function; -using TCleanupCommand = std::function; - -int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TCleanupCommand cleanup); - -std::string GetCmdList(); +class TRpsProvider { +public: + TRpsProvider(std::uint64_t rps); + void Reset(); + void Use(); + bool TryUse(); + std::uint64_t GetRps() const; + +private: + std::uint64_t Rps; + TDuration Period; + TInstant ProcessedTime; + TInstant LastCheck; + std::uint32_t Allowed = 0; + TInstant StartTime; +}; enum class ECommandType { Unknown, @@ -77,75 +102,42 @@ enum class ECommandType { All, // No free-arg passed: execute Create -> Run -> Cleanup in one process }; -ECommandType ParseCommand(const char* cmd); - -inline std::string JoinPath(const std::string& prefix, const std::string& path) { - return SloJoinPath(prefix, path); -} +struct TTableStats { + std::uint64_t RowCount = 0; + std::uint32_t MaxId = 0; +}; -inline std::string GetDatabase(const std::string& connectionString) { - return SloGetDatabaseFromConnectionString(connectionString); -} +using TCreateCommand = std::function; +using TRunCommand = std::function; +using TCleanupCommand = std::function; -inline std::string GenerateRandomString(std::uint32_t minLength, std::uint32_t maxLength) { - return SloGenerateRandomString(minLength, maxLength); -} +int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TCleanupCommand cleanup); -inline std::uint32_t GetSpecialId(std::uint32_t id) { - return SloGetSpecialId(id); -} +std::string GetCmdList(); +ECommandType ParseCommand(const char* cmd); -inline std::uint32_t GetShardSpecialId(std::uint64_t shardNo) { - return SloGetShardSpecialId(shardNo); -} +std::string JoinPath(const std::string& prefix, const std::string& path); -inline std::uint32_t GetHash(std::uint32_t value) { - return SloGetHash(value); -} +std::string GenerateRandomString(std::uint32_t minLength, std::uint32_t maxLength); -inline TSloGeneratorOptions MakeGeneratorOptions(const TCommonOptions& opts) { - TSloGeneratorOptions o; - o.MinLength = opts.MinLength; - o.MaxLength = opts.MaxLength; - return o; -} +NYdb::TParams PackValuesToParamsAsList(const std::vector& items, const std::string name = "$items"); -std::map MakeNativeSloOtelResourceAttributes(); -std::string NativeSloMeterSchemaVersion(); +// Returns special object_id within the same shard as given id +std::uint32_t GetSpecialId(std::uint32_t id); -inline void RetryBackoff( - NYdb::NTable::TTableClient& client, - std::uint32_t retries, - const NYdb::NTable::TTableClient::TOperationSyncFunc& func -) { - TDuration delay = TDuration::Seconds(5); - while (retries) { - NYdb::TStatus status = client.RetryOperationSync(func); - if (status.IsSuccess()) { - return; - } - --retries; - if (!retries) { - Cerr << "Create request failed after all retries." << Endl; - Cerr << status << Endl; - NYdb::NStatusHelpers::ThrowOnError(status); - } - Cerr << "Create request failed. Sleeping for " << delay << Endl; - Sleep(delay); - delay *= 2; - } -} +// Returns special object_id for given shard +std::uint32_t GetShardSpecialId(std::uint64_t shardNo); -NYdb::TParams PackValuesToParamsAsList(const std::vector& items, const std::string name = "$items"); +std::uint32_t GetHash(std::uint32_t value); std::string YdbStatusToString(NYdb::EStatus status); -struct TTableStats { - std::uint64_t RowCount = 0; - std::uint32_t MaxId = 0; -}; - TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableName); +std::string GetDatabase(const std::string& connectionString); + +std::map MakeNativeSloOtelResourceAttributes(); +std::string NativeSloMeterSchemaVersion(); + bool ParseOptionsCreate(int argc, char** argv, TCreateOptions& createOptions); bool ParseOptionsRun(int argc, char** argv, TRunOptions& runOptions); From 549d6eba37b00a9d3aafc4d2cc227d87c5dac84e Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Tue, 2 Jun 2026 12:40:38 +0300 Subject: [PATCH 25/27] fix metrics behaviour --- tests/slo_workloads/utils/metrics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/slo_workloads/utils/metrics.cpp b/tests/slo_workloads/utils/metrics.cpp index ec6b28f1f6..81f53ca6ba 100644 --- a/tests/slo_workloads/utils/metrics.cpp +++ b/tests/slo_workloads/utils/metrics.cpp @@ -132,7 +132,7 @@ class TOtelSharedPusher { } void Record(const std::string& operationType, const TRequestData& data) { - const bool success = data.StatusLabel == "SUCCESS"; + const bool success = data.Status == NYdb::EStatus::SUCCESS; auto& series = GetOrCreateSeries(operationType); OperationsTotal_->Add(uint64_t{1}, From 9d051f7fb0d8e1e0d82594afd8a7ea45430cc2d8 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Tue, 2 Jun 2026 12:50:05 +0300 Subject: [PATCH 26/27] fixed slo build --- .github/workflows/slo_native.yml | 193 ++++++++++++++++++ .github/workflows/slo_report.yml | 2 +- .../workflows/{slo.yml => slo_userver.yml} | 51 ++--- tests/slo_workloads/utils/metrics.cpp | 12 +- 4 files changed, 220 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/slo_native.yml rename .github/workflows/{slo.yml => slo_userver.yml} (72%) diff --git a/.github/workflows/slo_native.yml b/.github/workflows/slo_native.yml new file mode 100644 index 0000000000..3e317c4034 --- /dev/null +++ b/.github/workflows/slo_native.yml @@ -0,0 +1,193 @@ +name: SLO Native + +on: + pull_request: + types: [opened, reopened, synchronize, labeled] + +jobs: + ydb-slo-action: + if: contains(github.event.pull_request.labels.*.name, 'SLO') + + name: Run YDB SLO Tests + runs-on: ubuntu-latest + + permissions: + contents: read + + strategy: + fail-fast: false + matrix: + sdk: + - name: cpp-key-value + preset: release-test-clang + command: "" + + concurrency: + group: slo-${{ github.ref }}-${{ matrix.sdk.name }} + cancel-in-progress: true + + steps: + - name: Install dependencies + run: | + set -euxo pipefail + YQ_VERSION=v4.48.2 + BUILDX_VERSION=0.30.1 + COMPOSE_VERSION=2.40.3 + + sudo curl -fLo /usr/local/bin/yq \ + "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" + sudo chmod +x /usr/local/bin/yq + + sudo mkdir -p /usr/local/lib/docker/cli-plugins + + sudo curl -fLo /usr/local/lib/docker/cli-plugins/docker-buildx \ + "https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-amd64" + sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx + + sudo curl -fLo /usr/local/lib/docker/cli-plugins/docker-compose \ + "https://github.com/docker/compose/releases/download/v${COMPOSE_VERSION}/docker-compose-linux-x86_64" + sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose + + yq --version + docker --version + docker buildx version + docker compose version + + - name: Checkout current SDK version + uses: actions/checkout@v5 + with: + path: sdk-current + fetch-depth: 0 + submodules: true + + - name: Determine baseline commit + id: baseline + working-directory: sdk-current + run: | + set -euo pipefail + BASELINE=$(git merge-base HEAD origin/main) + echo "sha=${BASELINE}" >> "$GITHUB_OUTPUT" + + if git merge-base --is-ancestor "${BASELINE}" origin/main && \ + [ "$(git rev-parse origin/main)" = "${BASELINE}" ]; then + BASELINE_REF="main" + else + BRANCH=$(git branch -r --contains "${BASELINE}" | grep -v HEAD | head -1 | sed 's|.*/||' || echo "") + if [ -n "${BRANCH}" ]; then + BASELINE_REF="${BRANCH}@${BASELINE:0:7}" + else + BASELINE_REF="${BASELINE:0:7}" + fi + fi + echo "ref=${BASELINE_REF}" >> "$GITHUB_OUTPUT" + + - name: Checkout baseline SDK version + uses: actions/checkout@v5 + with: + ref: ${{ steps.baseline.outputs.sha }} + path: sdk-baseline + fetch-depth: 1 + submodules: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # Use current's workload harness (Dockerfile, sources, .dockerignore) for + # both builds so only the SDK library differs between current and + # baseline. Without this the baseline image picks up the harness from + # the merge-base commit, which can lag behind the action's contract. + # buildx also expects .dockerignore at the context root, not under + # tests/, so copy it up in each checkout. + - name: Stage workload harness + run: | + set -euxo pipefail + rm -rf sdk-baseline/tests/slo_workloads + cp -a sdk-current/tests/slo_workloads sdk-baseline/tests/slo_workloads + cp sdk-current/tests/slo_workloads/.dockerignore sdk-current/.dockerignore + cp sdk-baseline/tests/slo_workloads/.dockerignore sdk-baseline/.dockerignore + + # `cache-to: type=gha` does NOT export `--mount=type=cache` content, so + # ccache state is lost between runs. Persist /root/.ccache via host + # directory + cache-dance: actions/cache restores the host dir, the + # dance injects it into the BuildKit cache mount before the build and + # extracts the updated state afterwards for the next save. + - name: Restore ccache + id: ccache + uses: actions/cache@v4 + with: + path: ccache + key: slo-ccache-${{ matrix.sdk.preset }}-${{ github.run_id }} + restore-keys: | + slo-ccache-${{ matrix.sdk.preset }}- + + - name: Inject ccache into BuildKit + uses: reproducible-containers/buildkit-cache-dance@v3.1.2 + with: + cache-map: | + { + "ccache": "/root/.ccache" + } + # Always extract so newly-compiled TUs from this run are saved by + # actions/cache (key uses ${{ github.run_id }}, so each run gets + # its own snapshot). Without extraction the cache stays frozen at + # whatever was first persisted. + skip-extraction: false + + # A clean build of the SLO image takes ~30 min because the Dockerfile + # rebuilds the full C++ toolchain + abseil/protobuf/grpc from source. + # The GHA cache lets subsequent runs reuse every layer up to the SDK + # source COPY, so only the actual workload link step reruns (~3 min). + - name: Build current workload image + uses: docker/build-push-action@v6 + env: + DOCKER_BUILD_SUMMARY: "false" + DOCKER_BUILD_RECORD_UPLOAD: "false" + with: + context: sdk-current + file: sdk-current/tests/slo_workloads/Dockerfile + platforms: linux/amd64 + tags: ydb-app-current + load: true + build-args: PRESET=${{ matrix.sdk.preset }} + cache-from: type=gha,scope=slo-${{ matrix.sdk.preset }} + cache-to: type=gha,mode=max,scope=slo-${{ matrix.sdk.preset }} + + - name: Build baseline workload image + id: baseline-build + continue-on-error: true + uses: docker/build-push-action@v6 + env: + DOCKER_BUILD_SUMMARY: "false" + DOCKER_BUILD_RECORD_UPLOAD: "false" + with: + context: sdk-baseline + file: sdk-baseline/tests/slo_workloads/Dockerfile + platforms: linux/amd64 + tags: ydb-app-baseline + load: true + build-args: PRESET=${{ matrix.sdk.preset }} + cache-from: type=gha,scope=slo-${{ matrix.sdk.preset }} + + # If the historical commit lacks the SLO Dockerfile or can't compile, + # reuse the current image so the SLO run is still comparable against + # itself rather than failing outright. + - name: Fall back to current image for baseline + if: steps.baseline-build.outcome == 'failure' + run: | + echo "Baseline build failed; reusing current image as baseline." + docker tag ydb-app-current ydb-app-baseline + + - name: Run SLO Tests + uses: ydb-platform/ydb-slo-action/init@v2 + timeout-minutes: 30 + with: + github_issue: ${{ github.event.pull_request.number }} + github_token: ${{ secrets.GITHUB_TOKEN }} + workload_name: ${{ matrix.sdk.name }} + workload_duration: "600" + workload_current_ref: ${{ github.head_ref || github.ref_name }} + workload_current_image: ydb-app-current + workload_current_command: ${{ matrix.sdk.command }} --read-rps 1000 --write-rps 100 + workload_baseline_ref: ${{ steps.baseline.outputs.ref }} + workload_baseline_image: ydb-app-baseline + workload_baseline_command: ${{ matrix.sdk.command }} --read-rps 1000 --write-rps 100 diff --git a/.github/workflows/slo_report.yml b/.github/workflows/slo_report.yml index 0ccd36abe9..a6e8a5c00e 100644 --- a/.github/workflows/slo_report.yml +++ b/.github/workflows/slo_report.yml @@ -2,7 +2,7 @@ name: SLO Report on: workflow_run: - workflows: ["SLO"] + workflows: ["SLO Native", "SLO Userver"] types: - completed diff --git a/.github/workflows/slo.yml b/.github/workflows/slo_userver.yml similarity index 72% rename from .github/workflows/slo.yml rename to .github/workflows/slo_userver.yml index 6d01f11430..7882b8a2e2 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo_userver.yml @@ -1,4 +1,4 @@ -name: SLO +name: SLO Userver on: pull_request: @@ -8,7 +8,7 @@ jobs: ydb-slo-action: if: contains(github.event.pull_request.labels.*.name, 'SLO') - name: Run YDB SLO Tests (${{ matrix.workload }}, ${{ matrix.compiler }}) + name: Run YDB SLO Tests (userver) runs-on: ubuntu-latest timeout-minutes: 120 @@ -18,16 +18,13 @@ jobs: strategy: fail-fast: false matrix: - compiler: [clang, gcc] - workload: [native, userver] - include: - - workload: native - dockerfile: tests/slo_workloads/Dockerfile - - workload: userver - dockerfile: tests/slo_workloads/Dockerfile.userver + sdk: + - name: cpp-key-value-userver + preset: release-test-clang + command: "" concurrency: - group: slo-${{ github.ref }}-${{ matrix.workload }}-${{ matrix.compiler }} + group: slo-userver-${{ github.ref }}-${{ matrix.sdk.name }} cancel-in-progress: true steps: @@ -96,12 +93,6 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - # Use current's workload harness (Dockerfile, sources, .dockerignore) for - # both builds so only the SDK library differs between current and - # baseline. Without this the baseline image picks up the harness from - # the merge-base commit, which can lag behind the action's contract. - # buildx also expects .dockerignore at the context root, not under - # tests/, so copy it up in each checkout. - name: Stage workload harness run: | set -euxo pipefail @@ -115,9 +106,9 @@ jobs: uses: actions/cache@v4 with: path: ccache - key: slo-ccache-${{ matrix.workload }}-${{ matrix.compiler }}-${{ github.run_id }} + key: slo-userver-ccache-${{ matrix.sdk.preset }}-${{ github.run_id }} restore-keys: | - slo-ccache-${{ matrix.workload }}-${{ matrix.compiler }}- + slo-userver-ccache-${{ matrix.sdk.preset }}- - name: Inject ccache into BuildKit uses: reproducible-containers/buildkit-cache-dance@v3.1.2 @@ -135,15 +126,13 @@ jobs: DOCKER_BUILD_RECORD_UPLOAD: "false" with: context: sdk-current - file: sdk-current/${{ matrix.dockerfile }} + file: sdk-current/tests/slo_workloads/Dockerfile.userver platforms: linux/amd64 tags: ydb-app-current load: true - build-args: | - PRESET=release-test-${{ matrix.compiler }} - REF=${{ github.head_ref || github.ref_name }} - cache-from: type=gha,scope=slo-${{ matrix.workload }}-${{ matrix.compiler }} - cache-to: type=gha,mode=max,scope=slo-${{ matrix.workload }}-${{ matrix.compiler }} + build-args: PRESET=${{ matrix.sdk.preset }} + cache-from: type=gha,scope=slo-userver-${{ matrix.sdk.preset }} + cache-to: type=gha,mode=max,scope=slo-userver-${{ matrix.sdk.preset }} - name: Build baseline workload image id: baseline-build @@ -154,14 +143,12 @@ jobs: DOCKER_BUILD_RECORD_UPLOAD: "false" with: context: sdk-baseline - file: sdk-baseline/${{ matrix.dockerfile }} + file: sdk-baseline/tests/slo_workloads/Dockerfile.userver platforms: linux/amd64 tags: ydb-app-baseline load: true - build-args: | - PRESET=release-test-${{ matrix.compiler }} - REF=${{ steps.baseline.outputs.ref }} - cache-from: type=gha,scope=slo-${{ matrix.workload }}-${{ matrix.compiler }} + build-args: PRESET=${{ matrix.sdk.preset }} + cache-from: type=gha,scope=slo-userver-${{ matrix.sdk.preset }} - name: Fall back to current image for baseline if: steps.baseline-build.outcome == 'failure' @@ -175,11 +162,11 @@ jobs: with: github_issue: ${{ github.event.pull_request.number }} github_token: ${{ secrets.GITHUB_TOKEN }} - workload_name: cpp-key-value-${{ matrix.workload }}-${{ matrix.compiler }} + workload_name: ${{ matrix.sdk.name }} workload_duration: "600" workload_current_ref: ${{ github.head_ref || github.ref_name }} workload_current_image: ydb-app-current - workload_current_command: --read-rps 1000 --write-rps 100 + workload_current_command: ${{ matrix.sdk.command }} --read-rps 1000 --write-rps 100 workload_baseline_ref: ${{ steps.baseline.outputs.ref }} workload_baseline_image: ydb-app-baseline - workload_baseline_command: --read-rps 1000 --write-rps 100 + workload_baseline_command: ${{ matrix.sdk.command }} --read-rps 1000 --write-rps 100 diff --git a/tests/slo_workloads/utils/metrics.cpp b/tests/slo_workloads/utils/metrics.cpp index 81f53ca6ba..28e2316d1f 100644 --- a/tests/slo_workloads/utils/metrics.cpp +++ b/tests/slo_workloads/utils/metrics.cpp @@ -1,4 +1,5 @@ #include "metrics.h" +#include "utils.h" #include #include @@ -356,12 +357,13 @@ class TNoopMetricsPusher : public IMetricsPusher { std::unique_ptr CreateOtelMetricsPusher( const std::string& metricsPushUrl, - const std::string& operationType, - const std::map& resourceAttributes, - const std::string& meterSchemaVersion -) { + const std::string& operationType) +{ return std::make_unique( - GetOrCreateSharedPusher(metricsPushUrl, resourceAttributes, meterSchemaVersion), + GetOrCreateSharedPusher( + metricsPushUrl, + MakeNativeSloOtelResourceAttributes(), + NativeSloMeterSchemaVersion()), operationType); } From ee28a3c76d9160ed7079197c6029ad01978cca14 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Tue, 2 Jun 2026 13:57:47 +0300 Subject: [PATCH 27/27] fix userver build and issues --- .../userver/key_value/create.cpp | 5 +- tests/slo_workloads/userver/key_value/run.cpp | 88 ++++++++++++++++--- .../userver/key_value/userver_utils.cpp | 6 +- tests/slo_workloads/utils/utils.cpp | 40 +++++++++ tests/slo_workloads/utils/utils.h | 4 + tests/slo_workloads/utils/utils_main.cpp | 46 +--------- 6 files changed, 126 insertions(+), 63 deletions(-) diff --git a/tests/slo_workloads/userver/key_value/create.cpp b/tests/slo_workloads/userver/key_value/create.cpp index 87d5a118f5..4ccb568788 100644 --- a/tests/slo_workloads/userver/key_value/create.cpp +++ b/tests/slo_workloads/userver/key_value/create.cpp @@ -46,10 +46,7 @@ int DoCreate(TDatabaseOptions& dbOptions, int argc, char** argv) { << "' for progress details or Ctrl/Cmd+C to interrupt" << Endl; ShouldStop.store(false); - signal(SIGINT, [](int) { - Cerr << TInstant::Now().ToRfc822StringLocal() << " SIGINT received. Stopping..." << Endl; - ShouldStop.store(true); - }); + signal(SIGINT, [](int) { ShouldStop.store(true, std::memory_order_relaxed); }); const auto& opts = createOptions.CommonOptions; const std::string& prefix = opts.DatabaseOptions.Prefix; diff --git a/tests/slo_workloads/userver/key_value/run.cpp b/tests/slo_workloads/userver/key_value/run.cpp index 2de02015ca..32988f8880 100644 --- a/tests/slo_workloads/userver/key_value/run.cpp +++ b/tests/slo_workloads/userver/key_value/run.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -22,6 +23,51 @@ namespace uydb = userver::ydb; namespace { std::atomic ShouldStop{false}; +std::atomic ShowProgressRequested{false}; + +class TUserverRpsProvider { +public: + explicit TUserverRpsProvider(std::uint64_t rps) + : Rps_(rps) + , Period_(Max(TDuration::MilliSeconds(10), TDuration::MicroSeconds(1000000 / Rps_))) + {} + + void Reset() { ProcessedTime_ = TInstant::Now() - Period_ - Period_; } + + void Use() { + if (Allowed_) { + --Allowed_; + return; + } + while (!TryUse()) { + userver::engine::SleepFor(std::chrono::microseconds(Period_.MicroSeconds())); + } + } + +private: + bool TryUse() { + const TInstant now = TInstant::Now(); + Allowed_ = Rps_ * TDuration(now - ProcessedTime_).MicroSeconds() / 1000000; + if (Allowed_) { + ProcessedTime_ += TDuration::MicroSeconds(1000000 * Allowed_ / Rps_); + --Allowed_; + return true; + } + return false; + } + + std::uint64_t Rps_; + TDuration Period_; + TInstant ProcessedTime_; + std::uint32_t Allowed_ = 0; +}; + +void DrainCompletedTasks(std::vector>& tasks) { + for (auto& task : tasks) { + task.Wait(); + } + tasks.clear(); +} struct TProgressReporter { TStat* ReadStats = nullptr; @@ -55,6 +101,15 @@ struct TProgressReporter { TProgressReporter* GlobalReporter = nullptr; +void PollSignals() { + if (ShowProgressRequested.exchange(false, std::memory_order_relaxed)) { + Cout << TInstant::Now().ToRfc822StringLocal() << " SIGUSR1 handle" << Endl; + if (GlobalReporter) { + GlobalReporter->ShowProgress(); + } + } +} + void ExecuteQuery( uydb::TableClient& client, const uydb::OperationSettings& settings, @@ -127,16 +182,9 @@ int DoRun(TDatabaseOptions& dbOptions, int argc, char** argv) { GlobalReporter = &reporter; ShouldStop.store(false); - signal(SIGUSR1, [](int) { - Cout << TInstant::Now().ToRfc822StringLocal() << " SIGUSR1 handle" << Endl; - if (GlobalReporter) { - GlobalReporter->ShowProgress(); - } - }); - signal(SIGINT, [](int) { - Cerr << TInstant::Now().ToRfc822StringLocal() << " SIGINT received. Stopping..." << Endl; - ShouldStop.store(true); - }); + ShowProgressRequested.store(false); + signal(SIGUSR1, [](int) { ShowProgressRequested.store(true, std::memory_order_relaxed); }); + signal(SIGINT, [](int) { ShouldStop.store(true, std::memory_order_relaxed); }); TInstant start = TInstant::Now(); TInstant deadline = start + TDuration::Seconds(runOptions.CommonOptions.SecondsToRun); @@ -171,7 +219,7 @@ SELECT * FROM `%s` WHERE `object_id_key` = $object_id_key AND `object_id` = $obj objectIdRange, readQuery, deadline]() { readStats->Start(); - TRpsProvider rpsProvider(readRps); + TUserverRpsProvider rpsProvider(readRps); rpsProvider.Reset(); userver::engine::Semaphore semaphore{ @@ -180,6 +228,8 @@ SELECT * FROM `%s` WHERE `object_id_key` = $object_id_key AND `object_id` = $obj std::vector> tasks; while (!ShouldStop.load() && TInstant::Now() < deadline) { + PollSignals(); + std::uint32_t idToSelect = RandomNumber() % objectIdRange; const std::uint32_t objectIdKey = GetHash(idToSelect); @@ -206,6 +256,10 @@ SELECT * FROM `%s` WHERE `object_id_key` = $object_id_key AND `object_id` = $obj semaphore.unlock_shared(); } )); + + if (tasks.size() >= static_cast(maxInfly)) { + DrainCompletedTasks(tasks); + } } for (auto& task : tasks) { @@ -245,7 +299,7 @@ UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); writeCommon, maxId, writeQuery, deadline]() { writeStats->Start(); - TRpsProvider rpsProvider(writeRps); + TUserverRpsProvider rpsProvider(writeRps); rpsProvider.Reset(); TKeyValueGenerator generator(writeCommon, maxId); @@ -256,6 +310,8 @@ UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); std::vector> tasks; while (!ShouldStop.load() && TInstant::Now() < deadline) { + PollSignals(); + const auto value = BuildValueFromRecord(generator.Get()); writeGenerated.fetch_add(1); @@ -280,6 +336,10 @@ UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); semaphore.unlock_shared(); } )); + + if (tasks.size() >= static_cast(maxInfly)) { + DrainCompletedTasks(tasks); + } } for (auto& task : tasks) { @@ -295,6 +355,10 @@ UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); task.Wait(); } + if (ShouldStop.load()) { + Cerr << TInstant::Now().ToRfc822StringLocal() << " SIGINT received. Stopping..." << Endl; + } + Cout << "All jobs finished: " << TInstant::Now().ToRfc822StringLocal() << Endl; reporter.ShowProgress(); diff --git a/tests/slo_workloads/userver/key_value/userver_utils.cpp b/tests/slo_workloads/userver/key_value/userver_utils.cpp index 5661675fbe..71dc73b459 100644 --- a/tests/slo_workloads/userver/key_value/userver_utils.cpp +++ b/tests/slo_workloads/userver/key_value/userver_utils.cpp @@ -75,11 +75,13 @@ int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TClean ECommandType command = (argc > 0) ? ParseCommand(*argv) : ECommandType::All; if (command == ECommandType::Unknown) { - if (argv[0][0] == '-') { + if (argc > 0 && argv[0][0] == '-') { command = ECommandType::All; - } else { + } else if (argc > 0) { Cerr << "Unknown command '" << *argv << "'" << Endl; return EXIT_FAILURE; + } else { + command = ECommandType::All; } } diff --git a/tests/slo_workloads/utils/utils.cpp b/tests/slo_workloads/utils/utils.cpp index edb8e1de6f..022b62fe85 100644 --- a/tests/slo_workloads/utils/utils.cpp +++ b/tests/slo_workloads/utils/utils.cpp @@ -101,6 +101,46 @@ std::string GetDatabase(const std::string& connectionString) { return {}; } +std::string DefaultConnectionStringFromEnv() { + std::string cs = GetEnv("YDB_CONNECTION_STRING"); + if (!cs.empty()) { + return cs; + } + std::string endpoint = GetEnv("YDB_ENDPOINT"); + std::string database = GetEnv("YDB_DATABASE"); + if (!endpoint.empty() && !database.empty()) { + return TStringBuilder() << endpoint << "/?database=" << database; + } + return {}; +} + +bool ParseToken(std::string& token, std::string& tokenFile) { + if (!tokenFile.empty()) { + if (!token.empty()) { + Cerr << "Both token and token_file provided. Choose one." << Endl; + } else { + TFsPath path(tokenFile); + if (path.Exists()) { + token = Strip(TUnbufferedFileInput(path).ReadAll()); + return true; + } + Cerr << "Wrong path provided for token_file." << Endl; + } + } else if (!token.empty()) { + return true; + } else { + token = GetEnv("YDB_TOKEN"); + return true; + } + return false; +} + +void StartStatCollecting([[maybe_unused]] TDriver& driver, const std::string& statConfigFile) { + if (statConfigFile.empty()) { + return; + } +} + std::string GetCmdList() { return "create, run, cleanup (omit to run create -> run -> cleanup in one process)"; } diff --git a/tests/slo_workloads/utils/utils.h b/tests/slo_workloads/utils/utils.h index 3c436d7039..0a2f61e1aa 100644 --- a/tests/slo_workloads/utils/utils.h +++ b/tests/slo_workloads/utils/utils.h @@ -136,6 +136,10 @@ TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableN std::string GetDatabase(const std::string& connectionString); +std::string DefaultConnectionStringFromEnv(); +bool ParseToken(std::string& token, std::string& tokenFile); +void StartStatCollecting(NYdb::TDriver& driver, const std::string& statConfigFile); + std::map MakeNativeSloOtelResourceAttributes(); std::string NativeSloMeterSchemaVersion(); diff --git a/tests/slo_workloads/utils/utils_main.cpp b/tests/slo_workloads/utils/utils_main.cpp index c07792e003..6f1677912d 100644 --- a/tests/slo_workloads/utils/utils_main.cpp +++ b/tests/slo_workloads/utils/utils_main.cpp @@ -13,50 +13,6 @@ using namespace NLastGetopt; using namespace NYdb; -namespace { - -bool ParseToken(std::string& token, std::string& tokenFile) { - if (!tokenFile.empty()) { - if (!token.empty()) { - Cerr << "Both token and token_file provided. Choose one." << Endl; - } else { - TFsPath path(tokenFile); - if (path.Exists()) { - token = Strip(TUnbufferedFileInput(path).ReadAll()); - return true; - } - Cerr << "Wrong path provided for token_file." << Endl; - } - } else if (!token.empty()) { - return true; - } else { - token = GetEnv("YDB_TOKEN"); - return true; - } - return false; -} - -void StartStatCollecting([[maybe_unused]] TDriver& driver, const std::string& statConfigFile) { - if (statConfigFile.empty()) { - return; - } -} - -std::string DefaultConnectionStringFromEnv() { - std::string cs = GetEnv("YDB_CONNECTION_STRING"); - if (!cs.empty()) { - return cs; - } - std::string endpoint = GetEnv("YDB_ENDPOINT"); - std::string database = GetEnv("YDB_DATABASE"); - if (!endpoint.empty() && !database.empty()) { - return TStringBuilder() << endpoint << "/?database=" << database; - } - return {}; -} - -} // namespace - int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TCleanupCommand cleanup) { TOpts opts = TOpts::Default(); @@ -162,7 +118,7 @@ int DoMain(int argc, char** argv, TCreateCommand create, TRunCommand run, TClean StartStatCollecting(driver, statConfigFile); TDatabaseOptions dbOptions{driver, prefix}; - int result; + int result = EXIT_FAILURE; try { switch (command) { case ECommandType::Create: