From c282266e392a59a77175618c0bcae19466738556 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Fri, 17 Oct 2025 19:08:43 +0000 Subject: [PATCH 1/7] [C++ SDK] Supported metric loading to OTLP in SLO workload --- .../slo_workloads/key_value/generate.cpp | 3 +- .../tests/slo_workloads/key_value/key_value.h | 1 - .../cpp/tests/slo_workloads/key_value/run.cpp | 12 +- .../tests/slo_workloads/utils/executor.cpp | 108 +--- .../cpp/tests/slo_workloads/utils/executor.h | 24 +- .../sdk/cpp/tests/slo_workloads/utils/job.cpp | 12 +- .../sdk/cpp/tests/slo_workloads/utils/job.h | 6 +- .../cpp/tests/slo_workloads/utils/metrics.cpp | 150 ++++++ .../cpp/tests/slo_workloads/utils/metrics.h | 22 + .../tests/slo_workloads/utils/statistics.cpp | 501 +++--------------- .../tests/slo_workloads/utils/statistics.h | 190 ++----- .../cpp/tests/slo_workloads/utils/utils.cpp | 100 +++- .../sdk/cpp/tests/slo_workloads/utils/utils.h | 21 +- .../sdk/cpp/tests/slo_workloads/utils/ya.make | 2 + 14 files changed, 384 insertions(+), 768 deletions(-) create mode 100644 ydb/public/sdk/cpp/tests/slo_workloads/utils/metrics.cpp create mode 100644 ydb/public/sdk/cpp/tests/slo_workloads/utils/metrics.h diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/key_value/generate.cpp b/ydb/public/sdk/cpp/tests/slo_workloads/key_value/generate.cpp index 1486c9649aa9..4e6b8bb3d9f5 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/key_value/generate.cpp +++ b/ydb/public/sdk/cpp/tests/slo_workloads/key_value/generate.cpp @@ -8,7 +8,7 @@ using namespace NYdb::NTable; TGenerateInitialContentJob::TGenerateInitialContentJob(const TCreateOptions& createOpts, std::uint32_t maxId) - : TThreadJob(createOpts.CommonOptions) + : TThreadJob(createOpts.CommonOptions, "generate") , Executor(createOpts.CommonOptions, Stats, TExecutor::ModeBlocking) , PackGenerator( createOpts.CommonOptions @@ -81,5 +81,4 @@ UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); void TGenerateInitialContentJob::OnFinish() { Executor.Finish(); Executor.Wait(); - Stats.Flush(); } diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value.h b/ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value.h index 441f4995aed2..97406a7db5f6 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value.h +++ b/ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value.h @@ -48,7 +48,6 @@ class TReadJob : public TThreadJob { private: std::unique_ptr Executor; std::uint32_t ObjectIdRange; - bool SaveResult; }; int CreateTable(TDatabaseOptions& dbOptions); diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/key_value/run.cpp b/ydb/public/sdk/cpp/tests/slo_workloads/key_value/run.cpp index 656c40d17ae6..f6e385883191 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/key_value/run.cpp +++ b/ydb/public/sdk/cpp/tests/slo_workloads/key_value/run.cpp @@ -7,7 +7,7 @@ using namespace NYdb; using namespace NYdb::NTable; TWriteJob::TWriteJob(const TCommonOptions& opts, std::uint32_t maxId) - : TThreadJob(opts) + : TThreadJob(opts, "write") , Executor(opts, Stats, TExecutor::ModeNonBlocking) , Generator(opts, maxId) {} @@ -77,15 +77,13 @@ UPSERT INTO `%s` SELECT * FROM AS_TABLE($items); void TWriteJob::OnFinish() { Executor.Finish(); Executor.Wait(); - Stats.Flush(); } // Implementation of TReadJob TReadJob::TReadJob(const TCommonOptions& opts, std::uint32_t maxId) - : TThreadJob(opts) - , Executor(opts.RetryMode ? new TExecutorWithRetry(opts, Stats) : new TExecutor(opts, Stats)) + : TThreadJob(opts, "read") + , Executor(std::make_unique(opts, Stats)) , ObjectIdRange(static_cast(maxId * 1.25)) // 20% of requests with no result - , SaveResult(opts.SaveResult) {} void TReadJob::ShowProgress(TStringBuilder& report) { @@ -154,8 +152,4 @@ void TReadJob::OnFinish() { if (infly) { Cerr << "Warning: thread A finished while having " << infly << " infly requests." << Endl; } - Stats.Flush(); - if (SaveResult) { - Stats.SaveResult(); - } } diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/executor.cpp b/ydb/public/sdk/cpp/tests/slo_workloads/utils/executor.cpp index 5e45cef31caa..28add35c98e8 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/utils/executor.cpp +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/executor.cpp @@ -274,13 +274,17 @@ bool TExecutor::Execute(const NYdb::NTable::TTableClient::TOperationFunc& func) } } - TStatUnit stat = Stats.CreateStatUnit(); + auto stat = Stats.StartRequest(); + + auto future = InsistentClient.ExecuteWithRetry([func, stat](NYdb::NTable::TSession session) { + auto result = func(session); + return result; + }); - auto future = InsistentClient.ExecuteWithRetry(func); future.Subscribe([this, stat, SemaphoreWrapper](const TAsyncFinalStatus& future) mutable { Y_ABORT_UNLESS(future.HasValue()); TFinalStatus resultStatus = future.GetValue(); - Stats.Report(stat, resultStatus); + Stats.FinishRequest(stat, resultStatus); if (resultStatus) { CheckForError(*resultStatus); } @@ -351,7 +355,6 @@ bool TExecutor::IsStopped() { } void TExecutor::Finish() { - // Stats.UpdateSessionStats(InsistentClient.GetSessionStats()); with_lock(Lock) { if (!AllJobsLaunched) { AllJobsLaunched = true; @@ -361,9 +364,6 @@ void TExecutor::Finish() { } void TExecutor::UpdateStats() { - if (Infly > MaxSecInfly) { - MaxSecInfly = Infly; - } std::uint64_t activeSessions = InsistentClient.GetActiveSessions(); if (activeSessions > MaxSecSessions) { MaxSecSessions = activeSessions; @@ -382,10 +382,10 @@ void TExecutor::UpdateStats() { } void TExecutor::ReportStats() { + Stats.ReportStats(MaxSecSessions, MaxSecReadPromises, MaxSecExecutorPromises); TInstant now = TInstant::Now(); if (now.Seconds() > LastReportSec) { - Stats.ReportStats(MaxSecInfly, MaxSecSessions, MaxSecReadPromises, MaxSecExecutorPromises); - MaxSecInfly = 0; + Stats.ReportStats(MaxSecSessions, MaxSecReadPromises, MaxSecExecutorPromises); MaxSecSessions = 0; MaxSecReadPromises = 0; MaxSecExecutorPromises = 0; @@ -442,93 +442,3 @@ void TExecutor::Report(TStringBuilder& out) const { } } } - - -TExecutorWithRetry::TExecutorWithRetry(const TCommonOptions& opts, TStat& stats) - : TExecutor(opts, stats) -{} - -bool TExecutorWithRetry::Execute(const NYdb::NTable::TTableClient::TOperationFunc& func) { - auto threadFunc = [this, func]() { - if (IsStopped()) { - DecrementWaiting(); - return; - } - - with_lock(Lock) { - --Waiting; - if (Infly < Opts.MaxInfly) { - ++Infly; - if (Infly > MaxInfly) { - MaxInfly = Infly; - } - UpdateStats(); - } else { - Stats.ReportMaxInfly(); - UpdateStats(); - return; - } - } - - std::shared_ptr context = std::make_shared(Stats); - - auto executeOperation = [this, func]() { - return InsistentClient.ExecuteWithRetry(func); - }; - - context->HandleStatusFunc = std::make_unique>( - [this, executeOperation, context](const TAsyncFinalStatus& future) mutable { - Y_ABORT_UNLESS(future.HasValue()); - TFinalStatus resultStatus = future.GetValue(); - if (resultStatus) { - // Reply received - CheckForError(*resultStatus); - if (resultStatus->IsSuccess()) { - //Ok received - Stats.Report(context->LifeTimeStat, resultStatus->GetStatus()); - DecrementInfly(); - context->HandleStatusFunc.reset(); - return; - } - } - if (IsStopped() || TInstant::Now() - context->LifeTimeStat.Start > GlobalTimeout) { - // Application stopped working or global timeout reached. Ok reply hasn't received yet - Stats.Report(context->LifeTimeStat, TInnerStatus::StatusNotFinished); - DecrementInfly(); - context->HandleStatusFunc.reset(); - return; - } - Stats.Report(context->PerRequestStat, resultStatus); - context->PerRequestStat = Stats.CreateStatUnit(); - // Retrying: - executeOperation().Subscribe(*context->HandleStatusFunc); - }); - - context->Retries.fetch_add(1); - Y_ABORT_UNLESS(context->Retries.load() < 500, "Too much retries"); - - executeOperation().Subscribe(*context->HandleStatusFunc); - }; - - if (IsStopped()) { - return false; - } - - bool CanLaunchJob = false; - - with_lock(Lock) { - if (!AllJobsLaunched) { - CanLaunchJob = true; - ++Waiting; - } - } - - if (CanLaunchJob) { - if (!InputQueue->AddFunc(threadFunc)) { - DecrementWaiting(); - } - } - ++InProgressCount; - InProgressSum += InputQueue->Size(); - return true; -} diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/executor.h b/ydb/public/sdk/cpp/tests/slo_workloads/utils/executor.h index ff3ce28b078a..1316b8e6a59c 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/utils/executor.h +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/executor.h @@ -1,5 +1,6 @@ #pragma once +#include "statistics.h" #include "utils.h" #include @@ -56,7 +57,7 @@ class TInsistentClient { bool Valid = true; }; - struct TOperationContext : public TThrRefBase { + struct TOperationContext { bool Finished = false; TAdaptiveLock Lock; TCheckedIterator RetryIter; @@ -131,7 +132,6 @@ class TExecutor { std::uint32_t Wait(TDuration waitTimeout); bool IsStopped(); void Finish(); - std::uint32_t GetTotal() const; void Report(TStringBuilder& out) const; protected: @@ -167,8 +167,6 @@ class TExecutor { TInstant Deadline; // Last second we reported Infly std::uint64_t LastReportSec = 0; - // Max infly for current second - std::uint64_t MaxSecInfly = 0; // Max Active sessions for current second std::uint64_t MaxSecSessions = 0; @@ -178,21 +176,3 @@ class TExecutor { std::uint64_t MaxSecReadPromises = 0; std::uint64_t MaxSecExecutorPromises = 0; }; - -class TExecutorWithRetry : public TExecutor { -public: - struct TRetryContext { - TRetryContext(TStat& stat) - : LifeTimeStat(stat.CreateStatUnit()) - , PerRequestStat(stat.CreateStatUnit()) - {} - - TStatUnit LifeTimeStat; - TStatUnit PerRequestStat; - std::unique_ptr> HandleStatusFunc; - std::atomic Retries = 0; - }; - - TExecutorWithRetry(const TCommonOptions& opts, TStat& stats); - bool Execute(const NYdb::NTable::TTableClient::TOperationFunc& func) override; -}; diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/job.cpp b/ydb/public/sdk/cpp/tests/slo_workloads/utils/job.cpp index 23421bcb747c..cc627b2b707a 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/utils/job.cpp +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/job.cpp @@ -5,18 +5,12 @@ const std::string LogFileName = "benchmark.log"; -TThreadJob::TThreadJob(const TCommonOptions& opts) +TThreadJob::TThreadJob(const TCommonOptions& opts, const std::string& operationType) : RpsProvider(opts.Rps) , Prefix(opts.DatabaseOptions.Prefix) - , Stats( - opts.ReactionTime, - opts.ResultFileName, - !opts.DontPushMetrics, - opts.RetryMode - ) + , Stats(opts.DontPushMetrics ? std::nullopt : std::make_optional(opts.MetricsPushUrl), operationType) , StopOnError(opts.StopOnError) , MaxDelay(opts.ReactionTime) - , UseFollowers(opts.UseFollowers) { } @@ -28,7 +22,7 @@ void TThreadJob::Start(TInstant deadline) { void TThreadJob::StartThread() { auto threadFunc = [this]() { - Stats.Reset(); + Stats.Start(); RpsProvider.Reset(); DoJob(); Stats.Finish(); diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/job.h b/ydb/public/sdk/cpp/tests/slo_workloads/utils/job.h index 7e6e48bede5b..a57a790b432b 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/utils/job.h +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/job.h @@ -1,5 +1,6 @@ #pragma once +#include "statistics.h" #include "utils.h" #include @@ -7,7 +8,7 @@ class TThreadJob { public: - TThreadJob(const TCommonOptions& opts); + TThreadJob(const TCommonOptions& opts, const std::string& operationType); virtual ~TThreadJob() = default; virtual void Start(TInstant deadline); @@ -31,10 +32,9 @@ class TThreadJob { TStat Stats; bool StopOnError; TDuration MaxDelay; - bool UseFollowers; }; -class TJobContainer : public TThrRefBase { +class TJobContainer { public: void Add(TThreadJob* job); void Start(TInstant deadline = TInstant()); diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/metrics.cpp b/ydb/public/sdk/cpp/tests/slo_workloads/utils/metrics.cpp new file mode 100644 index 000000000000..a99a0588c5e2 --- /dev/null +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/metrics.cpp @@ -0,0 +1,150 @@ +#include "metrics.h" + +#include "utils.h" + +#include + +#include +#include + +#include + +using namespace std::chrono_literals; + +class TOtelMetricsPusher : public IMetricsPusher { +public: + TOtelMetricsPusher(const std::string& metricsPushUrl, const std::string& operationType) + : OperationType_(operationType) + { + auto exporterOptions = opentelemetry::exporter::otlp::OtlpHttpMetricExporterOptions(); + exporterOptions.url = metricsPushUrl; + + auto exporter = opentelemetry::exporter::otlp::OtlpHttpMetricExporterFactory::Create(exporterOptions); + + opentelemetry::sdk::metrics::PeriodicExportingMetricReaderOptions readerOptions; + readerOptions.export_interval_millis = 1000ms; + readerOptions.export_timeout_millis = 500ms; + + auto metricReader = opentelemetry::sdk::metrics::PeriodicExportingMetricReaderFactory::Create(std::move(exporter), readerOptions); + + MeterProvider_ = opentelemetry::sdk::metrics::MeterProviderFactory::Create(); + MeterProvider_->AddMetricReader(std::move(metricReader)); + + Meter_ = MeterProvider_->GetMeter("slo_workloads", NYdb::GetSdkSemver()); + + InitMetrics(); + } + + void PushRequestData(const TRequestData& requestData) override { + if (requestData.Status == NYdb::EStatus::SUCCESS) { + OperationsSuccessTotal_->Add(1, {{"operation_type", OperationType_}}); + } else { + ErrorsTotal_->Add(1, {{"status", YdbStatusToString(requestData.Status)}}); + OperationsFailureTotal_->Add(1, {{"operation_type", OperationType_}}); + } + OperationsTotal_->Add(1, {{"operation_type", OperationType_}}); + OperationLatencySeconds_->Record(requestData.Delay.SecondsFloat(), {{"operation_type", OperationType_}, {"status", YdbStatusToString(requestData.Status)}}); + RetryAttempts_->Record(requestData.RetryAttempts, {{"operation_type", OperationType_}}); + } + +private: + void InitMetrics() { + ErrorsTotal_ = Meter_->CreateUInt64Counter("sdk_errors_total", + "Total number of errors encountered, categorized by error type." + ); + + OperationsTotal_ = Meter_->CreateUInt64Counter("sdk_operations_total", + "Total number of operations, categorized by type attempted by the SDK." + ); + + OperationsSuccessTotal_ = Meter_->CreateUInt64Counter("sdk_operations_success_total", + "Total number of successful operations, categorized by type." + ); + + OperationsFailureTotal_ = Meter_->CreateUInt64Counter("sdk_operations_failure_total", + "Total number of failed operations, categorized by type." + ); + + OperationLatencySeconds_ = CreateDoubleHistogram("sdk_operation_latency_seconds", + "Latency of operations performed by the SDK in seconds, categorized by type and status.", + { + 0.001, // 1 ms + 0.002, // 2 ms + 0.003, // 3 ms + 0.004, // 4 ms + 0.005, // 5 ms + 0.0075, // 7.5 ms + 0.010, // 10 ms + 0.020, // 20 ms + 0.050, // 50 ms + 0.100, // 100 ms + 0.200, // 200 ms + 0.500, // 500 ms + 1.000, // 1 s + }, + "s" + ); + + RetryAttempts_ = Meter_->CreateInt64Gauge("sdk_retry_attempts", + "Current retry attempts, categorized by operation type." + ); + } + + std::unique_ptr> CreateDoubleHistogram( + const std::string& name, + const std::string& description, + const std::vector& buckets, + const std::string& unit = {}) + { + auto selector = std::make_unique( + opentelemetry::sdk::metrics::InstrumentType::kHistogram, + name, + unit + ); + + auto meterSelector = std::make_unique( + "slo_workloads", + NYdb::GetSdkSemver(), + "" + ); + + auto histogramConfig = std::make_shared(); + histogramConfig->boundaries_ = buckets; + + auto view = std::make_unique( + "", + "", + opentelemetry::sdk::metrics::AggregationType::kHistogram, + histogramConfig + ); + + MeterProvider_->AddView(std::move(selector), std::move(meterSelector), std::move(view)); + + return Meter_->CreateDoubleHistogram(name, description, unit); + } + + std::string OperationType_; + + std::unique_ptr MeterProvider_; + std::shared_ptr Meter_; + + std::unique_ptr> ErrorsTotal_; + std::unique_ptr> OperationsTotal_; + std::unique_ptr> OperationsSuccessTotal_; + std::unique_ptr> OperationsFailureTotal_; + std::unique_ptr> OperationLatencySeconds_; + std::unique_ptr> RetryAttempts_; +}; + +class TNoopMetricsPusher : public IMetricsPusher { +public: + void PushRequestData([[maybe_unused]] const TRequestData& requestData) override {} +}; + +std::unique_ptr CreateOtelMetricsPusher(const std::string& metricsPushUrl, const std::string& operationType) { + return std::make_unique(metricsPushUrl, operationType); +} + +std::unique_ptr CreateNoopMetricsPusher() { + return std::make_unique(); +} diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/metrics.h b/ydb/public/sdk/cpp/tests/slo_workloads/utils/metrics.h new file mode 100644 index 000000000000..139f5025a868 --- /dev/null +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/metrics.h @@ -0,0 +1,22 @@ +#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(); diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/statistics.cpp b/ydb/public/sdk/cpp/tests/slo_workloads/utils/statistics.cpp index 4b5d8e4855fd..15789e762067 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/utils/statistics.cpp +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/statistics.cpp @@ -1,114 +1,20 @@ #include "statistics.h" -#include -#include +#include "metrics.h" +#include "utils.h" -#include - - -TStatUnit::TStatUnit(const std::shared_ptr& periodData, TInstant startTime) - : PeriodData(periodData) - , Start(startTime) -{ -} - -void TStatUnit::Report(const TInnerStatus& status) { - PeriodData->AddStat(Delay(), status); -} - -TDuration TStatUnit::Delay() const { - return End - Start; -} - -TPeriodData::TPeriodData( - TStat* stats, - bool retryMode, - const TDuration maxDelay, - std::uint64_t currentSecond, - std::uint64_t currInfly, - std::uint64_t currSessions, - std::uint64_t currPromises, - std::uint64_t currExecutorPromises -) - : Stats(stats) - , RetryMode(retryMode) - , CurrentSecond(currentSecond) - , MaxDelay(maxDelay) -{ - Counters.Infly = currInfly; - Counters.ActiveSessions = currSessions; - Counters.ReadPromises = currPromises; - Counters.ExecutorPromises = currExecutorPromises; -} - -TPeriodData::~TPeriodData() { - Stats->ReportLatencyData(CurrentSecond, std::move(Counters), std::move(Replies), std::move(OkDelays), NotOkDelays); -} - -TStatUnit TPeriodData::CreateStatUnit(TInstant startTime) { - return TStatUnit(shared_from_this(), startTime); -} - -std::uint64_t TPeriodData::GetCurrentSecond() const { - return CurrentSecond; -} - -void TPeriodData::ReportMaxInfly() { - ++Replies.CountMaxInfly; -} - -void TPeriodData::ReportInfly(std::uint64_t infly) { - if (infly > Counters.Infly) { - Counters.Infly = infly; - } -} - -void TPeriodData::ReportActiveSessions(std::uint64_t sessions) { - if (sessions > Counters.ActiveSessions) { - Counters.ActiveSessions = sessions; - } -} - -// Debug use only: -void TPeriodData::ReportReadPromises(std::uint64_t promises) { - if (promises > Counters.ReadPromises) { - Counters.ReadPromises = promises; - } -} -void TPeriodData::ReportExecutorPromises(std::uint64_t promises) { - if (promises > Counters.ExecutorPromises) { - Counters.ExecutorPromises = promises; - } -} - -void TPeriodData::AddStat(TDuration delay, const TInnerStatus& status) { - if (status.InnerStatus == TInnerStatus::StatusReceived && status.YdbStatus == NYdb::EStatus::SUCCESS) { - OkDelays.push_back(delay); - } else { - NotOkDelays.push_back(delay); - } - - switch (status.InnerStatus) { - case TInnerStatus::StatusReceived: - if (status.YdbStatus != NYdb::EStatus::SUCCESS || !RetryMode || delay <= MaxDelay) { - ++Replies.Statuses[status.YdbStatus]; - } else { - ++Replies.CountHighLatency; - } - break; - case TInnerStatus::StatusApplicationTimeout: - ++Replies.ApplicationTimeout; - break; - case TInnerStatus::StatusNotFinished: - ++Replies.NotFinished; - break; - default: - Y_ABORT_UNLESS(status.InnerStatus); - break; - } -} namespace { + // Calculated percentiles for a period of time + struct TPercentile { + TDuration P50; + TDuration P90; + TDuration P95; + TDuration P99; + TDuration P99_9; + TDuration P100; + }; + void CalculatePercentiles(TPercentile& p, std::vector& delays) { size_t count = delays.size(); if (count) { @@ -123,41 +29,14 @@ namespace { } } -TStat::TStat( - TDuration maxDelay, - const std::string& resultFileName, - bool pushMetrics, - bool retryMode -) - : MaxDelay(maxDelay) - , StartTime(TInstant::Now()) - , PushMetrics(pushMetrics) - , RetryMode(retryMode) - , ResultFileName(resultFileName) +TStat::TStat(const std::optional& metricsPushUrl, const std::string& operationType) + : StartTime(TInstant::Now()) + , MetricsPusher(metricsPushUrl ? CreateOtelMetricsPusher(*metricsPushUrl, operationType) : CreateNoopMetricsPusher()) { MetricsPushQueue.Start(20); } -TStat::~TStat() { - Flush(); -} - -void TStat::Flush() { - std::uint64_t lastSecMeasured = TInstant::Now().Seconds(); - if (ActivePeriod) { - lastSecMeasured = ActivePeriod->GetCurrentSecond(); - ActivePeriod.reset(); - } - if (PushMetrics) { - ResetMetricsPusher(lastSecMeasured + 1); - MetricsPushQueue.Stop(); - } -} - -void TStat::Reset() { - if (PushMetrics) { - ResetMetricsPusher(TInstant::Now().Seconds() - 1); - } +void TStat::Start() { StartTime = TInstant::Now(); } @@ -165,356 +44,104 @@ void TStat::Finish() { FinishTime = TInstant::Now(); } -TStatUnit TStat::CreateStatUnit() { +std::shared_ptr TStat::StartRequest() { std::lock_guard lock(Mutex); - TInstant Now = TInstant::Now(); - CheckCurrentSecond(Now); - return ActivePeriod->CreateStatUnit(Now); -} - -void TStat::Report(TStatUnit& unit, const TInnerStatus& status) { - unit.End = TInstant::Now(); - OnReport(unit, status); -} - -void TStat::Report(TStatUnit& unit, TInnerStatus::EInnerStatus innerStatus) { - Report(unit, TInnerStatus(innerStatus)); -} - -void TStat::Report(TStatUnit& unit, const TFinalStatus& status) { - Report( - unit, - status - ? TInnerStatus(TInnerStatus::StatusReceived, status->GetStatus()) - : TInnerStatus(TInnerStatus::StatusApplicationTimeout) - ); + ++Infly; + return std::make_shared(TInstant::Now()); } -void TStat::Report(TStatUnit& unit, NYdb::EStatus status) { - Report(unit, TInnerStatus(TInnerStatus::StatusReceived, status)); -} - -void TStat::ReportMaxInfly() { +void TStat::FinishRequest(const std::shared_ptr& unit, const TFinalStatus& status) { std::lock_guard lock(Mutex); - ++Replies.CountMaxInfly; - TInstant Now = TInstant::Now(); - CheckCurrentSecond(Now); - ActivePeriod->ReportMaxInfly(); -} + unit->End = TInstant::Now(); -void TStat::ReportStats(std::uint64_t infly, std::uint64_t sessions, std::uint64_t readPromises, std::uint64_t executorPromises) { - std::lock_guard lock(Mutex); + auto delay = unit->End - unit->Start; - Counters.Infly = infly; - Counters.ActiveSessions = sessions; - Counters.ReadPromises = readPromises; - Counters.ExecutorPromises = executorPromises; - TInstant Now = TInstant::Now(); - CheckCurrentSecond(Now); - ActivePeriod->ReportInfly(infly); - ActivePeriod->ReportActiveSessions(sessions); - ActivePeriod->ReportReadPromises(readPromises); - ActivePeriod->ReportExecutorPromises(executorPromises); -} + --Infly; -void TStat::Reserve(size_t size) { - std::lock_guard lock(Mutex); + if (status) { + ++Statuses[status->GetStatus()]; + } else { + ++ApplicationTimeout; + } + + if (status && status->GetStatus() == NYdb::EStatus::SUCCESS) { + OkDelays.push_back(delay); + } - LatencyStats.reserve(size); - LatencyData.reserve(size); + ScheduleMetricsPush([this, delay, status, unit]() { + MetricsPusher->PushRequestData({ + .Delay = delay, + .Status = status->GetStatus(), + .RetryAttempts = unit->RetryAttempts + }); + }); } -void TStat::ReportLatencyData( - std::uint64_t currSecond, - TCounters&& counters, - TReplies&& replies, - std::vector&& oks, - std::vector& notOks -) { +void TStat::ReportMaxInfly() { std::lock_guard lock(Mutex); - LatencyStats.emplace_back(); - TPeriodStat& p = LatencyStats.back(); - p.Seconds = currSecond; - std::swap(p.Counters, counters); - std::swap(p.Replies, replies); - CalculatePercentiles(p.Oks, oks); - CalculatePercentiles(p.NotOks, notOks); - LatencyData.emplace_back(std::move(oks)); - PushMetricsData(p); + ++CountMaxInfly; } -void TStat::UpdateSessionStats( - const std::unordered_map& sessionStats -) { +void TStat::ReportStats(std::uint64_t sessions, std::uint64_t readPromises, std::uint64_t executorPromises) { std::lock_guard lock(Mutex); - SessionStats = sessionStats; + ActiveSessions = sessions; + ReadPromises = readPromises; + ExecutorPromises = executorPromises; } void TStat::PrintStatistics(TStringBuilder& out) { std::lock_guard lock(Mutex); - TInstant now = TInstant::Now(); - CheckCurrentSecond(now); - std::uint64_t total = GetTotal(); + std::uint64_t total = CountMaxInfly; + for (const auto& [status, counter] : Statuses) { + total += counter; + } + total += ApplicationTimeout; TDuration timePassed; if (FinishTime < StartTime) { // If we ask for current progress - timePassed = now - StartTime; + timePassed = TInstant::Now() - StartTime; } else { timePassed = FinishTime - StartTime; } std::uint64_t rps = total * 1000000 / timePassed.MicroSeconds(); out << total << " requests total" << Endl - << Replies.Statuses[NYdb::EStatus::SUCCESS] << " succeeded"; + << Statuses[NYdb::EStatus::SUCCESS] << " succeeded"; if (total) { - out << " (" << Replies.Statuses[NYdb::EStatus::SUCCESS] * 100 / total << "%)"; + out << " (" << Statuses[NYdb::EStatus::SUCCESS] * 100 / total << "%)"; } - for (const auto&[status, counter] : Replies.Statuses) { + for (const auto&[status, counter] : Statuses) { out << Endl << counter << " replies with status " << YdbStatusToString(status) << Endl; } - out << Endl << Replies.CountMaxInfly << " failed due to max infly" << Endl - << Replies.CountHighLatency << " OK results exceeded latency limit of " << MaxDelay << Endl - << Replies.ApplicationTimeout << " application timeouts" << Endl - << Replies.NotFinished << " requests not finished within program lifetime" << Endl + out << Endl << CountMaxInfly << " failed due to max infly" << Endl + << ApplicationTimeout << " application timeouts" << Endl << "Time passed: " << timePassed.ToString() << Endl << "Real rps: " << rps << Endl; - if (LatencyData.size()) { - CalculateGlobalPercentile(); - TPercentile& p = *GlobalPercentile; - out << "Global latency percentiles (" << LatencyData.size() << " seconds measured):" << Endl + if (OkDelays.size()) { + TPercentile p; + CalculatePercentiles(p, OkDelays); + + out << "Global latency percentiles:" << Endl << "P50: " << p.P50 << "\tP90: " << p.P90 << "\tP95: " << p.P95 << "\tP99: " << p.P99 << "\tP99.9: " << p.P99_9 << "\tP100: " << p.P100 << Endl; - CalculateFailSeconds(); - out << *FailSeconds << " seconds where p99 reached max delay of " << MaxDelay << Endl; } else { out << "Can't calculate latency percentiles: No data (zero requests measured)" << Endl; } } -void TStat::SaveResult() { - std::lock_guard lock(Mutex); - - if (LatencyData.size()) { - CalculateGlobalPercentile(); - CalculateFailSeconds(); - NJson::TJsonValue root; - root["Oks"] = Replies.Statuses[NYdb::EStatus::SUCCESS]; - root["Total"] = GetTotal(); - root["P99"] = GlobalPercentile->P99.MilliSeconds(); - root["FailSeconds"] = *FailSeconds; - root["StartTime"] = StartTime.Seconds(); - root["FinishTime"] = FinishTime.Seconds(); - NJson::TJsonValue& items = root["SessionCountsAtFinish"]; - items.SetType(NJson::JSON_ARRAY); - for (const auto& s : SessionStats) { - items.AppendValue(s.second); - } - TFileOutput resultFile(ResultFileName); - NJsonWriter::TBuf buf; - buf.WriteJsonValue(&root); - resultFile << buf.Str(); - Cout << "Result saved to file " << ResultFileName << Endl; - } -} - -void TStat::CalculateGlobalPercentile() { - if (GlobalPercentile) { - return; - } - std::vector fullData; - size_t totalSize = 0; - for (auto& periodData : LatencyData) { - totalSize += periodData.size(); - } - fullData.reserve(totalSize); - for (auto& periodData : LatencyData) { - fullData.insert(fullData.end(), periodData.begin(), periodData.end()); - } - GlobalPercentile = std::make_unique(); - CalculatePercentiles(*GlobalPercentile, fullData); -} - -namespace { - bool IsGoodInterval(const TPeriodStat& stat, const TDuration& maxDelay) { - for (const auto& [status, counter] : stat.Replies.Statuses) { - if (status != NYdb::EStatus::SUCCESS && counter) { - return false; - } - } - return stat.Oks.P99 <= maxDelay && !stat.Replies.CountMaxInfly && !stat.Replies.ApplicationTimeout; - } -} - -void TStat::CalculateFailSeconds() { - if (FailSeconds) { - return; - } - std::sort(LatencyStats.begin(), LatencyStats.end(), [&](const TPeriodStat& a, const TPeriodStat& b) { - return a.Seconds < b.Seconds; - }); - FailSeconds = std::make_unique(0); - std::uint64_t& failSeconds = *FailSeconds; - std::uint64_t lastSecChecked = LatencyStats[0].Seconds - 1; - for (auto& stat : LatencyStats) { - failSeconds += stat.Seconds - lastSecChecked - 1; - lastSecChecked = stat.Seconds; - if (!IsGoodInterval(stat, MaxDelay)) { - ++failSeconds; - } - } -} - -std::uint64_t TStat::GetTotal() { - std::uint64_t total = Replies.CountMaxInfly + Replies.CountHighLatency + Replies.NotFinished; - if (!RetryMode) { - for (const auto& [status, counter] : Replies.Statuses) { - total += counter; - } - total += Replies.ApplicationTimeout; - } else { - total += Replies.Statuses[NYdb::EStatus::SUCCESS]; - } - return total; -} - TInstant TStat::GetStartTime() const { return StartTime; } -void TStat::OnReport(TStatUnit& unit, const TInnerStatus& status) { - std::lock_guard lock(Mutex); - - TDuration delay = unit.Delay(); - switch (status.InnerStatus) { - case TInnerStatus::StatusReceived: - if (status.YdbStatus != NYdb::EStatus::SUCCESS || !RetryMode || delay <= MaxDelay) { - ++Replies.Statuses[status.YdbStatus]; - } else { - ++Replies.CountHighLatency; - } - break; - case TInnerStatus::StatusApplicationTimeout: - ++Replies.ApplicationTimeout; - break; - case TInnerStatus::StatusNotFinished: - ++Replies.NotFinished; - break; - default: - Y_ABORT_UNLESS(status.InnerStatus); - break; - } - // Saving delay stats - unit.Report(status); -} - -void TStat::CheckCurrentSecond(TInstant now) { - std::uint64_t currSecond = now.Seconds(); - if (currSecond > CurrentSecond) { - CurrentSecond = currSecond; - ActivePeriod = std::make_shared( - this, - RetryMode, - MaxDelay, - currSecond, - Counters.Infly, - Counters.ActiveSessions, - Counters.ReadPromises, - Counters.ExecutorPromises - ); - } -} - -void TStat::PushMetricsData(const TPeriodStat& p) { - if (!PushMetrics) { - return; - } - auto threadFunc = [this, p]() { - MetricsPusher->PushData(p); - }; - if (!MetricsPushQueue.AddFunc(threadFunc)) { - Cerr << TInstant::Now().ToRfc822StringLocal() << ": Failed to push data to solomon" << Endl; - } -} - -void TStat::ResetMetricsPusher(std::uint64_t timestamp) { - while (timestamp >= TInstant::Now().Seconds()) { - Sleep(TDuration::Seconds(1)); - } - TPeriodStat pStat; - pStat.Seconds = timestamp; - PushMetricsData(pStat); -} - -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"; +void TStat::ScheduleMetricsPush(std::function func) { + if (!MetricsPushQueue.AddFunc(func)) { + Cerr << TInstant::Now().ToRfc822StringLocal() << ": Failed to push metrics" << Endl; } } diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/statistics.h b/ydb/public/sdk/cpp/tests/slo_workloads/utils/statistics.h index 07c7033c80f7..d87926e4fde2 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/utils/statistics.h +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/statistics.h @@ -1,5 +1,7 @@ #pragma once +#include "metrics.h" + #include #include @@ -20,181 +22,67 @@ inline double GetMillisecondsDouble(const TDuration& d) { using TFinalStatus = std::optional; using TAsyncFinalStatus = NThreading::TFuture; -struct TInnerStatus { - enum EInnerStatus { - StatusUnknown, - StatusReceived, - StatusApplicationTimeout, - StatusNotFinished - }; - - TInnerStatus(EInnerStatus innerStatus = StatusUnknown, NYdb::EStatus ydbStatus = NYdb::EStatus::STATUS_UNDEFINED) - : InnerStatus(innerStatus) - , YdbStatus(ydbStatus) +// Request unit +struct TStatUnit { + TStatUnit(TInstant start) + : Start(start) + , RetryAttempts(0) + , IsFirstAttempt(true) {} - EInnerStatus InnerStatus; - NYdb::EStatus YdbStatus; -}; - -class TPeriodData; + void IncRetryAttempts() { + if (IsFirstAttempt) { + IsFirstAttempt = false; + } else { + ++RetryAttempts; + } + } -// Primitive latency unit -struct TStatUnit { - TStatUnit(const std::shared_ptr& periodData, TInstant startTime); - void Report(const TInnerStatus& status); - TDuration Delay() const; - - std::shared_ptr PeriodData; TInstant Start; TInstant End; -}; - -struct TCounters { - std::uint64_t Infly = 0; - std::uint64_t ActiveSessions = 0; - - // Debug use only: - std::uint64_t ReadPromises = 0; - std::uint64_t ExecutorPromises = 0; -}; - -struct TReplies { - std::map Statuses; - std::uint64_t CountMaxInfly = 0; - std::uint64_t CountHighLatency = 0; - std::uint64_t ApplicationTimeout = 0; - std::uint64_t NotFinished = 0; -}; -// Calculated percentiles for a period of time -struct TPercentile { - TDuration P50; - TDuration P90; - TDuration P95; - TDuration P99; - TDuration P99_9; - TDuration P100; + std::uint64_t RetryAttempts; + bool IsFirstAttempt; }; -// Accumulated statistics for a period of time -struct TPeriodStat { - std::uint64_t Seconds = 0; - TCounters Counters; - TReplies Replies; - TPercentile Oks; - TPercentile NotOks; -}; - -class TStat; - -// Full latency data for a period of time -class TPeriodData : public std::enable_shared_from_this { +class TStat { public: - TPeriodData( - TStat* stats, - bool RetryMode, - const TDuration maxDelay, - std::uint64_t currentSecond, - std::uint64_t currInfly, - std::uint64_t currSessions, - std::uint64_t currPromises, - std::uint64_t currExecutorPromises - ); - ~TPeriodData(); - TStatUnit CreateStatUnit(TInstant startTime); - void AddStat(TDuration delay, const TInnerStatus& status); - std::uint64_t GetCurrentSecond() const; - void ReportMaxInfly(); - void ReportInfly(std::uint64_t infly); - void ReportActiveSessions(std::uint64_t sessions); - - // Debug use only: - void ReportReadPromises(std::uint64_t promises); - void ReportExecutorPromises(std::uint64_t promises); + explicit TStat(const std::optional& metricsPushUrl, const std::string& operationType); -private: - TStat* Stats; - bool RetryMode; - std::uint64_t CurrentSecond; - std::vector OkDelays; - std::vector NotOkDelays; - TCounters Counters; - TReplies Replies; - TDuration MaxDelay; -}; + void Start(); + void Finish(); -class IMetricsPusher { -public: - virtual ~IMetricsPusher() = default; - virtual void PushData(const TPeriodStat& p) = 0; -}; + std::shared_ptr StartRequest(); + void FinishRequest(const std::shared_ptr& unit, const TFinalStatus& status); -class TStat { - friend class TPeriodData; -public: - TStat( - TDuration maxDelay, - const std::string& resultFileName, - bool pushMetrics, - bool retryMode - ); - ~TStat(); - void Reset(); - void Finish(); - void Flush(); - TStatUnit CreateStatUnit(); - void Report(TStatUnit& unit, const TInnerStatus& status); - void Report(TStatUnit& unit, TInnerStatus::EInnerStatus innerStatus); - void Report(TStatUnit& unit, const TFinalStatus& status); - void Report(TStatUnit& unit, NYdb::EStatus status); void ReportMaxInfly(); - void ReportStats(std::uint64_t infly, std::uint64_t sessions, std::uint64_t readPromises, std::uint64_t executorPromises); - void Reserve(size_t size); - void ReportLatencyData( - std::uint64_t currSecond, - TCounters&& counters, - TReplies&& replies, - std::vector&& oks, - std::vector& notOks - ); - void UpdateSessionStats(const std::unordered_map& sessionStats); + void ReportStats(std::uint64_t sessions, std::uint64_t readPromises, std::uint64_t executorPromises); void PrintStatistics(TStringBuilder& out); - void SaveResult(); TInstant GetStartTime() const; private: - void OnReport(TStatUnit& unit, const TInnerStatus& status); - void CheckCurrentSecond(TInstant now); - void PushMetricsData(const TPeriodStat& p); - void ResetMetricsPusher(std::uint64_t timestamp); - void CalculateGlobalPercentile(); - void CalculateFailSeconds(); - std::uint64_t GetTotal(); + void ScheduleMetricsPush(std::function func); std::recursive_mutex Mutex; TThreadPool MetricsPushQueue; - // Current period we collect stats for - std::shared_ptr ActivePeriod; - std::vector LatencyStats; - std::vector> LatencyData; - TDuration MaxDelay; + TInstant StartTime; TInstant FinishTime; - std::unordered_map SessionStats; - std::uint64_t CurrentSecond = 0; + // program lifetime - TCounters Counters; - TReplies Replies; - bool PushMetrics; - bool RetryMode; - std::string ResultFileName; - std::unique_ptr MetricsPusher; + std::uint64_t Infly = 0; + std::uint64_t ActiveSessions = 0; - std::unique_ptr GlobalPercentile; - std::unique_ptr FailSeconds; -}; + std::map Statuses; + std::uint64_t CountMaxInfly = 0; + std::uint64_t ApplicationTimeout = 0; + std::vector OkDelays; -std::string YdbStatusToString(NYdb::EStatus status); + // Debug use only: + std::uint64_t ReadPromises = 0; + std::uint64_t ExecutorPromises = 0; + + std::unique_ptr MetricsPusher; +}; diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.cpp b/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.cpp index ba8351e8f5f5..b00b555d2072 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.cpp +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.cpp @@ -289,6 +289,73 @@ std::uint32_t GetHash(std::uint32_t value) { return result; } +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"; + } +} + TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableName) { Cout << TInstant::Now().ToRfc822StringLocal() << " Getting table stats (maxId and count of rows) with ReadTable... " << Endl; @@ -358,10 +425,10 @@ TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableN return result; } -void ParseOptionsCommon(TOpts& opts, TCommonOptions& options, bool followers) { +void ParseOptionsCommon(TOpts& opts, TCommonOptions& options) { opts.AddLongOption("threads", "Number of threads to use").RequiredArgument("NUM") .DefaultValue(options.MaxInputThreads).StoreResult(&options.MaxInputThreads); - opts.AddLongOption("stop_on_error", "Stop thread if an error occured").NoArgument() + opts.AddLongOption("stop-on-error", "Stop thread if an error occured").NoArgument() .SetFlag(&options.StopOnError).DefaultValue(options.StopOnError); opts.AddLongOption("payload-min", "Minimum length of payload string").RequiredArgument("NUM") .DefaultValue(options.MinLength).StoreResult(&options.MinLength); @@ -371,20 +438,14 @@ void ParseOptionsCommon(TOpts& opts, TCommonOptions& options, bool followers) { .DefaultValue(options.A_ReactionTime).StoreResult(&options.A_ReactionTime); opts.AddLongOption("dont-push", "Do not push metrics").NoArgument() .SetFlag(&options.DontPushMetrics).DefaultValue(options.DontPushMetrics); - opts.AddLongOption("retry", "Retry each request until Ok reply or global timeout").NoArgument() - .SetFlag(&options.RetryMode).DefaultValue(options.RetryMode); - opts.AddLongOption("save-result", "Save result to file").NoArgument() - .SetFlag(&options.SaveResult).DefaultValue(options.SaveResult); - opts.AddLongOption("result-file-name", "Set result json file name").RequiredArgument("String") - .DefaultValue(options.ResultFileName).StoreResult(&options.ResultFileName); + opts.AddLongOption("metrics-push-url", "URL to push metrics").RequiredArgument("URL") + .DefaultValue(options.MetricsPushUrl).StoreResult(&options.MetricsPushUrl); opts.AddLongOption("app-timeout", "Use application timeout (over SDK)").NoArgument() .SetFlag(&options.UseApplicationTimeout).DefaultValue(options.UseApplicationTimeout); opts.AddLongOption("prevention-request", "Send prevention request at 1/2 of timeout").NoArgument() .SetFlag(&options.SendPreventiveRequest).DefaultValue(options.SendPreventiveRequest); - if (followers) { - opts.AddLongOption("followers", "Use followers").NoArgument() - .SetFlag(&options.UseFollowers).DefaultValue(options.UseFollowers); - } + + opts.MutuallyExclusive("dont-push", "metrics-push-url"); } bool CheckOptionsCommon(TCommonOptions& options) { @@ -396,16 +457,13 @@ bool CheckOptionsCommon(TCommonOptions& options) { Cerr << "--threads should be more than 0" << Endl; return false; } - if (!options.DontPushMetrics) { - Cerr << "Push metrics is not supported yet" << Endl; - return false; - } + return true; } -bool ParseOptionsCreate(int argc, char** argv, TCreateOptions& createOptions, bool followers) { +bool ParseOptionsCreate(int argc, char** argv, TCreateOptions& createOptions) { TOpts opts = TOpts::Default(); - ParseOptionsCommon(opts, createOptions.CommonOptions, followers); + ParseOptionsCommon(opts, createOptions.CommonOptions); opts.AddLongOption("count", "Total number of records to generate").RequiredArgument("NUM") .DefaultValue(createOptions.Count).StoreResult(&createOptions.Count); opts.AddLongOption("pack-size", "Number of new records in each create request").RequiredArgument("NUM") @@ -427,9 +485,9 @@ bool ParseOptionsCreate(int argc, char** argv, TCreateOptions& createOptions, bo return true; } -bool ParseOptionsRun(int argc, char** argv, TRunOptions& runOptions, bool followers) { +bool ParseOptionsRun(int argc, char** argv, TRunOptions& runOptions) { TOpts opts = TOpts::Default(); - ParseOptionsCommon(opts, runOptions.CommonOptions, followers); + ParseOptionsCommon(opts, runOptions.CommonOptions); opts.AddLongOption("time", "Time to run (Seconds)").RequiredArgument("Seconds") .DefaultValue(runOptions.CommonOptions.SecondsToRun).StoreResult(&runOptions.CommonOptions.SecondsToRun); opts.AddLongOption("read-rps", "Request generation rate for read requests (Thread A)").RequiredArgument("NUM") @@ -440,8 +498,6 @@ bool ParseOptionsRun(int argc, char** argv, TRunOptions& runOptions, bool follow .SetFlag(&runOptions.DontRunA).DefaultValue(runOptions.DontRunA); opts.AddLongOption("no-write", "Do not run writing requests (thread B)").NoArgument() .SetFlag(&runOptions.DontRunB).DefaultValue(runOptions.DontRunB); - opts.AddLongOption("no-c", "Do not run thread C").NoArgument() - .SetFlag(&runOptions.DontRunC).DefaultValue(runOptions.DontRunC); opts.AddLongOption("infly", "Maximum number of running jobs").RequiredArgument("NUM") .DefaultValue(runOptions.CommonOptions.MaxInfly).StoreResult(&runOptions.CommonOptions.MaxInfly); TOptsParseResult res(&opts, argc, argv); diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.h b/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.h index c05bf1677eb4..a4beaa45a022 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.h +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.h @@ -1,7 +1,5 @@ #pragma once -#include "statistics.h" - #include #include #include @@ -56,17 +54,13 @@ struct TCommonOptions { bool UseApplicationTimeout = false; bool SendPreventiveRequest = false; - //Generator options: + // Generator options: std::uint32_t MinLength = 20; std::uint32_t MaxLength = 200; - //Output options: - bool DontPushMetrics = true; - std::string ResultFileName = "slo_result.json"; - - bool UseFollowers = false; - bool RetryMode = false; - bool SaveResult = false; + // Output options: + bool DontPushMetrics = false; + std::string MetricsPushUrl = "http://localhost:9090/api/v1/otlp/v1/metrics"; }; struct TCreateOptions { @@ -79,7 +73,6 @@ struct TRunOptions { TCommonOptions CommonOptions; bool DontRunA = false; bool DontRunB = false; - bool DontRunC = false; std::uint32_t Read_rps = 1000; std::uint32_t Write_rps = 10; }; @@ -159,7 +152,9 @@ std::uint32_t GetShardSpecialId(std::uint64_t shardNo); std::uint32_t GetHash(std::uint32_t value); +std::string YdbStatusToString(NYdb::EStatus status); + TTableStats GetTableStats(TDatabaseOptions& dbOptions, const std::string& tableName); -bool ParseOptionsCreate(int argc, char** argv, TCreateOptions& createOptions, bool followers = false); -bool ParseOptionsRun(int argc, char** argv, TRunOptions& runOptions, bool followers = false); +bool ParseOptionsCreate(int argc, char** argv, TCreateOptions& createOptions); +bool ParseOptionsRun(int argc, char** argv, TRunOptions& runOptions); diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/ya.make b/ydb/public/sdk/cpp/tests/slo_workloads/utils/ya.make index 3c2032ce3e52..e2e30de8237e 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/utils/ya.make +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/ya.make @@ -4,11 +4,13 @@ SRCS( executor.cpp generator.cpp job.cpp + metrics.cpp statistics.cpp utils.cpp ) PEERDIR( + contrib/libs/opentelemetry-cpp library/cpp/json/writer ydb/public/sdk/cpp/src/client/table ydb/public/sdk/cpp/src/client/iam From 7fa943bbf8c782091de83559a23af69d2ce424fc Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 20 Oct 2025 16:15:33 +0000 Subject: [PATCH 2/7] Added CI workflow --- .github/workflows/slo.yml | 83 +++++++++++++++++++ .github/workflows/slo_report.yml | 22 +++++ .../slo_workloads/key_value/key_value.cpp | 4 +- .../cpp/tests/slo_workloads/key_value/ya.make | 5 -- .../cpp/tests/slo_workloads/utils/utils.cpp | 7 +- .../sdk/cpp/tests/slo_workloads/utils/utils.h | 4 +- 6 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/slo.yml create mode 100644 .github/workflows/slo_report.yml diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml new file mode 100644 index 000000000000..e91fd90616b3 --- /dev/null +++ b/.github/workflows/slo.yml @@ -0,0 +1,83 @@ +name: SLO + +on: + workflow_dispatch: + inputs: + github_pull_request_number: + required: true + slo_workload_duration_seconds: + default: '600' + required: false + slo_workload_read_max_rps: + default: '1000' + required: false + slo_workload_write_max_rps: + default: '100' + required: false + +jobs: + ydb-slo-action: + name: Run YDB SLO Tests + runs-on: ubuntu-latest + + strategy: + matrix: + include: + - workload: table + connection-string: grpc://localhost:2135/?database=/Root/testdb + run-args: | + --metrics-push-url http://localhost:9090/api/v1/otlp/v1/metrics \ + --time ${{inputs.slo_workload_duration_seconds || 600}} \ + --read-rps ${{inputs.slo_workload_read_max_rps || 1000}} \ + --write-rps ${{inputs.slo_workload_write_max_rps || 100}} \ + --read-timeout 1000 \ + --write-timeout 1000 + group: slo-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.commit_sha }} + fetch-depth: 0 + + - name: Configure git + uses: ./.github/actions/configure_git + + - name: Build SLO Workload Binary + run: | + ./ya make ydb/public/sdk/cpp/tests/slo_workloads/key_value -r + + - name: Initialize YDB SLO + uses: ydb-platform/ydb-slo-action/init@main + with: + github_pull_request_number: ${{ github.event.inputs.github_pull_request_number }} + github_token: ${{ secrets.GITHUB_TOKEN }} + workload_name: ${{ matrix.workload }} + ydb_database_node_count: 5 + + - name: Prepare SLO Database + run: | + ./ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value ${{ matrix.connection-string }} create + + - name: Run SLO Tests + run: | + ./ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value ${{ matrix.connection-string }} run ${{ matrix.run-args }} + + - if: always() + name: Store ydb chaos testing logs + run: | + docker logs ydb-chaos > chaos-ydb.log + + - if: always() + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.workload }}-chaos-ydb.log + path: ./chaos-ydb.log + retention-days: 1 + + - if: always() + name: Cleanup SLO Database + run: | + ./ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value ${{ matrix.connection-string }} cleanup diff --git a/.github/workflows/slo_report.yml b/.github/workflows/slo_report.yml new file mode 100644 index 000000000000..1467aeb17619 --- /dev/null +++ b/.github/workflows/slo_report.yml @@ -0,0 +1,22 @@ +name: SLO Report + +on: + workflow_run: + workflows: ['SLO'] + types: + - completed + +jobs: + test-ydb-slo-action: + runs-on: ubuntu-latest + name: Publish YDB SLO Report + permissions: + contents: read + pull-requests: write + if: github.event.workflow_run.conclusion == 'success' + steps: + - name: Publish YDB SLO Report + uses: ydb-platform/ydb-slo-action/report@main + with: + github_run_id: ${{ github.event.workflow_run.id }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value.cpp b/ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value.cpp index 9f48597019f7..1cb40f3620f8 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value.cpp +++ b/ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value.cpp @@ -70,12 +70,12 @@ int DoRun(TDatabaseOptions& dbOptions, int argc, char** argv) { if (!runOptions.DontRunA) { runOptions.CommonOptions.Rps = runOptions.Read_rps; - runOptions.CommonOptions.ReactionTime = TDuration::MilliSeconds(runOptions.CommonOptions.A_ReactionTime); + runOptions.CommonOptions.ReactionTime = runOptions.ReadTimeout; jobs->Add(new TReadJob(runOptions.CommonOptions, maxId)); } if (!runOptions.DontRunB) { runOptions.CommonOptions.Rps = runOptions.Write_rps; - runOptions.CommonOptions.ReactionTime = DefaultReactionTime; + runOptions.CommonOptions.ReactionTime = runOptions.WriteTimeout; jobs->Add(new TWriteJob(runOptions.CommonOptions, maxId)); } diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/key_value/ya.make b/ydb/public/sdk/cpp/tests/slo_workloads/key_value/ya.make index ff92607f3c81..37c92c37460a 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/key_value/ya.make +++ b/ydb/public/sdk/cpp/tests/slo_workloads/key_value/ya.make @@ -1,8 +1,3 @@ -SUBSCRIBER( - pnv1 - g:kikimr -) - PROGRAM() SRCS( diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.cpp b/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.cpp index b00b555d2072..cc0f2bc3e6b0 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.cpp +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.cpp @@ -16,7 +16,6 @@ using namespace NYdb; const TDuration DefaultReactionTime = TDuration::Minutes(2); const TDuration ReactionTimeDelay = TDuration::MilliSeconds(5); -const TDuration GlobalTimeout = TDuration::Minutes(2); const std::uint64_t PartitionsCount = 64; Y_DECLARE_OUT_SPEC(, NYdb::TStatus, stream, value) { @@ -434,8 +433,6 @@ void ParseOptionsCommon(TOpts& opts, TCommonOptions& options) { .DefaultValue(options.MinLength).StoreResult(&options.MinLength); opts.AddLongOption("payload-max", "Maximum length of payload string").RequiredArgument("NUM") .DefaultValue(options.MaxLength).StoreResult(&options.MaxLength); - opts.AddLongOption("timeout", "Read requests execution timeout [ms]").RequiredArgument("NUM") - .DefaultValue(options.A_ReactionTime).StoreResult(&options.A_ReactionTime); opts.AddLongOption("dont-push", "Do not push metrics").NoArgument() .SetFlag(&options.DontPushMetrics).DefaultValue(options.DontPushMetrics); opts.AddLongOption("metrics-push-url", "URL to push metrics").RequiredArgument("URL") @@ -500,6 +497,10 @@ bool ParseOptionsRun(int argc, char** argv, TRunOptions& runOptions) { .SetFlag(&runOptions.DontRunB).DefaultValue(runOptions.DontRunB); opts.AddLongOption("infly", "Maximum number of running jobs").RequiredArgument("NUM") .DefaultValue(runOptions.CommonOptions.MaxInfly).StoreResult(&runOptions.CommonOptions.MaxInfly); + opts.AddLongOption("read-timeout", "Read requests execution timeout [ms]").RequiredArgument("NUM") + .DefaultValue(runOptions.ReadTimeout).StoreResult(&runOptions.ReadTimeout); + opts.AddLongOption("write-timeout", "Write requests execution timeout [ms]").RequiredArgument("NUM") + .DefaultValue(runOptions.WriteTimeout).StoreResult(&runOptions.WriteTimeout); TOptsParseResult res(&opts, argc, argv); if (!CheckOptionsCommon(runOptions.CommonOptions)) { diff --git a/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.h b/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.h index a4beaa45a022..0f3f3d504e3b 100644 --- a/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.h +++ b/ydb/public/sdk/cpp/tests/slo_workloads/utils/utils.h @@ -10,7 +10,6 @@ extern const TDuration DefaultReactionTime; extern const TDuration ReactionTimeDelay; -extern const TDuration GlobalTimeout; extern const std::uint64_t PartitionsCount; struct TRecordData { @@ -48,7 +47,6 @@ struct TCommonOptions { std::uint32_t MaxCallbackThreads = 50; std::uint32_t MaxInfly = 500; std::uint32_t MaxRetries = 50; - std::uint64_t A_ReactionTime = 70; //ms TDuration ReactionTime = DefaultReactionTime; bool StopOnError = false; bool UseApplicationTimeout = false; @@ -75,6 +73,8 @@ struct TRunOptions { bool DontRunB = false; std::uint32_t Read_rps = 1000; std::uint32_t Write_rps = 10; + TDuration ReadTimeout = DefaultReactionTime; + TDuration WriteTimeout = DefaultReactionTime; }; class TRpsProvider { From ff302a5e1349f4c913537dd320e86431f27e6e25 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 20 Oct 2025 16:25:45 +0000 Subject: [PATCH 3/7] fix --- .github/workflows/slo.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index e91fd90616b3..7609d205a96f 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -32,6 +32,8 @@ jobs: --write-rps ${{inputs.slo_workload_write_max_rps || 100}} \ --read-timeout 1000 \ --write-timeout 1000 + + concurrency: group: slo-${{ github.ref }} cancel-in-progress: true From fc0c938b685798ac42c11e786c67c4b0502abe4b Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 20 Oct 2025 16:53:56 +0000 Subject: [PATCH 4/7] Fix --- .github/workflows/slo.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index 7609d205a96f..b988836f78fa 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -40,9 +40,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - with: - ref: ${{ inputs.commit_sha }} - fetch-depth: 0 - name: Configure git uses: ./.github/actions/configure_git From 424869f3281481d8cc0f1dc52e12359f23ab0524 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Tue, 21 Oct 2025 15:03:53 +0000 Subject: [PATCH 5/7] Workflow name fix --- .github/workflows/slo.yml | 2 +- .github/workflows/slo_report.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index b988836f78fa..a0401471ca00 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -1,4 +1,4 @@ -name: SLO +name: C++ SDK SLO on: workflow_dispatch: diff --git a/.github/workflows/slo_report.yml b/.github/workflows/slo_report.yml index 1467aeb17619..7ddc26e9be51 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: ['C++ SDK SLO'] types: - completed From 8cad0d769a1f8c04ae162d3bbab07a5ff27da4e3 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Fri, 24 Oct 2025 08:49:18 +0000 Subject: [PATCH 6/7] rename slo action --- .github/workflows/{slo.yml => slo_cpp_sdk.yml} | 2 +- .github/workflows/slo_report.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{slo.yml => slo_cpp_sdk.yml} (99%) diff --git a/.github/workflows/slo.yml b/.github/workflows/slo_cpp_sdk.yml similarity index 99% rename from .github/workflows/slo.yml rename to .github/workflows/slo_cpp_sdk.yml index a0401471ca00..1e36e84ebe9c 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo_cpp_sdk.yml @@ -1,4 +1,4 @@ -name: C++ SDK SLO +name: SLO CPP SDK on: workflow_dispatch: diff --git a/.github/workflows/slo_report.yml b/.github/workflows/slo_report.yml index 7ddc26e9be51..0afa3d6212ef 100644 --- a/.github/workflows/slo_report.yml +++ b/.github/workflows/slo_report.yml @@ -2,7 +2,7 @@ name: SLO Report on: workflow_run: - workflows: ['C++ SDK SLO'] + workflows: ['SLO CPP SDK'] types: - completed From cd1fbdec69db360021d711fab996b8d8dda57631 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 27 Oct 2025 09:08:54 +0000 Subject: [PATCH 7/7] fix issue --- .github/workflows/slo_cpp_sdk.yml | 9 +++++++++ .github/workflows/slo_report.yml | 22 ---------------------- 2 files changed, 9 insertions(+), 22 deletions(-) delete mode 100644 .github/workflows/slo_report.yml diff --git a/.github/workflows/slo_cpp_sdk.yml b/.github/workflows/slo_cpp_sdk.yml index 1e36e84ebe9c..fcd133b3d4ba 100644 --- a/.github/workflows/slo_cpp_sdk.yml +++ b/.github/workflows/slo_cpp_sdk.yml @@ -19,6 +19,9 @@ jobs: ydb-slo-action: name: Run YDB SLO Tests runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write strategy: matrix: @@ -80,3 +83,9 @@ jobs: name: Cleanup SLO Database run: | ./ydb/public/sdk/cpp/tests/slo_workloads/key_value/key_value ${{ matrix.connection-string }} cleanup + + - name: Publish YDB SLO Report + uses: ydb-platform/ydb-slo-action/report@main + with: + github_run_id: ${{ github.event.workflow_run.id }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/slo_report.yml b/.github/workflows/slo_report.yml deleted file mode 100644 index 0afa3d6212ef..000000000000 --- a/.github/workflows/slo_report.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: SLO Report - -on: - workflow_run: - workflows: ['SLO CPP SDK'] - types: - - completed - -jobs: - test-ydb-slo-action: - runs-on: ubuntu-latest - name: Publish YDB SLO Report - permissions: - contents: read - pull-requests: write - if: github.event.workflow_run.conclusion == 'success' - steps: - - name: Publish YDB SLO Report - uses: ydb-platform/ydb-slo-action/report@main - with: - github_run_id: ${{ github.event.workflow_run.id }} - github_token: ${{ secrets.GITHUB_TOKEN }}