diff options
author | Nikolay Shumkov <shumkovnd@ydb.tech> | 2025-02-14 21:08:44 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-14 21:08:44 +0300 |
commit | 5b11bce7be23074d80e0e3a6eb78345118602cb5 (patch) | |
tree | 332568b083e513fa62fc6614049d96b98a53cec6 | |
parent | 010e83fee42c48e44c89fbb43af2f80980b7c379 (diff) | |
download | ydb-5b11bce7be23074d80e0e3a6eb78345118602cb5.tar.gz |
Support QueryMeta and diagnostics (#11371)
Co-authored-by: Bulat <bylatgr@gmail.com>
23 files changed, 542 insertions, 68 deletions
diff --git a/ydb/core/grpc_services/query/rpc_execute_query.cpp b/ydb/core/grpc_services/query/rpc_execute_query.cpp index dcf0d45b38..8e0fe72525 100644 --- a/ydb/core/grpc_services/query/rpc_execute_query.cpp +++ b/ydb/core/grpc_services/query/rpc_execute_query.cpp @@ -179,6 +179,25 @@ bool NeedReportAst(const Ydb::Query::ExecuteQueryRequest& req) { } } +bool NeedCollectDiagnostics(const Ydb::Query::ExecuteQueryRequest& req) { + switch (req.exec_mode()) { + case Ydb::Query::EXEC_MODE_EXPLAIN: + return true; + + case Ydb::Query::EXEC_MODE_EXECUTE: + switch (req.stats_mode()) { + case Ydb::Query::StatsMode::STATS_MODE_FULL: + case Ydb::Query::StatsMode::STATS_MODE_PROFILE: + return true; + default: + return false; + } + + default: + return false; + } +} + class TExecuteQueryRPC : public TActorBootstrapped<TExecuteQueryRPC> { public: static constexpr NKikimrServices::TActivity::EType ActorActivityType() { @@ -284,6 +303,7 @@ private: req->pool_id()); ev->SetProgressStatsPeriod(TDuration::MilliSeconds(req->stats_period_ms())); + ev->Record.MutableRequest()->SetCollectDiagnostics(NeedCollectDiagnostics(*req)); if (!ctx.Send(NKqp::MakeKqpProxyID(ctx.SelfID.NodeId()), ev.Release(), 0, 0, Span_.GetTraceId())) { NYql::TIssues issues; @@ -403,6 +423,9 @@ private: if (NeedReportAst(*Request_->GetProtoRequest())) { response.mutable_exec_stats()->set_query_ast(kqpResponse.GetQueryAst()); } + if (NeedCollectDiagnostics(*Request_->GetProtoRequest())) { + response.mutable_exec_stats()->set_query_meta(kqpResponse.GetQueryDiagnostics()); + } } if (record.GetYdbStatus() == Ydb::StatusIds::SUCCESS) { diff --git a/ydb/core/grpc_services/rpc_execute_data_query.cpp b/ydb/core/grpc_services/rpc_execute_data_query.cpp index f134adf399..eb98c29674 100644 --- a/ydb/core/grpc_services/rpc_execute_data_query.cpp +++ b/ydb/core/grpc_services/rpc_execute_data_query.cpp @@ -27,6 +27,16 @@ using namespace Ydb; using namespace Ydb::Table; using namespace NKqp; +bool NeedCollectDiagnostics(const Ydb::Table::ExecuteDataQueryRequest& req) { + switch (req.collect_stats()) { + case Ydb::Table::QueryStatsCollection::STATS_COLLECTION_FULL: + case Ydb::Table::QueryStatsCollection::STATS_COLLECTION_PROFILE: + return true; + default: + return false; + } +} + using TEvExecuteDataQueryRequest = TGrpcRequestOperationCall<Ydb::Table::ExecuteDataQueryRequest, Ydb::Table::ExecuteDataQueryResponse>; @@ -147,6 +157,8 @@ public: req->has_query_cache_policy() ? &req->query_cache_policy() : nullptr, req->has_operation_params() ? &req->operation_params() : nullptr); + ev->Record.MutableRequest()->SetCollectDiagnostics(NeedCollectDiagnostics(*req)); + ReportCostInfo_ = req->operation_params().report_cost_info() == Ydb::FeatureFlag::ENABLED; ctx.Send(NKqp::MakeKqpProxyID(ctx.SelfID.NodeId()), ev.Release(), 0, 0, Span_.GetTraceId()); @@ -166,6 +178,9 @@ public: if (from.HasQueryStats()) { FillQueryStats(*to->mutable_query_stats(), from); to->mutable_query_stats()->set_query_ast(from.GetQueryAst()); + if (from.HasQueryDiagnostics()) { + to->mutable_query_stats()->set_query_meta(from.GetQueryDiagnostics()); + } return; } } diff --git a/ydb/core/grpc_services/rpc_stream_execute_scan_query.cpp b/ydb/core/grpc_services/rpc_stream_execute_scan_query.cpp index c37dc9b06e..6d2a6713be 100644 --- a/ydb/core/grpc_services/rpc_stream_execute_scan_query.cpp +++ b/ydb/core/grpc_services/rpc_stream_execute_scan_query.cpp @@ -93,6 +93,27 @@ bool NeedReportPlan(const Ydb::Table::ExecuteScanQueryRequest& req) { } } +bool NeedCollectDiagnostics(const Ydb::Table::ExecuteScanQueryRequest& req) { + switch (req.mode()) { + case ExecuteScanQueryRequest_Mode_MODE_EXPLAIN: + return true; + + case ExecuteScanQueryRequest_Mode_MODE_EXEC: + switch (req.collect_stats()) { + case Ydb::Table::QueryStatsCollection::STATS_COLLECTION_FULL: + case Ydb::Table::QueryStatsCollection::STATS_COLLECTION_PROFILE: + return true; + default: + break; + } + + return false; + + default: + return false; + } +} + bool CheckRequest(const Ydb::Table::ExecuteScanQueryRequest& req, TParseRequestError& error) { switch (req.mode()) { @@ -228,7 +249,7 @@ private: nullptr ); - ev->Record.MutableRequest()->SetCollectDiagnostics(req->Getcollect_full_diagnostics()); + ev->Record.MutableRequest()->SetCollectDiagnostics(NeedCollectDiagnostics(*req)); if (!ctx.Send(NKqp::MakeKqpProxyID(ctx.SelfID.NodeId()), ev.Release())) { NYql::TIssues issues; @@ -291,6 +312,7 @@ private: bool reportStats = NeedReportStats(*Request_->GetProtoRequest()); bool reportPlan = reportStats && NeedReportPlan(*Request_->GetProtoRequest()); + bool collectDiagnostics = NeedCollectDiagnostics(*Request_->GetProtoRequest()); if (reportStats) { if (kqpResponse.HasQueryStats()) { @@ -308,7 +330,9 @@ private: response.mutable_result()->mutable_query_stats()->set_query_ast(kqpResponse.GetQueryAst()); } - response.mutable_result()->set_query_full_diagnostics(kqpResponse.GetQueryDiagnostics()); + if (collectDiagnostics) { + response.mutable_result()->mutable_query_stats()->set_query_meta(kqpResponse.GetQueryDiagnostics()); + } Y_PROTOBUF_SUPPRESS_NODISCARD response.SerializeToString(&out); Request_->SendSerializedResult(std::move(out), record.GetYdbStatus()); diff --git a/ydb/core/kqp/common/compilation/events.h b/ydb/core/kqp/common/compilation/events.h index 9ed2d03e87..2daa210243 100644 --- a/ydb/core/kqp/common/compilation/events.h +++ b/ydb/core/kqp/common/compilation/events.h @@ -126,16 +126,14 @@ struct TEvRecompileRequest: public TEventLocal<TEvRecompileRequest, TKqpEvents:: }; struct TEvCompileResponse: public TEventLocal<TEvCompileResponse, TKqpEvents::EvCompileResponse> { - TEvCompileResponse(const TKqpCompileResult::TConstPtr& compileResult, NLWTrace::TOrbit orbit = {}, const std::optional<TString>& replayMessage = std::nullopt) + TEvCompileResponse(const TKqpCompileResult::TConstPtr& compileResult, NLWTrace::TOrbit orbit = {}) : CompileResult(compileResult) - , ReplayMessage(replayMessage) , Orbit(std::move(orbit)) { } TKqpCompileResult::TConstPtr CompileResult; TKqpStatsCompile Stats; std::optional<TString> ReplayMessage; - std::optional<TString> ReplayMessageUserView; NLWTrace::TOrbit Orbit; }; diff --git a/ydb/core/kqp/common/compilation/result.h b/ydb/core/kqp/common/compilation/result.h index f7206fceb1..a87294c8e8 100644 --- a/ydb/core/kqp/common/compilation/result.h +++ b/ydb/core/kqp/common/compilation/result.h @@ -16,7 +16,7 @@ struct TKqpCompileResult { TKqpCompileResult(const TString& uid, const Ydb::StatusIds::StatusCode& status, const NYql::TIssues& issues, ETableReadType maxReadType, TMaybe<TKqpQueryId> query = {}, TMaybe<TQueryAst> queryAst = {}, - bool needToSplit = false, const TMaybe<TString>& commandTagName = {}) + bool needToSplit = false, const TMaybe<TString>& commandTagName = {}, const TMaybe<TString>& replayMessageUserView = {}) : Status(status) , Issues(issues) , Query(std::move(query)) @@ -24,13 +24,14 @@ struct TKqpCompileResult { , MaxReadType(maxReadType) , QueryAst(std::move(queryAst)) , NeedToSplit(needToSplit) - , CommandTagName(commandTagName) {} + , CommandTagName(commandTagName) + , ReplayMessageUserView(replayMessageUserView) {} static std::shared_ptr<TKqpCompileResult> Make(const TString& uid, const Ydb::StatusIds::StatusCode& status, const NYql::TIssues& issues, ETableReadType maxReadType, TMaybe<TKqpQueryId> query = {}, - TMaybe<TQueryAst> queryAst = {}, bool needToSplit = false, const TMaybe<TString>& commandTagName = {}) + TMaybe<TQueryAst> queryAst = {}, bool needToSplit = false, const TMaybe<TString>& commandTagName = {}, const TMaybe<TString>& replayMessageUserView = {}) { - return std::make_shared<TKqpCompileResult>(uid, status, issues, maxReadType, std::move(query), std::move(queryAst), needToSplit, commandTagName); + return std::make_shared<TKqpCompileResult>(uid, status, issues, maxReadType, std::move(query), std::move(queryAst), needToSplit, commandTagName, replayMessageUserView); } std::shared_ptr<NYql::TAstParseResult> GetAst() const; @@ -47,6 +48,8 @@ struct TKqpCompileResult { bool NeedToSplit = false; TMaybe<TString> CommandTagName = {}; + TMaybe<TString> ReplayMessageUserView; + std::shared_ptr<const TPreparedQueryHolder> PreparedQuery; }; diff --git a/ydb/core/kqp/compile_service/kqp_compile_actor.cpp b/ydb/core/kqp/compile_service/kqp_compile_actor.cpp index 6b18134212..91cc89b8f5 100644 --- a/ydb/core/kqp/compile_service/kqp_compile_actor.cpp +++ b/ydb/core/kqp/compile_service/kqp_compile_actor.cpp @@ -353,7 +353,6 @@ private: replayMessage.InsertValue("query_id", Uid); replayMessage.InsertValue("version", "1.0"); - replayMessage.InsertValue("query_text", EscapeC(QueryId.Text)); NJson::TJsonValue queryParameterTypes(NJson::JSON_MAP); if (QueryId.QueryParameterTypes) { for (const auto& [paramName, paramType] : *QueryId.QueryParameterTypes) { @@ -365,7 +364,6 @@ private: replayMessage.InsertValue("query_syntax", ToString(Config->_KqpYqlSyntaxVersion.Get().GetRef())); replayMessage.InsertValue("query_database", QueryId.Database); replayMessage.InsertValue("query_cluster", QueryId.Cluster); - replayMessage.InsertValue("query_plan", queryPlan); replayMessage.InsertValue("query_type", ToString(QueryId.Settings.QueryType)); if (CollectFullDiagnostics) { @@ -380,6 +378,8 @@ private: ReplayMessageUserView = NJson::WriteJson(replayMessage, /*formatOutput*/ false); } + replayMessage.InsertValue("query_plan", queryPlan); + replayMessage.InsertValue("query_text", EscapeC(QueryId.Text)); replayMessage.InsertValue("table_metadata", TString(NJson::WriteJson(tablesMeta, false))); replayMessage.InsertValue("table_meta_serialization_type", EMetaSerializationType::EncodedProto); @@ -401,10 +401,12 @@ private: << ", issues: " << KqpCompileResult->Issues.ToString() << ", uid: " << KqpCompileResult->Uid); + if (ReplayMessageUserView) { + KqpCompileResult->ReplayMessageUserView = std::move(*ReplayMessageUserView); + } auto responseEv = MakeHolder<TEvKqp::TEvCompileResponse>(KqpCompileResult); responseEv->ReplayMessage = std::move(ReplayMessage); - responseEv->ReplayMessageUserView = std::move(ReplayMessageUserView); ReplayMessage = std::nullopt; ReplayMessageUserView = std::nullopt; auto& stats = responseEv->Stats; diff --git a/ydb/core/kqp/compile_service/kqp_compile_service.cpp b/ydb/core/kqp/compile_service/kqp_compile_service.cpp index 673b5a3909..b16ccd9b7c 100644 --- a/ydb/core/kqp/compile_service/kqp_compile_service.cpp +++ b/ydb/core/kqp/compile_service/kqp_compile_service.cpp @@ -610,7 +610,7 @@ private: if (compileResult->NeedToSplit) { Reply(compileRequest.Sender, compileResult, compileStats, ctx, - compileRequest.Cookie, std::move(compileRequest.Orbit), std::move(compileRequest.CompileServiceSpan), (CollectDiagnostics ? ev->Get()->ReplayMessageUserView : std::nullopt)); + compileRequest.Cookie, std::move(compileRequest.Orbit), std::move(compileRequest.CompileServiceSpan)); ProcessQueue(ctx); return; } @@ -635,7 +635,7 @@ private: for (auto& request : requests) { LWTRACK(KqpCompileServiceGetCompilation, request.Orbit, request.Query.UserSid, compileActorId.ToString()); Reply(request.Sender, compileResult, compileStats, ctx, - request.Cookie, std::move(request.Orbit), std::move(request.CompileServiceSpan), (CollectDiagnostics ? ev->Get()->ReplayMessageUserView : std::nullopt)); + request.Cookie, std::move(request.Orbit), std::move(request.CompileServiceSpan)); } } else { if (!hasTempTablesNameClashes) { @@ -647,7 +647,7 @@ private: LWTRACK(KqpCompileServiceGetCompilation, compileRequest.Orbit, compileRequest.Query.UserSid, compileActorId.ToString()); Reply(compileRequest.Sender, compileResult, compileStats, ctx, - compileRequest.Cookie, std::move(compileRequest.Orbit), std::move(compileRequest.CompileServiceSpan), (CollectDiagnostics ? ev->Get()->ReplayMessageUserView : std::nullopt)); + compileRequest.Cookie, std::move(compileRequest.Orbit), std::move(compileRequest.CompileServiceSpan)); } catch (const std::exception& e) { LogException("TEvCompileResponse", ev->Sender, e, ctx); @@ -809,7 +809,8 @@ private: if (compileResult->GetAst() && QueryCache->FindByAst(query, *compileResult->GetAst(), keepInCache)) { return false; } - auto newCompileResult = TKqpCompileResult::Make(CreateGuidAsString(), compileResult->Status, compileResult->Issues, compileResult->MaxReadType, std::move(query), compileResult->QueryAst); + auto newCompileResult = TKqpCompileResult::Make(CreateGuidAsString(), compileResult->Status, compileResult->Issues, compileResult->MaxReadType, std::move(query), compileResult->QueryAst, + false, {}, compileResult->ReplayMessageUserView); newCompileResult->AllowCache = compileResult->AllowCache; newCompileResult->PreparedQuery = compileResult->PreparedQuery; LOG_DEBUG_S(ctx, NKikimrServices::KQP_COMPILE_SERVICE, "Insert preparing query with params, queryId: " << query.SerializeToString()); @@ -865,7 +866,7 @@ private: void Reply(const TActorId& sender, const TKqpCompileResult::TConstPtr& compileResult, const TKqpStatsCompile& compileStats, const TActorContext& ctx, ui64 cookie, - NLWTrace::TOrbit orbit, NWilson::TSpan span, const std::optional<TString>& replayMessage = std::nullopt) + NLWTrace::TOrbit orbit, NWilson::TSpan span) { const auto& query = compileResult->Query; LWTRACK(KqpCompileServiceReply, @@ -878,7 +879,7 @@ private: << ", queryUid: " << compileResult->Uid << ", status:" << compileResult->Status); - auto responseEv = MakeHolder<TEvKqp::TEvCompileResponse>(compileResult, std::move(orbit), replayMessage); + auto responseEv = MakeHolder<TEvKqp::TEvCompileResponse>(compileResult, std::move(orbit)); responseEv->Stats = compileStats; if (span) { diff --git a/ydb/core/kqp/session_actor/kqp_query_state.cpp b/ydb/core/kqp/session_actor/kqp_query_state.cpp index 4c02f46b15..eaf20f4f23 100644 --- a/ydb/core/kqp/session_actor/kqp_query_state.cpp +++ b/ydb/core/kqp/session_actor/kqp_query_state.cpp @@ -143,9 +143,6 @@ bool TKqpQueryState::SaveAndCheckCompileResult(TEvKqp::TEvCompileResponse* ev) { return false; } Orbit = std::move(ev->Orbit); - if (ev->ReplayMessage) { - ReplayMessage = *ev->ReplayMessage; - } return true; } @@ -160,6 +157,10 @@ bool TKqpQueryState::SaveAndCheckCompileResult(TKqpCompileResult::TConstPtr comp return false; } + if (compileResult->ReplayMessageUserView && GetCollectDiagnostics()) { + ReplayMessage = *compileResult->ReplayMessageUserView; + } + YQL_ENSURE(CompileResult->PreparedQuery); const ui32 compiledVersion = CompileResult->PreparedQuery->GetVersion(); YQL_ENSURE(compiledVersion == NKikimrKqp::TPreparedQuery::VERSION_PHYSICAL_V1, diff --git a/ydb/core/kqp/ut/indexes/kqp_indexes_ut.cpp b/ydb/core/kqp/ut/indexes/kqp_indexes_ut.cpp index 8a56cf6c3c..f12cff2317 100644 --- a/ydb/core/kqp/ut/indexes/kqp_indexes_ut.cpp +++ b/ydb/core/kqp/ut/indexes/kqp_indexes_ut.cpp @@ -2698,7 +2698,7 @@ Y_UNIT_TEST_SUITE(KqpIndexes) { UNIT_ASSERT_C(value.IsMap(), "Incorrect Diagnostics"); UNIT_ASSERT_C(value.Has("query_id"), "Incorrect Diagnostics"); UNIT_ASSERT_C(value.Has("version"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(value.Has("query_text"), "Incorrect Diagnostics"); + UNIT_ASSERT_C(!value.Has("query_text"), "Incorrect Diagnostics"); UNIT_ASSERT_C(value.Has("query_parameter_types"), "Incorrect Diagnostics"); UNIT_ASSERT_C(value.Has("table_metadata"), "Incorrect Diagnostics"); UNIT_ASSERT_C(value["table_metadata"].IsArray(), "Incorrect Diagnostics: table_metadata type should be an array"); @@ -2706,7 +2706,7 @@ Y_UNIT_TEST_SUITE(KqpIndexes) { UNIT_ASSERT_C(value.Has("query_syntax"), "Incorrect Diagnostics"); UNIT_ASSERT_C(value.Has("query_database"), "Incorrect Diagnostics"); UNIT_ASSERT_C(value.Has("query_cluster"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(value.Has("query_plan"), "Incorrect Diagnostics"); + UNIT_ASSERT_C(!value.Has("query_plan"), "Incorrect Diagnostics"); UNIT_ASSERT_C(value.Has("query_type"), "Incorrect Diagnostics"); } diff --git a/ydb/core/kqp/ut/olap/helpers/query_executor.cpp b/ydb/core/kqp/ut/olap/helpers/query_executor.cpp index 7dbf61dd2f..648b5db6b9 100644 --- a/ydb/core/kqp/ut/olap/helpers/query_executor.cpp +++ b/ydb/core/kqp/ut/olap/helpers/query_executor.cpp @@ -5,13 +5,13 @@ namespace NKikimr::NKqp { -TVector<THashMap<TString, NYdb::TValue>> CollectRows(NYdb::NTable::TScanQueryPartIterator& it, NJson::TJsonValue* statInfo /*= nullptr*/, NJson::TJsonValue* diagnostics /*= nullptr*/) { +TVector<THashMap<TString, NYdb::TValue>> CollectRows(NYdb::NTable::TScanQueryPartIterator& it, NJson::TJsonValue* statInfo /*= nullptr*/, NJson::TJsonValue* meta /*= nullptr*/) { TVector<THashMap<TString, NYdb::TValue>> rows; if (statInfo) { *statInfo = NJson::JSON_NULL; } - if (diagnostics) { - *diagnostics = NJson::JSON_NULL; + if (meta) { + *meta = NJson::JSON_NULL; } for (;;) { auto streamPart = it.ReadNext().GetValueSync(); @@ -28,12 +28,10 @@ TVector<THashMap<TString, NYdb::TValue>> CollectRows(NYdb::NTable::TScanQueryPar if (plan && statInfo) { UNIT_ASSERT(NJson::ReadJsonFastTree(*plan, statInfo)); } - } - if (streamPart.HasDiagnostics()) { - auto diagnosticsString = TString{streamPart.GetDiagnostics()}; - if (!diagnosticsString.empty() && diagnostics) { - UNIT_ASSERT(NJson::ReadJsonFastTree(diagnosticsString, diagnostics)); + auto metaString = streamPart.GetQueryStats().GetMeta(); + if (metaString && !metaString->empty() && meta) { + UNIT_ASSERT(NJson::ReadJsonFastTree(*metaString, meta)); } } @@ -70,4 +68,4 @@ TVector<THashMap<TString, NYdb::TValue>> ExecuteScanQuery(NYdb::NTable::TTableCl return rows; } -}
\ No newline at end of file +} diff --git a/ydb/core/kqp/ut/olap/kqp_olap_ut.cpp b/ydb/core/kqp/ut/olap/kqp_olap_ut.cpp index c7af14d52d..a592340f6e 100644 --- a/ydb/core/kqp/ut/olap/kqp_olap_ut.cpp +++ b/ydb/core/kqp/ut/olap/kqp_olap_ut.cpp @@ -221,7 +221,7 @@ Y_UNIT_TEST_SUITE(KqpOlap) { Y_UNIT_TEST(AlterObjectDisabled) { auto settings = TKikimrSettings() .SetWithSampleTables(false); - TKikimrRunner kikimr(settings); + TKikimrRunner kikimr(settings); TLocalHelper(kikimr).CreateTestOlapTableWithoutStore(); { @@ -380,7 +380,7 @@ Y_UNIT_TEST_SUITE(KqpOlap) { } } - Y_UNIT_TEST(SimpleQueryOlapDiagnostics) { + Y_UNIT_TEST(SimpleQueryOlapMeta) { auto settings = TKikimrSettings() .SetWithSampleTables(false); TKikimrRunner kikimr(settings); @@ -393,7 +393,7 @@ Y_UNIT_TEST_SUITE(KqpOlap) { { TStreamExecScanQuerySettings settings; - settings.CollectQueryStats(ECollectQueryStatsMode::Full); + settings.CollectQueryStats(ECollectQueryStatsMode::Basic); auto it = client.StreamExecuteScanQuery(R"( --!syntax_v1 SELECT `resource_id`, `timestamp` @@ -402,15 +402,15 @@ Y_UNIT_TEST_SUITE(KqpOlap) { )", settings).GetValueSync(); UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); - NJson::TJsonValue jsonDiagnostics; - CollectRows(it, nullptr, &jsonDiagnostics); - UNIT_ASSERT_C(!jsonDiagnostics.IsDefined(), "Query result diagnostics should be empty, but it's not"); + NJson::TJsonValue jsonMeta; + CollectRows(it, nullptr, &jsonMeta); + UNIT_ASSERT_C(!jsonMeta.IsDefined(), "Query result meta should be empty, but it's not"); } { TStreamExecScanQuerySettings settings; settings.CollectQueryStats(ECollectQueryStatsMode::Full); - settings.CollectFullDiagnostics(true); + auto it = client.StreamExecuteScanQuery(R"( --!syntax_v1 SELECT `resource_id`, `timestamp` @@ -419,22 +419,22 @@ Y_UNIT_TEST_SUITE(KqpOlap) { )", settings).GetValueSync(); UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); - NJson::TJsonValue jsonDiagnostics; - CollectRows(it, nullptr, &jsonDiagnostics); - UNIT_ASSERT(!jsonDiagnostics.IsNull()); - - UNIT_ASSERT_C(jsonDiagnostics.IsMap(), "Incorrect Diagnostics"); - UNIT_ASSERT_C(jsonDiagnostics.Has("query_id"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(jsonDiagnostics.Has("version"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(jsonDiagnostics.Has("query_text"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(jsonDiagnostics.Has("query_parameter_types"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(jsonDiagnostics.Has("table_metadata"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(jsonDiagnostics.Has("created_at"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(jsonDiagnostics.Has("query_syntax"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(jsonDiagnostics.Has("query_database"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(jsonDiagnostics.Has("query_cluster"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(jsonDiagnostics.Has("query_plan"), "Incorrect Diagnostics"); - UNIT_ASSERT_C(jsonDiagnostics.Has("query_type"), "Incorrect Diagnostics"); + NJson::TJsonValue jsonMeta; + CollectRows(it, nullptr, &jsonMeta); + UNIT_ASSERT(!jsonMeta.IsNull()); + + UNIT_ASSERT_C(jsonMeta.IsMap(), "Incorrect Meta"); + UNIT_ASSERT_C(jsonMeta.Has("query_id"), "Incorrect Meta"); + UNIT_ASSERT_C(jsonMeta.Has("version"), "Incorrect Meta"); + UNIT_ASSERT_C(!jsonMeta.Has("query_text"), "Incorrect Meta"); + UNIT_ASSERT_C(jsonMeta.Has("query_parameter_types"), "Incorrect Meta"); + UNIT_ASSERT_C(jsonMeta.Has("table_metadata"), "Incorrect Meta"); + UNIT_ASSERT_C(jsonMeta.Has("created_at"), "Incorrect Meta"); + UNIT_ASSERT_C(jsonMeta.Has("query_syntax"), "Incorrect Meta"); + UNIT_ASSERT_C(jsonMeta.Has("query_database"), "Incorrect Meta"); + UNIT_ASSERT_C(jsonMeta.Has("query_cluster"), "Incorrect Meta"); + UNIT_ASSERT_C(!jsonMeta.Has("query_plan"), "Incorrect Meta"); + UNIT_ASSERT_C(jsonMeta.Has("query_type"), "Incorrect Meta"); } } @@ -2785,7 +2785,7 @@ Y_UNIT_TEST_SUITE(KqpOlap) { auto alterQuery = TStringBuilder() << R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, `COMPACTION_PLANNER.CLASS_NAME`=`lc-buckets`, `COMPACTION_PLANNER.FEATURES`=` - {"levels" : [{"class_name" : "Zero", "portions_live_duration" : "180s", "expected_blobs_size" : 2048000}, + {"levels" : [{"class_name" : "Zero", "portions_live_duration" : "180s", "expected_blobs_size" : 2048000}, {"class_name" : "Zero", "expected_blobs_size" : 2048000}, {"class_name" : "Zero"}]}`); )"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); @@ -2821,7 +2821,7 @@ Y_UNIT_TEST_SUITE(KqpOlap) { { auto alterQuery = R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, `COMPACTION_PLANNER.CLASS_NAME`=`lc-buckets`, `COMPACTION_PLANNER.FEATURES`=` - {"levels" : [{"class_name" : "Zero", "expected_blobs_size" : 1, "portions_count_available" : 3}, + {"levels" : [{"class_name" : "Zero", "expected_blobs_size" : 1, "portions_count_available" : 3}, {"class_name" : "Zero"}]}`); )"; auto result = session.ExecuteQuery(alterQuery, NQuery::TTxControl::NoTx()).GetValueSync(); diff --git a/ydb/core/kqp/ut/query/kqp_query_ut.cpp b/ydb/core/kqp/ut/query/kqp_query_ut.cpp index 88ed1d8600..4549a493ad 100644 --- a/ydb/core/kqp/ut/query/kqp_query_ut.cpp +++ b/ydb/core/kqp/ut/query/kqp_query_ut.cpp @@ -179,6 +179,78 @@ Y_UNIT_TEST_SUITE(KqpQuery) { UNIT_ASSERT_VALUES_EQUAL(counters.RecompileRequestGet()->Val(), 1); } + Y_UNIT_TEST(ExecuteDataQueryCollectMeta) { + auto setting = NKikimrKqp::TKqpSetting(); + auto serverSettings = TKikimrSettings() + .SetKqpSettings({setting}); + + TKikimrRunner kikimr(serverSettings); + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + { + UNIT_ASSERT(session.ExecuteSchemeQuery(R"( + CREATE TABLE `/Root/TestTable` ( + Key Uint64, + Value String, + PRIMARY KEY (Key) + ); + )").GetValueSync().IsSuccess()); + } + + { + const TString query(Q1_(R"( + SELECT * FROM `/Root/TestTable`; + )")); + + { + auto settings = TExecDataQuerySettings(); + settings.CollectQueryStats(ECollectQueryStatsMode::Full); + + auto result = session.ExecuteDataQuery(query, TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString().c_str()); + + auto stats = result.GetStats(); + UNIT_ASSERT(stats.has_value()); + + UNIT_ASSERT_C(stats->GetMeta().has_value(), "Query result meta is empty"); + + TStringStream in; + in << stats->GetMeta().value(); + NJson::TJsonValue value; + ReadJsonTree(&in, &value); + + UNIT_ASSERT_C(value.IsMap(), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_id"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("version"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_parameter_types"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("table_metadata"), "Incorrect Meta"); + UNIT_ASSERT_C(value["table_metadata"].IsArray(), "Incorrect Meta: table_metadata type should be an array"); + UNIT_ASSERT_C(value.Has("created_at"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_syntax"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_database"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_cluster"), "Incorrect Meta"); + UNIT_ASSERT_C(!value.Has("query_plan"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_type"), "Incorrect Meta"); + } + + { + auto settings = TExecDataQuerySettings(); + settings.CollectQueryStats(ECollectQueryStatsMode::Basic); + + auto result = session.ExecuteDataQuery(query, TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString().c_str()); + + auto stats = result.GetStats(); + UNIT_ASSERT(stats.has_value()); + + UNIT_ASSERT_C(!stats->GetMeta().has_value(), "Query result meta should be empty, but it's not"); + } + } + } + Y_UNIT_TEST(QueryCachePermissionsLoss) { TKikimrRunner kikimr; auto db = kikimr.GetTableClient(); diff --git a/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp b/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp index b27852b656..7daf90f727 100644 --- a/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp +++ b/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp @@ -650,6 +650,216 @@ Y_UNIT_TEST_SUITE(KqpQueryService) { } } + Y_UNIT_TEST(ExecuteCollectMeta) { + auto kikimr = DefaultKikimrRunner(); + auto db = kikimr.GetQueryClient(); + + { + TExecuteQuerySettings settings; + settings.StatsMode(EStatsMode::Full); + + auto result = db.ExecuteQuery(R"( + SELECT Key, Value2 FROM TwoShard WHERE Value2 > 0; + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + auto& stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + UNIT_ASSERT_C(!stats.query_meta().empty(), "Query result meta is empty"); + + TStringStream in; + in << stats.query_meta(); + NJson::TJsonValue value; + ReadJsonTree(&in, &value); + + UNIT_ASSERT_C(value.IsMap(), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_id"), "Incorrect Meta"); + UNIT_ASSERT_C(!value.Has("query_text"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("version"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_parameter_types"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("table_metadata"), "Incorrect Meta"); + UNIT_ASSERT_C(value["table_metadata"].IsArray(), "Incorrect Meta: table_metadata type should be an array"); + UNIT_ASSERT_C(value.Has("created_at"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_syntax"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_database"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_cluster"), "Incorrect Meta"); + UNIT_ASSERT_C(!value.Has("query_plan"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_type"), "Incorrect Meta"); + } + + { + TExecuteQuerySettings settings; + settings.StatsMode(EStatsMode::Basic); + + auto result = db.ExecuteQuery(R"( + SELECT Key, Value2 FROM TwoShard WHERE Value2 > 0; + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString().c_str()); + + auto& stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + UNIT_ASSERT_C(stats.query_meta().empty(), "Query result Meta should be empty, but it's not"); + } + + { + TExecuteQuerySettings settings; + settings.ExecMode(EExecMode::Explain); + + auto result = db.ExecuteQuery(R"( + SELECT Key, Value2 FROM TwoShard WHERE Value2 > 0; + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + UNIT_ASSERT(result.GetResultSets().empty()); + + UNIT_ASSERT(result.GetStats().has_value()); + + auto& stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + UNIT_ASSERT_C(!stats.query_ast().empty(), "Query result ast is empty"); + UNIT_ASSERT_C(!stats.query_meta().empty(), "Query result meta is empty"); + + TStringStream in; + in << stats.query_meta(); + NJson::TJsonValue value; + ReadJsonTree(&in, &value); + + UNIT_ASSERT_C(value.IsMap(), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_id"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("version"), "Incorrect Meta"); + UNIT_ASSERT_C(!value.Has("query_text"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_parameter_types"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("table_metadata"), "Incorrect Meta"); + UNIT_ASSERT_C(value["table_metadata"].IsArray(), "Incorrect Meta: table_metadata type should be an array"); + UNIT_ASSERT_C(value.Has("created_at"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_syntax"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_database"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_cluster"), "Incorrect Meta"); + UNIT_ASSERT_C(!value.Has("query_plan"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_type"), "Incorrect Meta"); + } + } + + Y_UNIT_TEST(StreamExecuteCollectMeta) { + auto kikimr = DefaultKikimrRunner(); + auto db = kikimr.GetQueryClient(); + + { + TExecuteQuerySettings settings; + settings.StatsMode(EStatsMode::Full); + + auto it = db.StreamExecuteQuery(R"( + SELECT Key, Value2 FROM TwoShard WHERE Value2 > 0; + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(it.GetStatus(), EStatus::SUCCESS, it.GetIssues().ToString()); + + TString statsString; + for (;;) { + auto streamPart = it.ReadNext().GetValueSync(); + if (!streamPart.IsSuccess()) { + UNIT_ASSERT_C(streamPart.EOS(), streamPart.GetIssues().ToString()); + break; + } + + const auto& execStats = streamPart.GetStats(); + if (execStats) { + auto& stats = NYdb::TProtoAccessor::GetProto(*execStats); + statsString = stats.query_meta(); + } + } + + UNIT_ASSERT_C(!statsString.empty(), "Query result meta is empty"); + + TStringStream in; + in << statsString; + NJson::TJsonValue value; + ReadJsonTree(&in, &value); + + UNIT_ASSERT_C(value.IsMap(), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_id"), "Incorrect Meta"); + UNIT_ASSERT_C(!value.Has("query_text"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("version"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_parameter_types"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("table_metadata"), "Incorrect Meta"); + UNIT_ASSERT_C(value["table_metadata"].IsArray(), "Incorrect Meta: table_metadata type should be an array"); + UNIT_ASSERT_C(value.Has("created_at"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_syntax"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_database"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_cluster"), "Incorrect Meta"); + UNIT_ASSERT_C(!value.Has("query_plan"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_type"), "Incorrect Meta"); + } + + { + auto settings = TExecuteQuerySettings().ExecMode(EExecMode::Explain); + + auto it = db.StreamExecuteQuery(R"( + SELECT Key, Value2 FROM TwoShard WHERE Value2 > 0; + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(it.GetStatus(), EStatus::SUCCESS, it.GetIssues().ToString()); + + TString statsString; + for (;;) { + auto streamPart = it.ReadNext().GetValueSync(); + if (!streamPart.IsSuccess()) { + UNIT_ASSERT_C(streamPart.EOS(), streamPart.GetIssues().ToString()); + break; + } + + const auto& execStats = streamPart.GetStats(); + if (execStats) { + auto& stats = NYdb::TProtoAccessor::GetProto(*execStats); + statsString = stats.query_meta(); + } + } + + UNIT_ASSERT_C(!statsString.empty(), "Query result meta is empty"); + + TStringStream in; + in << statsString; + NJson::TJsonValue value; + ReadJsonTree(&in, &value); + + UNIT_ASSERT_C(value.IsMap(), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_id"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("version"), "Incorrect Meta"); + UNIT_ASSERT_C(!value.Has("query_text"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_parameter_types"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("table_metadata"), "Incorrect Meta"); + UNIT_ASSERT_C(value["table_metadata"].IsArray(), "Incorrect Meta: table_metadata type should be an array"); + UNIT_ASSERT_C(value.Has("created_at"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_syntax"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_database"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_cluster"), "Incorrect Meta"); + UNIT_ASSERT_C(!value.Has("query_plan"), "Incorrect Meta"); + UNIT_ASSERT_C(value.Has("query_type"), "Incorrect Meta"); + } + + { + TExecuteQuerySettings settings; + settings.StatsMode(EStatsMode::Basic); + + auto it = db.StreamExecuteQuery(R"( + SELECT Key, Value2 FROM TwoShard WHERE Value2 > 0; + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(it.GetStatus(), EStatus::SUCCESS, it.GetIssues().ToString()); + + TString statsString; + for (;;) { + auto streamPart = it.ReadNext().GetValueSync(); + if (!streamPart.IsSuccess()) { + UNIT_ASSERT_C(streamPart.EOS(), streamPart.GetIssues().ToString()); + break; + } + + const auto& execStats = streamPart.GetStats(); + if (execStats) { + auto& stats = NYdb::TProtoAccessor::GetProto(*execStats); + statsString = stats.query_meta(); + } + } + + UNIT_ASSERT_C(statsString.empty(), "Query result meta should be empty, but it's not"); + } + } + void CheckQueryResult(TExecuteQueryResult result) { UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); UNIT_ASSERT_VALUES_EQUAL(result.GetResultSets().size(), 1); diff --git a/ydb/public/api/protos/ydb_query_stats.proto b/ydb/public/api/protos/ydb_query_stats.proto index 300d5d9837..34f4f49bdb 100644 --- a/ydb/public/api/protos/ydb_query_stats.proto +++ b/ydb/public/api/protos/ydb_query_stats.proto @@ -43,4 +43,7 @@ message QueryStats { string query_ast = 5; uint64 total_duration_us = 6; uint64 total_cpu_time_us = 7; + // will be filled only in MODE_EXPLAIN or in MODE_EXEC with QueryStatsCollection.Mode >= STATS_COLLECTION_FULL, + // collects additional meta about query compilation, including table metadata + string query_meta = 8; } diff --git a/ydb/public/api/protos/ydb_table.proto b/ydb/public/api/protos/ydb_table.proto index 7a91977050..3d69ae628f 100644 --- a/ydb/public/api/protos/ydb_table.proto +++ b/ydb/public/api/protos/ydb_table.proto @@ -1274,7 +1274,7 @@ message ExecuteScanQueryRequest { QueryStatsCollection.Mode collect_stats = 8; // works only in mode: MODE_EXPLAIN, // collects additional diagnostics about query compilation, including query plan and scheme - bool collect_full_diagnostics = 9; + bool collect_full_diagnostics = 9 [deprecated=true]; } message ExecuteScanQueryPartialResponse { @@ -1292,7 +1292,7 @@ message ExecuteScanQueryPartialResult { Ydb.TableStats.QueryStats query_stats = 6; // works only in mode: MODE_EXPLAIN, // collects additional diagnostics about query compilation, including query plan and scheme - string query_full_diagnostics = 7; + string query_full_diagnostics = 7 [deprecated = true]; } // Returns information about an external data source with a given path. diff --git a/ydb/public/lib/ydb_cli/commands/ydb_service_table.cpp b/ydb/public/lib/ydb_cli/commands/ydb_service_table.cpp index 08437be17a..7bf7b38233 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_service_table.cpp +++ b/ydb/public/lib/ydb_cli/commands/ydb_service_table.cpp @@ -9,8 +9,11 @@ #include <ydb-cpp-sdk/client/proto/accessor.h> #include <library/cpp/json/json_prettifier.h> +#include <library/cpp/json/json_writer.h> + #include <google/protobuf/util/json_util.h> +#include <util/string/escape.h> #include <util/string/split.h> #include <util/folder/path.h> #include <util/folder/dirut.h> @@ -365,6 +368,8 @@ void TCommandExecuteQuery::Config(TConfig& config) { config.Opts->AddLongOption('q', "query", "Text of query to execute").RequiredArgument("[String]").StoreResult(&Query); config.Opts->AddLongOption('f', "file", "Path to file with query text to execute") .RequiredArgument("PATH").StoreResult(&QueryFile); + config.Opts->AddLongOption("diagnostics-file", "Path to file where the diagnostics will be saved.") + .RequiredArgument("[String]").StoreResult(&DiagnosticsFile); AddOutputFormats(config, { EDataFormat::Pretty, @@ -507,13 +512,51 @@ void TCommandExecuteQuery::PrintDataQueryResponse(NTable::TDataQueryResult& resu } } // TResultSetPrinter destructor should be called before printing stats + std::optional<std::string> statsStr; + std::optional<std::string> plan; + std::optional<std::string> ast; + std::optional<std::string> meta; + const std::optional<NTable::TQueryStats>& stats = result.GetStats(); if (stats.has_value()) { - Cout << Endl << "Statistics:" << Endl << stats->ToString(); - PrintFlameGraph(stats->GetPlan()); + if (stats->GetMeta()) { + meta = stats->GetMeta(); + } + if (stats->GetPlan()) { + plan = stats->GetPlan(); + } + ast = stats->GetAst(); + statsStr = stats->ToString(); + Cout << Endl << "Statistics:" << Endl << statsStr; + PrintFlameGraph(plan); } - if (FlameGraphPath && !stats.has_value()) - { + + if (!DiagnosticsFile.empty()) { + TFileOutput file(DiagnosticsFile); + + NJson::TJsonValue diagnosticsJson(NJson::JSON_MAP); + + if (statsStr) { + diagnosticsJson.InsertValue("stats", *statsStr); + } + if (ast) { + diagnosticsJson.InsertValue("ast", *ast); + } + if (plan) { + NJson::TJsonValue planJson; + NJson::ReadJsonTree(*plan, &planJson, true); + diagnosticsJson.InsertValue("plan", planJson); + } + if (meta) { + NJson::TJsonValue metaJson; + NJson::ReadJsonTree(*meta, &metaJson, true); + metaJson.InsertValue("query_text", EscapeC(Query)); + diagnosticsJson.InsertValue("meta", metaJson); + } + file << NJson::PrettifyJson(NJson::WriteJson(diagnosticsJson, true), false); + } + + if (FlameGraphPath && !stats.has_value()) { Cout << Endl << "Flame graph is available for full or profile stats only" << Endl; } } @@ -635,7 +678,7 @@ namespace { } Y_UNREACHABLE(); } - + template <typename TQueryPart> const NQuery::TExecStats& GetStats(const TQueryPart& part) { if constexpr (std::is_same_v<TQueryPart, NTable::TScanQueryPart>) { @@ -730,8 +773,10 @@ int TCommandExecuteQuery::ExecuteQueryImpl(TConfig& config) { template <typename TIterator> bool TCommandExecuteQuery::PrintQueryResponse(TIterator& result) { - TMaybe<TString> stats; + std::optional<std::string> stats; std::optional<std::string> fullStats; + std::optional<std::string> meta; + std::optional<std::string> ast; { TResultSetPrinter printer(OutputFormat, &IsInterrupted); @@ -748,10 +793,14 @@ bool TCommandExecuteQuery::PrintQueryResponse(TIterator& result) { if (HasStats(streamPart)) { const auto& queryStats = GetStats(streamPart); stats = queryStats.ToString(); + ast = queryStats.GetAst(); if (queryStats.GetPlan()) { fullStats = queryStats.GetPlan(); } + if (queryStats.GetMeta()) { + meta = queryStats.GetMeta(); + } } } } // TResultSetPrinter destructor should be called before printing stats @@ -767,6 +816,31 @@ bool TCommandExecuteQuery::PrintQueryResponse(TIterator& result) { queryPlanPrinter.Print(TString{*fullStats}); } + if (!DiagnosticsFile.empty()) { + TFileOutput file(DiagnosticsFile); + + NJson::TJsonValue diagnosticsJson(NJson::JSON_MAP); + + if (stats) { + diagnosticsJson.InsertValue("stats", *stats); + } + if (ast) { + diagnosticsJson.InsertValue("ast", *ast); + } + if (fullStats) { + NJson::TJsonValue planJson; + NJson::ReadJsonTree(*fullStats, &planJson, true); + diagnosticsJson.InsertValue("plan", planJson); + } + if (meta) { + NJson::TJsonValue metaJson; + NJson::ReadJsonTree(*meta, &metaJson, true); + metaJson.InsertValue("query_text", EscapeC(Query)); + diagnosticsJson.InsertValue("meta", metaJson); + } + file << NJson::PrettifyJson(NJson::WriteJson(diagnosticsJson, true), false); + } + PrintFlameGraph(fullStats); if (IsInterrupted()) { @@ -1029,7 +1103,7 @@ void TCommandReadTable::Config(TConfig& config) { .NoArgument().SetFlag(&FromExclusive); config.Opts->AddLongOption("to-exclusive", "Don't include the right border element into response") .NoArgument().SetFlag(&ToExclusive); - + AddLegacyJsonInputFormats(config); AddOutputFormats(config, { diff --git a/ydb/public/lib/ydb_cli/commands/ydb_service_table.h b/ydb/public/lib/ydb_cli/commands/ydb_service_table.h index 041543abb3..a1c690708e 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_service_table.h +++ b/ydb/public/lib/ydb_cli/commands/ydb_service_table.h @@ -123,6 +123,7 @@ private: TString TxMode; TString QueryType; bool BasicStats = false; + TString DiagnosticsFile; }; class TCommandExplain : public TTableCommand, public TCommandWithOutput, TCommandQueryBase, TInterruptibleCommand { diff --git a/ydb/public/lib/ydb_cli/commands/ydb_sql.cpp b/ydb/public/lib/ydb_cli/commands/ydb_sql.cpp index b11f65a2ef..5ca0e0a7e9 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_sql.cpp +++ b/ydb/public/lib/ydb_cli/commands/ydb_sql.cpp @@ -1,6 +1,8 @@ #include "ydb_sql.h" #include <library/cpp/json/json_reader.h> +#include <library/cpp/json/json_writer.h> +#include <library/cpp/json/json_prettifier.h> #include <ydb/public/lib/json_value/ydb_json_value.h> #include <ydb-cpp-sdk/library/operation_id/operation_id.h> #include <ydb/public/lib/ydb_cli/common/interactive.h> @@ -10,6 +12,7 @@ #include <ydb/public/lib/ydb_cli/common/waiting_bar.h> #include <ydb-cpp-sdk/client/proto/accessor.h> #include <util/generic/queue.h> +#include <util/string/escape.h> #include <google/protobuf/text_format.h> namespace NYdb { @@ -40,6 +43,8 @@ void TCommandSql::Config(TConfig& config) { .StoreTrue(&ExplainAnalyzeMode); config.Opts->AddLongOption("stats", "Execution statistics collection mode [none, basic, full, profile]") .RequiredArgument("[String]").StoreResult(&CollectStatsMode); + config.Opts->AddLongOption("diagnostics-file", "Path to file where the diagnostics will be saved.") + .RequiredArgument("[String]").StoreResult(&DiagnosticsFile); config.Opts->AddLongOption("syntax", "Query syntax [yql, pg]") .RequiredArgument("[String]").DefaultValue("yql").StoreResult(&Syntax) .Hidden(); @@ -183,6 +188,7 @@ int TCommandSql::PrintResponse(NQuery::TExecuteQueryIterator& result) { std::optional<std::string> stats; std::optional<std::string> plan; std::optional<std::string> ast; + std::optional<std::string> meta; { TResultSetPrinter printer(OutputFormat, &IsInterrupted); @@ -204,13 +210,16 @@ int TCommandSql::PrintResponse(NQuery::TExecuteQueryIterator& result) { if (queryStats.GetPlan()) { plan = queryStats.GetPlan(); } + if (queryStats.GetMeta()) { + meta = queryStats.GetMeta(); + } } } } // TResultSetPrinter destructor should be called before printing stats if (ExplainAst) { Cout << "Query AST:" << Endl << ast << Endl; - + if (IsInterrupted()) { Cerr << "<INTERRUPTED>" << Endl; return EXIT_FAILURE; @@ -235,6 +244,31 @@ int TCommandSql::PrintResponse(NQuery::TExecuteQueryIterator& result) { queryPlanPrinter.Print(TString{*plan}); } + if (!DiagnosticsFile.empty()) { + TFileOutput file(DiagnosticsFile); + + NJson::TJsonValue diagnosticsJson(NJson::JSON_MAP); + + if (stats) { + diagnosticsJson.InsertValue("stats", *stats); + } + if (ast) { + diagnosticsJson.InsertValue("ast", *ast); + } + if (plan) { + NJson::TJsonValue planJson; + NJson::ReadJsonTree(*plan, &planJson, true); + diagnosticsJson.InsertValue("plan", planJson); + } + if (meta) { + NJson::TJsonValue metaJson; + NJson::ReadJsonTree(*meta, &metaJson, true); + metaJson.InsertValue("query_text", EscapeC(Query)); + diagnosticsJson.InsertValue("meta", metaJson); + } + file << NJson::PrettifyJson(NJson::WriteJson(diagnosticsJson, true), false); + } + if (IsInterrupted()) { Cerr << "<INTERRUPTED>" << Endl; return EXIT_FAILURE; diff --git a/ydb/public/lib/ydb_cli/commands/ydb_sql.h b/ydb/public/lib/ydb_cli/commands/ydb_sql.h index fc86a9dc3b..bfa617ae66 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_sql.h +++ b/ydb/public/lib/ydb_cli/commands/ydb_sql.h @@ -28,6 +28,7 @@ private: int PrintResponse(NQuery::TExecuteQueryIterator& result); TString CollectStatsMode; + TString DiagnosticsFile; TString Query; TString QueryFile; TString Syntax; diff --git a/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/query/stats.h b/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/query/stats.h index 902478b446..0b7ca6cc6d 100644 --- a/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/query/stats.h +++ b/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/query/stats.h @@ -29,6 +29,7 @@ public: std::optional<std::string> GetPlan() const; std::optional<std::string> GetAst() const; + std::optional<std::string> GetMeta() const; TDuration GetTotalDuration() const; TDuration GetTotalCpuTime() const; diff --git a/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/table/query_stats/stats.h b/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/table/query_stats/stats.h index 38740d2dd1..9eda4620ff 100644 --- a/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/table/query_stats/stats.h +++ b/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/table/query_stats/stats.h @@ -30,7 +30,7 @@ namespace NTable { enum class ECollectQueryStatsMode { None = 0, // Stats collection is disabled Basic = 1, // Aggregated stats of reads, updates and deletes per table - Full = 2, // Add per-stage execution profile and query plan on top of Basic mode + Full = 2, // Add per-stage execution profile, query plan and query meta on top of Basic mode Profile = 3 // Detailed execution stats including stats for individual tasks and channels }; diff --git a/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/table/table.h b/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/table/table.h index df5b02d916..e83101f28d 100644 --- a/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/table/table.h +++ b/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/table/table.h @@ -1164,6 +1164,7 @@ struct TStreamExecScanQuerySettings : public TRequestSettings<TStreamExecScanQue // Collect runtime statistics with a given detalization mode FLUENT_SETTING_DEFAULT(ECollectQueryStatsMode, CollectQueryStats, ECollectQueryStatsMode::None); + // Deprecated. Use CollectQueryStats >= ECollectQueryStatsMode::Full to get QueryMeta in QueryStats // Collect full query compilation diagnostics FLUENT_SETTING_DEFAULT(bool, CollectFullDiagnostics, false); }; @@ -2109,6 +2110,7 @@ public: const TQueryStats& GetQueryStats() const { return *QueryStats_; } TQueryStats ExtractQueryStats() { return std::move(*QueryStats_); } + // Deprecated. Use GetMeta() of TQueryStats bool HasDiagnostics() const { return Diagnostics_.has_value(); } const std::string& GetDiagnostics() const { return *Diagnostics_; } std::string&& ExtractDiagnostics() { return std::move(*Diagnostics_); } diff --git a/ydb/public/sdk/cpp/src/client/query/stats.cpp b/ydb/public/sdk/cpp/src/client/query/stats.cpp index e56d5533f6..5106687f23 100644 --- a/ydb/public/sdk/cpp/src/client/query/stats.cpp +++ b/ydb/public/sdk/cpp/src/client/query/stats.cpp @@ -31,6 +31,7 @@ std::string TExecStats::ToString(bool withPlan) const { if (!withPlan) { proto.clear_query_plan(); proto.clear_query_ast(); + proto.clear_query_meta(); } TStringType res; @@ -58,6 +59,16 @@ std::optional<std::string> TExecStats::GetAst() const { return proto.query_ast(); } +std::optional<std::string> TExecStats::GetMeta() const { + auto proto = Impl_->Proto; + + if (proto.query_meta().empty()) { + return {}; + } + + return proto.query_meta(); +} + TDuration TExecStats::GetTotalDuration() const { return TDuration::MicroSeconds(Impl_->Proto.total_duration_us()); } |